Editeur de format d'édition

wiskywisky Membre
20:59 modifié dans Vos applications #1
Bonjour à  tous,

Je vous décrit rapidement le projet.
Mon application, permet de générer un fichier XML (UTF-8) contenant les détails d'une mise en page d'impression. Je m'explique, grâce à  un moteur d'édition, le fichier XML qui va bien et un tableau de valeur, l'impression ce réaliser et se met en page.
Le fichier XML contient, les coordonnées des lignes, cadre, emplacement de valeur etc...
Je cherche à  faire en sorte que lors de l'édition ou la création d'un fichier de format, il soit possible de visualiser le résultat. En clair, quel objet faut t'il utiliser pour arriver à  faire une image de preview et pouvoir dessiner les informations saisie par l'utilisateur (trait, rectangle, rond, texte, etc...) (si l'image pouvait être enregistrable cela serai super).

Je sais pas si j'ai été clair. Le but avoué de ce projet est de me facilité la tâche au travail. J'ai déjà  un programme qui fait à  peut de chose près la même chose sauf qu'il est prévu pour générer un code REALBasic et il est codé avec REALbasic (j'ai fait tellement vite que c'est irrécupérable. Sauf le principe ;) ).

Je vous remercie d'avance de votre aide :)
«13

Réponses

  • MalaMala Membre, Modérateur
    20:59 modifié #2
    Je dirais que dans ton cas le mieux est d'utiliser la classe NSImage pour créer une image vièrge dans laquelle tu va dessiner ton rendu.

    Pour ce qui est des tracés, regardes du côté de la classe NSBezierPath.

    Pour l'affichage, il te suffit ensuite d'envoyer ton instance NSImage vers une NSImageView.

    Et NSImage a bien sûr tout ce qu'il faut pour sauvegarder dans différents formats.

  • wiskywisky Membre
    20:59 modifié #3
    OK, merci beaucoup :)
  • wiskywisky Membre
    20:59 modifié #4
    Petit déterrage ! Mais ça reste sur le sujet !

    Plutôt que d'avoir une image à  générer et à  afficher, je souhaite que l'utilisateur puisse interagir avec les objets. De la même façon que OmniGraffle !
    En clair faire un éditeur vectoriel avec la possibilité de changer le "zoom" d'affichage pour permettre la vue global ou plus précise des objets et bien sûr la gestion des plans (premier, dernier, etc...).

    Comment s'y prendre ? Doit-je créer des surcharges de NSImageView que je placerait dans une vue et qui contiendrais l'objet ?
    Existe t-il un objet qui permet de gérer d'autre objet et l'affichage de dessin vectoriel ?

    Merci de vos réponse ;)
  • CéroceCéroce Membre, Modérateur
    20:59 modifié #5
    C'est justement ce que fait mon logiciel (http://www.ceroce.com), et crois-moi, ce n'est pas simple.

    Il faut créer une sous-classe de NSView et dessiner grâce à  NSBezierPath ou autres. J'ai déjà  dit que ce n'était pas simple ?
    Pour l'ordre des figures, ce n'est par contre pas très compliqué: elles sont organisées dans un NSArray, celle se trouvant à  l'index 0 étant la première dessinée, il suffit de changer l'ordre dans la liste pour changer de plan.

    Et pour le zoom ? Tu veux zoomer à  200%: il faut tout dessiner deux fois plus grand.
  • wiskywisky Membre
    20:59 modifié #6
    C'est bien ce que je pensais !

    Le plus dur dans mon cas va être de dessiner avec NSBezierPath. Je ne l'ai jamais fait !
    La gestion des objets n'est pas très compliqué au final ;)
    J'ai en soit 4 objets simples (trait, texte, image, rectangle). Ces objet permettent la construction des objet plus complexe comme un tableau, texte + bordure, etc...

    Au final, mon logiciel très limitatif (et ultra-lourd) écrit en REALbasic vas vite sauté avec son remplaçant Cocoa !

    Pour l'affichage des objets tu ajouter à  la NSView principale les autres NSView en tant que enfants ? Mais pour garder l'ordre des plans il ne faut pas ajouter du dernier plan vers le premier ?
  • CéroceCéroce Membre, Modérateur
    20:59 modifié #7
    dans 1234101353:

    Le plus dur dans mon cas va être de dessiner avec NSBezierPath. Je ne l'ai jamais fait !


    Non, pas du tout, dessiner avec NSBezierPath n'a rien de sorcier.
    Le plus dur, et de très loin, va être de synchroniser le dessin avec le modèle.


    Pour l'affichage des objets tu ajoutes à  la NSView principale les autres NSView en tant que enfants ?


    Non, je n'utilise qu'une seule vue, pour deux raisons.
    La première c'est qu'Apple déconseille, pour des raisons de performances, d'utiliser trop de NSViews (chaque changement de vue produit un changement de Graphic Context).
    La seconde, c'est que tu vas devoir dessiner des rectangles de sélection et des poignées pour déplacer tes vues. Ceci amène beaucoup de questions: sur quelle vue les afficher ? De plus, toutes les vues possèdent des systèmes de coordonnées différents: faire les conversions va te pourrir la vie.


    Mais pour garder l'ordre des plans il ne faut pas ajouter du dernier plan vers le premier ?


    Si, très exactement, ce qui apparaà®t au fond (= au dernier plan), est ce qui est dessiné en premier. Les autres plans le recouvrent.

    Consulte le Cocoa Drawing Guide, c'est une doc bien fichue.
  • wiskywisky Membre
    20:59 modifié #8
    Donc, une sous-classe de NSView pour afficher le rendu. Comment gérer dans ce cas le clic sur les objets pour l'ajout des poignées ? Puis le clic + déplacement sur les poignées ?
    Comment récupérer les nouvelles propriétés ?




    En PJ : une image écran de la vue dessiné.
  • AliGatorAliGator Membre, Modérateur
    20:59 modifié #9
    NSBezierPath possède une méthode [tt]containsPoint[/tt].
    Il te suffit de boucler sur tous tes bezier paths (que tu stokes dans un tableau) et tester cette méthode jusqu'à  ce qu'il réponde "YES", et tu as alors trouvé ton BezierPath et donc ton objet cliqué.

    Attention, si tu utilises un NSArray pour stocker toutes tes pièces et donc tous tes BezierPath, si tu parcourres ce tableau dans le sens normal pour demander à  chaque pièce de se dessiner, il faut alors parcourir le tableau dans le sens inverse pour tester le clic. En effet, si certains de ces objets se recouvrent/chevauchent, il faut bien tester d'abord les objets au premier plan avant ceux en arrière plan, donc tester ceux qui sont dessinés en dernier d'abord et ceux dessiné en premier à  la fin ;)
  • wiskywisky Membre
    20:59 modifié #10
    Ok, donc mon tableau contient mes NSBezierPath et je test avec containsPoint pour savoir quel est l'élément cliqué. Donc le focus et les poignées sont dessiné avec NSBezierPath mais ne sont pas stocké dans le tableau.
    Pour les déplacement il faut gérer avec les évènements de la souris sur la NSView ? Et selon les mouvements on redessine le focus et l'objet qui a bougé ? J'imagine qu'il faut tout redessiner à  chaque changement (ou les changement sur l'objet change l'affichage) ?
  • NoNo Membre
    février 2009 modifié #11
    dans 1234105084:


    Pour l'affichage des objets tu ajoutes à  la NSView principale les autres NSView en tant que enfants ?

    Non, je n'utilise qu'une seule vue, pour deux raisons.
    La première c'est qu'Apple déconseille, pour des raisons de performances, d'utiliser trop de NSViews (chaque changement de vue produit un changement de Graphic Context).
    La seconde, c'est que tu vas devoir dessiner des rectangles de sélection et des poignées pour déplacer tes vues. Ceci amène beaucoup de questions: sur quelle vue les afficher ? De plus, toutes les vues possèdent des systèmes de coordonnées différents: faire les conversions va te pourrir la vie.


    Pour le cas de l'affichage des "poignées", je peux te dévoiler la technique qu'a utilisé Apple pour IB 1 et 2 (pour IB3, vu les modifications importantes, je ne sais pas).
    Pour des raisons non dévoilables, j'avais fait un peu de reverse-engineering sur IB.
    Le dessin des lignes (pour relier 2 objets) ou des poignées de redimensionnement était fait dans une fenêtre recouvrant toutes les autres, et dont le fond était transparent.

    Je crois avoir aussi lu quelque part que le Finder faisait de même pour créer ses rectangles de sélection.

    Bien sûr, cette méthode introduit aussi pas mal de complexité entre synchronisation des coordonnées du dessin des poignées/lignes et de celles du dessin "en dessous".
  • CéroceCéroce Membre, Modérateur
    20:59 modifié #12
    dans 1234106847:

    Donc, une sous-classe de NSView pour afficher le rendu. Comment gérer dans ce cas le clic sur les objets pour l'ajout des poignées ? Puis le clic + déplacement sur les poignées ?



    Il te faut supplanter les méthodes -mouseDown:, -mouseUp: et -mouseDragged: de NSView.

    Pour l'exemple de l'ajout de poignées, dans -mouseDown:
    - tu regardes si le clic a eu lieu dans une figure (grâce à  la méthode containsPoint, comme indiqué par Ali, en énumérant à  l'envers)
    - si c'est dans une figure, et si elle ne faisait pas partie de la sélection courante, elle devient la sélection (sauf si la touche Maj. est appuyée, auquel cas elle agrandit la sélection...) => C'est à  ce moment que tu utilises la méthode -bounds des NSBezierPath pour concocter un rectangle englobant toutes les figures.
    - sinon, (clic dans le vide) la sélection devient vide.

    Tu vois l'idée, c'est à  toi de gérer tous les cas !


    Comment récupérer les nouvelles propriétés ?


    Par exemple, pour gérer de déplacement d'une figure:
    - Dans mouseDown: tu as mémorisé les coordonnées de premier clic
    - dans mouseDragged: tu fais la différence des coordonnées du clic et coordonnées courantes. Tu appliques ensuite ces nouvelles coordonnées dans l'objet (couche modèle) représenté par ton NSBezierPath. À cet instant, il faudra alors redessiner la figure. Pour commencer, je te conseille de redessiner toute la vue, plutôt que seulement les figures impactées par le déplacement.


    Forcément, ça n'est pas simple, ça fait des mois que je travaille dessus à  plein temps, il y a bien une raison.
  • wiskywisky Membre
    février 2009 modifié #13
    Bah je serait aussi à  plein temps. C'est pour le boulot mais bon ça ira plus vite à  développer que mon ancienne app.



  • CéroceCéroce Membre, Modérateur
    20:59 modifié #14
    dans 1234115286:

    Le dessin des lignes (pour relier 2 objets) ou des poignées de redimensionnement était fait dans une fenêtre recouvrant toutes les autres, et dont le fond était transparent.


    J'avais lu quelque part que c'était ainsi que IB faisait pour afficher les lignes représentant les outlets. Mais en y réflechissant, ça apporte pas mal d'inconvénients. Par exemple, si la poignée arrive en bord de page, tu ne profites plus du défilement automatique de NSScrollView.
  • wiskywisky Membre
    20:59 modifié #15
    Pour redessiner avec des NSBezierPath déjà  crée il suffi de faire [bp stroke] dans la fonction Draw de la NSView ? il faut recréer la NSView ?
  • NoNo Membre
    20:59 modifié #16
    dans 1234116166:

    dans 1234115286:

    Le dessin des lignes (pour relier 2 objets) ou des poignées de redimensionnement était fait dans une fenêtre recouvrant toutes les autres, et dont le fond était transparent.


    J'avais lu quelque part que c'était ainsi que IB faisait pour afficher les lignes représentant les outlets. Mais en y réflechissant, ça apporte pas mal d'inconvénients. Par exemple, si la poignée arrive en bord de page, tu ne profites plus du défilement automatique de NSScrollView.


    Oui.
    Je pense que la solution de la fenêtre transparente n'a pour seul avantage que de pouvoir dessiner quelque chose entre 2 fenêtres (et donc forcément entre des views différentes).
  • wiskywisky Membre
    février 2009 modifié #17
    Si je doit refaire le dessin de tout les bezierpath le fait de les mettre dans le tableau ne sert que lors de la détection des clics ?


    EDIT :
    Je viens de terminé la mie en place du zoom et dé-zoom ! Cela ce fait assez simplement et demande de tout redessiner!
  • AliGatorAliGator Membre, Modérateur
    20:59 modifié #18
    Bah moi pour mon programme actuel j'ai ceci :
    • Une classe "AliPiece" qui représente une piece de mon jeu
    • Cette classe AliPiece contient un bezierpath (enfin CGMutablePathRef, vu que je suis sur iPhone, mais c'est pareil) en variable d'instance, et une méthode "containsPoint" (qui rappelle la méthode containsPoint de mon bezierpath mais bon pour iPhone cette méthode n'est pas implémentée sous le même nom et en plus j'applique des éventuelles transformations affines à  mes pièces avant le test)
    • Tout plein d'autres variables d'instance (position de la pièce, rotation, etc...) et de méthodes propres à  ma pièce (sa couleur, sa valeur, son nom... plein de trucs utiles pour mon jeu dans mon cas)
    • Et une méthode "draw" dans cette classe AliPiece, qui applique les transformations affines que je garde dans ma classe, puis dessine le bezierpath (et restaure le graphic context d'avant les transformations)


    Et du coup, j'ai un NSArray contenant toutes mes "AliPieces", et dans le "drawRect" de la vue je parcoure ce tableau pour appeler le "draw" de chaque AliPiece... et dans mon event mouseDown (enfin touchBegan vu que c'est iPhone) je parcoure le tableau en sens inverse pour appeler le containsPoint.


    Tout ça pour dire que l'intérêt d'avoir un tableau c'est pas que pour le hitTest pour savoir quelle pièce est sélectionnée, mais aussi parce que bien souvent ce n'est pas un tableau de BezierPath mais un tableau d'objets contenant entre autres des bezierpath (mais aussi d'autres trucs représentant ta "pièce"), et que ça permet donc de gérer tes pièces de façon indépendantes.[/list]
  • wiskywisky Membre
    20:59 modifié #19
    J'ai du mal a comprendre le concept de Contexte. ta fonction Draw, du moment qu'elle est appelé par drawRect de la NSView suffit pour qu'elle dessine dans le contexte graphique ? Le fait de restaurer le contexte sert à  quoi ?


    Désolé avec mes questions bidon mais j'aime comprendre ce que je fait et comme l'anglais n'est pas mon for la doc est dur à  comprendre pour moi  :-\\
  • AliGatorAliGator Membre, Modérateur
    20:59 modifié #20
    L'histoire du contexte, en fait c'est parce que je modifie le contexte actuel en appliquant une transformation affine avant de dessiner ma pièce (parce que je veux la dessiner tournée d'un angle donné et translatée d'une certaine position...), donc une fois que j'ai dessiné ma pièce, je "restaure" l'état d'avant, pour pas que mes modifications (en l'occurrence l'application de ma transformation affine) risquent d'impacter les actions de dessins effectuées après mon drawRect (sinon les transformations affines vont se cumuler)

    Donc pour te donner une idée, dans mon "drawRect" de ma vue j'ai :
    NSEnumerator* e = [pieces objectEnumerator];<br />	AliPiece* p;<br />	while( (p = [e nextObject]) ) {<br />		[p draw];<br />	}<br />
    
    et la méthode "draw" de mon AliPiece ressemble à  :
    -(void)draw {<br />	CGContextRef ctx = UIGraphicsGetCurrentContext();<br />	CGContextSaveGState(ctx);<br />	<br />	CGContextConcatCTM(ctx, CGAffineTransformMakeTranslation(position.x, position.y));<br />	CGContextConcatCTM(ctx, CGAffineTransformMakeRotation(rotCount*M_PI_4));<br />	CGContextSetFillColorWithColor(ctx, [fillColor CGColor]);<br />	CGContextSetStrokeColorWithColor(ctx, [strokeColor CGColor]);<br /><br />	CGContextAddPath(ctx, path);<br />	CGContextDrawPath(ctx, kCGPathFillStroke);<br />	<br />	CGContextRestoreGState(ctx);<br />}
    
    Bon c'est du code pour iPhone, qui fait appel directement aux méthode CoreGraphics, donc ça va p'tet pas te parler : toi tu utiliserais plutôt des NSAffineTransforms pour créer et appliquer tes transformations (translation, rotation), et des NSBezierPath pour dessiner ta pièce au lieu des CGMutablePathRef que moi j'utilise... donc bon ça sera pas les mêmes méthodes mais l'idée est là .
    Comme j'applique une transformation, une fois fini je restaure l'état pour éviter que la transformation reste appliquée pour la pièce suivante, quoi.
  • CéroceCéroce Membre, Modérateur
    20:59 modifié #21
    dans 1234129945:

    J'ai du mal a comprendre le concept de Contexte. ta fonction Draw, du moment qu'elle est appelé par drawRect de la NSView suffit pour qu'elle dessine dans le contexte graphique ? Le fait de restaurer le contexte sert à  quoi ?


    Un graphic context tout propre est donné à  ta vue à  chaque fois que la méthode drawRect: est appelée. À cet instant, tu peux dessiner.
    Le contexte possède ce qu'ils appellent une Current Transform Matrix (CTM): il s'agit d'une matrice de transformation affine, qui s'applique à  tout ce qui est dessiné. Par exemple, si tu voulais dessiner deux fois plus gros, tu pourrais modifier la CTM, mais il faudrait rétablir ensuite la CTM dans son état initial pour afficher correctement les poignées (sinon, elles apparaà®traient aussi 2x plus grosses).

    Note que tant que tu te restreins aux NSBezierPaths, tu peux appliquer une transformation affine avec la méthode -transformUsingAffineTransform:.

    Comme tu peux le voir, il y a beaucoup de méthodes envisageables. C'est à  toi de voir ce qui convient le mieux à  ton application.
  • Philippe49Philippe49 Membre
    20:59 modifié #22
    Sous XLib, le contexte graphique est l'ensemble des données à  transmettre à  la carte graphique pour réaliser l'environnement de dessin, (en gros, parce que c'est du très bas niveau, et peu importe le détail)  :
    • Nombre de bits par pixels (plans de l'écran),
    • Fonctions logiques pour la combinaison des bits pour chaque pixel,
    • Masques de plans,
    • Bouts et jointures des lignes,
    • Clippage,
    • Définition du système de coordonnées (la NSAffineTransform par exemple)
    etc ...



  • wiskywisky Membre
    20:59 modifié #23
    J'ai bien compris maintenant l'idée de Contexte Graphique.
    Mais maintenant, ma fonction draw dessine selon l'origine qui est placé en bas à  gauche mais il est souvent plus commode de le mettre en haut à  gauche. De plus, quand le dessin est plus petit que la zone d'affichage j'aimerai centré mon dessin. Style photoshop ;)
    Seulement je ne vois pas comment faire ! et vous ?


    Pour la PJ:
    Le cadre rouge représente les bords de ma NSView. Dans ce cas j'aimerais que la NSView soit centrée dans la zone d'affichage, est-ce possible ? si non, est t'il possible de la mettre au moins en haut à  gauche ?
  • Philippe49Philippe49 Membre
    20:59 modifié #24
    dans 1234179399:

    Mais maintenant, ma fonction draw dessine selon l'origine qui est placé en bas à  gauche mais il est souvent plus commode de le mettre en haut à  gauche. De plus, quand le dessin est plus petit que la zone d'affichage j'aimerai centré mon dessin. Style photoshop ;)
    Seulement je ne vois pas comment faire ! et vous ?


    méthode de NSView : isFlipped
  • Philippe49Philippe49 Membre
    20:59 modifié #25
    Pour garder centré, utilise la property center.
    On fait une arborescence de vues, où les deux premières sont fixes :
      contentView -> zoneDessin -> vueOùOnDessine
    et à  chaque mise à  jour,
      vueOùOnDessine.center=zoneDessin.center

  • wiskywisky Membre
    20:59 modifié #26
    Merci !

    Je comprend pas ton arborescence. Ma zone de droite est une scrool view qui contiens la vue dans lequel je dessine.
    Je sais bien que NSScoolView contiens une vue de type NSClipView qui contiens ma NSView.
    Je doit donc centrer NSView selon NSClipView ?

    j'ai pas trouvé non plus de méthode "center" NSView mais "centerScanRect:" qui permet de centrer un rectangle ;)
  • Philippe49Philippe49 Membre
    20:59 modifié #27
    dans 1234185169:

    Je comprend pas ton arborescence. Ma zone de droite est une scrool view qui contiens la vue dans lequel je dessine.

    Bon tu as choisi pour ce que j'appelle zoneDessin une scroll-view, ce qui donne l'arborescence
    contentView -> scrollView -> clipView --> vueOùOnDessine

    dans 1234185169:

    j'ai pas trouvé non plus de méthode "center" NSView mais "centerScanRect:" qui permet de centrer un rectangle ;)

    Oui sorry, sur la plate-forme Mac OS (je suis sur iPhone actuellement), la property center n'est disponible que via les CALayer, qui n'arrangera pas ton coup ici.

    dans 1234185169:

    Je sais bien que NSScoolView contiens une vue de type NSClipView qui contiens ma NSView.
    Je doit donc centrer NSView selon NSClipView ?

    Le fait de mettre ceci dans une ScrollView change la donne, car désormais ce n'est plus ta vue qui va se déplacer mais le rectangle bounds de la clip view.
    Cherche donc du côté des méthodes de la clip view scrollToPoint: et documentRect/documentVisibleRect


  • CéroceCéroce Membre, Modérateur
    20:59 modifié #28
    dans 1234179399:

    Le cadre rouge représente les bords de ma NSView. Dans ce cas j'aimerais que la NSView soit centrée dans la zone d'affichage, est-ce possible ? si non, est t'il possible de la mettre au moins en haut à  gauche ?


    Je ne sais pas comment on fait. J'ai essayé plusieurs tactique, sans succès.


    méthode de NSView : isFlipped


    Le problème, c'est que cette méthode a pour conséquence de modifier la CTM.
    Les images se retrouvent alors la tête en bas.
  • wiskywisky Membre
    20:59 modifié #29
    dans 1234188618:

    dans 1234179399:

    Le cadre rouge représente les bords de ma NSView. Dans ce cas j'aimerais que la NSView soit centrée dans la zone d'affichage, est-ce possible ? si non, est t'il possible de la mettre au moins en haut à  gauche ?


    Je ne sais pas comment on fait. J'ai essayé plusieurs tactique, sans succès.


    méthode de NSView : isFlipped


    Le problème, c'est que cette méthode a pour conséquence de modifier la CTM.
    Les images se retrouvent alors la tête en bas.


    C'est ce que j'ai cru voir, comment faire alors ? J'ai essayez plein de chose sans sucés !
    Pour le centrage c'est du détail et c'est pas très important mais le point en haut à  gauche est plus pratique qu'en bas à  gauche  ???
  • Philippe49Philippe49 Membre
    février 2009 modifié #30
    Tu cherches l'impossible. Bien sur que si l'origine est en haut à  gauche et les deux coordonnées positives, le point bas-gauche d'un rectangle aura forcément une ordonnée supérieure à  celle du point Haut-Gauche.
    Cependant avec les view "flipped" ou non, cela peut rester opaque par l'utilisation des méthodes convertPoint: convertRect: ...  de NSView.


    dans 1234195418:

    mais le point en haut à  gauche est plus pratique qu'en bas à  gauche   ???

    Là  je ne suis pas d'accord : c'est comme cela qu'on fait à  la main ...
    Je trouve que le graphisme sous Cocoa possède deux grandes qualités en ce qui concerne le repérage : celle de faire les choses à  l'endroit, et celle d'utiliser les flottants au lieu des entiers.
  • wiskywisky Membre
    20:59 modifié #31
    Donc en clair c'est pas possible d'avoir l'origine en haut à  gauche ! Bon et bien il faudra m'y faire !

    Un grand merci à  vous tous, je vais continuer de coder et le reviens si j'ai des questions !

    <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <br />
    aller une petite tourné  :p :p :p :p :p :p :p :p :p :p :p
Connectez-vous ou Inscrivez-vous pour répondre.