Page de garde Les librairies AWT et Swing
Page de garde Les librairies AWT et Swing ESSI 2, Septembre 2002 Yannis. BRES@cma. inria. fr http: //www. yannis. bres. name
Introduction Les GUI en Java Le JDK comporte de nombreuses classes permettant la construction de Graphical User Interfaces (interfaces homme/machine) très évoluées De nombreux composants simples (étiquettes, boutons, cases à cocher, zones de texte, …) ou très évolués (arbres, tables, éditeur de texte formatté, …) sont disponibles
Introduction Deux packages java. awt Abstract Window Toolkit Utilise les composant graphiques natifs (peers) Comportements différents selon les plate-formes Exécution assez rapide javax. swing 100% pure Java Librairie complètement écrite en Java Construite au-dessus d’un sous-ensemble fiable (portable) de l’AWT Librairie très puissante, composants très évolués (arbres, tables, …) Séparation modèle-représentation, apparence modifiable (pluggable look-and-feels) Exécution assez lente
Principes de base Un composant graphique dérive de : java. awt. Component ou javax. swing. JComponent Les composant graphiques sont contenus par des java. awt. Container Les composants s’ajoutent par : Container. add( Component ) (placement non contraint) Container. add( Component, Object ) (placement contraint) Le placement des composants est géré par des java. awt. Layout. Manager Les containers ont chacun un Layout Manager par défaut Le Layout Manager se modifie par set. Layout( Layout. Manager ) Théoriquement : pas de placement absolu Les containers sont des composants ! Possibilité d'emboîter récursivement des containers
"Structures" à connaître java. awt. Point, utilisée notamment pour les positions : class java. awt. Point { public int x; public int y; … } java. awt. Dimension : class java. awt. Dimension { public int width; public int height; … }
"Structures" à connaître java. awt. Rectangle, utilisée notamment pour les bounding boxes : class java. awt. Rectangle { public int x; public int y; public int width; public int height; … } java. awt. Insets indique les espaces que les composants doivent laisser sur les côtés : class java. awt. Insets { public int top; public int left; public int bottom; public int right; … }
Première fenêtre
Première fenêtre javax. swing. JFrame est la fenêtre de base Swing Le container principal de javax. swing. JFrame s'obtient par : get. Content. Pane() javax. swing. JLabel est une étiquette affichant du texte et/ou une icône Une fois les composants ajoutés, pack() réalise la fenêtre La fenêtre est rendue visible par set. Visible( true ) Enfin, la fenêtre est détruite par dispose()
Première fenêtre 1 ère méthode Première fenêtre 1ère méthode (instanciation directe) : JFrame frame= new JFrame( "Première fenêtre" ); Container content_pane= frame. get. Content. Pane(); Content_pane. add( new JLabel( "Hello World !", Swing. Constants. CENTER ) ); frame. pack(); frame. set. Visible( true ); ··· frame. dispose();
Première fenêtre 2 ème méthode Première fenêtre 2ème méthode (spécialisation) : public class Fenetre extends javax. swing. JFrame { public Fenetre( String title ) { super( title ); Container content_pane= get. Content. Pane(); content_pane. add( new JLabel( "Hello World !", Swing. Constants. CENTER ) ); pack(); set. Visible( true ); } } new Fenetre( "Première fenêtre" );
Créer son composant Créer un objet dérivant de java. awt. Component ou javax. swing. JComponent L'objet indique sa dimension préférée par la méthode : java. awt. Dimension get. Preferred. Size() L'objet se dessine dans les méthodes : void paint( java. awt. Graphics g ) void paint. Component( java. awt. Graphics g ) (Component) (JComponent) g, le contexte graphique reçu, permet le dessin, l'affichage de texte, … Pour les JComponents, il faut généralement commencer par appeler la méthode paint. Component(…) de la classe mère : super. paint. Component( g )
Créer son composant Exemple
Créer son composant Exemple public class Diagonal. Component extends javax. swing. JComponent { public Diagonal. Component() { } public Dimension get. Preferred. Size() { return new Dimension( 300, 240 ); } public void paint. Component( Graphics g ) { g. draw. Line( 0, 0, get. Width(), get. Height() ); g. draw. Line( get. Width(), 0, 0, get. Height() ); } }
java. awt. Graphics, le contexte graphique Le contexte graphique, java. awt. Graphics, permet : De dessiner des figures simples et complexes vides : draw. Line(…), draw. Rect(…), draw. Oval(…), draw. Polygon(…) De dessiner des figures simples et complexes pleines : fill. Rect(…), fill. Oval(…), fill. Polygon(…) D'afficher du texte : draw. String(…) De changer la couleur, la fonte, le mode : set. Color(…), set. Font(…), set. Paint. Mode()/set. XORMode(…) D'afficher des images …
L'affichage de texte en Java Graphics. draw. String(…) utilise la fonte préalablement définie par : set. Font( java. awt. Font ) Les fontes sont généralement créées par le constructeur : Font( String name, int style, int size ) name : nom logique ("Monospaced", "Sans. Serif", …) ou nom de fonte style : PLAIN, BOLD, ITALIC ou BOLD|ITALIC size : taille en points Les métriques de fontes s'obtiennent notamment par : get. Font. Metrics() dans java. awt. Graphics Les métriques de chaînes de caractères s'obtiennent notamment par : get. Line. Metrics(…) dans java. awt. Font
L'affichage de texte en Java Métriques de fontes Une fonte est caractérisée par sa métrique : java. awt. Font. Metrics et java. awt. font. Line. Metrics Une métrique indique notamment les informations suivantes :
Containers et Layout Managers Les principaux containers Les plus simples : java. awt. Panel, javax. swing. JComponent javax. swing. Box java. awt. Scroll. Pane et javax. swing. JScroll. Pane javax. swing. JSplit. Pane javax. swing. JTabbed. Pane javax. swing. JDesktop. Pane et javax. swing. JInternal. Frame ···
Containers et Layout Managers Les Layout Managers Responsables : Du placement relatif des composants dans un container Du calcul des dimensions minimales et préférées des containers … Deux interfaces : java. awt. Layout. Manager (placements simples) java. awt. Layout. Manager 2 (placements contraints)
Containers et Layout Managers java. awt. Flow. Layout Positionnement à la suite, "passage à la ligne automatique" Alignements : centré, à gauche, à droite Les composants ont des tailles quelconques Surtout utilisé pour les boutons
Containers et Layout Managers java. awt. Grid. Layout Disposition matricielle Grid. Layout( int rows, int columns ) add( new JLabel( "Label n° 1", Swing. Constants. CENTER ) ); add( new JLabel( "Label n° 2", Swing. Constants. CENTER ) ); ··· Les composants ont tous la même largeur et la même hauteur
Containers et Layout Managers java. awt. Border. Layout Positionnement sur les quatre côtés d’un container ou au centre : add( new JLabel( "NORTH", Swing. Constants. CENTER ), Border. Layout. NORTH ); ···
Containers et Layout Managers java. awt. Grid. Bag. Layout Le plus puissant de tous ! Mais aussi le plus complexe… Le plan est découpée en lignes et colonnes Chaque composant occupe une ou plusieurs cellules (en largeur comme en hauteur) Suite aux retaillements : La taille des cellules évolue proportionnellement à la demande Les composants peuvent s'étendre dans les deux directions, une seule ou pas du tout Les composants se positionnent dans leurs cellules à l'un des 8 points cardinaux ou au centre Les contraintes de placements sont transmises par la "structure" : java. awt. Grid. Bag. Constraints
Containers et Layout Managers java. awt. Grid. Bag. Layout Exemple : Trois panneaux (listes déroulantes, cases à cocher, boutons) gérés respectivement par deux Grid. Bag. Layout et un Flow. Layout Ces trois panneaux sont gérés par un troisième Grid. Bag. Layout
Containers et Layout Managers java. awt. Grid. Bag. Layout La "structure" java. awt. Grid. Bag. Constraints : int gridx, int gridy Coordonnées de la première cellule occupée par le composant RELATIVE (défaut) pour une incrémentation automatique int gridwidth, int gridheight Nombre de cellules occupées par le composant REMAINDER indique le composant utilise le reste de la ligne/colonne int fill (NONE, HORIZONTAL, VERTICAL, BOTH) Détermine si le composant s'étend si la dimension de ses cellules augmente int anchor (CENTER (défaut), NORTHEAST, …) Emplacement du composant dans ses cellules s'il ne s'étend pas complètement double weightx, double weighty Facteurs de distribution de l'espace entre colonnes/lignes
Les boîtes de dialogue javax. swing. JDialog Très similaires aux fenêtres standard javax. swing. JFrames : Peuvent être modales (i. e. conserver le focus) Sont généralement attachées à une autre JDialog ou JFrame
Les boîtes de dialogue communes javax. swing. JOption. Pane : Affichage de messages Questions simples (Oui/Non, Ok/Annuler, …) Saisie de valeur unique
Les boîtes de dialogue communes javax. swing. JFile. Chooser : Sélection de fichier(s) (ouvrir, enregistrer sous, …)
Les boîtes de dialogue communes javax. swing. JColor. Chooser :
Les bordures Les JComponents peuvent se voir attribuer une bordure par : set. Border( border ) Les bordures peuvent être composées
Les bordures sont des objets implémentants l'interface : javax. swing. border. Border Dans le package : javax. swing. border Dans le but de partager les bordures identiques, on ne construit pas les bordures à partir des constructeurs des classes, mais via la Factory : javax. swing. Border. Factory : static Border create. Lowered. Bevel. Border() static Border create. Etched. Border() … Les bordures affectent les Insets des composants qui en ont : Pensez-y dans vos méthodes paint. Component( Graphics ) !
Composants Etiquettes Les étiquettes javax. swing. JLabel Affiche une icône, du texte, ou les deux Peut-être grisée Peut-être associé à un composant et un mnémonique Alignement vertical et horizontal (gauche/haut, centré, droite/bas) …
Composants Boutons de commandes Les boutons de commandes javax. swing. JButton Affiche une icône, du texte, ou les deux
Composants Cases à cocher Les cases à cocher javax. swing. JCheck. Box
Composants Boutons radio Les boutons radios javax. swing. JRadio. Button
Composants Boutons radio Exclusions entre boutons javax. swing. JButton. Group Permet la gestion d'exclusion mutuelles entre boutons Généralement : JRadio. Button, JRadio. Button. Menu. Item
Composants Boutons bascules Les boutons bascules javax. swing. JToggle. Button
Composants Champs de texte Les champs de texte javax. swing. JText. Field
Composants Zones de texte Les zones de texte javax. swing. JText. Area
Composants Listes Les listes javax. swing. JList
Composants Combos Les combos javax. swing. JCombo. Box
Composants Barres de progression Les barres de progression javax. swing. JProgress. Bar
Composants Glissières Les glissières javax. swing. JSlider
Composants Menus déroulants Les menus déroulants javax. swing. JMenu
Composants Menus flottants Les menus flottants javax. swing. JPopup. Menu
Composants Barres d'outils Les barres d'outils javax. swing. JTool. Bar
Composants Info-bulles Les info-bulles javax. swing. JTool. Tip
Composants Arbres Les arbres javax. swing. JTree
Composants Tables Les tables javax. swing. JTable
Composants Editeurs de texte Les éditeurs de texte javax. swing. JText. Component
Multiple Document Interface Le JDesktop. Pane est un conteneur spécialisé pour des classes dérivées de JInternal. Frame
Séparation données/composant Le composant agit comme interface entre l’utilisateur et les données : celles-ci sont gérées par des modèles Le composant : Interroge le modèle pour déterminer son état Interroge le modèle pour afficher les données Transmet au modèle les modifications à apporter aux données Les modèles sont des interfaces ; des implémentations courantes sont fournies Ex. : une JTable fait appel aux méthode suivante de Table. Model : public int get. Column. Count() public int get. Row. Count() public Object get. Value. At( int row, int col)
Communication entre composants La classe AFrame, dérivant de JFrame, utilise un JButton : AFrame connait JButton AFrame communique avec le JButton en lui appliquant ses méthodes JButton ne connaît (théoriquement) pas AFrame Comment JButton communique avec AFrame ? AFrame va se mettre à l’écoute du JButton
Communication entre composants Les Listeners De nombreux composants comportent des méthodes : add. XXXListener( XXXListener ) remove. XXXListener( XXXListener ) XXXListener étant une interface déclarant un petit nombre de méthodes Ces méthodes sont appelées lorsque l’objet a quelque chose à signaler à ses Listeners Exemple : public interface Action. Listener extends java. util. Event. Listener { public void action. Performed( Action. Event event ); } event permet de connaître la source de l’événement, le type d’action, … Utilisable avec JButton : button. add. Action. Listener( Action. Listener )
Communication entre composants Mise en œuvre des Listeners 1ère méthode implémentation directe de l’interface Action. Listener public class AFrame extends javax. swing. JFrame implements java. awt. event. Action. Listener { JButton button 1, button 2, . . . ; ··· button 1. add. Action. Listener( this ); button 2. add. Action. Listener( this ); ··· public void action. Performed( Action. Event event ) { if (event. get. Source() == button 1) ··· else if (event. get. Source() == button 2) ···. . . } ··· }
Communication entre composants Mise en œuvre des Listeners 1ère méthode implémentation directe de l’interface Action. Listener Deux inconvénients majeurs : La classe AFrame déclare publiquement "être" un Action. Listener, alors qu’aucune autre classe qu’elle-même n’est capable de s’en servir comme Action. Listener L’identification de la source de l’action (button 1, button 2, …) impose une recherche linéaire parmi les sources potentielles
Communication entre composants Mise en œuvre des Listeners 2ère méthode création d’un objet implémentant l’interface Action. Listener public class AFrame extends javax. swing. JFrame { JButton button 1, button 2, . . . ; ··· button 1. add. Action. Listener( new Action. Listener() { public void action. Performed( Action. Event event ) { ··· } } ); ··· button 2. add. Action. Listener( new Action. Listener(). . . }
Communication entre composants Les Adapters Lorsque les interfaces XXXListener comportent de nombreuses méthodes : Il existe une classe XXXAdapter correspondante, implémentant XXXListener Toutes les méthodes déclarées par l’interface ont un corps vide Lorsqu’on n’utilisera qu’une petite partie des méthodes, on spécialisera donc l’Adapter
Communication entre composants Générer des événements Utiliser une interface existante ou définir le Listener (et éventuellement l’Adapter) La classe JComponent déclare la variable protected Event. Listener. List listener. List Event. Listener. List maintient des paires (Classe de Listener , instance de Listener) Event. Listener. List définit notamment les méthodes suivantes : public void add( Class une_class, Event. Listener listener ) public void remove( Class une_class, Event. Listener listener ) public Object[] get. Listener. List() get. Listener. List renvoie un tableau t à 2 n éléments où, pour tout n : t[n] est un objet Class t[n+1] est l’instance de Listener correspondante
Communication entre composants Générer des événements Définir les méthodes suivantes, qui délèguent le travail à Event. Listener. List : public void add. Listener( XXXListener listener ) public void remove. Listener( XXXListener listener ) Définir des méthodes fire. YYY : ··· YYYEvent yyy_event= new YYYEvent( this ); ··· protected void fire. YYY() { Object[] listeners= listener. List. get. Listener. List(); for ( int i= listeners. length– 2 ; i >= 0 ; i-= 2 ) { if (listeners[i] == XXXListener. class) ((Foo. Listener)listeners[i+1]). yyy( yyy_event ); } }
Communication entre composants Listeners courants Action. Listeners courants actions java. awt. event. Action. Listener : void action. Performed( java. awt. Action. Event ) Enregistré par add. Action. Listener(…) auprès de : javax. swing. JButton javax. swing. JMenu. Item javax. swing. JToggle. Button … javax. swing. Abstract. Button
Communication entre composants Listeners courants Item. Listeners courants changements d’état java. awt. event. Item. Listener : void item. State. Changed( java. awt. event. Item. Event ) Enregistré par add. Item. Listener(…) auprès de : javax. swing. JButton javax. swing. JMenu. Item javax. swing. JToggle. Button javax. swing. JCombo. Box … javax. swing. Abstract. Button
Communication entre composants Listeners courants Key. Listeners courants événements clavier java. awt. event. Key. Listener : void key. Pressed( java. awt. event. Key. Event ) void key. Released( java. awt. event. Key. Event ) void key. Typed( java. awt. event. Key. Event ) Enregistré par add. Key. Listener(…) auprès de : javax. swing. JComponent (en fait : java. awt. Component) …
Communication entre composants Listeners courants Mouse. Listeners courants événements souris de base java. awt. event. Mouse. Listener : void mouse. Clicked( java. awt. event. Mouse. Event ) void mouse. Entered( java. awt. event. Mouse. Event ) void mouse. Exited( java. awt. event. Mouse. Event ) void mouse. Pressed( java. awt. event. Mouse. Event ) void mouse. Released( java. awt. event. Mouse. Event ) Enregistré par add. Mouse. Listener(…) auprès de : javax. swing. JComponent (en fait : java. awt. Component) …
Communication entre composants Listeners courants Mouse. Motion. Listeners courants événements mouvements de souris java. awt. event. Mouse. Motion. Listener : void mouse. Dragged( java. awt. event. Mouse. Event ) void mouse. Moved( java. awt. event. Mouse. Event ) Enregistré par add. Mouse. Motion. Listener(…) auprès de : javax. swing. JComponent (en fait : java. awt. Component) …
Communication entre composants Listeners courants Focus. Listeners courants focus java. awt. event. Focus. Listener : void focus. Gained( java. awt. event. Focus. Event ) void focus. Lost( java. awt. event. Focus. Event ) Enregistré par add. Focus. Listener(…) auprès de : javax. swing. JComponent (en fait : java. awt. Component) …
Communication entre composants Listeners courants Window. Listeners courants événements liés aux fenêtres java. awt. event. Window. Listener : void window. Activated( java. awt. event. Window. Event ) void window. Closing( java. awt. event. Window. Event ) void window. Deactivated( java. awt. event. Window. Event ) void window. Deiconified( java. awt. event. Window. Event ) void window. Iconified( java. awt. event. Window. Event ) void window. Opened( java. awt. event. Window. Event ) Enregistré par add. Mouse. Listener(…) auprès de : javax. swing. JFrame javax. swing. JDialog … java. awt. Window
Communication entre composants Listeners courants Component. Listeners courants événements liés aux composants java. awt. event. Component. Listener : void component. Hidden( java. awt. event. Component. Event ) void component. Moved( java. awt. event. Component. Event ) void component. Resized( java. awt. event. Component. Event ) void component. Shown( java. awt. event. Component. Event ) Enregistré par add. Component. Listener(…) auprès de : javax. swing. JComponent (en fait : java. awt. Component) …
Communication entre composants Gestion interne des événements L'OS notifie la VM d'événements : Déplacements et clicks souris, frappes clavier (événements d'inputs) Perte, gain du focus Portions d'écran à mettre à jour … La VM insère ces événements, ainsi que ceux générés par le programme, sous forme d'objets dans une queue (file) :
Communication entre composants Gestion interne des événements Ces événements sont analysés par le dispatching thread Thread : unité d'exécution qui s'exécute en parallèle d'autres, au sein d'un programme Les événements d'inputs et de focus sont transmis aux composants, qui en informent leurs listeners Les mises à jour de l'affichage sont transmises au Repaint. Manager, qui appelle les méthodes paint(…) des composants concernés, qui appellent enfin paint. Component(…) Les événements de mise à jour de l'affichage ont la plus faible priorité…
Le Repaint. Manager javax. swing. Repaint. Manager Responsable de : La gestion des dirty regions de l'écran, à mettre à jour La gestion du double-buffering … Une seule instance par application, renvoyée par : Repaint. Manager. current. Manager( null ) Optimisation possible de la mise à jour d'un composant complexe en : 1°/ Obtenant sa dirty region (un java. awt. Rectangle) par : Rectangle Repaint. Manager. get. Dirty. Region( JComponent ) 2°/ Ne redessinant que ce qui doit l'être en contrôlant les intersections entre les bounding boxes et la dirty region avec : boolean intersects( Rectangle )
Le Repaint. Manager javax. swing. Repaint. Manager Pour les descendants de JComponent, les appels de méthodes lors de la mise à jour de l'affichage sont : paint( Graphics ) paint. Component( Graphics ) paint. Border( Graphics ) paint. Children( Graphics )
La clipping area Synonyme de dirty region pour un composant : Zone en dehors de laquelle les ordres graphiques n'ont pas d'effet S'obtient par le contexte graphique : Rectangle get. Clip. Bounds()
Pluggable Look & Feel L'AWT délégait l'affichage et le comportement à des composants pairs du système Swing délégue l'affichage et le comportement à des composants Java pluggables javax. swing. UIManager propose de nombreuses méthodes pour gérer les L&F : Obtenir le L&F courant : Look. And. Feel get. Look. And. Feel() Obtenir la classe du L&F implémentant le L&F de la plate-forme d'exécution : String get. System. Look. And. Feel. Class. Name() Obtenir la classe du L&F cross-platform : String get. Cross. Platform. Look. And. Feel. Class. Name() Lister les L&F installés : UIManager. Look. And. Feel. Info[] get. Installed. Look. And. Feels()
Pluggable Look & Feel Modifier le Look & Feel Les JComponents exhibent une méthode : void set. UI( Component. UI new_ui ) Généralement, on change le L&F général : UIManager. set. Look. And. Feel( new_LF_class_name ); Puis on lance une mise à jour de l'arbre des composants : Swing. Utilities. update. Component. Tree. UI( component ); component devrait être un composant "racine" : Component Swing. Utilities. get. Root( Component component )
Les Applets Une applet est une petite application en Java, destinée à être intégrée dans une page HTML Une application standalone : N'a pas d'exigence en terme de type (elle étend au minimum java. lang. Object) N'a qu'un seul point d'entrée : public static main( String[] ) Une applet : Doit au moins étendre java. applet. Applet ou javax. swing. JApplet N'a pas de point d'entrée exigé Graphe d'héritage : java. lang. Object java. awt. Component java. awt. Container java. awt. Panel java. applet. Applet javax. swing. JApplet
Les Applets Etat des applets Une applet peut être dans 4 états : A chaque transition d'état une des méthodes suivante est invoquée : public void init() public void start() public void stop() public void destroy()
Les Applets Arguments des applets Une application standalone reçoit les paramètres de la ligne de commande en argument de la méthode statique main sous forme de liste de valeurs de type String Une applet reçoit les paramètres jouxtant le tag HTML sous forme d'ensemble de paires <nom, valeur> de type String, lisibles par la méthode : public String get. Parameter( String parameter_name )
Les Applets Le tag <APPLET>, fonctionnant si la version de la JVM du navigateur est correcte : <APPLET [CODEBASE= codebase. URL] CODE= applet. File [ALT= alternate. Text] [NAME= applet. Instance. Name] WIDTH= pixels HEIGHT= pixels [ALIGN= alignment] [VSPACE= pixels] [HSPACE= pixels]> [<PARAM NAME= applet. Parameter 1 VALUE= value>] [<PARAM NAME= applet. Parameter 2 VALUE= value>]. . . [alternate. HTML] </APPLET>
Les Applets Les plugins Java Les navigateurs étant souvent en retard de plusieurs versions de JVM (+ M$ qui ne met volontairement pas à jour la JVM d’IE), Sun a développé une technique de plugins Java : Les navigateurs n'exécutent plus l'applet, mais un plugin qui exécute lui-même l'applet Le plugin peut-être téléchargé à la volée Internet Explorer utilise le tag <OBJECT>, Netscape Navigator le tag <EMBED>… Il faut donc faire trois versions de tags…
Les Applets Le tag <OBJECT> (Internet Explorer) <OBJECT classid="clsid: 8 AD 9 C 840 -044 E-11 D 1 -B 3 E 9 -00805 F 499 D 93" WIDTH= pixels HEIGHT= pixels CODEBASE= "http: //java. sun. com/products/plugin/1. 2. 2/ jinstall-1_2_2 -win. cab#Version=1, 2, 2, 0"> [CODEBASE= codebase. URL] <PARAM NAME= CODE VALUE= applet. File> <PARAM NAME= "type" VALUE= "application/x-java-applet; version=1. 2. 2"> <PARAM NAME= "scriptable " VALUE="false"> [<PARAM NAME= CODEBASE VALUE= codebase. URL>] [<PARAM NAME= applet. Parameter 1 VALUE= value>]. . . [<NOEMBED> alternate. HTML] <NOEMBED> </OBJECT>
Les Applets Le tag <EMBED> (Netscape Navigator) <EMBED type= "application/x-java-applet; version=1. 2. 2" CODE= applet. File WIDTH= pixels HEIGHT= pixels pluginspage= "http: //java. sun. com/products/plugin/1. 2. 2/ plugin-install. html"> scriptable= false [<PARAM NAME= applet. Parameter 1 VALUE= value>]. . . [<NOEMBED> alternate. HTML </NOEMBED>] </EMBED>
- Slides: 81