Le Glisser-Déposer - Partie 1
Note de l’éditeur : Cet excellent article de Renaud traite du glisser-déposer d’images entre deux applications (Safari par exemple et celle présentée dans l’article). Le traitement de fichier en provenance du Finder sera traité dans le prochain article de cette série.
Il est un concept qui appartient particulièrement à ce qu’on pourrait qualifier de « culture Mac », à savoir le glisser-déposer. Pour une fois, il sera inutile de tenter de vous exposer ce concept selon le point de vue de l’utilisateur, vous le savez tous parfaitement. Cependant, pour le développeur, le point de vue est différent et quelques explications seront nécessaires.
Cette suite de tutoriels a pour vocation de vous exposer de façon globale les différentes possibilités permettant d’ajouter des fonctionnalités de glisser-déposer existant dans l’Application Kit de Mac OS X. Les deux premiers articles sont consacrés à l’explication du déroulement des sessions de glisser déposer entre deux sous-classes de NSView. Le premier décrira les principes généraux de l’envoi et de la réception et le deuxième approfondira ces principes, en particulier pour les échanges impliquant des fichiers. Le troisième va décrire les spécificités du glisser-déposer dans la classe NSTableView, et surtout sa sous-classe NSOutlineView.
Principes généraux
Pour implémenter un glisser-déposer, plusieurs objets sont nécessaires : premièrement, une source et une destination, qui seront des sous-classes de NSView ou NSWindow et un presse-papier servant d’intermédiaire durant le déplacement. Le dernier objet impliqué est une image permettant à l’utilisateur d’avoir un aperçu du type de donnée, ou l’objet lui-même, qu’il transfère durant la session de glisser-déposer.
L’envoyeur fournit le presse-papier dont la fonction est de stocker une information et de permettre sa transformation si elle est inadaptée à la destination ; et l’image représentant les données contenues dans le presse-papier. Il spécifiera également quelles seront les opérations que la destination pourra faire à partir de l’objet qu’elle recevra.
La fonction de la destination est de déterminer si l’objet envoyé par la source peut être accepté, d’après les deux critères suivants:
- Le premier est le plus évident, il faut que l’objet reçu soit d’un type acceptable, il serait par exemple ridicule d’envoyer un fichier mp3 dans un champ texte.
- Le deuxième critère est le type d’opérations permises par l’envoyeur, une zone de l’écran correspondant à une corbeille refusera par exemple un fichier que l’objet envoyeur ne veut pas voir effacé.
Si l’objet n’est pas adapté, la destination a également pour fonction d’appliquer les changements nécessaires à l’acceptation des données. Ce cas sera par exemple celui d’une zone de texte n’acceptant pas le texte formaté recevant du texte en provenance d’un navigateur, où le texte est toujours formaté.
Bien entendu, un objet peut être à la fois source et destination.
Les grands principes généraux sont maintenant exposés, passons à la réalisation pratique.
Source
Dans un premier temps, nous nous intéresserons à la création d’un objet qui n’aura pour unique vocation que d’envoyer des objets.
Explications
Pour avoir une source, il est nécessaire de sous-classer soit NSView soit NSWindow. Plusieurs choses sont à faire : la première est d’écraser les méthodes -(void)mouseDown:(NSEvent*)theEvent ou -(void)mouseDragged:(NSEvent*)theEvent. La méthode écrasée devra invoquer la méthode dragImage:at:offset:event:pasteboard:source:slideBack: qui permettra de démarrer la session de glisser-déposer à proprement parler. Examinons les arguments :
-(void)dragImage:(NSImage*)anImage
at:(NSPoint)imageLoc
offset:(NSSize)mouseOffset
event:(NSEvent*)theEvent
pasteboard:(NSPasteboard*)pboard
source:(id)sourceObject
slideBack:(BOOL)slideBack
- anImage : l’image qui illustrera les données transférées à l’utilisateur ;
- imageLoc : la position de départ de la session, soit l’endroit où l’utilisateur a cliqué ;
- mouseOffset : positionnement relatif de l’image par rapport au curseur, est donc nécessaire si vous désirez par exemple centrer l’image par rapport au curseur ;
- theEvent : événement par lequel la session de glisser-déposer a été entamée ;
- pboard : presse-papier dans lequel seront stockées différentes informations utiles à la destination, c’est-à-dire le type de données transférées ;
- sourceObject : contrôleur de l’opération de glisser déposer, doit être conforme au protocole NSDraggingSource ;
- slideBack : si slideBack vaut YES, une animation montrera l’image revenir à la source si l’opération a échoué.
sourceObject mérite quelques explications supplémentaires. Une fois la session de glisser-déposer entamée, une série de messages vont lui être envoyés. Dans un premier temps, seule une méthode sera intéressante à implémenter dans la classe de sourceObject : draggingSourceOperationMaskForLocal: avec pour argument un booléen. Ce message renvoie les opérations permises par cet objet, l’argument vaudra NO si la destination se situe dans la même application que la source, et YES dans le cas contraire. En règle générale, sourceObject est la source, mais rien n’empêche d’utiliser un autre objet à cet usage, la seule condition requise étant l’implémentation de la méthode précédente. La liste suivante renseigne les principales opérations possibles :
- NSDragOperationCopy : les données représentées par l’image peuvent être copiées ;
- NSDragOperationLink : les données peuvent être partagées ;
- NSDragOperationMove : les données peuvent être déplacées ;
- NSDragOperationDelete : les données peuvent être supprimées ;
- NSDragOperationNone : aucune opération n’est permise.
Pour combiner les différents types d’opérations, il suffit d’utiliser l’opérateur | (pipe). Par exemple, pour permettre une copie et un déplacement, il faut écrire NSDragOperationCopy|NSDragOperationMove.
Un autre objet mérite notre attention : pboard. Il s’agit d’un presse-papier qui aura pour nom NSDragPboard. Nous aurons également à lui spécifier le type de données qu’il pourra manipuler à l’aide de la méthode -declareTypes:owner:, qui prend pour argument un tableau contenant différentes constantes et un objet. Les constantes servent à définir les types de données que le presse-papier pourra contenir :
- NSFileContentsPboardType : représente le contenu d’un fichier ;
- NSFilenamesPboardType : représente un NSArray contenant un nom de fichier ou plus ;
- NSHTMLPboardType : représente du HTML ;
- NSPDFPboardType : représente des données sous forme de pdf ;
- NSRTFPboardType ou NSRTFDPboardType : représente du texte enrichi ;
- NSStringPboardType : représente une chaîne de caractères ;
- NSTIFFPboardType : représente une image au format TIFF.
Il existe d’autres types décrits dans la page d’aide relative à la classe NSPasteBoard. Si vous désirez créer votre propre type de données, n’importe quelle instance de NSString peut être utilisée pour le désigner.
L’objet placé comme argument de owner sera celui qui fournira les données au presse-papier, les données appartenant à la classe NSData. Pour ce faire, il lui faudra implémenter la méthode -pasteBoard:provideDataForType:, dont le premier argument est le presse-papier à remplir, et le second le type de donnée demandé. Pour remplir le presse-papier, on lui enverra un message -setData:forType:, avec comme arguements les données proprement dit, et le type, qui est une des constantes décrites ci-dessus.
L’avantage de fournir plusieurs types de données est le suivant : certains types de données sont parfois plus avantageux, mais ne peuvent être lus par tous les programmes. Par exemple imaginons que vous faites un schéma en utilisant un programme qui fait du dessin vectoriel. Si vous glissez un élément d’un schéma vers un autre, cet élément gardera ses propriétés. Par contre, si vous souhaitez copier ce schéma dans un mail, mais que le programme de mail ne gère pas ce format, il serait donc impossible à priori de faire le copier coller (ou le glisser déposer, si vous préférez) tel quel. Seulement comme vous aurez spécifié plusieurs types de données pour votre presse-papier, le presse-papier interrogera la destination, et lui fournira le type de donnés qui est le plus appropriée. Pour l’exemple donné, on pourra imaginer que le schéma est convertit en image jpeg, qui pourra alors être intégrée dans le mail.
Mise en pratique
L’objectif sera de faire une application très simple qui contient juste un NSImageView qu’on éditera de façon à pouvoir exporter l’image ailleurs par simple glisser-déposer.
La première chose à faire est de créer un projet de type Application Cocoa non basée sur des documents. Éditez le fichier MainMenu.nib en ajoutant un gros NSImageView dans la fenêtre principale, dans l’inspecteur, cochez la case « Editable », puis créez une sous-classe de NSImageView (par exemple DragImageView), créez les fichiers pour cette classe et pour finir modifier la classe de la NSImageView que vous avez rajoutée en DragImageView. Sauvez, et c’est tout pour Interface Builder. Retour maintenant à ProjectBuilder/Xcode pour l’édition de DragImageView.m (il ne faut rien rajouter dans l’interface). Commençons par le plus simple, l’implémentation de draggingSourceOperationMaskForLocal::
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag { return NSDragOperationCopy; }
Vous l’aurez compris, la seule opération permise sera la copie.
Ensuite, écrasons la méthode mouseDown: de notre classe.
- (void)mouseDown:(NSEvent*)event {
if ([self image]) {
NSPasteboard* dragPasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
[dragPasteboard declareTypes:
[NSArray arrayWithObject:NSTIFFPboardType]
owner:self];
[self dragImage: [self image] at: [event locationInWindow]
offset: NSZeroSize
event: event
pasteboard: dragPasteboard
source: self
slideBack: YES];
}
}
La première opération à faire consiste à vérifier l’existence de l’image dans le NSImageView. S’il n’y en a pas, inutile de commencer la session de glisser déposer. Si par contre elle existe, deux objets sont créés, l’image qui sera affichée durant la session et le presse-papier.
- (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type {
if([type isEqualToString: NSTIFFPboardType] ){
[sender setData:[[self image] TIFFRepresentation]
forType:NSTIFFPboardType];
}
}
Que dire de plus ? Compilez, exécutez et testez. Il reste quand même quelques améliorations cosmétiques à faire, par exemple l’image devrait être transparente lors de la session de glisser-déposer, et il serait intéressant de redimensionner l’image si elle est trop grande, mais ceci vous est laissé à titre d’exercice. Vous trouverez cependant le corrigé dans le projet final.
Destination
La deuxième étape consiste à comprendre comment un objet peut en accepter un autre.
Un peu de théorie…
Pour créer une destination, plusieurs choses sont à définir. Comme pour la source, il sera nécessaire de faire une sous-classe de NSView. La première chose à définir est le type de donnée que la destination est apte à recevoir, ce qui se fait via la méthode -registerForDraggedTypes:, qui prend pour un argument un NSArray contenant le nom des différents types de données qui pourront être acceptés, par ordre de préférence décroissante. Les différents types de données ont été données dans le point traitant du presse-papier pour l’implémentation de la source.
La seconde étape consiste à définir une série de méthodes qui seront tour à tour invoquées au cours de la session. Pour ce premier exemple, nous ne retiendrons que les principales :
- draggingEntered: : cette méthode est invoquée lorsque le curseur rentre dans la zone occupée par la destination, elle doit renvoyer l’opération que la destination va effectuer et peut également contenir le code nécessaire à la mise à jour de l’affichage de la vue pour illustrer le fait que l’opération puisse être effectuée ;
- draggingExited: : cette méthode est invoquée lorsque le curseur quitte la zone occupée par la destination, son principal usage est de mettre l’affichage à jour ;
- prepareForDragOperation: : cette méthode a pour fonction de valider la session de glisser-déposer selon la dernière valeur renvoyée par draggingEntered:, c’est à ce moment que l’image représentant les données disparaît, il faut également réinitialiser l’affichage de la vue s’il a été mis à jour lors de l’entrée du curseur dans la destination ; elle doit renvoyer un booléen, qui, s’il vaut YES permettra le passage à l’étape suivante et s’il vaut NO la session sera clôturée ;
- performDragOperation: : cette méthode est celle pendant laquelle les données seront importées du presse papier, pour passer à la dernière étape, elle doit renvoyer YES ;
Toutes ces méthodes prennent un argument : sender, qui est l’objet qui les invoque. C’est à partir de cet objet que la destination pourra « prendre connaissance » d’informations qui permettront de savoir si une opération peut-être validée ou non, ou encore le type d’opération à effectuer. Cet objet est généré automatiquement au début de la session, vous n’aurez donc pas à vous soucier de sa création. La liste suivante reprend les méthodes les plus souvent utilisées pour savoir si une opération peut être acceptée ou non :
draggingSourceOperationMask : cette méthode renvoie les types d’opération fait que la source permet à la destination ;
draggingPasteboard : cette méthode renvoie le presse-papier utilisé lors de la session, et donc, indirectement, aux données elles-mêmes.
… de la pratique…
Après cette description de méthodes, passons à la réalisation de l’exemple. Pour ce faire, nous allons créer une application dont la vocation sera simplement d’accepter une image en provenance d’autres applications. Pour ce faire, créez un nouveau projet Cocoa non basé sur des documents. Éditez MainMenu.nib, ajoutez une NSView dans la fenêtre principale, créez une sous classe de NSView (par exemple DropView), créez les fichiers pour DropView, et attribuez la classe DropView à la vue que vous avez ajouté. Éditez maintenant DropView.h, et ajoutez-y les variables d’instance suivantes : BOOL isHighlighted; et NSImage* image;. La variable isHighlighted est utilisée par la méthode drawRect: pour déterminer s’il est nécessaire de tracer un rectangle de mise en évidence, ce qui devra être fait lorsque le curseur survole la vue pendant une opération de glisser déposer. Allons maintenant dans DropView.m et ajoutons la méthode suivante :
-(id)initWithFrame :(NSRect)frame {
self = [super initWithFrame:frame];
isHighlighted = NO;
[self registerForDraggedTypes:[NSImage imagePasteboardTypes]];
return self;
}
-(void)dealloc {
if (image) [image release];
[super dealloc];
}
Cette étape est la classique étape d’initialisation d’un nouvel objet. À la quatrième ligne, on enregistre l’objet de façon à signaler que le type de données qu’il acceptera seront des images.
-(void)drawRect:(NSRect)rect {
[super drawRect:rect];
[image drawAtPoint:NSMakePoint(0,0)
fromRect:NSZeroRect
operation:NSCompositeSourceOver fraction:1.0];
if(isHighlighted){
[[NSColor highlightColor] set];
[NSBezierPath setDefaultLineWidth: 5];
[NSBezierPath strokeRect: rect];
}
}
Cette deuxième étape comporte deux parties principales, la première et le traçage de l’image et la seconde consiste à tracer un rectangle autour de la vue, si isHighlighted vaut YES.
L’étape suivante consiste à ajouter la méthode draggingEntered: :
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
if ( [NSImage canInitWithPasteboard:[sender draggingPasteboard]] &&
[sender draggingSourceOperationMask] & NSDragOperationCopy ) {
isHighlighted=YES;
[self setNeedsDisplay:YES];
return NSDragOperationCopy;
}
return NSDragOperationNone;
}
Cette méthode a deux objectifs, la première est de vérifier si les données contenues dans le presse-papier sont valables, et si tel est le cas, la deuxième sera alors de mettre à jour l’affichage et de renvoyer l’opération permise par cette destination. Dans notre exemple, nous ne permettrons que la copie (ou bien entendu aucune si presse-papier ne contient aucune donnée valable). La condition [NSImage canInitWithPasteboard:[sender draggingPasteboard]] && [sender draggingSourceOperationMask] & NSDragOperationCopy se décompose comme suit: tout d’abord, on vérifie la validité des données contenues (avec [NSImage canInitWithPasteboard:[sender draggingPasteboard]]), si ce n’est pas le cas, NO est renvoyé pour la condition, et donc NSDragOperationNone pour la méthode. Si la condition est vérifiée, on passe alors à la suite, c’est-à-dire qu’on vérifie si parmi les opérations conformes pour la source, on peut trouver la copie, ce qui est représenté dans le code par [sender draggingSourceOperationMask] & NSDragOperationCopy. Vous pouvez consulter en annexe une petite explication sur les opérateurs & et &&, si leur signification dans ce code ne vous semble pas évidente. Si ces deux conditions ont été vérifiées, l’affichage est mis à jour en changeant la variable d’instance isHighlighted, et en demandant à l’objet de se redessiner. On renvoie également NSDragOperationCopy.
- (void)draggingExited:(id <NSDraggingInfo>)sender {
isHighlighted =NO;
[self setNeedsDisplay: YES];
}
Cette méthode est appelée lorsque la souris qui la destination, il n’y a pas grand-chose à en dire. Sa seule fonction est de mettre à jour l’affichage, en enlevant le rectangle bleu qui a été ajouté précédemment.
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
isHighlighted =NO;
[self setNeedsDisplay: YES];
return YES;
}
Cette méthode est appelée lorsque l’utilisateur « lâche » l’objet qu’il tient. Deux choses seront donc à faire : mettre à jour l’affichage, et voir si la suite de la session peut être effectuée, ce qui sera le cas, puisqu’il n’aurait pas été possible d’arriver à cette étape sans que les données ne soient valides. Cette méthode reverra donc YES.
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
if (image) [image release];
image = [[NSImage alloc] initWithPasteboard: [sender draggingPasteboard]];
[self setNeedsDisplay:YES];
return YES;
}
Maintenant que les préliminaires ont été faits, il ne reste plus qu’à terminer la session de glisser déposer, en faisant la seule chose, au final, intéresse l’utilisateur à savoir placer l’image dans notre vue.
… et du perfectionnement
Bien que le code présent ici fonctionne, il y a encore un défaut à notre vue (dans les deux sens du terme). L’image déposée n’est pas remise à l’échelle en fonction de la taille de la fenêtre. Pour ce faire, il sera nécessaire de la redimensionner, ce que nous ferons uniquement dans le cas où l’image serait plus grande que l’instance de DropView. Ce code pourra par exemple être réalisé dans la méthode -drawRect:. Il se présente de la manière suivante :
-(void)drawRect:(NSRect)rect {
[super drawRect:rect];
NSSize frameSize = [self frame].size;
NSSize imageSize = [image size];
NSRect aRect = NSZeroRect;
float scale = 1.0;
scale = fminf(
(frameSize.height/imageSize.height),
(frameSize.width/imageSize.width));
if ( scale < 1.0 ) {
aRect.size.height = (imageSize.height * scale);
aRect.size.width = (imageSize.width * scale);
} else {
aRect.size = imageSize;
}
[image drawInRect:aRect
fromRect:NSZeroRect
operation:NSCompositeSourceOver fraction:1.0];
if(isHighlighted){
[[NSColor blueColor] set];
[NSBezierPath setDefaultLineWidth: 5];
[NSBezierPath strokeRect: rect];
}
}
Source et destination
Il reste maintenant à faire une vue qui peut à la fois émettre des images, et en recevoir. Rien de plus simple, il suffit de combiner les méthodes vues dans la section traitant des sources et celle des destinations, et le tour est joué. On pourra éventuellement rajouter un petit perfectionnement dont la vocation est de contrôler le cas où l’objet source serait l’objet destination, pour éviter de gaspiller des cycles processeur pour rien. Cela se fera en envoyant le message en vérifiant la condition[sender draggingSource]!=self.
Le code pour performDragOperation: devient donc :
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
if ([sender draggingSource]!=self) {
if (image) [image release];
image = [[NSImage alloc] initWithPasteboard: [sender draggingPasteboard]];
[self setNeedsDisplay:YES];
}
return YES;
}
Vous trouverez les sources pour l’exemple final ici.
Le mot de la fin
Cet article vous aura expliqué les bases du glisser déposer. Il sera cependant nécessaire d’approfondir les concepts abordés ici, afin de pouvoir concevoir une application Mac digne de ce nom. Ce sera l’objectif du prochain article, dans lequel nous traiterons de la manipulation des différents types de presse-papiers, et en particulier celui permettant de manipuler des fichiers.
Résumé
Source
Pour qu’une vue puisse être utilisée pour commencer une session de glisser-déposer, il est nécessaire d’invoquer la méthode suivante dans -mouseDown: :
- (void)dragImage:(NSImage*)anImage
at:(NSPoint)imageLoc
offset:(NSSize)mouseOffset
event:(NSEvent*)theEvent
pasteboard:(NSPasteboard*)pboard
source:(id)sourceObject
slideBack:(BOOL)slideBack
dont les arguments sont :
- anImage : l’image qui illustrera les données transférées à l’utilisateur ;
- imageLoc : la position de départ de la session ;
- mouseOffset : positionnement relatif de l’image par rapport au curseur ;
- theEvent : événement par lequel la session de glisser-déposer a été entamée ;
- pboard : presse-papier dans lequel seront stockées différentes informations transférées ;
- sourceObject : contrôleur de l’opération de glisser déposer ;
- slideBack : indique la présence d’une animation si la session a échoué.
pBoard est un presse papier portant le nom NSDragPboard. Il sera également nécessaire de préciser les différents types de données que ce presse-papier pourra gérer (-declareTypes:owner;), dont les noms sont donnés dans la section « Constantes ».
L’objet identifié en tant que sourceObject aura à inclure le méthode suivante :
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
qui a à renvoyer le masque des opérations permises. flag sert à préciser si source et destination sont dans la même application.
L’objet déclaré en tant que owner du presse-papier dans la méthode -declareTypes:owner: aura à inclure la méthode :
- (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type
qui sert à remplir le presse-papier avec des données de type précisé par type. Pour remplir le presse-papier, on utilise la méthode -setData:forType:.
Destination
Pour commencer, il faut envoyer le message -registerForDraggedTypes: à la destination, qui prend pour argument un NSArray contenant les différents types de données acceptées (décrites dans « Constantes »), par ordre de préférence décroissante. Il faudra ensuite implémenter les quatre méthodes suivantes :
- draggingEntered: : invoquée lorsque le curseur rentre dans la zone occupée par la destination, renvoie l’opération effectuée et peut également mettre à jour l’affichage;
- draggingExited: : invoquée lorsque le curseur quitte la zone occupée par la destination, mets à l’affichage à jour ;
- prepareForDragOperation: : valide la session de glisser-déposer selon la dernière valeur renvoyée par -draggingEntered:, renvoie un booléen pour continuer ou arrêter la session, mets à jour l’affichage ;
- performDragOperation: : importation des données proprement parler, renvoie un booléen qui continue ou arrête la session ;
Toutes ces méthodes prennent comme argument un objet généré automatiquement, qui permet d’obtenir des infos sur la session. Les principales informations sont :
- draggingSourceOperationMask : renvoie les types d’opération permises;
- draggingPasteboard : renvoie le presse-papier utilisé lors de la session ;
- sender : renvoie l’objet source.
Constantes
Opérations permises
- NSDragOperationCopy : les données représentées par l’image peuvent être copiées
- NSDragOperationLink : les données peuvent être partagées
- NSDragOperationMove : les données peuvent être déplacées
- NSDragOperationDelete : les données peuvent être supprimées
- NSDragOperationNone : aucune opération n’est permise
Types de données
- NSFileContentsPboardType : représente le contenu d’un fichier
- NSFilenamesPboardType : représente un NSArray contenant un nom de fichier ou plus
- NSHTMLPboardType : représente du HTML
- NSPDFPboardType : représente des données sous forme de pdf
- NSRTFPboardType ou NSRTFDPboardType : représente du texte enrichi
- NSStringPboardType : représente une chaîne de caractères
- NSTIFFPboardType : représente une image au format TIFF
Annexe : Masques et opérateurs de bits
Un masque dans la terminologie Cocoa est un nombre représentant une combinaison de différentes propriétés, les propriétés en question ne pouvant avoir que deux états, activé ou non. Pour le cas cité dans le texte, nous nous intéressons à l’ensemble des opérations permises lors d’une session de glisser-déposer, pour lequel nous avons une liste de plusieurs types d’opérations, et nous précisons parmi les différents types ceux qui sont activés ou non (en fait, par défaut, ils sont tous désactivés, et on ne spécifie que ceux qui vont être activés). On peut remarquer que chacune d’entre elles est associé un nombre, qui est en fait une puissance de 2. Toute l’information utile est reprise dans le tableau suivant (certains types n’ont pas été exposés dans l’article présent, ne vous en étonnez pas).
| Opération |
Nombre |
Représentation binaire |
|---|---|---|
| NSDragOperationNone |
0 |
0 |
| NSDragOperationCopy |
1 |
1 |
| NSDragOperationLink |
2 |
10 |
| NSDragOperationGeneric |
4 |
100 |
| NSDragOperationPrivate |
8 |
1000 |
| NSDragOperationMove |
16 |
10000 |
| NSDragOperationDelete |
32 |
100000 |
Lorsqu’on veut permettre plusieurs opérations, on utilise l’opération |, qui ici peut être assimilé à une addition. Ainsi pour permettre la copie et le déplacement, on le codera de la manière suivante : NSDragOperationMove|NSDragOperationCopy, ce qui aura pour effet de renvoyer un nombre valant 16+1=17. Les choses seront en fait plus claires si, au lieu de s’intéresser à la représentation décimale d’un nombre, on considère la représentation binaire : partons du principe que le premier chiffre en partant de la droite représente la copie (NSDragOperationCopy), le second la liaison (NSDragOperationLink), le troisième l’opération générique(NSDragOperationGeneric),… Si un « chiffre » dans la suite binaire vaut 0, l’opération associé ne sera pas permise, et s’il vaut 1 elle sera permise. Pour le cas où nous permettons la copie et le déplacement, cela revient à additionner 10000 et 00001, ce qui donne 10001.
Maintenant que la construction d’un masque est comprise, il peut être intéressant de comprendre comment on peut en « extraire » l’information utile. Pour cela nous devons faire un détour par les opérateurs de bit.
Opérateurs de bits &, | et ^
En C, il existe trois types d’opérateurs de bit : &, |, et ^. Dans les manuels d’introduction au C, on décrit généralement ces opérateurs dans le cas bien précis des comparaisons sur deux booléens, qui bien que tout à fait correct, ne représente en fait qu’un cas particulier de l’usage de ces opérateurs. En fait, contrairement aux opérateurs logiques and, or,… qu’on retrouve dans d’autres langages, les opérateurs &, | et ^ ne s’appliquent pas à des booléens, mais à n’importe quelle suite de bits. Lorsque deux suites sont comparées, les bits correspondants sont comparés deux à deux, suivant les règles énoncées dans le cas des booléens. Prenons le cas des nombres 25 et 19, ou sous forme binaire 11001 et 10011. Les résultats suivant les différents opérateurs sont :
|
Opérateur |
Résultat (binaire) |
Résultat (décimal) |
|---|---|---|
|
& |
10001 |
17 |
|
| |
11011 |
27 |
|
^ |
01010 |
10 |
Dans le cas des masques, nous faisons ce type de comparaisons. Pour la construction du masque, nous utilisons |, comme il a été vu plus haut, et pour savoir si un attribut est activé dans un masque, nous utilisons & en comparant d’une part le masque et d’autre part l’attribut recherché, qui renvoie un nombre qui ne vaut 0 que dans le cas où le masque ne contiendrait pas l’attribut recherché.
Connecteurs logiques && et ||
Contrairement à & et |, && et || ne sont pas des opérateurs de bits, mais bien des connecteurs logiques, ce qui signifie en gros qu’ils permettent la combinaison de plusieurs conditions.
Dans l’expression exp1 && exp2, exp1 est d’abord évalué, si sa valeur est nulle, exp2 n’est pas évalué. La valeur renvoyée est alors nulle. Si par contre sa valeur n’est pas nulle, exp2 est évaluée, et la valeur renvoyée est non nulle si exp2 ne l’est pas. Le code :
If ( a && b )
insrt1 ;
else
instr2 ;
est donc équivalent à :
if (a) {
if (b) instr1;
} else
instr2;
Dans l’expression exp1 || exp2, exp1 est évalué, s’il est non nul, exp2 n’est pas évalué et l’expression renvoyée est non nulle. Si par contre exp1 est nul, alors exp2 est évalué et l’expression renvoyée sera nulle si exp2 l’est également. Le code :
If ( a || b ) insrt1; else instr2;
est donc équivalent à :
If (a)
instr1;
else if (b)
instr1;
else
instr2;
© Juin 2004 Renaud pour Project:Omega
Chargement
Commentaires récents