Eclipse (PDE) - Gérer les associations de fichiers

Cet article ouvre sur une nouvelle série de tutoriels consacrée au développement de plugins pour Eclipse.

L’environnement de développement utilisé est Eclipse RCP (l’IDE "par défaut" sans aucun plugin) et PDE (Plugin Environment Developement).

Je ne ferais pas de tutoriel complet sur la mise en place de cet environnement de développement, vous trouverez facilement tout ce qu’il faut sur le net, par exemple ici : http://www.eclipse.org/pde/

Ce premier sujet sera consacré aux méthodes à implémenter pour personnaliser les préférences de l’IDE.
Plus spécialement cette fois-ci, la personnalisation des associations de fichiers avec certains éditeurs. Deux cas se présentent :

L’extension de fichier considérée existe déjà dans la liste des extensions gérées dans les préférences (par défaut *.html ou *.htm) :

Dans ce cas le plus simple, le code suivant permet de modifier l’association par défaut :

IEditorRegistry registry = PlatformUI.getWorkbench().getEditorRegistry();
registry.setDefaultEditor("*.html", "org.eclipse.ui.DefaultTextEditor");
((EditorRegistry)registry).saveAssociations();

Ce code peut être implémenté dans une classe qui s’éxecute au lancement de l’IDE, ou dans tout autre classe, puisque la méthode saveAssociations() met à jour les registres.

L’extension de fichier considéré n’existe pas par défaut :

Dans ce cas, il est impossible d’ajouter cette nouvelle association au lancement de l’IDE en passant par les registres d’éditeurs.

Il faut créer un éditeur personnalisé et l’associer à la nouvelle extension à travers le fichier plugin.xml : il faut utiliser le point d’extension "org.eclipse.ui.editors" : la section extensions permet de spécifier toutes les nouvelles extensions de fichiers qui seront associées par défaut à cet éditeur.

Pour en savoir plus, un très bon tutoriel a été rédigé sur developpez.com, je vous conseille d’aller y jeter un oeil. Et pour quelques détails supplémentaires, un autre tutoriel Aptana.


Fichier(s) joint(s) :

Eclipse (PDE) - Modifier le comportement par défaut d’un composant graphique

Nous allons voir maintenant comment interagir avec l’interface de l’IDE pendant son lancement pour agir sur un composant graphique.

L’exemple utilisé ici est l’ajout d’un MouseListener sur le TreeViewer dans la vue "ProjectExplorer".

Premièrement, pour accéder à ce composant, il faut, au lancement de l’IDE, passer par la fenêtre active puis entrer dans la vue ProjectExplorer. Voici le code nécessaire :

IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getWorkbenchWindows()[0];
IWorkbenchPage page = window.getActivePage();
IViewPart viewPart = null;
try {
      viewPart = page.showView("org.eclipse.ui.navigator.ProjectExplorer");
} catch (PartInitException e) {
      System.out.println(e);
}
CommonNavigator nav = (CommonNavigator) viewPart;
CommonViewer viewer = nav.getCommonViewer();
Tree tree = viewer.getTree();
tree.addMouseMoveListener(list);

Voici donc le déroulement :

  • On accède en premier au Workbench (ensemble de fenêtres),
  • On récupère la page active
  • On accède à la vue ProjectExplorer qui est de type CommonNavigator
  • On récupère l’arbre qui gère l’arborescence des ressources du projet,
  • On lui affecte un listener personnalisé pour une action précise.

Cependant, si vous exécutez ce code dans une classe au lancement de l’IDE, vous allez être confronté à une erreur :

"org.eclipse.swt.SWTException: Invalid thread access".

En effet, lorsque l’interface est en cours de création, le thread courant est l’IUThread d’Eclipse. Donc tout autre bout de code (thread) tentant d’accéder à un élément graphique en parallèle est rejeté.

Pour contrer ce problème, il faut accéder à l’UIThread et bloquer son exécution le temps de réaliser les actions requises. Pour ce faire, voici le code à implémenter :

Display.getDefault().syncExec(new Runnable() {
   public void run() {
     IWorkbench workbench = PlatformUI.getWorkbench();
     IWorkbenchWindow window = workbench.getWorkbenchWindows()[0];
     IWorkbenchPage page = window.getActivePage();
     IViewPart viewPart = null;
     try {
        viewPart = page.showView("org.eclipse.ui.navigator.ProjectExplorer");
     } catch (PartInitException e) {
        System.out.println(e);
     }
     CommonNavigator nav = (CommonNavigator) viewPart;
     CommonViewer viewer = nav.getCommonViewer();
     Tree tree = viewer.getTree();
     tree.addMouseMoveListener(list);
   }
});

Ainsi, on spécifie au thread courant d’exécuter de manière synchrone notre code.


Fichier(s) joint(s) :



[Non résolu] Swftools et zones de texte dynamiques

Un problème un fois de plus assez complexe.

Voici le contexte :

J’ai développé un moteur de recherche en Flash pour du contenu textuel au sein de fichiers SWF. Basé sur des zones de textes dynamiques et de polices embarquées, tout fonctionne très bien, les résultats de la recherche étant surlignés dans les textes (avec TextSnapShot).

Le coeur du problème :

Ce système fonctionne très bien avec des SWF créés à la main. Le soucis est que je désormais le faire fonctionner avec des SWF générés dynamiquement à partir de l’outil pdf2swf des Swftools.

Après décompilation des fichiers générés, on peut facilement constater que cet outil crée des zones de texte statiques pour tout ce qui est textuel.

Il faut donc pouvoir modifier le code source de Swftools pour lui faire générer plutôt des zones de texte dynamiques… Pas très simple sous Windows!!

Solution :

Pour le moment je n’ai pas trouvé de solution à mon problème. J’ai tout juste réussi à remplacer bêtement dans le code source tout les "StaticText" par "DynamicText", recompiler le tout, mais sans grand succès!! Un très bon tutorial pour recompiler Swftools sous Windows est disponible à cette adresse (accessible depuis la FAQ su site officiel)

Très peu de sites/forums/mailinglist abordent le sujet de la customization de swftools.

Alors j’en apelle à la communauté pour savoir si quelqu’un à déjà eu à faire ce genre de modification!! N’hésitez pas à fournir des propositions!


Fichier(s) joint(s) :



Papervision3D et moteur physique

Lors du développement d’une application 3D il est souvent intéressant d’ajouter des notions de physique, comme la gravité, les collisions, rebonds...

Il existe principalement deux moteurs de physique 3D pour Flash : Jiglib et WowEngine. Je vais expliquer dans cet article l’utilisation de WowEngine.

Voici comment il s’utilise dans une application, couplé avec PaperVision3D :

L’animation dispose, de façon bien distincte, du moteur de rendu et du moteur de physique. Ils sont entièrement indépendants. Le site du projet WowEngine présente un exemple très clair pour comprendre comment coupler les deux, regardons un peu le code (fournit ici) :

public function createOneSphere(x:int,y:int,z:int,radius:int): void
{
 //we create a physics sphere the param is position x,y,z the radius, if it's fixed or not and the mass
 var wowSphere:WSphere = new WSphere(x,y,z,radius,false,0.1);
 wowSphere.elasticity=0.5;
 wowSphere.friction=0.02;
 wow.addParticle(wowSphere);
 
 //and the 3dObject on the 3d renderer starting by the material...
 var material:WireframeMaterial=new WireframeMaterial(0,100,.1)
 //...and the 3DObject
 var sphere:Sphere = new Sphere(material , radius, 5, 3 );
 sphere.x=-wowSphere.px;
 sphere.y=-wowSphere.py;
 sphere.z=-wowSphere.pz;
 scene.addChild(sphere);
 //
 sphereArray.push({physics:wowSphere,render:sphere});
}
private function enterFrameHandler( event : Event ):void 
{
 //we update the physics engine
 wow.step()
 
 // we link the  physics sphere to the rendered sphere
 var ballCount:int=sphereArray.length;
 while(ballCount--){
  var physics:WSphere=sphereArray[ballCount].physics
  var render:Sphere=sphereArray[ballCount].render
  render.x=physics.px;
  render.y=-physics.py;
  //trace(physics.py)
  render.z=physics.pz;
 }
 //we update the 3d view
 renderer.renderScene(scene,camera, viewport);
} 

Après avoir initialisé Papervision et WowEngine (respectivement dans les méthodes "setupPapervision()" et "setupWow()"), cette méthode permet de créer une sphère dans l’environnement 3D réagissant aux lois de gravitation.

Le principe est le suivant :

D’une part, on crée, dans le moteur physique, une WSphere qui sera soumise à différentes contraintes comme l’élasticité (capacité de rebondir), les frictions (en plus de la gravité). D’autre part, on crée dans le moteur de rendu, une Sphere simple.

La liaison entre les deux est faite dans la méthode "enterFrameHandler" : on peut voir qu’elle met à jour la position de l’objet graphique (Sphere) selon la position de l’objet physique (WSphere).

La mise en place d’un moteur physique dans un projet 3D n’est donc pas plus compliqué que cela : il suffit de créer les deux moteurs indépendamment et de mettre périodiquement à jour le rendu en fonction de l’évolution des données physiques.


Fichier(s) joint(s) :



Flash Player 8/AS 2 et Localization: ma méthode

Tout d’abord, pourquoi un article sur Flash Player 8 et Actionscript 2, à l’heure de l’apogée de la technologie Flex/AS3 ? Réponse simple, au vu des statistiques de pénétration du player. En effet, Flex c’est génial, mais beaucoup d’utilisateurs sont encore munis de la version 8 du player, incompatible avec Flex.

Je viens donc de travailler sur une application dont la principale particularité est qu’elle est codée uniquement en AS2, pour cibler la majorité des utilisateurs. Le problème que j’ai rencontré est le suivant : je devais internationaliser l’interface, c’est-à-dire permettre de changer de langue à n’importe quel moment. Alors vous me direz, avec Flex c’est facile, avec les ResourceBundle. Certes, mais pour ce qui est de l’AS2, c’est une autre histoire...

J’ai donc fais pas mal de recherches sur le net, mais finalement rien de concluant : la majorité des solutions que j’ai trouvées consistait à créer un tableau, global à l’application, rescençant une liste de mot-clés associés à une langue. Ensuite, une fonction était chargée de changer la valeur dans toutes les zones de textes impliquées. C’est le principe utilisé par la classe la plus courante, distribuée par Shaoken. Ceci est parfois très pratique, sauf que dans mon cas, la liste des champs à manipuler est assez importante et répartie dans des profondeurs de clip d’autant plus complexe. Enfin mon application étant destinée à être customisée par des clients, je n’ai aucun moyen de connaitre précisément la liste des champs mis en place… Un vrai dilemne n’est-ce pas?

La seule solution qui s’offre donc à moi, est de me reposer sur un modèle évènementiel, comme notre cher Flex. Seconde embuche! Flash est plutôt capricieux quand il s’agit de gérer des évènements. Mais voilà mon raisonnement :

Il faudrait que j’utilise un objet global à mon application, qui gèrerait seul tout cet aspet de localization (langue courante, dictionnaire…). De plus, il faudrait que tout mes champs se mettent à jour lorsque l’utilisateur appuie sur un bouton. Mais je ne sais connait pas la liste des champs dans l’appli. Il faut donc que ce soit eux-mêmes qui prennent connaissance du changement de langue.

Donc deux étapes : créer cet objet global, jusque là rien de compliqué, une simple classe suffira. Cependant, il devra être capable de diffuser l’évènement du changement de langue... Il faudra donc étendre la classe EventDispatcher. Ensuite, pour les champs je vais devoir créer un composant, basé sur un champ texte dynamique, mais capable de répondre à un évènement. Soit!

Parlons peu, parlons bien. Commençons par la classe gérant la localization :

import mx.events.EventDispatcher;
import mx.xpath.XPathAPI;
import mx.utils.Delegate;
 
class data.com.Localizer extends EventDispatcher {
 
 private var dispatchEvent:Function;
 private var dispatchQueue:Function;
 public var addEventListener:Function;
 public var removeEventListener:Function;
 
 private var localeXML:XML;
 private var dictionnary:Object;
 
 public var language = "fr";
 public var dictionnaryPath = "";
 
 function Localizer(){
  mx.events.EventDispatcher.initialize(this);
  this.dictionnary = new Object();
  this.localeXML = new XML();
  this.localeXML.ignoreWhite = true;
  this.localeXML.onLoad = Delegate.create(this, dotranslate);
 }
 
 function loadDico(){
  this.localeXML.load(this.dictionnaryPath);
 }
 
 function dotranslate(loaded:Boolean){
  if(loaded){
   translate();
  } else {
   trace("Error loading dictionnary!");
  }
 }
 
 function translate(){
  var aNodes:Array = this.localeXML.firstChild.childNodes;
  var nMaxNodes:Number = aNodes.length;
  for (var i:Number = 0; i < nMaxNodes; i++){
   var sVarName:String = aNodes[i].attributes.id;
   var sVarValue = XPathAPI.selectSingleNode(aNodes[i], "/*/"+this.language).firstChild.nodeValue;
   if (sVarValue == undefined)
    sVarValue = XPathAPI.selectSingleNode(aNodes[i], "/*/default").firstChild.nodeValue;
   if (sVarValue == undefined) {
    trace("Localized XML node can't be selected, please verify your language or set a default value tag");
   }
   this.dictionnary[sVarName] = sVarValue;
 
  }
  dispatchEvent({target:this, type:"languageChanged"});
 }
}

Comme vous pouvez le voir, cette class n’est pas bien complexe et repose sur le code de Shaoken. Voici son fonctionnement : à la création de l’objet, j’initialise la classe EventDispatcher pour pouvoir ensuite utiliser ses méthodes. Le fichier XML "localeXML" contient le dictionnaire, nous verrons sa syntaxe juste après. La méthode "translate()" parse le fichier et remplit le tableau "this.dictionnary" en fonction de la langue choisie. Une fois le tableau remplit, on envoie l’évènement avertissant que la traduction est disponible.

Regardons maintenant la structure du XML en question :



 Traduire
 Translate
 Traduire


 ...


Pour chaque variable, on a la liste des textes dans chanque langue.

Passons maintenant au plus important, le composant! :

#initclip
import mx.utils.Delegate;
 
LTextField.prototype = new MovieClip();
 
function LTextField () {
 this.textFormatter = new TextFormat();
 this.init();
}
 
LTextField.prototype.init = function () {
 _root.languageWizard.addEventListener("languageChanged",Delegate.create(this,LTextField.prototype.translation));
}
 
LTextField.prototype.translation = function () {
 this.label_txt.text = _root.languageWizard.dictionnary[this.labelKey];
 this.textFormatter.size = this.fontSize;
 this.textFormatter.align = this.halign;
 this.label_txt.setTextFormat(this.textFormatter);
}
 
// Connect the class with the linkage ID for this movie clip
Object.registerClass("LocalizedTextField", LTextField);
#endinitclip

J’ai ainsi créé un simple clip, avec sur la scène un DynamicTextField nommé : "label_txt". Voici le principe : quand le composant est créé sur la scène, le constructeur est appelé, puis la fonction "init()" qui va ajouter sur notre objet global un écouteur de l’évènement de changement de langue, lié à la fonction d’actualisation du composant ("translation()"). Ainsi, je découple complètement le gestionnaire des objets acteurs.

Je peux donc placer mes champs texte n’importe où dans l’application, à n’importe quel niveau, puisqu’ils sont directement liés à mon objet global. Ce dernier est créé par une méthode aussi simple que :

_root.languageWizard = new Localizer();
_root.languageWizard.dictionnaryPath = "data/localization.xml";
_root.languageWizard.loadDico();

Ensuite, voici le comportement du bouton permettant de passer d’une langue à l’autre :

function triggerLocale(){
 if(_root.languageWizard.language=="fr"){
  _root.languageWizard.language = "en";
  cbtTranslate.gotoAndStop(2);
 } else {
  _root.languageWizard.language = "fr";
  cbtTranslate.gotoAndStop(1);
 }
 _root.languageWizard.translate();
}
 
cbtTranslate.onPress = triggerLocale;

Maintenant, et parce que cela n’a pas été aussi simple qu’il n’y parait, je vous vous lister l’ensemble des difficultés que j’ai eu à créer ce code, afin de vous les épargner si vous avez à le réutiliser! :

  1. Pour ceux qui auraient oublié comment créer un composant dans Flash, voici un très bon tuto.
  2. Pour créer un objet capable de diffuser des évènements, il est indispensable d’étendre la classe EventDispatcher, et de déclarer toutes ses méthodes (removeEventListener…)
  3. Depuis le composant, lorsque l’on affecte un écouteur à l’objet global, il est impératif d’utiliser la classe Delagate, sans quoi Flash essaiera de trouver la méthode indiquée au même niveau que celui de l’objet (donc au niveau global (_root) et non pas dans le composant…)
  4. Enfin, je m’excuse auprès des puristes qui militent conter l’utilisation du _root, mais c’est une souplesse erreur que nous permet encore l’AS2

Fichier(s) joint(s) :