[Résolu] NSUndoManager et les groupes

wiskywisky Membre
mars 2010 modifié dans API AppKit #1
Bonjour à  tous,

Voilà  pas mal de temps que je n'ai pas posé de question. Maintenant j'ai un petit problème que je n'arrive pas à  résoudre. (sans blague !)
Dans une application basé sur NSDocument, je souhaite ajouter la gestion de l'annulation. Seulement voilà , ça bug lors des annulations. Plus précisément, lors de l'annulation d'un groupe.
Voici le code qui ajoute le groupe d'annulation :
NSUndoManager * undo = [_delegate undoManager];<br />	//Fermeture du groupe d&#39;annulation si il y en a un ouvert puis ouverture d&#39;un nouveau groupe.<br />	if([undo groupingLevel]){<br />		[undo endUndoGrouping];<br />	}<br />	[undo beginUndoGrouping];<br />	<br />	id obj = [[_listObject objectAtIndex:_objSelected] retain];<br />	NSLog(@&quot;retain count : %i&quot;,[obj retainCount]);<br />	[_listObject removeObjectAtIndex:_objSelected];<br />	_objSelected++;<br />	[_listObject insertObject:obj atIndex:_objSelected];<br />	[obj release];<br />	if(_delegate){<br />		[_delegate setSelectedObjectAtIndex:_objSelected];<br />	}<br />	//Ajout des annulations. Première étape, sélectionner l&#39;objet qui a subit un modif puis appeller la fonction qui fait l&#39;éffet inverse.<br />	[[undo prepareWithInvocationTarget:self] setSelectedObject:_objSelected];<br />	[[undo prepareWithInvocationTarget:self] orderUpSelectedObject:self];<br />	if(![undo isUndoing]){<br />		[undo setActionName:@&quot;down plan&quot;];<br />	}<br />	[undo endUndoGrouping];<br />

Quelque explication seront surement les bienvenues ! Le logiciel permet la réalisation de dessin vectoriel simple (rectangle, trait, texte, tableau par combinaison des autres objets). La fonction ci dessus permet de modifier l'ordre du plan (premier plan, fond, etc...) de l'objet sélectionné.
Donc l'opération inverse consiste à  re-sélectionner l'objet puis le changer de plan dans l'autre sens. Sauf que cela fonctionne que si je ne fait qu'une annulation. La seconde annulation n'intervertis pas les bons objets.
Ma question est donc de savoir où je me trompe. Ne n'omet pas qu'il puisse y avoir une amélioration à  faire sur les fonctions de changement de plan  ::)

Réponses

  • CéroceCéroce Membre, Modérateur
    17:38 modifié #2
    Quelques remarques:
    - habituellement, le changement de sélection n'est pas une action qui s'annule ou se rétablit
    - il n'est nécessaire de créer des groupes que lorsque les actions prennent plus qu'une boucle de la run-loop: un groupe est créé automatiquement au début de la run-loop et conclu à  la fin de la run-loop. Typiquement, les groupes ne sont utilisés que pour la frappe au clavier.
    - pour déboguer, je te conseille d'implémenter également le rétablissement, et de donner un nom à  l'action courante (c'est ce que tu fais). Ceci parce que pour bien implémenter l'annulation, chaque action doit avoir une action réciproque.
    - même si la doc d'Apple est (volontairement?) muette à  ce sujet, les objets qui sont placés dans l'Undo Manager sont retenus; je le sais pour l'avoir expérimenté, et d'ailleurs, c'est bien le comportement attendu. Cela implique également que: 1) les objets auront beau avoir été libérés par la couche modèle, ils n'auront pas été désalloués pour autant et 2) si ces objets prennent beaucoup de place en mémoire, il vaut mieux limiter le nombre d'annulations possibles (les objets retenus seront alors libérés).


    Ceci dit, à  mon avis, tu as un problème de stratégie. Je dis ça parce que j'avais eu de grosses difficultés à  implémenter cette même fonction sur Marquise, puis j'ai fini par tout reposer à  plat.

    Voici un exemple avec quatre objets, A B C D. Au départ A est le plus proche, D le plus éloigné. Si A et D sont sélectionnés, et qu'on choisit "avancer d'un plan", l'ordre attendu sera A B D C. Pour annuler, il faudra donc reculer D, mais surtout pas A.
    En y réfléchissant, tu vas trouver six cas particuliers de ce type. De fait, la bonne stratégie est plutôt de sauvegarder une copie du NSMutableArray qui contient les objets pour que ce soit l'ordre des objets qui soit mémorisé. Lors de l'annulation, tu remplacera le NSMutableArray courant par le NSMutableArray copié alors: tes objets auront ainsi recouvré leur position initiale.
  • wiskywisky Membre
    17:38 modifié #3
    Merci pour l'explication, je vois bien ce que tu veux dire. J'implémente toujout le rétablissement en même temps que l'annulation. Pour ce qui est du retain fait par le undo manager, je m'en suis rendu compte car j'arrive déjà  à  annuler l'ajout et la suppression d'objet.
    Au final, j'ai ecrit une methode qui gère le changement de plan. Ce qui donne :
    - (void)placeObjectAtPlan:(int)fromPlan toPlan:(int)toPlan<br />{<br />	<br />	id obj = [[_listObject objectAtIndex:fromPlan] retain];<br />	[_listObject removeObjectAtIndex:fromPlan];<br />	<br />	[_listObject insertObject:obj atIndex:toPlan];<br />	[obj release];<br />	if(_delegate){<br />		[_delegate setSelectedObjectAtIndex:toPlan];<br />	}<br />	<br />	NSUndoManager * undo = [_delegate undoManager];<br />	[[undo prepareWithInvocationTarget:self] placeObjectAtPlan:toPlan toPlan:fromPlan];<br />	if(![undo isUndoing]){<br />		[undo setActionName:@&quot;change plan&quot;];<br />	}<br />	[self reDraw];<br />}
    

    Comme ça l'annulation fait l'effet inverse. Par contre, ta façon de faire est consommatrice de mémoire car mon NSMutableArray pointe vers de nombreux objets ;)
Connectez-vous ou Inscrivez-vous pour répondre.