IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Élaboration: GUI par événements


Sujet :

C

  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2004
    Messages
    152
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Août 2004
    Messages : 152
    Par défaut Élaboration: GUI par événements
    Bonsoir,

    Je suis à la recherche d'aide et de documentations pour faire une GUI (Graphical User Interface) directement en C en passant par OpenGL pour le rendu. Évidemment, c'est pas évident, car je compte faire du événementiel, avec un peu d'orientation objet à la façon C, histoire que ça soit évolutif aussi.

    J'ai fais quelques recherches mais je n'ai pas trouvé pour élaborer l'organisation et faire communiquer les différentes parties entre-elles, c'est à dire les fenêtres avec le "gestionnaire principal", les widgets (composants genre champs de texte) avec la fenêtre, etc... Je pensais faire une espèce d'arbre: Gestionnaire > Fenêtres > Widgets complexes > widgets primitifs. Il y a un gestionnaire pour plusieurs fenêtres; les widgets complexes c'est un groupement de widget (exemple: Un champ de texte avec un bouton valider). Il y aura sûrement une méthode Draw appelée récursivement pour chacun des éléments.

    Maintenant, je réfléchit sur les événements: quand on tape quelque chose dans un champs, quand on a un champs actif, un bouton est cliché, etc... Faut-il plutôt faire un gestionnaire d'événements global (envoyé au gestionnaire) ou bien local (parent-enfant). Ce qui serait pas mal, c'est des articles traitant de GUI de manière abstraite, ou avec OpenGL (j'y crois pas trop). Sinon, vos conseils sont les bien venus aussi. Merci !

  2. #2
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    27 129
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2008
    Messages : 27 129
    Billets dans le blog
    150
    Par défaut
    Bonjour,

    En fait les premières lignes de votre message m'ont étonné. Effectivement une GUI avec OpenGL est vraiment un concept très spécial. Car OpenGL ne fait pas de la gestion de fenpetre mais juste de l'affiche, et il affiche une fenêtre. Alors même si on fait un semblant de GUI dedans ... vous pourrez toujours bouger toute la GUI juste en cliquant sur la fenêtre OpenGL

    Du coup, lorsque l'on parle de GUI, on parle de gestionnaire de fenetre, ce qui nous amène à la XLib pour Linux / Unix et la WinAPI pour Windows. Voici les bases.
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2004
    Messages
    152
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Août 2004
    Messages : 152
    Par défaut
    Effectivement, ça porte à confusion. Dans mon cas ce serait de faire un gestionnaire de fenêtres/d'interface rendu par OpenGL en "3D" dans un contexte créé par l'OS. Donc je ne me soucie pas l'API qui crée/gère la fenêtre OpenGL en question puisque je passe par glut. Donc je parle bien de dessiner avec les primitives d'OpenGL les fenêtres, boîtes de texte tout ça, de gérer les fenêtres drag(-and-drop), événements (input-output), moi-même. Le but étant d'avoir une interface dans un jeu (oui toujours le même jeu )

    Si je poste dans ce forum, c'est bien parce que je m'intéresse au côté programmation C et non du côté OpenGL (pour l'instant).

    Quel est le bon terme alors ? Me semblait que GUI convenait très bien, puisque c'est un concept abstrait, sinon le nom c'est Window-Manager peut-être ? J'espère que je me suis bien exprimé cette fois-ci.

  4. #4
    Membre expérimenté
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2008
    Messages
    187
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo

    Informations forums :
    Inscription : Octobre 2008
    Messages : 187
    Par défaut
    Citation Envoyé par jamesb Voir le message
    Quel est le bon terme alors ? Me semblait que GUI convenait très bien, puisque c'est un concept abstrait, sinon le nom c'est Window-Manager peut-être ? J'espère que je me suis bien exprimé cette fois-ci.
    Pour moi, GUI convient très bien meme si le terme est moins précis que Gestionnaire de Fenetre (un gestionnaire de fenetre est un GUI mais pas l'inverse).

    Pour les évènements, je te propose une solution globale :
    - seul le gestionnaire de fenetre détecte les évènements
    - lorsque l'utilisateur clique sur l'écran, le gestionnaire de fenetre cherche (récursivement par exemple) le widget qui se trouve sous la souris. Ce widget devient alors "actif"
    - lorsque l'utilisateur appuie sur une touche, alors une fonction sera appelée avec comme paramètre le widget actif. En testant le type de ce widdget, tu peut rajouter du comportement de manièrte extensible. Par exemple pour un champ texte, la fonction callback aura pour role de concaténer au texte courant le caractère correspondant au clavier.

    Avec cette architecture, tu pourras facilement rajouter de nouveaux évènements, comme le drag & drop, le double clic, etc.

  5. #5
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    27 129
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2008
    Messages : 27 129
    Billets dans le blog
    150
    Par défaut
    Pardon pour la confusion que j'avais apporté avec mon histoire de gestionnaire de fenêtre. Il est vrai que je n'avais pas compris le problème, ou plus précisément, je n'avais pas pensé à ce cas.
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  6. #6
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2004
    Messages
    152
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Août 2004
    Messages : 152
    Par défaut
    dancingmad: merci pour ton avis ! Je pensais également à un tel système, récursif. Ça va pas être facile au début mais une fois que j'aurais mis le gestionnaire d'événements (haut-niveau) je pourrais commencer à m'amuser (onClick, onDoubleClick, OnMouseOut, onKeyUp, etc... à la javascript).

    LittleWhite: Pas de problème, j'ai sûrement pas assez détailler ce que je voulais faire.

    D'ailleurs j'ai une idée que je vais sûrement creuser: le concept d'overlay. Je m'explique, un overlay est une couche qui dessine à l'écran (1 passe), on peut faire une couche jeu et une couche GUI, appelé dans cet ordre. Ça me permettrais d'avoir des overlay spécialisée 1/ jeu ; 2/ carte 2D ; 3/ GUI-fenêtres ; 4/ sur-couche chat genre Steam par exemple. Je pense que c'est une très bonne idée sachant que chaque couche est implémentée de manière indépendante et au-dessus de tout ça il y aurait un overlays-manager qui appelle les méthodes draw des overlays. Un seul overlay pourra être "séléctionné" (par clique de l'utilisateur ou shift-tab par exemple) et ainsi recevoir les input. Ce qui permettrait de vraiment séparer les choses.

    Donc je vais pouvoir coder la GUI dans un overlay spécialisé, j'espère que ça sera réutilisable. Vu que je peux faire à la POO en C, ça devrait pas trop poser de problème si j'ai plusieurs UI à implémenter (exemple: GUI/carte -> boutons etc...). Mon struct pour le moment ressemble à ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // Generic overlay
    struct __overlay {
    	// Delete
    	void(*Delete)(Overlay *lay);
     
    	// Set the matrices
    	void(*InitMatrices)(Overlay *lay);
    	// Draw the lay
    	void(*Draw)(Overlay *lay);
     
    	// Input if active
    	void (*EventKeyboard)(Overlay *lay, int key);
    	void (*EventMouse)(Overlay *lay, int x, int y);
    };
    Il me faut encore élaborer le gestionnaire d'événements global (au niveau du overlays-manager) pour ensuite les envoyer aux fonctions Event* de l'overlay sélectionné.
    Qu'en pensez-vous, c'est génial, non ?

  7. #7
    Expert confirmé

    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    10 610
    Détails du profil
    Informations personnelles :
    Âge : 67
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 10 610
    Billets dans le blog
    2
    Par défaut
    primo, ton idée d'overlay est assez absurde, car elle va dépendre de l'outil d'affichage, tout comme le contenu et la manipulation des fenêtres, et les fonctionalités... Un overlay est quelque chose qui est affiché en dernier.. Il suffit donc de "modéliser" ton overlay comme n'importe quelle autre couche (et donc d'y stocker lignes, textes, polygones, images, etc..) et simplement d el'afficher en dernier.. Mais cette modélisation n'a rien à voir avec l'outil d'affichage final...



    Secondo, quand on veut faire ce genre de choses, il faut penser à un "double" héritage...


    L'ensemble des fonctionalités dites "visuelles" doivent être indépendante d ela plateforme d'affichage, mais doivent appeller des fonctions dont tu définis l'API, avec une structure indépendante, qui contiendra un pointeur (void) vers la structure de cet outil (avec ses propres variaibles/types) ; et dont la biblothèque (que tu fabriqueras) appelleras les fonctions de l'outil particulier en question..

    Exemple d'une telle structure avec X11 :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    typedef struct pIstruct {
     
         void *Parent ;
     
         char *MaMachine ;
         int     MonParamètre ;
    } IndptStruct ;
    Exemple de la structure X11 (qui sera celle déclarée comme variable par exemple dans le main)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    typedef struct pXStruct {
     
          IndptStruct  *IndStruct ;
     
          Display *dpy ;
          GraphicContext GC ;
         XRegion toto ;
    } XStruct ;


    Librarie "générique" (qui utilsera la structure générique)

    • Trace_Ligne
    • Trace_Symbole
    • Trace_Polygone
    • Effectue_Boucle
    • Affiche_Film
    • ...


    Exemple de Trace_Polygone :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void Trace_Polygone ( Indpt_Struct *IStruct, int *X, int *Y, int N)
    {
         int i, X1, Y1 ;
     
        for ( i = 0 ; i < N ; i++ )
          {
               if ( i > 0 )
                   Trace_Segment ( IStruct, X1, Y1, X[i], Y|i] );
     
               X1 = X|i] ;
               Y1 = Y[i] ; 
          }
     
         Trace_Segment ( IStruct, X1, Y1, X[0], Y[0])
    }
    Trace_Segment est la fonction qu'il faudra ré-implémenter chaque fois que tu changes de bliblothèque graphique, mais à l'inverse tu n'auras jamais à changer le reste de ton code...

    Elle ne sera pas générique, mais du coup la seule chose que tu auras à faire quand tu changes d'outil graphique sera de re-linker avec la traduction de cette nouvelle fonction...

    Exemple avec X11 :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void Trace_Segment ( IndptStruct *IStruct, int X1, int Y1, int X2, int Y2 )
    {
         XStruct *xstruct = (XStruct *)IStruct->Parent ;
     
         XDrawLine ( xstruct->dpy, X1, Y1, X2, Y2); 
    }


    Miantenant, la même chose peut s'appliquer pour un gestionnaire d'événements..

  8. #8
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2004
    Messages
    152
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Août 2004
    Messages : 152
    Par défaut
    Bonjour souviron34 et merci pour ton conseil.

    Je ne vois pas bien pourquoi mon idée d'overlay (qui ressemble plus à des layers tout court finalement) serait absurde. Ils permettraient, théoriquement, de séparer le rendu des éléments graphiques (GUI, carte, jeu) de leur résultat à l'écran (le layers manager étant là pour gérer cela). Par exemple je pourrais dessiner le layer de jeu et de carte aussi bien en plein écran qu'en petit, les repositionner, redimensionner comme je le souhaite sans que cela ne les affectent. Ces layers ne dépendront pas de l'outil d'affichage si j'implémente une API abstraite, je ne comprends donc pas ce que tu veux dire ? De plus je ne comptais pas seulement faire une seule (sur-)couche mais plusieurs, comme je les ai listées, y compris la partie principale: ce qui est affiché en 3D pour le jeu (rendu en premier) ! Mais ce n'est que mon idée pour l'instant.

    Évidemment, pour le moment, je n'ai pas fais d'abstraction pour les graphismes, j'utilise souvent directement les fonctions OpenGL même si j'ai fais quelques fonctions "pont" pour dessiner triangle / quads notamment (pas encore d'API / struct). La séparation de l'API graphique est-elle si importante ? On peut faire tout une API abstraite, comme tu l'illustres, mais cela rajoute beaucoup de code au final, est-ce que ça en vaut la chandelle ? D'ailleurs je ne sais pas si je compte utiliser autre chose qu'OpenGL, enfin, c'est vrai que si c'est séparé c'est plus propre et portable ! Je ne sais pas vraiment quoi choisir et surtout comment le faire (proprement) ne l'ayant encore jamais fait. Je songe aussi aux optimisations au travers de l'abstraction comme les DisplayLists et les VBO, et tout ce qui est en rapport avec les shaders également ! Est-ce possible ? Cela ne me semble pas facile.

    D'autre part, je comptais utiliser l'héritage à la C++ objet, comme décrit ici pour mes overlays (comme illustré ci-dessus, il est "virtuel"). Est-ce que c'est mieux de faire des pointeurs sur les parents hérités plutôt que de faire du transtypage ? Il me semble que je ne pourrais obtenir cette liberté dans mes différents layers qu'en passant par le transtypage (ajout de variables spécifiques, fonctions, etc...).

    Citation Envoyé par souviron34
    Il suffit donc de "modéliser" ton overlay comme n'importe quelle autre couche
    de quelle couche parles-tu ? Est-ce que tu parles de mon concept de layers ? J'ai sûrement utiliser le terme overlay abusivement en voulant plutôt signifier un concept de rendu par couche/layers indépendante au rendu et aux événements.

    Citation Envoyé par souviron34
    Miantenant, la même chose peut s'appliquer pour un gestionnaire d'événements..
    Il me semble avoir assez bien compris ton exemple d'API abstraire pour le rendu avec son implémentation spécifique pour X, cependant je ne saisis pas comment appliquer ça aux événements. Cela me semble vraiment très différent, pourrais-tu développer un petit peu plus, stp, je t'en remercie.

  9. #9
    Expert confirmé
    Avatar de diogene
    Homme Profil pro
    Enseignant Chercheur
    Inscrit en
    Juin 2005
    Messages
    5 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Enseignant Chercheur
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2005
    Messages : 5 761
    Par défaut
    D'autre part, je comptais utiliser l'héritage à la C++ objet, comme décrit ici pour mes overlays
    Dans ce cas, je te conseille de lire la discussion héritage en C qui apporte des informations sur ce sujet.

  10. #10
    Membre Expert
    Inscrit en
    Avril 2010
    Messages
    1 495
    Détails du profil
    Informations forums :
    Inscription : Avril 2010
    Messages : 1 495
    Par défaut
    salut,

    tu peux aussi regarder ce projet dont la finalité correspond assez bien à ce que tu veux.

  11. #11
    Expert confirmé

    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    10 610
    Détails du profil
    Informations personnelles :
    Âge : 67
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 10 610
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par jamesb Voir le message
    Je ne vois pas bien pourquoi mon idée d'overlay (qui ressemble plus à des layers tout court finalement) serait absurde. Ils permettraient, théoriquement, de séparer le rendu des éléments graphiques (GUI, carte, jeu) de leur résultat à l'écran (le layers manager étant là pour gérer cela). Par exemple je pourrais dessiner le layer de jeu et de carte aussi bien en plein écran qu'en petit, les repositionner, redimensionner comme je le souhaite sans que cela ne les affectent.

    Parce qu'un overlay n'est qu'une couche de dessin parmi d'autres, qui toutes ont le même "problème", c'est à dire un affichage dépendant de la taille de la fenêtre, le choix des couleurs, les données qui vont dessus, le zoom choisi, etc etc...


    Et que l'approche "overlay = classe" n'est pas efficace...


    Ce qu'il faut c'est une clase "couche dessin", dont une instance est par exemple le fond, une autre la couche au dessus, une autre la couche overlay, et qui toutes ont la même méthode "refesh" ou "repaint", comme ça il n'y a pas de duplication de code...


    Exemple :

    supposons que tu dessines une fenêtre avec une bordure (différents rectangles ou lignes), une carte en fond, puis par dessus des villes, puis des voitures, et enfin des "overlay", c'est à dire par exemple un commentaire ou une info ou quelque chose d'autre..


    Chacune de ces couches sera virtuellement dessinée avec des appels à des trace-ligne, trcae_rect, trace_cercle, trace_texte, etc etc..

    Il est assez lourd (et gigantesque et compliqué du point de vue de la maintenance) de spécialiser l'affichage de ces couches...

    Ce que je voulais dire est donc de se servir d'un concept de couche, une classe, qui a sa méthode "repaint" qui psse à travers tous les objets de cette couche..

    Chaqe "couche" est une couche d'overlay..

    La "vraie" overlay est uniquement la dernière à se tracer, donc la dernière dans la liste ou le tableau...

    C'est tout..



    Quant au gestionniare, je ne sais pas exactement pour openGL, mais j'aurais tendance à dire, puisque c'est basé sur X11, que c'est la même philosophie : un gestionnaire serveur, qui centralise et dispatch tout en fonction des "enregistrements" de demandes...

    Et que donc c'est une bonne approche..

  12. #12
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2004
    Messages
    152
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Août 2004
    Messages : 152
    Par défaut
    @diogene: Oui, c'est d'où je viens justement. Ils débattent du transtypage vs l'héritage pointé (néologisme de ma part). Il me semble que dans mon cas le plus propre est le transtypage.

    @minnesota: Je suis désolé mais je ne saisis pas bien leurs buts exactement, ça m'as l'air d'être abstrait pour le moment. Est-ce qu'il y a une page qui explique concrètement leurs idées (novatrices) ?

    Citation Envoyé par souviron34 Voir le message
    Il est assez lourd (et gigantesque et compliqué du point de vue de la maintenance) de spécialiser l'affichage de ces couches...
    Cela ne me semble pas le cas justement. Chaque couche étant suffisamment différente pour ne partager que des méthodes de l'API graphique. Exemple: 1/ La couche principale jeu: dessine les modèles ; 2/ La couche "GUI": dessine des fenêtres et des widgets. Quel est le seul point commun entre ces couches ? L'API graphique qui dessine. Ces couches n'utilisent pas de modèles 3D, de matrices (projection 3D vs 2D) communs. De plus les événements (qui seront reçus d'une manière homogène entre les couches) sont utilisés différents. La vue en 3D (à la FPS) est différente que le déplacement d'une fenêtre en drag and drop. Il me semble donc que la spécialisation n'est pas compliquée mais impérative, je ne vois pas d'autres solutions ? Gigantesque sûrement mais pas plus que la grandeur du jeu lui même puisqu'on déporte l'affichage d'une fonction main à celle d'un draw dans une couche.

    Citation Envoyé par souviron34 Voir le message
    Ce que je voulais dire est donc de se servir d'un concept de couche, une classe, qui a sa méthode "repaint" qui psse à travers tous les objets de cette couche..
    Je doute que ça soit faisable. Cela signifierait que chaque objet à dessiner doit être rendu objet également dans le code avec une méthode Draw. D'accord ça m'a l'air faisable mais je tombe à nouveau dans le transtypage. D'une part j'ai des modèles (qui ont déjà une méthode draw) mais aussi une planète (également un draw), sûrement des particules. Ces objets sont très différents à la base. Il faut donc que je rajoute un pointeur de fonction au début de chacun pour que je puisse l'appeler sans ce soucier de leurs différences.

    Citation Envoyé par souviron34 Voir le message
    Quant au gestionniare, je ne sais pas exactement pour openGL, mais j'aurais tendance à dire, puisque c'est basé sur X11, que c'est la même philosophie : un gestionnaire serveur, qui centralise et dispatch tout en fonction des "enregistrements" de demandes...

    Et que donc c'est une bonne approche..
    Je regarderais de plus près X11 car les codes et les documentations sont sûrement disponibles facilement. J'espère simplement que leur méthode est applicable avec OpenGL.

  13. #13
    Membre Expert
    Inscrit en
    Avril 2010
    Messages
    1 495
    Détails du profil
    Informations forums :
    Inscription : Avril 2010
    Messages : 1 495
    Par défaut
    Citation Envoyé par jamesb Voir le message
    @minnesota: Je suis désolé mais je ne saisis pas bien leurs buts exactement, ça m'as l'air d'être abstrait pour le moment. Est-ce qu'il y a une page qui explique concrètement leurs idées (novatrices) ?
    il y a une synthèse.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Windev : Gestion de liaison série par événement série
    Par jurassic pork dans le forum Contribuez
    Réponses: 2
    Dernier message: 02/10/2023, 11h11
  2. Nouvelle fenêtre générée par évènement
    Par goofyto8 dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 30/07/2013, 07h49
  3. Modification d'un objet GUI par un thread
    Par Dazdh dans le forum Interfaces Graphiques en Java
    Réponses: 2
    Dernier message: 24/03/2009, 13h52
  4. Lecture de fichiers wave par événements extérieurs
    Par Jean Breil dans le forum Pascal
    Réponses: 0
    Dernier message: 19/09/2007, 00h19
  5. Transfert d'icone par évènement
    Par microJaP dans le forum Delphi
    Réponses: 2
    Dernier message: 28/04/2007, 15h37

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo