Cet exercice est beaucoup moins anodin qu’il a l’air. Avant de commencer à
programmer nous devons répondre aux questions suivantes:
- Quelle structure utiliser pour ce tableau à deux dimensions?
- Où mettre les textes de chacune des cases?
- Qui est modèle de quoi?
Arbitrairement, décidons que les textes affichés dans chacun des
rectangles sont des instances d’un classe modèle donnant accès à
leurs contenus textuels. Appelons cette classe ValueModel. Chaque
instance de cette classe se distinguera des autres par la valeur de sa
variable d’instance contents. Ce début de réflection nous permets
de définir cette classe ainsi que les méthodes d’accès à sa variable
d’instance:
Model subclass: #ValueModel
instanceVariableNames: ’contents’
classVariableNames: ’’
poolDictionaries: ’’
category: ’Cours-Smalltalk’
|
|
ValueModel>>contents
^contents
|
|
contents: aValue
contents:= aValue.
|
|
Pour implémenter le tableau à deux dimension
nous pouvons utiliser la classe Matrix du système
SQUEAK.44
Une matrice45
peut être créée avec le message rows:columns:, où l’argument rows: donne
le nombre de lignes et l’argument columns: donne le nombre des
colonnes.
Pour l’accès aux éléments d’une matrice SQUEAK offre la méthode at:at:
où le premier argument est l’indice de la ligne et le deuxième l’indice de la
colonne d’un élément particulier. La modification d’un élément se fait avec
la méthode at:at:put:. Bien entendu, comme toute collection – Matrix est
sous-classe de la classe Collection – les matrices offrent les méthode do: et
indicesDo: pour parcourir l’ensemble de ses éléments, la méthodes
collect: pour construire une nouvelle matrice avec chacun des éléments
obtenu par l’application du bloc argument aux éléments de la matrice
receveur, toute une variété de méthodes testant la présence d’un
objet particulier dans la matrice: includes:, includes:AnyOf: et
includes:AllOf:. Les méthodes rowCount et columnCount livrent
respectivement le nombre de lignes et le nombre de colonnes de la
matrice.
Nous allons donc construire la classe TableauDeTexte qui doit pouvoir
contenir une instance de la classe Matrix dont chaque élément doit être
une instance de la classe ValueModel qui contient une chaîne de
caractères qui doit être affiché dans la case correspondant à cet
élément. Cet enchaînement des valeurs est esquissé dans la figure
A.51.
Continuons alors la programmation de notre tableau de textes par la
définition de la classe TableauDeTexte:
Object subclass: #TableauDeTexte
instanceVariableNames: ’textMatrix’
classVariableNames: ’’
poolDictionaries: ’’
category: ’Cours-Smalltalk’
|
|
et sa méthode d’initialisation:
TableauDeTexte class>>width: x height: y
^ super new setMatrixWidth: y height: x
|
|
Cette méthode transmet le message setMatrixWidth:height: à l’instance
nouvellement créée de la classe TableauDeText. Cette méthode doit
initialiser la variable d’instance textMatrix à une matrice de x lignes et y
colonnes.
Elle doit également initialiser chaque élément de la matrice à une instance
de la classe ValueModel, dont la variable d’instance contents prendra
comme valeur la chaîne de caractères correspondant à un Point avec les
coordonnées les indices de l’élément de la matrice dont il sera la valeur.
Voici alors la méthode réalisant ces initialisations:
TableauDeTexte>>setMatrixWidth: x height: y
| value |
textMatrix := Matrix rows: x columns: y.
1
to: x
do: [:ro | 1
to: y
do: [:co |
value :=
ValueModel new
contents:
ro asString , ’@’ , co asString;
yourself.
textMatrix
at: ro
at: co
put: value]]
|
|
La transmission du message yourself à la nouvelle instance de
ValueModel (dans la quatorzième ligne) est crucial: les éléments de la
matrice ne doivent pas contenir le texte, mais le modèle du texte.
Comme toujours, vérifions le bon fonctionnement du programme en
inspectant une instance de notre classe. La figure A.52 montre une suite
d’inspecteurs qui retrace la même chaîne de valeurs que celle montrée dans
la figure A.51. En haut à gauche de la figure se trouve l’inspecteur sur
l’instance de la classe TableauDeText créée par la transmission
TableauDeTexte width: 5 height: 4. Sa variable d’instance textMatrix
contient bien une instance de la classe Matrix. L’inspecteur sur cette
instance se trouve en haut à droite de la figure. La variable d’instance
contents de la classe Matrix, la variable contenant l’ensemble de
ses éléments, contient bien une collection d’instances de la classe
ValueModel. L’inspecteur situé en bas à gauche de la figure, un
inspecteur sur cette variable contents, montre que la matrice est
internement implémenter comme un tableau, comme une instance de la
classe Array. Nous y avaons lançé un inspecteur sur le premier
élément, qui est, comme montré dans l’inspecteur en bas à droite dans
la figure, une instance de la classe ValueModel dont la variable
d’instance contents contient la chaîne de carcatère ’1@1’. C’est
bien un Point dont la coordonnée x est le numéro de la ligne et
la coordonnée y le numéro de la colonne de cet élément dans la
matrice.
Tournons-nous alors vers la programmation de l’interface utilisateur
graphique. Pour cela, principalement, nous devons créer une vue pour
chacun des éléments de la matrice textMatrix. Chaque vue sera un
PluggableTextMorph. Sa taille sera déterminée par un simple calcul sur le
nombre de lignes et de colonnes de la matrice: par rapport à la hauteur de
la fenêtre principale, sa hauteur sera 1 /
nombre de lignes et sa largeur sera
1 /
nombre do colonnes.
Voici alors notre méthode openInWorld pour les instances de la classe
TableauDeTexte:
TableauDeTexte>>openInWorld
| window textView testViewRectangle |
window := SystemWindow labelled: ’Tableau de Textes’.
window model: self.
1
to: textMatrix rowCount
do: [:ro | 1
to: textMatrix columnCount
do: [:co |
textView :=
self textViewOnRow: ro
column: co.
testViewRectangle :=
self rectangleForRow: ro
column: co.
window addMorph: textView
frame: testViewRectangle]].
window openInWorld
|
|
Elle crée, avec la méthode textViewOnRow:column: que nous verrons par la
suite, une PluggableTextMorph pour chaque élément de la matrice,
calcule, avec la méthode rectangleForRow:column:, le rectangle
dans lequel il faut insérer ce morph dans le morph principal, une
instance de SystemWindow. Voici la définition de ces deux méthodes
auxiliaires:
TableauDeTexte>>textViewOnRow: row column: column
| model textView |
model := textMatrix at: row at: column.
textView := PluggableTextMorph
on: model
text: #contents
accept: #contents:.
^ textView
|
|
TableauDeTexte>>rectangleForRow: row column: column
| width height extent left top |
height := 1 / textMatrix rowCount.
width := 1 / textMatrix columnCount.
extent := width @ height.
left := width * (column - 1).
top := height * (row - 1).
^ left @ top extent: extent
|
|
Si nous activons mainteant la transmission:
(TableauDeTexte width: 5 height: 4) openInWorld
nous obtenons la vue de la figure A.53 où chaque sous-fenêtre de texte
possède sa propre barre de défilement.
| FIG. A.53: | Une
instance de
TableauDeTexte
avec des barres de
défilement |
| |
Si nous ne voulons pas de ces barres de défilement, comme dans la figure
6.29 (page 545), il suffit d’envoyer aux PluggableTextMorph représentant
ces sous-fenêtres le message hideScrollBarIndefinitely. Par exemple,
nous pouvons insérer la ligne
textView hideScrollBarIndefinitely.
comme avant dernière ligne dans méthode textViewOnRow:column:.
Quand nous positionnons la souris dans un de ces morphs et nous la
cliquons, un curseur de texte (représenté comme une barre verticale de
couleur verte) apparaît au début du texte. Nous pouvons alors insérer du
texte supplémentaire, modifier le texte qui s’y trouve, voire l’éliminer
complètement.
Dès que vous modifier le texte d’une manière quelconque apparît un cadre
rouge autour de la sous-fenêtre de laquelle le texte a été modifié. Vous avez
sûrement observé l’apparition d’un tel cadre lors de l’édition de méthodes.
Tout PluggableTextMorph indique de cette manière que son texte a changé
et que ce changement n’a pas encore été sauvegardé. Ainsi le cadre rouge
entourant la sous-fenêtre contenant le texte d’une méthode que vous avez
modifiée, disparaît dès que vous sauvegardez (avec un «Alt+s») les
modifications.
En clair: tout PluggableTextMorph vous livre – sans même que vous le
demandez – accès à l’editeur de texte de Squaek. C’est fort utile. Mais,
si nous l’essayons avec nos textes, comme montré dans la figure
A.54,
| FIG. A.54: | Une
instance de
TableauDeTexte
avec des textes
édités |
|
|
nous pouvons bien modifier les textes, mais après un «Alt+s» le cadre
rouge ne diparait pas. Ainsi (c’est difficile à voir dans la figure)
toutes les sous-fenêtres éditées dans la figure A.54 gardent leur cadre
rouge.
Cela veut dire que les textes ne sont pas modifiable. Seulement la vue du
texte peut être modifié. Pour que le texte lui même soit modifiable, il faut
que l’instance de la classe ValueModel qui contient ce texte permette sa
modification. Sinon le texte ne sera accéssible que pour la lecture et non pas
aussi pour l’écriture.
Pour qu’un PluggableTextMorph ait droit à modifier le texte qu’il affiche il
faut que lors de sa création le sélecteur d’une méthode du modèle ait été
donné comme argument accept:. Reprenons notre méthode de creation des
morphs:
TableauDeText>>textViewOnRow: row column: column
| model textView |
model := textMatrix at: row at: column.
textView := PluggableTextMorph
on: model
text: #contents
accept: #contents:.
textView hideScrollBarIndefinitely.
^ textView
|
|
Nous avons donné le sélecteur contents:. Ceci permet au morph de
connaître la méthode du modèle qui peut modifier le text avec la méthode
contents:. Le modèle indique au morph qu’il veut bien modifier le texte en
retournant true quand on lui transmet ce message.
Ainsi la méthode de modification de texte, dans notre cas c’est la méthode
content:, a un double rôle: celui de modifier son texte et celui de signaler
aux morphs ayant des vues sur le texte que l’instance accept ou pas des
modification.
Nous devons donc modifier la définition de la méthode contents:
de:
ValueModel>>contents: aValue
contents := aValue.
|
|
vers:
ValueModel>>contents: aValue
contents := aValue.
^ true
|
|
pour que les modifications de l’éditeur soient prises en compte par le
modèle.
Notons qu’un PluggableTextMorph garde les textes qu’il affiche sous
forme de texte, donc comme une instance de la classe Text. Ces
instances contiennent en plus des chaînes de caractères formant
le texte propremnt dit, des informations sur son style, les mises
en formes etc. Si le modèle désire récupérer juste la chaîne de
caractères correspondant au texte, il doit faire une conversion, comme
ci-dessous:46
ValueModel>>contents: aValue
contents := aValue asString.
^ true
|
|