Corriger le bug du NSRoundRectBezelStyle dans Tiger

laurrislaurris Membre
mars 2007 modifié dans Objective-C, Swift, C, C++ #1
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
<br />//&nbsp; CellDebug.m<br />//&nbsp; PSRuleEditor<br /><br />#import &quot;CellDebug.h&quot;<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:@&quot;ArrowImage&quot; ofType:@&quot;png&quot;];<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 !

Réponses

  • 20:28 modifié #2
    dans 1174042895:

    - 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)


    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).
  • schlumschlum Membre
    20:28 modifié #3
    Il me semble que ces méthodes non documentées permettent de dessiner juste les doubles flèches où on veut :
    - (void)_drawThemePopUpBorderWithFrame:(NSRect)frame inView:(NSView*)view bordered:(BOOL)bordered style:(int)style;<br />- (void)_drawStandardPopUpBorderWithFrame:(NSRect)frame inView:(NSView*)view;
    
  • laurrislaurris Membre
    mars 2007 modifié #4
    dans 1174055067:

    Il me semble que ces méthodes non documentées permettent de dessiner juste les doubles flèches où on veut :
    - (void)_drawThemePopUpBorderWithFrame:(NSRect)frame inView:(NSView*)view bordered:(BOOL)bordered style:(int)style;<br />- (void)_drawStandardPopUpBorderWithFrame:(NSRect)frame inView:(NSView*)view;
    



    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.
  • schlumschlum Membre
    20:28 modifié #5
    Je ne sais pas... Par définition, c'est non documenté, donc on ne sait rien sur "style" par exemple...  :-\\

    Mais en changeant le contexte graphique, on devrait pouvoir bidouiller avec, non ? (scale...)
  • laurrislaurris Membre
    mars 2007 modifié #6
    dans 1174064461:

    Je ne sais pas... Par définition, c'est non documenté, donc on ne sait rien sur "style" par exemple...  :-\\

    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.
  • fouffouf Membre
    20:28 modifié #7
    Faut utiliser class-dump qui est un executable que l'on fait tourner pour récupérer par exemple les headers (y compris les méthodes undocumented) du AppKit.
    Par contre, je ne me souvient plus de l'addresse exacte de logiciel :(
  • 20:28 modifié #8
    dans 1174064461:

    Je ne sais pas... Par définition, c'est non documenté, donc on ne sait rien sur "style" par exemple...  :-\\


    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.
  • schlumschlum Membre
    mars 2007 modifié #9
    dans 1174066896:

    Faut utiliser class-dump qui est un executable que l'on fait tourner pour récupérer par exemple les headers (y compris les méthodes undocumented) du AppKit.
    Par contre, je ne me souvient plus de l'addresse exacte de logiciel :(

    Je préfère "otool -vo"  :P

    La syntaxe du run-time est :

    v -> void (0)
    c -> char (4) / BOOL (4)
    C -> unsigned char (4)
    i -> int (4)
    I -> unsigned int (4)
    s -> short (4)
    S -> unsigned short (4)
    l -> long (4)
    L -> unsigned long (4)
    q -> long long (8)
    Q -> unsigned long long (8)
    f -> float (4)
    d -> double (8) / long double (16)
    * -> char * (4) / unsigned char * (4) / STR (4)

    r -> const
    ^ -> * (4)

    n -> in
    N -> inout
    o -> out
    O -> bycopy
    R -> byref
    V -> oneway

    {name=...} -> struct name / C++ class (sizeof(struct name))
    (name=...) -> union name (sizeof(union name))
    bi -> bits field (size  i)

    [it] -> t [ i] (4)
    [i[j...t...]] -> t [ i][j]... (4)
    ^[i...t...] -> t (*)[ i]... (4)

    @ -> id (4)
    : -> SEL (4)
    # -> Class (4)
    ^? -> <unknown>  * (4) / IMP (function pointer)
    ? -> <unknown> (?)

    B -> bool / _Bool (4)
    -> <unknown> & (4)


    Et la forme des définitions :
    return type - args size - arg 1 type - arg 1 pos - ... - arg N type - arg N pos
    


    (sinon, "class-dump" est compilable avec Fink...)
  • schlumschlum Membre
    mars 2007 modifié #10
    dans 1174067694:

    dans 1174064461:

    Je ne sais pas... Par définition, c'est non documenté, donc on ne sait rien sur "style" par exemple...  :-\\


    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.

    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.
  • mars 2007 modifié #11
    Pas la peine de tester 4G possibilités. Il y a fort à  parier que style est lié à  une variable d'instance qui prend des valeurs qui elles sont publiques.

    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.
  • laurrislaurris Membre
    20:28 modifié #12
    Est-ce que vous croyez qu'il serait possible de faire ceci dans l'implementation de la sous-classe de NSPopupButtoncell :
    1- Créer une NSView bidon
    2- Utiliser
    - (void)_drawThemePopUpBorderWithFrame:(NSRect)frame inView:(NSView*)view bordered:(BOOL)bordered style:(int)style;
    
    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.

  • 20:28 modifié #13
    Même pas la peine de faire une NSView bidon, comme c'est une cell, c'est fait pour être dessiné directement dans une vue. Donc tout ce que tu as à  faire, c'est créer un NSPopUpButtonCell que tu paramètres comme il faut (taille par ex) et dans le drawRect de ta vue, tu appelles direct la fameuse méthode cachée en prenant ta cell.
  • laurrislaurris Membre
    20:28 modifié #14
    dans 1174130933:

    Même pas la peine de faire une NSView bidon, comme c'est une cell, c'est fait pour être dessiné directement dans une vue. Donc tout ce que tu as à  faire, c'est créer un NSPopUpButtonCell que tu paramètres comme il faut (taille par ex) et dans le drawRect de ta vue, tu appelles direct la fameuse méthode cachée en prenant ta cell.


    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 ?
  • mars 2007 modifié #15
    Pour ta vue/cell perso, tu ajoutes une variable d'instance de type NSPopUpButtonCell. Dans le initWithFrame:/init, tu initialises cette variable:

    [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.
  • schlumschlum Membre
    20:28 modifié #16
    dans 1174131439:

    dans 1174130933:

    Même pas la peine de faire une NSView bidon, comme c'est une cell, c'est fait pour être dessiné directement dans une vue. Donc tout ce que tu as à  faire, c'est créer un NSPopUpButtonCell que tu paramètres comme il faut (taille par ex) et dans le drawRect de ta vue, tu appelles direct la fameuse méthode cachée en prenant ta cell.


    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 ?


    Pour dessiner à  une échelle réduite, à  mon avis, il suffit de bidouiller avec le contexte graphique...
  • 20:28 modifié #17
    Dans le cas présent, une transformation affine (version cocoa de "bidouille avec le contexte graphique") est une mauvaise idée:
    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.
  • schlumschlum Membre
    20:28 modifié #18
    dans 1174134897:

    Dans le cas présent, une transformation affine (version cocoa de "bidouille avec le contexte graphique") est une mauvaise idée:
    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 ?  ???
  • laurrislaurris Membre
    mars 2007 modifié #19
    Ca y est j'ai bon, on arrête tout !
    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.
    <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 />- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{<br />	BOOL isRegularControlSize = [self controlSize]==NSRegularControlSize;<br />	<br />	if(isRegularControlSize)[self setArrowPosition:NSPopUpNoArrow];<br />	[super drawWithFrame:cellFrame inView:controlView];<br />	if(isRegularControlSize)[self setArrowPosition:NSPopUpArrowAtBottom];<br />	<br />	if([self bezelStyle]==NSRoundRectBezelStyle){						<br />		cellFrame.origin.x-=10;		<br />		[self _drawThemePopUpBorderWithFrame:cellFrame inView:controlView bordered:NO style:1];		<br />	}<br />}<br /><br />@end<br />
    


    A tester sur toutes les versions 10.4.x pour être sûr.
    Merci encore pour vos suggestions !
Connectez-vous ou Inscrivez-vous pour répondre.