Corriger le bug du NSRoundRectBezelStyle dans Tiger
laurris
Membre
Dans Tiger, il y a un (ou des) problèmes avec le style NSRoundRectBezelStyle appliqué à un NSPopUpButtonCell ou NSButtonCell.
Tout laisse croire qu'il s'agit de bugs et qu'ils seront corrigés dans Leopard.
Apparemment la cell ne dessine pas ses sous-vues au bon endroit ce qui fait que:
- Le texte n'est pas bien centré verticalement dans un popup.
- Les flèches du popup se retrouvent beaucoup trop à droite.
- Les images d'un bouton sont mangées sur la droite.
Ils ont peut-être la même origine. En attendant de la trouver, voici ce que j'ai fait pour les corriger individuellement.
Il s'agit de deux classes qui se substituent à NSButtonCell et NSPopUpButtonCell en utilisant poseAsClass: au chargement.
l'avantage par rapport à un sous-classage classique c'est qu'on a pas besoin d'initialiser une cell et de faire un setCell pour chaque boutton créé. Tous les popup et boutons de l'appli sont corrigés automatiquement.
Attention, il faut supprimer la flèche normale du popup avec [[popup cell] setArrowPosition:NSPopUpNoArrow]. Et dans mon cas la police est [NSFont systemFontOfSize:10].
CellDebug.m
CellDebug.h
Il suffit de compiler cette classe dans son projet pour que les corrections deviennent effectives.
- Dans le bundle, il faut une image arrowImage.png pour la flèche. (Si vous savez comment récupérer cette flèche direct à partir d'un popup, je suis preneur)
- Ne prend pas en compte le changement de style ou de cell à la volée.
Observations, améliorations bienvenues !
Tout laisse croire qu'il s'agit de bugs et qu'ils seront corrigés dans Leopard.
Apparemment la cell ne dessine pas ses sous-vues au bon endroit ce qui fait que:
- Le texte n'est pas bien centré verticalement dans un popup.
- Les flèches du popup se retrouvent beaucoup trop à droite.
- Les images d'un bouton sont mangées sur la droite.
Ils ont peut-être la même origine. En attendant de la trouver, voici ce que j'ai fait pour les corriger individuellement.
Il s'agit de deux classes qui se substituent à NSButtonCell et NSPopUpButtonCell en utilisant poseAsClass: au chargement.
l'avantage par rapport à un sous-classage classique c'est qu'on a pas besoin d'initialiser une cell et de faire un setCell pour chaque boutton créé. Tous les popup et boutons de l'appli sont corrigés automatiquement.
Attention, il faut supprimer la flèche normale du popup avec [[popup cell] setArrowPosition:NSPopUpNoArrow]. Et dans mon cas la police est [NSFont systemFontOfSize:10].
CellDebug.m
<br />// CellDebug.m<br />// PSRuleEditor<br /><br />#import "CellDebug.h"<br /><br />@implementation DebugButtonCell<br /><br />+(void)load{<br /> if(MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_4)<br /> [self poseAsClass:[NSButtonCell class]];<br />}<br /><br />- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *) controlView{<br /> if([self bezelStyle]==NSRoundRectBezelStyle){<br /> cellFrame.origin.x -=2;<br /> cellFrame.size.width +=4;<br /> }<br /> [super drawInteriorWithFrame:cellFrame inView:controlView];<br />}<br /><br />@end<br /><br /><br />@implementation DebugPopUpButtonCell<br /><br />+(void)load{<br /> if(MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_4)<br /> [self poseAsClass:[NSPopUpButtonCell class]];<br />}<br /><br />- (id) init{<br /> self = [super init]; <br /> if (self != nil) {<br /> NSString *arrowImagePath = [[NSBundle mainBundle] pathForResource:@"ArrowImage" ofType:@"png"];<br /> arrowImage = [[NSImage alloc] initWithContentsOfFile:arrowImagePath];<br /> }<br /> <br /> return self;<br />}<br /><br />- (void) dealloc {<br /> [arrowImage release];<br /> [super dealloc];<br />}<br /><br />- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{<br /> <br /> [super drawWithFrame:cellFrame inView:controlView];<br /> if([self bezelStyle]==NSRoundRectBezelStyle){ <br /> float x, y; <br /> x = NSMaxX(cellFrame) - [arrowImage size].width - 10.0;<br /> y = (NSHeight(cellFrame) - [arrowImage size].height) / 2 + [arrowImage size].height;<br /> <br /> [arrowImage compositeToPoint:NSMakePoint(x, y) operation:NSCompositeSourceOver];<br /> }<br />}<br /><br />- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *) controlView{<br /> <br /> if([self bezelStyle]==NSRoundRectBezelStyle){<br /> cellFrame.size.width -=14;<br /> cellFrame.origin.y +=2;<br /> }<br /> [super drawInteriorWithFrame:cellFrame inView:controlView];<br />}<br />@end<br /><br />
CellDebug.h
<br />@interface DebugPopUpButtonCell : NSPopUpButtonCell {<br />NSImage *arrowImage;<br />}<br /><br />@end<br /><br />@interface DebugButtonCell : NSButtonCell {<br /><br />}<br /><br />@end<br />
Il suffit de compiler cette classe dans son projet pour que les corrections deviennent effectives.
- Dans le bundle, il faut une image arrowImage.png pour la flèche. (Si vous savez comment récupérer cette flèche direct à partir d'un popup, je suis preneur)
- Ne prend pas en compte le changement de style ou de cell à la volée.
Observations, améliorations bienvenues !
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Je crains que tu n'aies d'autre choix: les doubles flèches des popups ne sont pas accessibles séparément via l'appearance manager (enfin, les fonctions HITheme qui sont censées contenir les primitives pour le dessin de contrôles), alors que les simples flèches des popdown, oui (et dans toutes les directions en plus, ce que ne permet pas cocoa).
Le problème c'est que les flèches ne sont pas mises à l'échelle en fonction de la taille du popup.
- Si on diminue la hauteur par defaut avec setFrame:, elles sont dessinées pareil.
- Si on change la taille avec [cell setControlSize:NSMiniControlSize] elles disparaissent.
- Si j'essaie de surcharger ta méthode privée en modifiant le frame, la taille de la flèche ne change pas avec la hauteur du frame.
PS: Apparemment mon bug 1 (centrage vert. du texte) n'en est pas un. Il faut spécifier NSminiControlSize et c'est bon. Par contre les deux autres restent quel que soit le controlSize.
Mais en changeant le contexte graphique, on devrait pouvoir bidouiller avec, non ? (scale...)
Je ne sais pas trop non plus
... mais d'après ce que j'ai testé, le style c'est le bezelStyle (NSRoundrectbezelStyle dans ce cas).
Pour changer le contexte, je suis d'accord ... pour que quelqu'un s'y mette. Le problème c'est que le changement d'échelle modifierait les flèches mais aussi la bordure du bouton, non ?
Ou alors le faire en deux passes: une avec bordered=NO pour diminuer les flèches; et une autre à taille normale avec bordered=YES mais pas les flèches.
A part ça, comment on les trouve ces méthodes non documentées ? ca m'intrigue.
Par contre, je ne me souvient plus de l'addresse exacte de logiciel
Non documenté ne signifie pas inaccessible: si tu veux "explorer", la méthode la plus simple consiste tout simplement à ...sous-classer et surcharger. Une méthode privée est point de vue exécution parfaitement identique à une méthode publique. La seule différence étant qu'elle a été déclarée dans une catégorie dont l'interface n'est pas publique.
Je préfère "otool -vo" :P
La syntaxe du run-time est :
Et la forme des définitions :
(sinon, "class-dump" est compilable avec Fink...)
Oui, bien sûr, mais va tester les 4G possibilités fournies par un "type" intÂ
PS : elles ne sont pas forcément déclarées dans une catégorie...
Elles sont en général déclarées directement dans le .m de la classe.
Par exemple, je serais pas étonné que style soit en fait le bezel style, et un sous-classage permetrait d'affirmer ou d'infirmer assez rapidement cette hypothèse.
RPS: regarde bien comment sont déclarées ce qu'on appelle les méthodes privées: ce n'est jamais qu'une catégorie où interface et implémentation sont dans le fichier .m: ce qui est compris dans "catégorie dont l'interface n'est pas publique". Mais il y en a encore un autre type de méthodes privées: celles qui sont déclarées dans des fichiers .h qui sont privés, mais les outils de retro-engeneering ne permettent pas ffde faire la distinction entre ces 2 types de méthodes privées.
1- Créer une NSView bidon
2- Utiliser pour dessiner uniquement la flèche dans cette vue sachant qu'avec bordered=NO il ne reste que la flèche.
3- récupérer une image à partir du rect formé par la flèche.
4- cacher l'image pour la récupérer plus tard.
Ca éviterait d'embarquer un fichier image dans le bundle.
J'ai pas compris. Moi je voudrais récupérer l'image pour pouvoir ensuite la réduire, la mettre en cache, et la réutiliser à chaque fois qu'une cell s'auto-dessine. Comment je récupère l'image dans drawrect ? Ou alors, si je la récupère pas, comment je la redessine dans drawrect à une échelle réduite ?
[tt]theCell = [NSPopUpButtonCell new];
[theCell setControlSize:NSSmallControlSize]; // de cette manière, la flèche devrait se dessiner en plus petit - sinon il y NSMiniControlSize et NSRegularControlSize
// d'autres réglages pour la cell[/tt]
Dans le drawRect: si tu utilises un nsview, tu mets:
[tt]NSRect arrowRect = //tu calcules le bon rect
[theCell _drawThemePopUpBorderWithFrame:arrowRect inView:self bordered:NO style:0];[/tt]
Si tu utilises une sous-classe de NSCell, c'est dans le drawWithFrame:inView: qu'il faudra mettre le code suivant:
[tt]NSRect arrowRect = //tu calcules le bon rect
[theCell _drawThemePopUpBorderWithFrame:arrowRect inView:view bordered:NO style:0];[/tt]
De cette manière, tu n'a pas besoin de cacher l'image des flèches, pas besoin de la redimensionner (tu le fais avec le setControlSize:), et surtout, lorsque l'indépendance de la résolution arrivera*, tu auras la garantie que les flèches se dessineront proprement si l'interface de l'utilisateur n'est pas à l'échelle standard.
*il faut déjà penser en fonction de ça et éviter un maximum les images bitmap, ce sera du temps gagné par la suite.
Note: code non testé, c'est pour te montrer l'idée.
Pour dessiner à une échelle réduite, à mon avis, il suffit de bidouiller avec le contexte graphique...
1. on ne sait pas de quelle manière sont stockées les dites flèches: si elles sont stockées sous forme vectorielle, pas de problème, mais si elles sont stockées sous forme de bitmap, un redimensionnement donnera toujours un résultat affreux, et de ce que j'en sais, les contrôles sous Tiger sont plutôt bitmap que vectoriels.
2. en admettant que ces flèches soient stockées de manière vectorielle, il n'y a aucune garantie que la double flèche "mini" soit une version mise à l'échelle de la double flèche "regular". Je dirais même que c'est peu probable, dans la mesure où l'espacement entre les 2 demi-flèches est prévu pour être un nombre de points entier, pour avoir un résultat plaisant à l'oe“il.
Quand je parle de contexte graphique, je parle bien sûr de CoreGraphics... par le truc bizarroà¯de qu'ils ont fait en Cocoa :P
Qu'est-ce qui empêche de faire un espacement variable avec un dessin vectoriel ? ???
En fait c'est tout simple. Il suffit d'appeller la méthode de schlum avec le bezelStyle de Renaud (comme ça tout le monde est content).
Dans mon emportement, j'avais oublié que le bug ne se produisait QUE lorsque le style est NSRoundRectBezelStyle. Si le style est NSRoundedBezelStyle, la flèche est dessinée au bon endroit et à la bonne échelle quel que soit la taille du contrôle. Donc il suffit d'appeler la méthode de Shlum avec bordered=NO et style=1 pour dessiner la flèche correctement !! c'est tout con.
Cependant, il y a deux cas:
1- controlSize= small ou mini : le bug fait que les flèches ne sont pas dessinées.
2- controlSize= regular : elles sont dessinées au milieu. Pour éviter que les flèches soient dessinées deux fois, il faut modifier arrowPosition à la volée.
Voici le patch final, à améliorer éventuellement.
A tester sur toutes les versions 10.4.x pour être sûr.
Merci encore pour vos suggestions !