6 octobre 2004

Utile : conserver l'indentation et création de nom "simples"

Etant donné que je suis plus occupé à réinstaller mon ordinateur et à me réinstaller moi-même qu'à coder (ben oui, je déménage), je ressors quelques lignes de PHP que j'utilise depuis longtemps pour alimenter quelque peu ce blog qui à tendance à roupiller. :P

La première fonction sert à générer des noms "simples", pouvant servir à des URL. C'est en fait la fonction que j'utilise pour les adresses de mes pages. Elle élimine les accents, ponctuations et mots de moins de 3 lettres, et remplace les espaces par des tirets :function simpleName($str) {
        $araccent = array("#[áåâäàã]#","#ç#","#[êéèë]#","#[íîïì]#","#[óôöòõ]#","#[ûüùú]#","#ñ#","#[ÿý]#", "#[^\w ]#");
        $arnoaccent = array("a", "c", "e", "i", "o", "u", "n", "y", "");
        $ret = preg_replace("#(\W|\s)\w{1,2}(?=\W|\s)#", "$1$2", $str);
        $ret = preg_replace(array("#\s{2,}#","# #"),array(" ", "-"), trim(preg_replace($araccent, $arnoaccent, strtolower($ret))));
        return $ret;
}
La deuxième me sert à conserver l'identation du code lorsqu'il est affiché en HTML. Pour cela, la chaine retournée alterne les espaces "normaux" et les espaces insécables HTML  . Elle utilise en majorité str_replace pour de meilleures performances. C'est ce code qui est utilisé par ma classe Colori :function keepHTMLIndent($str) {
        $ret = str_replace("  ","  ",$str);
        $ret = str_replace("  ","  ",$ret);
        $ret = str_replace("\t","   ",$ret);
        $ret = preg_replace("/^ {1}/m", ' ',$ret);
        return $ret;
}

Et wala, à bientôt ! ^^

29 septembre 2004

Envie de meurtre

Venant d'investir dans un ordinateur de type barebone, je me suis vu confronté à des incompatibilités entre mon ancien matériel et le nouveau. Ayant tout simplement transféré le disque dur d'une machine à l'autre, je n'avais plus qu'à ré-installer windows sur le version actuelle. J'ai fais cette manipulation des dizaines de fois, et ca a toujours fonctionné.

Mais voila-t-y pas que au démarrage, Windows m'invite gentimment à faire un scandisk pour vérifier mon disque dur. Sachant qu'il va me les briser systématiquement au démarrage, je me dis autant le faire tout de suite. L'erreur qu'il ne fallait pas faire ... :(

J'écris maintenant depuis ma nouvelle installation, purgée de tous les documents que j'ai créé sur ce disque dur depuis mon précédent formatage (dans les règles celui-là) : classes AS2, articles en cours de rédaction, la totalité de mes images et musiques, mes réalisations pro, bref TOUS MES FICHIERS DEPUIS DEUX MOIS !!! :x

Je suis sûr que les murs ont eu plus mal que mon crâne ...

24 septembre 2004

Vous êtes arrivé à destination

Si vous voyez ce post, cela veut dire que vous venez de rejoindre le nouveau serveur. Vous pouvez détacher vos ceintures et vous relaxer jusqu'à l'arret de l'appareil ... :mrgreen:

Bref, vous pouvez vous jeter à nouveau sur les commentaires, et apprécier ce nouvel hébergement. 8)

A ce sujet, l'interface d'administration du serveur est Plesk 7 : un nombre incroyable d'options configurables, et une interface assez esthétique en plus ce qui ne gache rien ! Un coup de chapeau à cette application donc ! :)

21 septembre 2004

Pratiques de programmation PHP #1 : La base de données.

Passé maitre dans l'art du teasing, voici maintenant le vrai début de cette série. :D

Je rappelle qu'il s'agit là, d'une série sur les bonnes pratiques du PHP, et non pas de tutoriel d'apprentissage. Pour les comprendre, il s'agit donc de déjà savoir faire du PHP avec MySQL, que ce soit à un niveau plus ou moins avancé. Pour cet article, je considère que vous savez déjà créer une BDD, une table, et concevoir un minimum pour avoir quelque chose de fonctionnel (a défaut d'être souple et/ou performant).

1. Ai-je besoin d'une base de données ?

La base de données est un système "simple" qui sert a stoquer des données (oui, je suis pas aller le chercher loin ! :$) ... Il s'agit donc de la base sur laquelle repose la plupart des applications dynamiques. Les applications les plus connues utilisant une base de données sont des forums, des blogs ou des boutiques en ligne. Elles permettent non seulement d'enregistrer des données, mais également des les récupérer de manière selective au moyen du langage SQL.
Mais attention à ne pas faire non plus du tout BDD ! En effet, certaines situations ne nécessite pas de base de données :

  • Gestion de fichiers : la plupart des informations d'un fichier sont directement accessibles depuis PHP (nom, taille, date de création/modification). Une BDD qui stockerait des noms de fichiers avec ces informations ne serait qu'une répétition de ce que nous pouvons savoir sans cela (souvenez-vous de la règle d'or ;)) ! En effet, il devient difficile à ce moment là ce garder une cohérence permanente entre les données. Par exemple, on peut envisager créer un diaporama sans base de données, qui se contenterait d'afficher les images présentes dans un répertoire. Par contre, si l'on veut donner à ces images un titre ou le nom de celui qui a pris la photo, une base de vient nécessaire.
  • Données de configuration : pour moi, une BDD est axée sur le contenu. Les directives de configuration telles que des chemins racines, la langue d'une application ou autre donnée nécessaire à l'initialisation de celle-ci doivent être regroupées dans un espace qui leur est propre. A priori, il peut s'agir d'un fichier .ini ou XML, mais certainement pas d'une base de données (ou alors une BDD dédiée). Déjà, les paramètres de connexion à une base doivent bien être initialisés quelque part, je préfère alors y mettre l'ensemble des paramètres de l'application. En gros, je dirais qu'une base doit contenir les données qui servent à faire tourner l'application, pas à la lancer ... :)

2. Créer sa base de données

2.1 Conventions de nommage et de structure.
Comme pour le code, il est bon de se tenir à des conventions lorsque l'on crée une base de données. Voici celles que j'utilise :

  • 2.1.1 Utiliser un préfixe pour les noms de table.
    Il s'agit d'un "identifiant" qui permet de regrouper les tables par fonctionnalités. En général, il contient entre 2 et 4 lettres. Par exemple, pour le site d'une boutique appellée "Vraiment Pas Cher", tous mes noms de tables vont commencer par 'vpc_'. Dans l'optique d'un hébergement partageant plusieurs applications, cela me permet d'avoir plusieurs tables utilisateurs ou articles. En plus de différencier quelle table est utilisée par quelle application uniquement par son nom, c'est aussi utile dans phpMyAdmin pour les regrouper, quand les tables sont affichées par ordre alphabétique ! 8) Une seule application de portail pourrait également dissocier les différents types de données gérées : usr_ pour la gestion utilisateurs, cmd_ pour les commandes, prd_ pour les produits, etc...
  • 2.1.2 Utiliser des noms sans casse.
    En effet, si aujourd'hui vous vous servez de MySQL, sachez que la version Windows est insensible à la casse, mais pas la version Linux. D'autres systèmes ou bases de données peuvent être plus ou moins sensibles à la casse. C'est pourquoi j'utilise systématiquement des noms de table et de champs uniquement en minuscule.
  • 2.1.3 Utiliser des noms de table sans separation.
    En gros, ca revient à ne pas mettre de underscore ( _ ) dans un nom de table "normale". Nous verrons plus tard que cette règle possède une exception qui vient la confirmer ... ;)
  • 2.1.4 Noms de table au singulier
    Le nom d'une table doit décrire ce que contient une ligne de cette table. Les noms de tables se doivent donc d'être au singulier.
  • 2.1.5 Clés primaires.
    Chaque table possède une clé primaire numérique, dont le nom est id_. Le préfixe dont je parle plus haut n'est pas considéré comme faisant partie du nom de la table. Par exemple, une table supv_voyage aura une clé primaire du nom de 'id_voyage'. En MySQL, la clé primaire aura l'attribut auto_increment. Je me sers du type INT(10) UNSIGNED pour mes clés primaires.
  • 2.1.6 Langue de la base.
    Personnellement, j'utilise généralement l'anglais dans mes noms de table et de champs, mais le plus important est de rester constant dans la langue que l'on utilise. Nous resterons en français dans les exemples de cet article.

D'autres viendront au cours de l'article pour enrichir celles-ci, au moment où nous aborderons les notions qui justifient ces conventions.


2.2 Un premier schéma
Imaginons que l'on décide de créer un jeu en ligne nommé "Maxi Questions". Ce jeu consiste en des questions sur divers thèmes, et les joueurs peuvent y répondre. A chaque question, un joueur ayant répondu juste gagne 2 point, un joueur ayant répondu faux perd 1 point.
En prenant compte des données "classiques" dont aurait besoin ce type d'application, nous avons besoin pour l'utilisateur d'un login et d'un mot de passe. Il va nous falloir aussi connaitre son score. Une question a un libellé et un theme. Une réponse consiste en un texte et une correction (juste ou faux). Voyons le trés mauvais exemple duquel nous pouvons partir pour améliorer les choses :


Voyons maintenant comment modifier ce schéma pour qu'il soit rigoureux et performant :
2.2.1 Les clés étrangères.
Dans l'exemple précédent, la table des réponses utilise le pseudo pour savoir quel utilisateur à entré cette réponse. Cela pose plusieurs problèmes :
  • Tout d'abord, en terme de performances. En effet, effectuer une jointure sur un champ texte est bien plus exigeant que sur un champ numérique.
  • En terme de fonctionnalité. Imaginons que pour une raison ou pour un autre, nous décidions d'autoriser plusieurs pseudos identiques s'ils ont des mot de passe différents (a ne pas conseiller, mais il me fallait bien un exemple :P) ... Comment retrouver alors celui qui a répondu ?
Dans ce cas précis, il est préférable d'utiliser l'id_utilisateur pour faire la liaison entre mxq_reponse et mxq_utilisateur. C'est là qu'intervient une convention suplémentaire : les clés étrangères se font sur une clé primaire, et portent le nom de cette clé primaire.
Voici ce que donne le schéma à l'issue de cette première étape :

2.2.2 Regroupement des colonnes
Voici un des premiers aspects de notre règle d'or : "Ne pas répeter deux fois la même chose !!!" ! :)
En effet, le thème d'une question est ici saisi en toutes lettres dans la table question. Or, il se peut tout à fait qu'il existe plusieurs questions qui ont le même thême, c'est même à priori pour ça qu'il existe. Donc, si on ne veut pas répeter plusieurs fois le thême "Nature", il va falloir le centraliser. Le meilleur moyen est donc de créer une table qui va contenir les thêmes, et faire un clé étrangère dans la table des questions. Cette solution amène plusieurs avantages :

  • Si l'on veut changer le nom d'un thème, il ne suffit de le modifier qu'une seule fois pour que cela soit appliqué partout.
  • On évite les incohérences en entrant à chaque fois le nom du thême pour chaque nouvelle question. Si c'était le cas, nous ne sommes jamais à l'abris d'une faute de frappe, de casse, ou de doublons.
  • Si l'on veut faire un formulaire qui permet de choisir un thême, il est bien plus facile de créer une liste déroulante avec le contenu d'une table à part. L'identifiant du thême sera ensuite passé en paramètre au filtre.
  • Et du coup, on améliore les performances des requêtes : en effet, une comparaison sur un numérique est bien plus rapide que sur une chaine de caractères, de la même manière que pour les clés étrangères.
Un indice pour détecter les colonnes à externaliser est de savoir quelles vont être les colonnes qui seront selectionnables via une liste déroulante dans les différents formulaires de l'application. Voici le schéma avec les thêmes externalisés :

2.2.3 Les données déductibles
Voici encore une déclinaison de la regle d'or. En effet, pourquoi enregistrer le score, alors que nous pouvons le déduire trés facilement en fonction du nombre de réponses fausses et justes ? Le champ score de la table des utilisateurs est donc superflu. Nous pouvons l'enlever sans perdre une seule information :


Cette règle toutefois n'est pas systématique : en effet, on peut vouloir changer les attributions de score en cours de jeu, mais conserver les scores tels qu'ils étaient avant l'ancien barême. C'est assez inhabituel de changer les règles en cours de jeu, mais sachez que dans ce cas là, le champ "score" retrouve toute justification.

2.2.4 Penser aux données statistiques
Dans toute application, et plus particulièrement s'il s'agit d'une application web, il est indispensable de développer des outils statistiques. Dans notre cas, il peut être utile de savoir la moyenne du nombre de réponse à une question, du nombre d'inscrits pas jour, le temps de réponse moyen à une question, etc...
C'est pourquoi sur la plupart de mes tables, je crée un champ nommé date_creation, dont la valeur par défaut est la date de création de la ligne (en MySQL : NOW()). Afin de pouvoir obtenir un feedback efficace, je vais donc rajouter ce champ aux tables principales. Ce ne sera pas nécessaire pour la table des thèmes : en effet, il y a peu de chance qu'on ai besoin de savoir quand un thème a été créé. A la rigueur, si l'on veut savoir quand un thème a été utilisé pour la première fois, il suffit de trouver la date de création de la première question de ce thème ...

2.2.4 Eviter la suppression de données
La plupart des grosses applications sur des données plus ou moins sensibles évitent au possible de supprimer des données. Il peut s'agir de raisons toutes simples d'archivage de la table elle-même, ou pour éviter d'avoir à supprimer des données d'autres tables que l'on veut conserver et qui sont liées à la table principale. En effet, imaginons qu'on veuille supprimer un utilisateur de la base. Que faire alors de ses réponses ? Si on les conserver, elles deviendront des réponses orphelines. Si on les supprime, les statistiques deviendront complètement fausses.
Et imaginons que cet utilisateur décide de revenir tout en conservant son score, il serait bien plus facile de l'avoir simplement désactivé. Il suffirait alors de le réactiver pour le réintégrer au jeu.
C'est pourquoi la plupart des tables succeptibles d'avoir des données ne servant plus possèdent toutes un champ "actif", qui contiendra 0 ou 1 selon que la ligne est active ou non :


Il faudra prévoir dans notre application de ne choisir que les lignes actives lorsque cela sera nécéssaire. Par exemple, pour un thème, on peut ne sélectionner que les thèmes actifs lorsque l'on crée une question, mais choisir tous les thèmes quand on consulte l'historique ...

2.3 Optimisation
2.3.1 Les indexes "simples"
Nous avons déjà vu dans le chapitre précédent que le fait d'utiliser des clés étrangères numériques améliore les performances. Il existe un moyen de les améliorer encore plus avec les indexes. Un index consiste à stocker dans un espace accessible rapidement les valeurs d'un colonne. Ainsi, si l'on accède souvent à une colonne indexée, ce sera bien plus rapide que sur une colonne normale. Par exemple, je conseille de créer un index sur toutes les colonnes de clés étrangères avec MySQL. Pour la création d'index, il est facile de le faire avec phpMyAdmin. Pour en créer un à la main, référez vous à la documentation SQL.
Dans notre exemple, les indexes seraient positionnés sur mxq_reponse.id_question, mxq_reponse.id_utilisateur, mxq_quesiton.id_theme.

2.3.2 Les indexes uniques
Encore plus rapide que les indexes dits "simples", les index uniques peuvent se faire sur une colonne ou un groupe de colonnes qui n'aura pas deux fois la même valeur. Dans notre exemple, si l'on considère qu'un même utilisateur ne peut pas répondre deux fois à la même question, cela signifie que le couple id_question/id_utilisateur n'aura jamais deux fois la même valeur. Une index unique sur ce couple de colonne est donc tout à fait opportun : mxq_reponse.id_utilisateur et mxq_reponse.id_question ! Cet index unique viendra s'ajouter aux indexes simples dont j'ai parlé plus haut. Une coup d'oeil sur la documentation SQL vous renseignera sur la syntaxe à utiliser (avec l'option "UNIQUE").

2.4 Notions supplémentaires
Les clés étrangères que nous avons vu jusqu'ici permettent de créer une relation 1,N (le 1 étant représenté par un losange blanc, et le N par un rond noir). En gros, cela veut dire qu'un utilisateur peut correspondre à plusieurs (N) réponses, mais qu'une réponse ne peut correspondre qu'à un utilisateur et un seul (1) ... Il s'agit de la cardinalité.

En plus ce cette cardinalité, il en existe deux autres :

2.4.1 Les relations "N,N"
Imaginons que nous voulions implémenter un système de groupes d'utilisateurs. En gros, un utilisateur peut appartenir plusieurs groupes (N), et un groupe peut avoir plusieurs utilisateurs (N) ... On est donc dans le cas d'une cardinalité N,N. Ce type de relation n'existe pas de manière indépendante. Il a besoin d'un table intermédiaire, qu'on peut appeler "Tables N,N". C'est là qu'intervient l'exception aux règles de nommage des tables dont j'ai parlé plus haut. En effet, les tables N,N ont le nom de chacune des deux tables à mettre en relation séparées par un underscore ( _ ) (l'ordre ayant peu d'importance : celle qui vous parait le plus importante est à mettre en premier) ... Cette table va contenir deux clés étrangères vers les deux tables à relier, ces deux clés étrangères formant un index unique. En effet, un groupe ne peut pas contenir deux fois le même utilisateur ... Le résultat est le suivant :


Ce type de relation est utilisé par exemple pour un caddie sur un site de ecommerce : un produit peut-être dans plusieurs caddies, et un caddie peut contenir plusieurs produits.

2.4.2 Les relations "1,1"
Par abus de langage, on peut considérer que la relation 0...1,1 rentre également dans le même cadre. Cela signifie qu'une table a une correspondance directe avec aucune ou une seule ligne de l'autre. En pratique, il s'agit d'une clé étrangère associée à un index unique. Cela peut servir lorsque l'on sait que des données d'une table ne seront pas forcément nécessaire pour toutes ses lignes. Imaginons par exemple que certains utilisateurs sont des administrateurs, et qu'ils ont un mot de passe différent pour accéder à la partie administration du site.
La solution de facilité serait de rajouter un champ 'mot_passe_admin' dans la table utilisateur. Mais il ne faut pas créer de champs dont la plupart seront vides. Il vaut bien mieux créer une table d'administrateur, reliée à la table utilisateur. Nous savons alors que chaque utilisateur à alors une correspondance dans la table adimnistrateur (s'il est admin) ou aucune (s'il est simple joueur). Voilà ce que cela donne :


L'index unique (non visible sur le schéma) sert à s'assurer que l'utilisateur n'aura pas deux mot de passes possibles pour la partie administration. Par similitude avec les tables question/theme, on voit bien que si l'index unique n'existait pas, on pourrait avoir plusieurs lignes administrateur correspondant à un seul utilisateur ...

Les schémas de base de données qui illustreront les articles sont fait avec un outil OpenSource performant que je vous conseille : DBDesigner (Window & Linux). Il est optimisé pour MySQL, mais peut servir d'outil de conception pour tout type de base.

Pratiques de programmation PHP #0 : Présentation et conventions.

Plus de deux mois après l'introduction sur cette série d'articles sur les pratiques de programmation en PHP, voici le premier opus de présentation.

Conventions

Afin de réduire les lourdeurs du texte, je parlerai de BDD pour dire "Base de Données", et de POO pour "Programmation Orientée Objet". Les autres abréviations seront précisées au début de chaque article.

La plupart des pratiques qui seront abordées seront valables pour PHP4, et les spécificités de PHP5 qui seront abordées seront différenciées visiblement dans les articles. Le moteur de BDD utilisé sera MySQL, le plus fréquemment utilisé avec PHP (bien que PHP5 propose maintenant une intégration avec SQLite).

Avant de commencer

La règle d'or qui vous suivra durant toute votre lecture sera : "NE PAS REPETER PLUSIEURS FOIS LA MEME CHOSE !!!". Si vous ne voyez pas déjà à quoi je fais allusion, ne vous inquiétez pas, vous aurez l'occasion de la revoir très souvent dans les différents articles qui suivront. En effet, si la règle reste la même, elle peut prendre bien des formes différentes selon la situation.

Je vous rappelle à nouveau qu'il s'agit là de mes pratiques, et aucunement d'une méthode universelle à suivre absolument. Seulement cela peut poser à mon avis de bonnes bases pour un code efficace et clair : en tout cas, ça marche pour moi ! :P

Et maintenant ?

Voici un petit aperçu des domaines qui seront abordés. Cette liste est susceptible d'évoluer durant la rédaction en fonction des besoin et remarques qui seront exprimés dans les commentaires ou de mes envies :

  • La base de données
  • L'organisation des fichiers de travail et du code
  • Ergonomie et astuces diverses
  • La POO

La première partie pratique que nous aborderons est donc la base de données ... a très bientôt ! ;)

17 septembre 2004

Tween2 : quand Tween rencontre EventDispatcher

Sortant tout juste d'un développement utilisant des composants Macromedia à profusion, j'ai pas la même occasion eu l'opportunité de commencer à fréquenter le model évenementiel de ceux-ci, introduit par la classe EventDispatcher ... En effet, jusqu'à aujourd'hui, j'étais assez satisfait des services de AsBroadcaster (d'ailleurs, je ne trouve toujours pas d'utilité à la classe BroadcasterMX, offrant exactement la même chose mais en AS, donc moins performant) ...

Il ne m'a pas fallu longtemps pour me rendre compte de l'interêt de ce nouveau système, que ce soit en terme d'externalisation de l'interception des événements (même si c'était déjà possible avant), de performance acrues du fait de la diffusion propre à chaque évenement, mais aussi et surtout de souplesse grâce à la délégation d'évenement. D'ailleurs, je suis bien fan de la classe EventDelegate à mon sens bien plus élégante que Delegate, fourni avec la MAJ 7.2 de Flash.

J'ai donc suivi la voie tracée par Grant Skinner, qui propose ses classes XML2 et LoadVars2, utilisant EventDispatcher, en créant ma classe fille de l'illustre Tween de Penner (maintenant intégrée à la distribution de Flash MX 2004 en tant que mx.transitions.Tween), qui utilise dorénavant EventDispatcher.

Deux petits avantages mineurs amenés aussi par cette version sont qu'on ne peut du coup plus utiliser les méthodes "old-school" directement avec l'objet Tween, qui sont devenues des membres privés, forcant à externaliser le code sous forme d'écouteurs, et également le typage fort des paramètres du constructeur ... En gros, ca oblige un peu plus à faire les choses correctement ;)

Exemple d'utilisation :import com.lalex.animation.Tween2;
import com.robertpenner.easing.Expo;
var tw:Tween2 = new Tween2(myClip, "_x", Expo.easeOut, 100, 300, 32);
var listen:Object = {};
listen.motionChanged = function(e:Object) {
   trace("Clip position : " + e.position);
}
tw.addEventListener("motionChanged", listen);

On peut imaginer que les "classes" (?) telles que Mouse; Stage ou Keyboard pourraient être remplacées par leur "version 2" assez facilement (sans héritage celle-là par contre), mais cela mérite des tests : je ne suis pas persuadé des performances prodiguées par des évenements qui interviennent aussi souvent ... :o

::Télécharger Tween2.zip::

13 septembre 2004

Essential ActionScript 2.0 : la review

Colin Moock s'est imposé depuis son livre ActionScript for Flash MX : the Definitive Guide comme ayant écris l'ouvrage de référence des programmeurs Flash. Aujourd'hui, il remet le couvert et se penche sur le "nouveau" langage de Macromedia, introduit dans Flash MX 2004, avec Essential ActionScript 2.0.

Loin de remplacer son précédant ouvrage, cette nouvelle référence se veut comme étant un complément de celui-ci. En effet, alors que le premier était une liste quasi-exhaustive de l'AS1, EAS2 s'attaque à la philosophie de développement complètement revue d'AS2, du fait de son orientation objet. Il s'adresse donc en bloc aux programmeurs AS1 intermédiaires, ainsi qu'aux développeurs objets issus de langages plus "traditionnels" tels que Java ou C++, et aux programmeurs AS2 intermédiaires et même confirmés.

La première partie du livre aborde la syntaxe AS2. Mêlant habilement l'initiation à la programmation objet, et la description des instructions introduites par la nouvelle mouture d'ActionScript, cette partie fourni au futur programmeur AS2 tous les outils nécessaires à ses premiers développements. N'oubliant pas d'aller au fond des choses, Colin Moock décortique chaque instruction à l'extrême, soulignant les particularités des nombreuses situations qui peuvent se présenter au codeur face aux instructions AS2.
Parfois répétitif dans son discours, mais jamais rébarbatif, il mets systématiquement le doigt sur les erreurs que l'on fait fréquemment lorsque l'on aborde l'AS2, et souligne les avantages indéniables qu'apporte ce nouveau langage, mais aussi cette nouvelle façon d'aborder le développement dans Flash.
Non content de donner une explication théorique exhaustive des syntaxes de base, des exemples concrets sont apportés avant d’aborder chaque nouvelle notion importante, nous guidant ainsi au travers du développement d'une classe ImageViewer, dans laquelle sont utilisées les notions précédemment abordées de manière plus formelle.

La deuxième partie aborde les méthodes à utiliser lors du développement d'application. L'auteur nous y présente tout d'abord sa méthode de travail "de base" notamment en terme de hiérarchie des dossiers et documents. Sans être révolutionnaire, elle a le mérite d'être simple, claire et efficace. Si vous n'avez pas déjà vos propres habitudes de développement objet, je dirais tout simplement que l'essayer c'est l'adopter, et si vous les avez déjà, vous pourrez ainsi les confronter à celles qui sont données dans ce chapitre.
Les chapitres suivant nous emmènent vers la programmation "visuelle", tout d'abord en abordant les composants et leur utilisation, en exposant notamment leur intégration à une application concrète de convertisseur de devises, puis en consacrant un chapitre entier à l'héritage de MovieClip, nous en dévoilant ainsi toutes ses subtilités, et également les aspects à prendre en compte pour effectuer un choix entre l'héritage et la composition avec MovieClip.
Cette partie se finit en décortiquant les différents moyens mis à notre disposition pour distribuer un code, que ce soit en OpenSource ou sans mettre les fichiers sources à dispositions des utilisateurs, en énonçant clairement les avantages, inconvénients et difficultés de chacune des méthodes.

La troisième partie se penche sur 4 Design Patterns, parmi les plus utilisés dans Flash que sont l'Observer, le Singleton, le Model-View-Controller (MVC) et la Délégation d'événements. Plus qu'une description approfondie de ces patterns, il s'agit plutôt dans cette partie de montrer leur utilité au sein d'applications AS2, ainsi que leur implémentation au sein d'exemple toujours aussi concrets.

Je ne peux que supposer la réaction des différentes catégories de lecteurs visées par ce livre :

  • Pour les développeurs AS1, il introduit en douceur mais également en profondeur toutes les subtilités de AS2. Etant déjà convaincu, je ne peux que suggérer, mais je dirais que ce livre peut réussir à convaincre ceux qui sont encore réfractaire à l'AS2, en soulignant tous les avantages que cela emmène, que ce soit en terme d'organisation du code, mais aussi d'aide au développement prodiguée par les erreurs de compilation. Un coup de chapeau concernant le détail exhaustif des différentes possibilités qui existent quand on surcharge une méthode, et concernant le chapitre sur le gestion d'erreurs qui est un bijou de précision qui sont tout deux des bijoux de précision, allant vraiment au fond des choses.
  • Pour les développeurs Objet découvrant l'AS2, en plus de décrire précisément les différences avec des langages reconnus tels que Java, l'ouvrage s'attache à faire découvrir la philosophie de développement à aborder avec Flash, qui diffère de par son orientation clairement graphique. Pour ceux qui sont déjà à l'aise avec les Design Patterns, il s'agit aussi de voir de bonnes implémentations AS2, pour ainsi se plonger dans le langage.
  • Pour les développeurs AS2, qu'ils soient intermédiaires ou aguerris, il s'agit là d'une revue de détail, répondant la plupart du temps aux questions que l'on peut se poser sur quelques finesses du langage. Si l'on peut finir le livre en pensant ne pas avoir appris énormément par rapport à ce que l'on savait déjà, le simple fait de se remettre à programmer après cette lecture change la manière dont on voit le code. En gros, pour moi, j'ai vraiment l'impression que les qualités pédagogiques de l'auteur ont clarifié certaines notions qui m'étaient connues jusque là, mais peut-être un peu embrouillées ! :$

En conclusion, je dirais que Colin Moock nous offre un nouvel ouvrage de référence, qui saura satisfaire de par son contenu riche et détaillé tous les développeurs accomplis ou en devenir. Véritable fondation des connaissances à posséder en ActionScript 2.0, il s'avère la rampe de lancement idéale pour aller encore plus loin dans la Programmation Orientée Objet avec AS2.

Pour finir, je vous conseille de lire suite à ce livre celui sur les Design Patterns par le Gang Of Four, que j'ai moi-même lu sur les conseil de Francis BOURRE, et qui est d'ailleurs cités par Colin Moock dans la troisième partie de l'ouvrage. Il s'agit là d'une véritable mine d'or sur les DP.

3 septembre 2004

BLDoc : documentation Javadoc-like pour AS2

Je n'aime pas reprendre les nouvelles, mais ce n'est pas sans une certaine fébrilité que j'ai appris sur le site de Chafic Kazoun et Samuel Neff (et en français chez petepx) que la béta public de BLDoc 1.0 était enfin disponible.

Depuis les prémices de ce projet, il s'est annoncé comme étant le logiciel le plus prometteur en la matière. Quelques échanges de mails avec Samuel Neff m'avaient fait entrevoir les possibilités énormes du soft, et les avantages qu'il représentait pour la documentation des développements AS2. Un problème juridique concernant mon propre développement d'un parseur de doc en PHP m'ayant empêché de participer à l'alpha privée, je vais enfin pouvoir l'essayer, et ainsi reprendre le développement de ma version, afin qu'elle soit compatible avec BLDoc ! 8)

Foncez donc, l'inscription à la beta publique c'est chez B-Line Express :)

25 août 2004

StageManager 0.1

Pour continuer dans la série des 0.1, voici aujourd'hui une classe que je termine tout juste, servant à gérer le positionnement et la taille de clips dans des animations Flash plein écran. En effet, alors qu'il est maintenant possible de créer des site Flash occupant la totalité d'une fenêtre grâce à la classe Stage, trés peu de site l'utilisent ...

J'ai donc crée ce set de classe afin de faciliter la gestion des clips dans cette situation précise. On peut ainsi décider de la position d'un clip et de son alignement sur la scène, ainsi que sa taille. Il est ainsi possible de profiter presque de la même souplesse qu'offre HTML pour des applications qui pourraient ainsi occuper la totalité de la surface de l'animation.

Voici un exemple d'utilisation:import com.lalex.stage.StageManager;
import com.lalex.movieclip.SuperClip;
var stageMgr:StageManager = StageManager.getInstance();
// Alignement en haut à gauche, sans marge, de largeur 100%, hauteur inchangée
stageMgr.addElement(new SuperClip(_root.taskbar), {align:"left,top", margin:0, size:"100%,none"});
// Aligné à droite, avec une marge (à droite) de 10px
stageMgr.addElement(new SuperClip(_root.rightMenu), {align:"right", margin:10});
// Aligné en bas avec une marge (verticale) de 5% de la hauteur de l'anim
stageMgr.addElement(new SuperClip(_root.footer), {align:"bottom", margin:"none,5%"});

Bien qu'il manque encore des fonctionnalités ou des options de positionnement, StageManager est dors et déjà utilisable, et la classe SuperClip devrait donner naissance à une fonctionnalité similaire aux méthodes de positionnement de Stéphan Guénette.

L'utilisation de la composition dans SuperClip permet ainsi de gérer des clips dont le redimensionnement devrait se faire de manière spécifique, comme des composants par exemple. Il suffirait alors de créer une classe qui implémente l'interface IClip. Bref, en créant la classe appropriée, vous pouvez gérer virtuellement tout objet qui s'affiche à l'écran. 8)

::Télécharger StageManager.zip::

24 août 2004

Paris, c'est fini ...

... et dire que c'était la ville de mon premier vrai boulot de Flasheur ! :(
Paris, c'est fini ...
etc...

Aprés une reflexion intense et trés désagréable, ma situation familiale m'impose aujourd'hui de rester sur la Côté d'Azur. J'ai du donc avec énormément de regret décliner l'offre de l'agence Les Chinois, auprés desquels je m'excuse à nouveau publiquement pour cet impair trés embarassant ... et qui me prive également d'exercer mon métier dans des conditions idéales, avec une équipe jeune et trés symapthique, et dont je me faisais déjà une grande joie. :(

Du coup, si vous êtes un bon codeur AS2 sur Paris, essayez de leur écrire pour tenter votre chance auprés de cette société qui promet un travail passionant et d'un haut niveau technique ! :)

Me concernant, je me retrouve du coup à nouveau sur le marché du travail, avec une contrainte qui n'est pas des moindre : rester dans la région niçoise ... :o Je suis donc ouvert à toute proposition, que ce soit dans le développement Flash, qui a bien évidemment ma préférence, ou le développement PHP. Je suis également ouvert à tout poste de programmeur dans d'autres domaines (du web si possible), si on veut bien me laisser le temps de m'y mettre, sachant que j'apprends trés vite en général. Un poste en télétravail est également tout à fait envisageable ! ;)

21 août 2004

Livre avec pages à tourner

En me balandant sur l'excellent blog Quasimondo, je suis tombé sur un lien vers une librairie permettant de reproduire l'effet trés en vogue d'un livre dont on tourne les pages, fait par Ipari Grafika. Il dispose d'un foule d'options, comme la transparence, l'utilisation de zones pour tourner une page, des pages solides (typiquement la couverture d'un livre). La demonstration est assez édifiante, voire impressionante !

Vous pouvez même télécharger les sources ! 8)

20 août 2004

Propriétés virtuelles et méthodes

Dans un post précédent, on me demandait s'il n'était pas plus pratique de créer directement des propriétés virtuelles plutôt que des méthodes accesseurs et des propriétés virtuelles qui utilisent ces méthodes. Ma réponse est simple : NON !

C'est tout à fait flagrant dans le cadre de la problématique qui se pose à moi actuellement. Je désire créer une classe qui pourrait prendre en entrée un objet "affichable" qu'il est possible de positionner et de déplacer au moyen de méthodes prédéfinies. Je voudrais également que les composants v2 puissent être gérés par cette application.

Or, dans les composants v2, et plus particulièrement la classe UIObject, il est en effet possible de déplacer un composant et de le redimensionner respectivement au moyen des méthode move() et setSize() ... Par contre, le seul moyen de récupérer ses dimensions ou sa position est d'utiliser les propriétés virtuelles x, y, width, height ... Aucune méthode getX(), getY(), getWidth() ou getHeight() n'est au rendez-vous.

Il m'est donc impossible de créer une interface dont je puisse me servir pour le typage du paramètre de ma méthode, étant donné que les propriétés virtuelles ne peuvent être renseignées dans une interface ... et donc d'utiliser ces propriétés dans ma méthode ! :x A moins d'utiliser des hacks pour déjouer les erreurs du compilateur, mais les interfaces servant uniquement au moment de la compilation, ca n'aurait aucun sens ! 8|

Voici le code de Macromedia :/**
* width of object
* Read-Only:  use setSize() to change.
* @helpid 3982
*/

        function get width():Number
        {
                return _width;
        }
/**
* height of object
* Read-Only:  use setSize() to change.
* @helpid 3964
*/

        function get height():Number
        {
                return _height;
        }
/**
* x = left of object
* Read-Only:  use move() to change.
* @helpid 3983
*/

        function get x():Number
        {
                return _x;
        }
/**
* y = top of object
* Read-Only:  use move() to change.
* @helpid 3984
*/

        function get y():Number
        {
                return _y;
        }
Imaginez ensuite que le code ait été le suivant :/**
* width of object
* Read-Only:  use setSize() to change.
* @helpid 3982
*/

        function getWidth():Number
        {
                return _width;
        }
/**
* width of object
* Read-Only:  use setSize() to change.
* @helpid 3982
*/

        function get width():Number
        {
                return getWidth();
        }
// etc ... pour les autres propriétés

J'aurais alors pu créer une interface listant les méthodes setWidth(), setHeight(), etc... et ainsi utiliser mes classes "home-made" ou des composants. Les interfaces n'ont vraisemblablement pas été une préoccupation principale de Macromedia. D'ailleurs, on n'en trouve aucune dans les classes fournies avec Flash ! :o

19 août 2004

SuperColor 0.3 : le contraste en plus

On vient de me faire remarquer que la dernière version du color_toolkit de Robert PENNER permet également de gérer le contraste d'un clip. Je me suis donc empressé de rajouter ces fonctionnalités à ma classe SuperColor qui passe pour l'occasion en version 0.3.

J'en ai également profité pour modifier le nom du package : en effet, avec un nom de domaine comme www.lalex.com, il est plus logique de garder com.lalex en nom de package ... même si le coté commercial ne me plait pas vraiment ! :o

/**
 * Super Color
 * extended Color class
 *
 * -----------------------------------
 *
 * Based on <a href="http://www.robertpenner.com/">Robert PENNER</a> Color Toolkit v1.3
 *
 * Properties
 *
 *   _brightness
 *   _brightOffset
 *   _contrast
 *   _negative
 *
 *   _rgb
 *   _rgbStr
 *   _red
 *   _green
 *   _blue
 *
 *   _redPercent
 *   _greenPercent
 *   _bluePercent
 *
 *   _redOffset
 *   _greenOffset
 *   _blueOffset
 *
 * Methods
 *
 *   getTarget()
 *
 *   setRGBStr()
 *   getRGBStr()
 *   setRGB2()
 *   getRGB2()
 *   reset()
 *
 *   setBrightness()
 *   getBrightness()
 *   setBrightOffset()
 *   getBrightOffset()
 *       setContrast()
 *   getContrast()
 *
 *   getNegative()
 *   setNegative()
 *   invert()
 *
 *   setTint()
 *   getTint()
 *   setTint2()
 *   getTint2()
 *   setTintOffset()
 *   getTintOffset()
 *
 *   setRed()
 *   getRed()
 *   setGreen()
 *   getGreen()
 *   setBlue()
 *   getBlue()
 *
 *   setRedPercent()
 *   getRedPercent()
 *   setGreenPercent()
 *   getGreenPercent()
 *   setBluePercent()
 *   getBluePercent()
 *
 *   setRedOffset()
 *   getRedOffset()
 *   setGreenOffset()
 *   getGreenOffset()
 *   setBlueOffset()
 *   getBlueOffset()
 *
 * @version 0.3
 * @author <a href="http://www.lalex.com/">LAlex</a>
 * @since 19/08/2004
 */

class com.lalex.movieclip.SuperColor extends Color {
        private var _targetMC:MovieClip;
        /**
         * Constructor
         * @param clip Clip to apply the color
         */

        function SuperColor(clip:MovieClip) {
                super(clip);
                _targetMC = clip;
        }
        /**
         * Returns affected clip
         * @return Targeted MovieClip
         */

        public function getTarget():MovieClip {
                return _targetMC;
        }
                // ----------o RGB


        /**
         * Set an RGB value from an hexadecimal string
         * @param hexStr Hexadecimal value string
         */

        public function setRGBStr(hexStr:String) {
                // grab the last six characters of the string
                hexStr = hexStr.substr (-6, 6);
                setRGB (parseInt (hexStr, 16));
        }
        /**
         * Get the RGB value as a string
         * @return Hexadecimal value string
         */

        public function getRGBStr():String {
                var hexStr:String = getRGB().toString(16);
                // fill in zeroes as needed
                var toFill:Number = 6 - hexStr.length;
                while (toFill--) hexStr = "0" + hexStr;
                return hexStr.toUpperCase();
        }
        /**
         * Set red, green, and blue with normal numbers
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         */

        public function setRGB2(r:Number, g:Number, b:Number) {
                setRGB (r << 16 | g << 8 | b);
        } // Branden Hall - <a href="http://www.figleaf.com">www.figleaf.com</a>
        /**
         * @return Object with r, g, and b properties
         */

        public function getRGB2():Object {
                var t = getTransform();
                return {r:t.rb, g:t.gb, b:t.bb};
        }
        /**
         * Reset the color object to normal
         */

        public function reset() : Void {
                setTransform ({ra:100, ga:100, ba:100, rb:0, gb:0, bb:0});
        }


                // ----------o Brightness


        /**     
         * Brighten just like Property Inspector of MovieClip
         * @param val Brightness between -100 and 100
         */

        public function setBrightness(val:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        ra = ga = ba =  100 - Math.abs(val);
                        rb = gb = bb = (val > 0) ? val * (256/100) : 0;
                }
                setTransform(trans);
        }
        /**
         * @return Brightness set with setBrightness
         * @see setBrightness
         */

        public function getBrightness():Number {
                var trans:Object = getTransform();
                with (trans) {
                        return rb ? 100 - ra : ra - 100;
                }
        }
        /**
         * Set brightness offset
         * @param val Offset between -255 and 255
         */

        public function setBrightOffset(val:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        rb = gb = bb = val;
                }
                setTransform(trans);
        }
        /**
         * @return Brightness set with setBrightOffset
         * @see setBrightOffset
         */

        public function getBrightOffset():Number {
                return getTransform().rb;
        }


                // ----------o Contrast
        /**
         * Set contrast
         * @param val Percent between -100 and 100
         */

        public function setContrast(val:Number) {
                var trans:Object = {};
                trans.ra = trans.ga = trans.ba = val;
                trans.rb = trans.gb = trans.bb = 128 - (128/100 * val);
                setTransform(trans);
        }
        /**
         * @return Contrast set with setContrast
         * @see setContrast
         */

        public function getContrast():Number {
            return getTransform().ra;
        }
                // ----------o Negative and invert

        /**
         * Produce a negative image of the normal appearance
         * @param percent Between 0 and 100
         */

        public function setNegative(percent:Number) {
            var t:Object = {};
            t.ra = t.ga = t.ba = 100 - 2 * percent;
            t.rb = t.gb = t.bb = percent * (255/100);
            setTransform (t);
        }
        /**
         * @return Negative percentage
         * @see setNegative
         */

        public function getNegative():Number {
            return getTransform().rb * (100/255);
        }
        /**
         * Invert the current color values
         */

        public function invert() {
                var trans:Object = getTransform();
                with (trans) {
                        ra = -ra;
                        ga = -ga;
                        ba = -ba;
                        rb = 255 - rb;
                        gb = 255 - gb;
                        bb = 255 - bb;
                }
                setTransform (trans);
        }


                // ----------o Tint


        /**
         * Tint with a color just like Property Inspector
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         * @param percent Between 0 and 100
         */

        public function setTint(r:Number, g:Number, b:Number, percent:Number) {
                var ratio = percent / 100;
                var trans = {rb:r*ratio, gb:g*ratio, bb:b*ratio};
                trans.ra = trans.ga = trans.ba = 100 - percent;
                setTransform (trans);
        }
        /**
         * @return tint object containing r, g, b, and percent properties
         * @see setTint
         */

        public function getTint():Object {
                var trans:Object = getTransform();
                var tint:Object = {percent: 100 - trans.ra};
                var ratio:Number = 100 / tint.percent;
                tint.r = trans.rb * ratio;
                tint.g = trans.gb * ratio;
                tint.b = trans.bb * ratio;
                return tint;
        }
        /**
         * tint with a color - alternate approach
         * @param rgb Color number between 0 and 0xFFFFFF
         * @param percent Between 0 and 100
         */

        public function setTint2(rgb:Number, percent:Number) {
                var r:Number = (rgb >> 16) ;
                var g:Number = (rgb >> <img src="http://common.lalex.com/themes/devblog/smilies/icon_cool.gif" alt="8)" class="smiley" /> & 0xFF;
                var b:Number = rgb & 0xFF;
                var ratio:Number = percent / 100;
                var trans:Object = {rb:r*ratio, gb:g*ratio, bb:b*ratio};
                trans.ra = trans.ga = trans.ba = 100 - percent;
                setTransform (trans);
        }
        /**
         * @return a tint object containing rgb (a 0xFFFFFF number) and percent properties
         * @see setTint2
         */

        public function getTint2():Object {
                var trans:Object = getTransform();
                var tint:Object = {percent: 100 - trans.ra};
                var ratio:Number = 100 / tint.percent;
                tint.rgb = (trans.rb*ratio)<<16 | (trans.gb*ratio)<<8 | trans.bb*ratio;
                return tint;
        }


                // ----------o Tint offset


        /**
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         */

        public function setTintOffset(r:Number, g:Number, b:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        rb = r;
                        gb = g;
                        bb = b;
                }
                setTransform (trans);
        }
        /**
         * @return Object containing r, g, b properties
         * @see setTintOffset
         */

        public function getTintOffset():Object {
                var t:Object = getTransform();
                return {r:t.rb, g:t.gb, b:t.bb};
        }


                // ----------o Color values

        /**
         * Set red value
         * @param amount Between 0 and 255
         */

        public function setRed(amount:Number) {
                var t = getTransform();
                setRGB (amount << 16 | t.gb << 8 | t.bb);
        }
        /**
         * Get red value
         * @return Value between 0 and 255
         * @see setRed
         */

        public function getRed():Number {
                return getTransform().rb;
        }
        /**
         * Set green value
         * @param amount Between 0 and 255
         */

        public function setGreen(amount:Number) {
                var t = getTransform();
                setRGB (t.rb << 16 | amount << 8 | t.bb);
        }
        /**
         * Get green value
         * @return Value between 0 and 255
         * @see setGreen
         */

        public function getGreen():Number {
                return getTransform().gb;
        }
        /**
         * Set blue value
         * @param amount Between 0 and 255
         */

        public function setBlue(amount:Number) {
                var t = getTransform();
                setRGB (t.rb << 16 | t.gb << 8 | amount);
        }
        /**
         * Get blue value
         * @return Value between 0 and 255
         * @see setBlue
         */

        public function getBlue():Number {
                return getTransform().bb;
        }


                // ----------o Color percentages

        /**
         * Set red percentage
         * @param percent Between -100 and 100
         */

        public function setRedPercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ra = percent;
                setTransform (trans);
        }
        /**
         * Get red percentage
         * @return Value between -100 and 100
         * @see setRedPercent
         */

        public function getRedPercent():Number {
                return getTransform().ra;
        }
        /**
         * Set green percentage
         * @param percent Between -100 and 100
         */

        public function setGreenPercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ga = percent;
                setTransform (trans);
        }
        /**
         * Get green percentage
         * @return Value between -100 and 100
         * @see setGreenPercent
         */

        public function getGreenPercent():Number {
                return getTransform().ga;
        }
        /**
         * Set blue percentage
         * @param percent Between -100 and 100
         */

        public function setBluePercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ba = percent;
                setTransform (trans);
        }
        /**
         * Get blue percentage
         * @return Value between -100 and 100
         * @see setBluePercent
         */

        public function getBluePercent():Number {
                return getTransform().ba;
        }


                // ----------o Color offsets

        /**
         * Set red offset
         * @param offset Between -255 and 255
         */

        public function setRedOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.rb = offset;
                setTransform (trans);
        }
        /**
         * Get red offset
         * @return Value between -255 and 255
         * @see setRedOffset
         */

        public function getRedOffset():Number {
                return getTransform().rb;
        }
        /**
         * Set green offset
         * @param offset Between -255 and 255
         */

        public function setGreenOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.gb = offset;
                setTransform (trans);
        }
        /**
         * Get green offset
         * @return Value between -255 and 255
         * @see setGreenOffset
         */

        public function getGreenOffset():Number {
                return getTransform().gb;
        }
        /**
         * Set blue offset
         * @param offset Between -255 and 255
         */

        public function setBlueOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.bb = offset;
                setTransform (trans);
        }
        /**
         * Get blue offset
         * @return Value between -255 and 255
         * @see setBlueOffset
         */

        public function getBlueOffset():Number {
                return getTransform().bb;
        }

                // ----------o Getter/Setter

        /**
         * RGB value
         * @type Number
         */

        function set _rgb(val:Number) {
                setRGB(val);
        }
        function get _rgb():Number {
                return getRGB();
        }
        /**
         * RGB value
         * @type String
         */

        function set _rgbStr(val:String) {
                setRGBStr(val);
        }
        function get _rgbStr():String {
                return getRGBStr();
        }
        /**
         * Brightness value
         * @type Number
         */

        function set _brightness(b:Number) {
                setBrightness(b);
        }
        function get _brightness():Number {
                return getBrightness();
        }
        /**
         * Brightness offset
         * @type Number
         */

        function set _brightOffset (b:Number) {
                setBrightOffset(b);
        }
        public function get _brightOffset():Number {
                return getBrightOffset();
        }
        /**
         * Contrast
         * @type Number
         */

        function set _contrast (c:Number) {
                setContrast(c);
        }
        public function get _contrast():Number {
                return getContrast();
        }
        /**
         * Negative percentage
         * @type Number
         */

        public function set _negative(p:Number) {
                setNegative(p);
        }
        public function get _negative():Number {
                return getNegative();
        }
        /**
         * Red value
         * @type Number
         */

        public function set _red(v:Number) {
                setRed(v);
        }
        public function get _red():Number {
                return getRed();
        }
        /**
         * Green value
         * @type Number
         */

        public function set _green(v:Number) {
                setGreen(v);
        }
        public function get _green():Number {
                return getGreen();
        }
        /**
         * Blue value
         * @type Number
         */

        public function set _blue(v:Number) {
                setBlue(v);
        }
        public function get _blue():Number {
                return getBlue();
        }
        /**
         * Red percentage
         * @type Number
         */

        public function set _redPercent(v:Number) {
                setRedPercent(v);
        }
        public function get _redPercent():Number {
                return getRedPercent();
        }
        /**
         * Green percentage
         * @type Number
         */

        public function set _greenPercent(v:Number) {
                setGreenPercent(v);
        }
        public function get _greenPercent():Number {
                return getGreenPercent();
        }
        /**
         * Blue percentage
         * @type Number
         */

        public function set _bluePercent(v:Number) {
                setBluePercent(v);
        }
        public function get _bluePercent():Number {
                return getBluePercent();
        }
        /**
         * Red offset
         * @type Number
         */

        public function set _redOffset(v:Number) {
                setRedOffset(v);
        }
        public function get _redOffset():Number {
                return getRedOffset();
        }
        /**
         * Green offset
         * @type Number
         */

        public function set _greenOffset(v:Number) {
                setGreenOffset(v);
        }
        public function get _greenOffset():Number {
                return getGreenOffset();
        }
        /**
         * Blue offset
         * @type Number
         */

        public function set _blueOffset(v:Number) {
                setBlueOffset(v);
        }
        public function get _blueOffset():Number {
                return getBlueOffset();
        }
}

::Télécharger SuperColor.zip::

18 août 2004

Deformation d'image : un trés bon article

Il fut un temps durant lequel j'ai essayé désespérement de déformer une image via ActionScript. Aucun des articles que j'avais trouvé sur le sujet n'étaient assez clairs, alors je me suis lancé dans des calculs trigonométriques interminables, accompagnés de nombreux schémas qui ont fait souffrir mon pauvre bloc-note pendant quelques temps. D'ailleurs, ma bibliothèque sur les matrices et vecteurs a été en partie développée dans ce but.

Alors que j'avais laissé tomber ce projet, n'en ayant pas réellement besoin dans l'immédiat, dehix de l'équipe Toy-Toy m'a envoyé vers un trés bon article sur GotoAndPlay.it : The art of scriptable skew. Je vous le conseille à mon tour : il est vraiment trés clair et trés simple à mettre en oeuvre ! 8)

17 août 2004

Au sommaire ...

Aprés de petites "vacances d'ordi" (mais seulement d'ordi, le boulot a continué), le temps de fêter les 25 ans de mes pieds, de reformater ma machine, et de nettoyer une à une les touches de mon clavier au coton-tige, je reviens donc sur la toile, et vous proposerait bientôt le premier article de ma série sur la programmation PHP/MySQL ... le temps de le réécrire car il a subit une combustion spontanée lors du formatage précédemment cité ! :$

Je travaille également sur lecodage "from scratch" du site Fast2reply (qui ne sera jamais fini à l'heure) sans compter le Zend Contest, l'apprentissage des rudiments de Flash Communication Server MX et de Unity 2, histoire de me tenir un peu à la page sur les technologies multi-utilisateurs montantes, sans oublier de finir de lire Essential ActionScript 2.0 ! ;)

De tout ça, je pense sortir quelques billets qui je l'espère vous interesseront ! ;)

Sinon, signalons quand-même sous forme de coup de chapeau deux billets de Francis BOURRE, qui nous signale sur son blog les nouveaux-nés du blogging flash francophone, et nous gratifie d'un superbe nouvel opus de sa série sur les design patterns : le pattern Commande

5 août 2004

Cloner : le clonage non-thérapeutique d'objets

Suite à la discussion sur la "deep copy" et la "shallow copy" qu'a introduit zwetan, je me suis attaqué à une ébauche de classe permettant de faire du clonage. Je l'ai testée sur quelques types de bases et quelques classe maisons, et pour l'instant seules les classes MovieClip et Color m'ont posé un problème ... ce qui est logique à priori ! :P

Le principe court-circuite l'instanciation avec new, et se sert des proriétés __proto__ et __constructor__ pour créer le clone :/**
 * Cloner class
 *
 * @version 0.1
 * @author <a href="http://www.lalex.com/">LAlex</a>
 * @since 05/08/2004
 */

class net.lalex.core.Cloner {
        /**
         * Create a clone of an object
         * Works for all datatypes, except MovieClip and Color
         *
         * @param o Object to get a clone
         * @param deep Allow to disable deep copy
         */

        public static function clone(o:Object, deep:Boolean):Object {
                // Set the "deep" param to true by default
                if (deep == undefined) {
                        deep = true;
                }
                // Can't clone MovieClip instances
                if (o instanceof MovieClip || o instanceof Color) {
                        trace("Cloner error : can't clone MovieClip or Color instances !");
                // If object, create a clone and return it
                } else if (typeof o == "object") {
                        // Return objet
                        var ret:Object = {};
                        // Object's constructor
                        var cst:Function = o["__constructor__"];
                        // Assign original prototype to clone
                        ret.__proto__ = o.__proto__;
                        // Objects created with {} have no __constructor__ property
                        if (cst != undefined) {
                                ret["__constructor__"] = cst;
                                cst.apply(ret);
                        }
                        // Copy all properties from the original to the clone
                        // Only "object" properties need to be cloned for deep copy
                        // Also works for arrays ... <img src="http://common.lalex.com/themes/devblog/smilies/icon_cool.gif" alt="8-)" class="smiley" />
                        for (var curProp:String in o) {
                                ret[curProp] = typeof o[curProp] == "object" && deep ? Cloner.clone(o[curProp]) : o[curProp];
                        }
                        return ret;
                }
                // If simple type, returns it
                return o;
        }
}
// Utilisation
import net.lalex.core.Cloner;
var monTableau:Array = [1,2,3,4,5];
// monClone contient un clone de monTableau
var monClone = Cloner.clone(monTableau);
Au début, pour créer la "base" du clone, j'utilisais l'instruction suivante :var ret:Object = new o["__constructor__"]();Cela ne marchait pour les objets créés avec les accolades, étant donné que ces objets ne possèdent pas de propriété __constructor__ comme Timothée Groleau nous le signale dans les commentaires de mon article sur le prototypes ... 8)

Wala, je suis bien évidemment ouvert à tout feedback, que ce soit pour signaler un disfonctionnement sur certaines classes ou types de données, ou pour suggérer une amélioration de cette implémentation du clonage ... ^^

SuperColor : récupérer la cible (et correction de bug)

Un des aspects pas pratiques de la classe Color est l'impossibilité de que l'on a de récupérer le clip ciblé par cet objet. J'ai donc un peu modifié ma classe SuperColor afin qu'elle contienne une méthode qui renvoie une référence au clip affecté par les changements de couleurs :
/**
 * Super Color
 * extended Color class
 *
 * -----------------------------------
 *
 * Based on <a href="http://www.robertpenner.com/">Robert PENNER</a> Color Toolkit v1.2
 *
 * Properties
 *
 *   _brightness
 *   _brightOffset
 *   _negative
 *
 *   _rgb
 *   _rgbStr
 *   _red
 *   _green
 *   _blue
 *
 *   _redPercent
 *   _greenPercent
 *   _bluePercent
 *
 *   _redOffset
 *   _greenOffset
 *   _blueOffset
 *
 * Methods
 *
 *   getTarget()
 *
 *   setRGBStr()
 *   getRGBStr()
 *   setRGB2()
 *   getRGB2()
 *   reset()
 *
 *   setBrightness()
 *   getBrightness()
 *   setBrightOffset()
 *   getBrightOffset()
 *
 *   setTint()
 *   getTint()
 *   setTint2()
 *   getTint2()
 *   setTintOffset()
 *   getTintOffset()
 *
 *   getNegative()
 *   setNegative()
 *   invert()
 *
 *   setRed()
 *   getRed()
 *   setGreen()
 *   getGreen()
 *   setBlue()
 *   getBlue()
 *
 *   setRedPercent()
 *   getRedPercent()
 *   setGreenPercent()
 *   getGreenPercent()
 *   setBluePercent()
 *   getBluePercent()
 *
 *   setRedOffset()
 *   getRedOffset()
 *   setGreenOffset()
 *   getGreenOffset()
 *   setBlueOffset()
 *   getBlueOffset()
 *
 * @version 0.2
 * @author <a href="http://www.lalex.com/">LAlex</a>
 * @since 05/08/2004
 */

class com.lalex.movieclip.SuperColor extends Color {
        private var _targetMC:MovieClip;
        /**
         * Constructor
         * @param clip Clip to apply the color
         */

        function SuperColor(clip:MovieClip) {
                super(clip);
                _targetMC = clip;
        }
        /**
         * Returns affected clip
         * @return Targeted MovieClip
         */

        public function getTarget():MovieClip {
                return _targetMC;
        }
                // ----------o RGB


        /**
         * Set an RGB value from an hexadecimal string
         * @param hexStr Hexadecimal value string
         */

        public function setRGBStr(hexStr:String) {
                // grab the last six characters of the string
                hexStr = hexStr.substr (-6, 6);
                setRGB (parseInt (hexStr, 16));
        }
        /**
         * Get the RGB value as a string
         * @return Hexadecimal value string
         */

        public function getRGBStr():String {
                var hexStr:String = getRGB().toString(16);
                // fill in zeroes as needed
                var toFill:Number = 6 - hexStr.length;
                while (toFill--) hexStr = "0" + hexStr;
                return hexStr.toUpperCase();
        }
        /**
         * Set red, green, and blue with normal numbers
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         */

        public function setRGB2(r:Number, g:Number, b:Number) {
                setRGB (r << 16 | g << 8 | b);
        } // Branden Hall - <a href="http://www.figleaf.com">www.figleaf.com</a>
        /**
         * @return Object with r, g, and b properties
         */

        public function getRGB2():Object {
                var t = getTransform();
                return {r:t.rb, g:t.gb, b:t.bb};
        }
        /**
         * Reset the color object to normal
         */

        public function reset() : Void {
                setTransform ({ra:100, ga:100, ba:100, rb:0, gb:0, bb:0});
        }


                // ----------o Brightness


        /**     
         * Brighten just like Property Inspector of MovieClip
         * @param val Brightness between -100 and 100
         */

        function setBrightness(val:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        ra = ga = ba =  100 - Math.abs(val);
                        rb = gb = bb = (val > 0) ? val * (256/100) : 0;
                }
                setTransform(trans);
        }
        /**
         * @return Brightness set with setBrightness
         * @see setBrightness
         */

        function getBrightness():Number {
                var trans:Object = getTransform();
                with (trans) {
                        return rb ? 100 - ra : ra - 100;
                }
        }
        /**
         * Set brightness offset
         * @param val Offset between -255 and 255
         */

        function setBrightOffset(val:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        rb = gb = bb = val;
                }
                setTransform(trans);
        }
        /**
         * @return Brightness set with setBrightOffset
         * @see setBrightOffset
         */

        function getBrightOffset():Number {
                return getTransform().rb;
        }


                // ----------o Tint


        /**
         * Tint with a color just like Property Inspector
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         * @param percent Between 0 and 100
         */

        public function setTint(r:Number, g:Number, b:Number, percent:Number) {
                var ratio = percent / 100;
                var trans = {rb:r*ratio, gb:g*ratio, bb:b*ratio};
                trans.ra = trans.ga = trans.ba = 100 - percent;
                setTransform (trans);
        }
        /**
         * @return tint object containing r, g, b, and percent properties
         * @see setTint
         */

        public function getTint():Object {
                var trans:Object = getTransform();
                var tint:Object = {percent: 100 - trans.ra};
                var ratio:Number = 100 / tint.percent;
                tint.r = trans.rb * ratio;
                tint.g = trans.gb * ratio;
                tint.b = trans.bb * ratio;
                return tint;
        }
        /**
         * tint with a color - alternate approach
         * @param rgb Color number between 0 and 0xFFFFFF
         * @param percent Between 0 and 100
         */

        public function setTint2(rgb:Number, percent:Number) {
                var r:Number = (rgb >> 16) ;
                var g:Number = (rgb >> <img src="http://common.lalex.com/themes/devblog/smilies/icon_cool.gif" alt="8)" class="smiley" /> & 0xFF;
                var b:Number = rgb & 0xFF;
                var ratio:Number = percent / 100;
                var trans:Object = {rb:r*ratio, gb:g*ratio, bb:b*ratio};
                trans.ra = trans.ga = trans.ba = 100 - percent;
                setTransform (trans);
        }
        /**
         * @return a tint object containing rgb (a 0xFFFFFF number) and percent properties
         * @see setTint2
         */

        public function getTint2():Object {
                var trans:Object = getTransform();
                var tint:Object = {percent: 100 - trans.ra};
                var ratio:Number = 100 / tint.percent;
                tint.rgb = (trans.rb*ratio)<<16 | (trans.gb*ratio)<<8 | trans.bb*ratio;
                return tint;
        }


                // ----------o Tint offset


        /**
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         */

        public function setTintOffset(r:Number, g:Number, b:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        rb = r;
                        gb = g;
                        bb = b;
                }
                setTransform (trans);
        }
        /**
         * @return Object containing r, g, b properties
         * @see setTintOffset
         */

        public function getTintOffset():Object {
                var t:Object = getTransform();
                return {r:t.rb, g:t.gb, b:t.bb};
        }


                // ----------o Negative and invert

        /**
         * Produce a negative image of the normal appearance
         * @param percent Between 0 and 100
         */

        public function setNegative(percent:Number) {
            var t:Object = {};
            t.ra = t.ga = t.ba = 100 - 2 * percent;
            t.rb = t.gb = t.bb = percent * (255/100);
            setTransform (t);
        }
        /**
         * @return Negative percentage
         * @see setNegative
         */

        public function getNegative():Number {
            return getTransform().rb * (100/255);
        }
        /**
         * Invert the current color values
         */

        public function invert() {
                var trans:Object = getTransform();
                with (trans) {
                        ra = -ra;
                        ga = -ga;
                        ba = -ba;
                        rb = 255 - rb;
                        gb = 255 - gb;
                        bb = 255 - bb;
                }
                setTransform (trans);
        }


                // ----------o Color values

        /**
         * Set red value
         * @param amount Between 0 and 255
         */

        public function setRed(amount:Number) {
                var t = getTransform();
                setRGB (amount << 16 | t.gb << 8 | t.bb);
        }
        /**
         * Get red value
         * @return Value between 0 and 255
         * @see setRed
         */

        public function getRed():Number {
                return getTransform().rb;
        }
        /**
         * Set green value
         * @param amount Between 0 and 255
         */

        public function setGreen(amount:Number) {
                var t = getTransform();
                setRGB (t.rb << 16 | amount << 8 | t.bb);
        }
        /**
         * Get green value
         * @return Value between 0 and 255
         * @see setGreen
         */

        public function getGreen():Number {
                return getTransform().gb;
        }
        /**
         * Set blue value
         * @param amount Between 0 and 255
         */

        public function setBlue(amount:Number) {
                var t = getTransform();
                setRGB (t.rb << 16 | t.gb << 8 | amount);
        }
        /**
         * Get blue value
         * @return Value between 0 and 255
         * @see setBlue
         */

        public function getBlue():Number {
                return getTransform().bb;
        }


                // ----------o Color percentages

        /**
         * Set red percentage
         * @param percent Between -100 and 100
         */

        public function setRedPercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ra = percent;
                setTransform (trans);
        }
        /**
         * Get red percentage
         * @return Value between -100 and 100
         * @see setRedPercent
         */

        public function getRedPercent():Number {
                return getTransform().ra;
        }
        /**
         * Set green percentage
         * @param percent Between -100 and 100
         */

        public function setGreenPercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ga = percent;
                setTransform (trans);
        }
        /**
         * Get green percentage
         * @return Value between -100 and 100
         * @see setGreenPercent
         */

        public function getGreenPercent():Number {
                return getTransform().ga;
        }
        /**
         * Set blue percentage
         * @param percent Between -100 and 100
         */

        public function setBluePercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ba = percent;
                setTransform (trans);
        }
        /**
         * Get blue percentage
         * @return Value between -100 and 100
         * @see setBluePercent
         */

        public function getBluePercent():Number {
                return getTransform().ba;
        }


                // ----------o Color offsets

        /**
         * Set red offset
         * @param offset Between -255 and 255
         */

        public function setRedOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.rb = offset;
                setTransform (trans);
        }
        /**
         * Get red offset
         * @return Value between -255 and 255
         * @see setRedOffset
         */

        public function getRedOffset():Number {
                return getTransform().rb;
        }
        /**
         * Set green offset
         * @param offset Between -255 and 255
         */

        public function setGreenOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.gb = offset;
                setTransform (trans);
        }
        /**
         * Get green offset
         * @return Value between -255 and 255
         * @see setGreenOffset
         */

        public function getGreenOffset():Number {
                return getTransform().gb;
        }
        /**
         * Set blue offset
         * @param offset Between -255 and 255
         */

        public function setBlueOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.bb = offset;
                setTransform (trans);
        }
        /**
         * Get blue offset
         * @return Value between -255 and 255
         * @see setBlueOffset
         */

        public function getBlueOffset():Number {
                return getTransform().bb;
        }

                // ----------o Getter/Setter

        /**
         * RGB value
         * @type Number
         */

        function set _rgb(val:Number) {
                setRGB(val);
        }
        function get _rgb():Number {
                return getRGB();
        }
        /**
         * RGB value
         * @type String
         */

        function set _rgbStr(val:String) {
                setRGBStr(val);
        }
        function get _rgbStr():String {
                return getRGBStr();
        }
        /**
         * Brightness value
         * @type Number
         */

        function set _brightness(b:Number) {
                setBrightness(b);
        }
        function get _brightness():Number {
                return getBrightness();
        }
        /**
         * Brightness offset
         * @type Number
         */

        function set _brightOffset (b:Number) {
                setBrightOffset(b);
        }
        public function get _brightOffset():Number {
                return getBrightOffset();
        }
        /**
         * Negative percentage
         * @type Number
         */

        public function set _negative(p:Number) {
                setNegative(p);
        }
        public function get _negative():Number {
                return getNegative();
        }
        /**
         * Red value
         * @type Number
         */

        public function set _red(v:Number) {
                setRed(v);
        }
        public function get _red():Number {
                return getRed();
        }
        /**
         * Green value
         * @type Number
         */

        public function set _green(v:Number) {
                setGreen(v);
        }
        public function get _green():Number {
                return getGreen();
        }
        /**
         * Blue value
         * @type Number
         */

        public function set _blue(v:Number) {
                setBlue(v);
        }
        public function get _blue():Number {
                return getBlue();
        }
        /**
         * Red percentage
         * @type Number
         */

        public function set _redPercent(v:Number) {
                setRedPercent(v);
        }
        public function get _redPercent():Number {
                return getRedPercent();
        }
        /**
         * Green percentage
         * @type Number
         */

        public function set _greenPercent(v:Number) {
                setGreenPercent(v);
        }
        public function get _greenPercent():Number {
                return getGreenPercent();
        }
        /**
         * Blue percentage
         * @type Number
         */

        public function set _bluePercent(v:Number) {
                setBluePercent(v);
        }
        public function get _bluePercent():Number {
                return getBluePercent();
        }
        /**
         * Red offset
         * @type Number
         */

        public function set _redOffset(v:Number) {
                setRedOffset(v);
        }
        public function get _redOffset():Number {
                return getRedOffset();
        }
        /**
         * Green offset
         * @type Number
         */

        public function set _greenOffset(v:Number) {
                setGreenOffset(v);
        }
        public function get _greenOffset():Number {
                return getGreenOffset();
        }
        /**
         * Blue offset
         * @type Number
         */

        public function set _blueOffset(v:Number) {
                setBlueOffset(v);
        }
        public function get _blueOffset():Number {
                return getBlueOffset();
        }
}

Vous pouvez donc télécharger cette mise à jour si le coeur vous en dit, qui en plus corrige un ch'tit bug qui s'était glissé sur la propriété _blueOffset ! ;) Cette classe inaugure aussi le fait que je n'utiliserais plus le this systématique dans mes classes. :)

::Télécharger SuperColor.zip::

30 juillet 2004

Composition et __resolve : simplifiez vous la composition

En train de coder un gestionnaire de positionnement pour des clips sur la scène, je me vois confronté au problême bien connu du centrage des clips. En effet, les coordonnées _x et _y ne sont pas forcément celles du coin supérieur gauche du clip. Le canadien Stéphan Guénette s'est déjà penché sur cet épineux problême, et propose une solution bien pratique sur son blog ... 8)

Seulement, cette solution me pose quelques problêmes, tout d'abord parce qu'il s'agit d'une manipulation de prototypes, bien peu conseillée en AS2, et ensuite parce qu'à chaque accés à une propriété de positionnement, il s'effectue un appel à la méthode getBounds() ... Je ne me suis pas encore penché sur le second problême, mais concernant le premier, j'ai trouvé une petite astuce bien pratique pour accéder facilement aux propriétés d'un MovieClip membre d'une classe, et ce sans utiliser l'héritage.

Il s'agit en fait d'utiliser la méthode non documentée __resolve(), dont voici mon utilisation :dynamic class net.lalex.movieclip.SuperClip {
        // Clip ciblé
        private var _targetMC:MovieClip;
        // Constructeur
        public function SuperClip(mc:MovieClip) {
                _targetMC = mc;
        }
        // Redirige les requêtes non existantes vers le clip
        // contenu dans la propriété _targetMC
        public function __resolve(p:String) {
                // Si c'est une méthode, on l'applique au clip lui-même
                if (typeof _targetMC[p] == "function") {
                        return function() {
                                return _targetMC[p].apply(_targetMC, arguments);
                        }
                // Sinon, on retourne la propriété
                } else {
                        return _targetMC[p];
                }
        }
}
Comme vous le constatez, __resolve() permet d'intercepter les appels à des membres d'une classe qui n'existent pas, et d'en faire un peu ce que l'on veut. Le seul problême est que l'on ne peut pas accéder en écriture aux propriétés du clip. Comme il s'agit là d'une classe native, il faut donc ne pas oublier de créer les accesseurs pour modifier les propriétés (setX, setY, setRotation, etc...) ... mais si l'on s'en tient à l'utilisation des accesseurs pour la manipulation de propriétés lors du développement de classes "maisons", ce qui est bien plus conseillé en POO, cela fonctionne parfaitement ! :D Voici un exemple d'utilisation :import net.lalex.movieclip.SuperClip;
// Création de l'instance de SuperClip
var monClip:SuperClip = new SuperClip(_root.monCarre);
// Accés à une méthode du clip contenu dans monClip
// donc, une méthode de _root.monCarre
// Ici, il s'agit de mettre monCarre au dessus des tous les autres
monClip.swapDepths(monClip._parent.getNextHighestDepth());

Evidemment, ca tient tout autant de la bidouille que la solution avec les prototypes, mais je préfère malgré tout avoir une "vraie" classe ! :)

28 juillet 2004

Interfaces et héritage : compilo : 1 - LAlex : 0

Un aspect singulier des interfaces face à l'héritage est que le compilateur de Flash MX 2004 ne se contente pas de vérifier si une classe implémente l'interface, mais il vérifie aussi que les méthodes devant être implémentées le sont ...

Pour etre plus clair, j'ai découvert cela en faisant des tests en réimplémentant ma bibliothèque sur les matrices. Ma classe Matrix hérite d'un classe Grid. Cette classe Grid possède une méthode clone(). J'ai voulu créer une interface IClonable pour m'assurer qu'une matrice puisse être clonée, et ayant oublié de créer la méthode, je me suis rendu compte que le compilateur laissait passer malgré tout ... :o En fait, cette méthode existe déjà dans Grid, alors que Grid n'implémente pas IClonable ! :)

Voici le code que le compilateur laisse passer :/*
 * interface IClonable
 */

interface net.lalex.interfaces.IClonable {
        public function clone():Object;
}
/*
 * classe Grid
 */

class net.lalex.data.Grid extends Array {
        // Plein de code ...
        // Retourner un clone de la grille
        public function clone():Object {
                var ret:Grid = new Grid(_gridWidth, _gridHeight);
                ret.copyFrom(this);
                return ret;
        }
        // Copy les éléments d'une autre grille
        public function copyFrom(g:Grid):Void {
                // Code
        }
}
/*
 * classe Matrix
 */

class net.lalex.maths.Matrix extends net.lalex.data.Grid implements net.lalex.interfaces.IClonable {
        // Aucune méthode 'clone()'
}

Normal allez-vous me dire ? Oui, en effet. :D Mais je ne pensais pas que le compilateur allait si loin. Ca ne m'arrange pas en fait, car j'aurais bien voulu forcer la classe Matrix à surcharger la méthode clone() ... :(

27 juillet 2004

Ellipsis : pas encore au courant ?

Si vous habitez sur une autre planète, ou si vous n'avez pas encore ouvert votre aggrégateur de news, sachez que la mise à jour de Flash MX 2004, le portant ainsi à sa version 7.2, est disponible dés aujourd'hui en version anglaise. Je suis donc la tendance du jour qui veut que tout le monde l'annonce ! :P

Pour rappel, l'update 7.2, anciennement Ellipsis, apporte les modifications suivantes :

  • Lancement de l'IDE plus rapide
  • Compilation jusqu'à deux fois plus rapide
  • Plus de 400 codes d'exemple supplémentaires
  • Plus de documentation sur l'utilisation des composants
  • Plus de 100 bugs corrigés
  • Et tout ce qui est indiqué dans les Release Notes

Il ne reste plus qu'à attendre la version française ! :)

- page 6 de 15 -