2011-03-24 15 views
8

¿Es posible obtener el marco de un NSStatusItem después de haberlo agregado a la barra de estado en Cocoa? Cuando se inicia mi aplicación, agrego un elemento a la barra de estado del sistema, y ​​me gustaría saber dónde está posicionado, es posible.Cómo obtener el marco para NSStatusItem

Respuesta

1

Si ha establecido una vista personalizada en el elemento de estado:

NSRect statusRect = [[statusItem view] frame]; 
NSLog(@"%@", [NSString stringWithFormat:@"%.1fx%.1f",statusRect.size.width, statusRect.size.height]); 

lo contrario, no creo que sea posible el uso de las APIs disponibles y documentados.

Editar: Comentarios incorporados.

+2

No me funcionó. Un NSStatusItem no parece tener una vista predeterminada, por lo que [statusItem view] devuelve null. – blutfink

+0

Como dice el documento, "Devuelve la vista personalizada que se muestra en la posición del receptor en la barra de estado", no la vista de NSStatusItem. –

+1

Esto solo funciona si ha configurado una vista personalizada en el elemento de estado. – Fabian

-3

se puede hackear la Ivar ventana como esta:

@interface NSStatusItem (Hack) 

- (NSRect)hackFrame; 

@end 

@implementation NSStatusItem (Hack) 

- (NSRect)hackFrame 
{ 
    int objSize = class_getInstanceSize([NSObject class]) ; 
    id * _ffWindow = (void *)self + objSize + sizeof(NSStatusBar*) + sizeof(CGFloat) ; 
    NSWindow * window = *_ffWindow ; 

    return [window frame] ; 
} 

@end 

Esto es útil para elementos de estado sin una vista personalizada.

probado en León

25

El siguiente parece funcionar - He visto soluciones similares para las aplicaciones de iOS y supuestamente permitir que la sumisión a la tienda de aplicaciones, ya que todavía está utilizando métodos estándar de SDK.

NSRect frame = [[statusBarItem valueForKey:@"window"] frame]; 
+2

Esto funciona, y es útil cuando no ha configurado una vista personalizada para el elemento de estado. – Fabian

+1

Esto supone que NSStatusItem (suponiendo que eso es lo que 'statusBarItem' es) tiene algo que KVC puede usar como propiedad' window'. Eso no está garantizado para ser el caso. Obtendrá una excepción (no compatible con KVC para esta clave) si/cuando eso desaparezca. Tampoco contaría con esta revisión pasajera de la App Store; si no lo hacen ya, algún día podrán comenzar a verificar cómo usas KVC (buscando usos como este, donde accedes a métodos privados/ivars). –

+1

¿Se trata de una llamada de API privada que podría hacer que te rechacen del proceso de revisión de la tienda de aplicaciones? –

24

Con 10.10, NSStatusItem tiene una propiedad button que se utiliza para obtener la posición de elemento de estado sin fijar una vista personalizada.

NSStatusBarButton *statusBarButton = [myStatusItem button]; 
NSRect rectInWindow = [statusBarButton convertRect:[statusBarButton bounds] toView:nil]; 
NSRect screenRect = [[statusBarButton window] convertRectToScreen:rectInWindow]; 
NSLog(@"%@", NSStringFromRect(screenRect)); 
+0

Consejo impresionante, pero 10.10 aún no es popular. :( –

0

Esto es posible sin API privada. Aquí hay una categoría para NSScreen. Utiliza el análisis de imagen para ubicar la imagen del elemento de estado en la barra de menú. Afortunadamente, las computadoras son realmente rápidas. :)

Siempre que sepa cómo se ve la imagen del elemento de estado, y puede pasarlo como un NSImage, este método debería encontrarlo.

Funciona tanto para el modo oscuro como para el modo normal. Tenga en cuenta que la imagen que pase debe ser negra. Las imágenes en color probablemente no funcionen tan bien.

@implementation NSScreen (LTStatusItemLocator) 

// Find the location of IMG on the screen's status bar. 
// If the image is not found, returns NSZeroPoint 
- (NSPoint)originOfStatusItemWithImage:(NSImage *)IMG 
{ 
    CGColorSpaceRef  csK = CGColorSpaceCreateDeviceGray(); 
    NSPoint    ret = NSZeroPoint; 
    CGDirectDisplayID screenID = 0; 
    CGImageRef   displayImg = NULL; 
    CGImageRef   compareImg = NULL; 
    CGRect    screenRect = CGRectZero; 
    CGRect    barRect = CGRectZero; 
    uint8_t    *bm_bar = NULL; 
    uint8_t    *bm_bar_ptr; 
    uint8_t    *bm_compare = NULL; 
    uint8_t    *bm_compare_ptr; 
    size_t    bm_compare_w, bm_compare_h; 
    BOOL    inverted = NO; 
    int     numberOfScanLines = 0; 
    CGFloat    *meanValues = NULL; 

    int     presumptiveMatchIdx = -1; 
    CGFloat    presumptiveMatchMeanVal = 999; 


    // If the computer is set to Dark Mode, set the "inverted" flag 
    NSDictionary *globalPrefs = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain]; 
    id style = globalPrefs[@"AppleInterfaceStyle"]; 
    if ([style isKindOfClass:[NSString class]]) { 
     inverted = (NSOrderedSame == [style caseInsensitiveCompare:@"dark"]); 
    } 

    screenID = (CGDirectDisplayID)[self.deviceDescription[@"NSScreenNumber"] integerValue]; 

    screenRect = CGDisplayBounds(screenID); 

    // Get the menubar rect 
    barRect = CGRectMake(0, 0, screenRect.size.width, 22); 

    displayImg = CGDisplayCreateImageForRect(screenID, barRect); 
    if (!displayImg) { 
     NSLog(@"Unable to create image from display"); 
     CGColorSpaceRelease(csK); 
     return ret; // I would normally use goto(bail) here, but this is public code so let's not ruffle any feathers 
    } 

    size_t bar_w = CGImageGetWidth(displayImg); 
    size_t bar_h = CGImageGetHeight(displayImg); 

    // Determine scale factor based on the CGImageRef we got back from the display 
    CGFloat scaleFactor = (CGFloat)bar_h/(CGFloat)22; 

    // Greyscale bitmap for menu bar 
    bm_bar = malloc(1 * bar_w * bar_h); 
    { 
     CGContextRef bmCxt = NULL; 

     bmCxt = CGBitmapContextCreate(bm_bar, bar_w, bar_h, 8, 1 * bar_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone); 

     // Draw the menu bar in grey 
     CGContextDrawImage(bmCxt, CGRectMake(0, 0, bar_w, bar_h), displayImg); 

     uint8_t minVal = 0xff; 
     uint8_t maxVal = 0x00; 
     // Walk the bitmap 
     uint64_t running = 0; 
     for (int yi = bar_h/2; yi == bar_h/2; yi++) 
     { 
      bm_bar_ptr = bm_bar + (bar_w * yi); 
      for (int xi = 0; xi < bar_w; xi++) 
      { 
       uint8_t v = *bm_bar_ptr++; 
       if (v < minVal) minVal = v; 
       if (v > maxVal) maxVal = v; 
       running += v; 
      } 
     } 
     running /= bar_w; 
     uint8_t threshold = minVal + ((maxVal - minVal)/2); 
     //threshold = running; 


     // Walk the bitmap 
     bm_bar_ptr = bm_bar; 
     for (int yi = 0; yi < bar_h; yi++) 
     { 
      for (int xi = 0; xi < bar_w; xi++) 
      { 
       // Threshold all the pixels. Values > 50% go white, values <= 50% go black 
       // (opposite if Dark Mode) 

       // Could unroll this loop as an optimization, but probably not worthwhile 
       *bm_bar_ptr = (*bm_bar_ptr > threshold) ? (inverted?0x00:0xff) : (inverted?0xff:0x00); 
       bm_bar_ptr++; 
      } 
     } 


     CGImageRelease(displayImg); 
     displayImg = CGBitmapContextCreateImage(bmCxt); 

     CGContextRelease(bmCxt); 
    } 


    { 
     CGContextRef bmCxt = NULL; 
     CGImageRef img_cg = NULL; 

     bm_compare_w = scaleFactor * IMG.size.width; 
     bm_compare_h = scaleFactor * 22; 

     // Create out comparison bitmap - the image that was passed in 
     bmCxt = CGBitmapContextCreate(NULL, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone); 

     CGContextSetBlendMode(bmCxt, kCGBlendModeNormal); 

     NSRect imgRect_og = NSMakeRect(0,0,IMG.size.width,IMG.size.height); 
     NSRect imgRect = imgRect_og; 
     img_cg = [IMG CGImageForProposedRect:&imgRect context:nil hints:nil]; 

     CGContextClearRect(bmCxt, imgRect); 
     CGContextSetFillColorWithColor(bmCxt, [NSColor whiteColor].CGColor); 
     CGContextFillRect(bmCxt, CGRectMake(0,0,9999,9999)); 

     CGContextScaleCTM(bmCxt, scaleFactor, scaleFactor); 
     CGContextTranslateCTM(bmCxt, 0, (22. - IMG.size.height)/2.); 

     // Draw the image in grey 
     CGContextSetFillColorWithColor(bmCxt, [NSColor blackColor].CGColor); 
     CGContextDrawImage(bmCxt, imgRect, img_cg); 

     compareImg = CGBitmapContextCreateImage(bmCxt); 


     CGContextRelease(bmCxt); 
    } 




    { 
     // We start at the right of the menu bar, and scan left until we find a good match 
     int numberOfScanLines = barRect.size.width - IMG.size.width; 

     bm_compare = malloc(1 * bm_compare_w * bm_compare_h); 
     // We use the meanValues buffer to keep track of how well the image matched for each point in the scan 
     meanValues = calloc(sizeof(CGFloat), numberOfScanLines); 

     // Walk the menubar image from right to left, pixel by pixel 
     for (int scanx = 0; scanx < numberOfScanLines; scanx++) 
     { 

      // Optimization, if we recently found a really good match, bail on the loop and return it 
      if ((presumptiveMatchIdx >= 0) && (scanx > (presumptiveMatchIdx + 5))) { 
       break; 
      } 

      CGFloat xOffset = numberOfScanLines - scanx; 
      CGRect displayRect = CGRectMake(xOffset * scaleFactor, 0, IMG.size.width * scaleFactor, 22. * scaleFactor); 
      CGImageRef displayCrop = CGImageCreateWithImageInRect(displayImg, displayRect); 

      CGContextRef compareCxt = CGBitmapContextCreate(bm_compare, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone); 
      CGContextSetBlendMode(compareCxt, kCGBlendModeCopy); 

      // Draw the image from our menubar 
      CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), displayCrop); 

      // Blend mode difference is like an XOR 
      CGContextSetBlendMode(compareCxt, kCGBlendModeDifference); 

      // Draw the test image. Because of blend mode, if we end up with a black image we matched perfectly 
      CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), compareImg); 

      CGContextFlush(compareCxt); 

      // Walk through the result image, to determine overall blackness 
      bm_compare_ptr = bm_compare; 
      for (int i = 0; i < bm_compare_w * bm_compare_h; i++) 
      { 
       meanValues[scanx] += (CGFloat)(*bm_compare_ptr); 
       bm_compare_ptr++; 
      } 
      meanValues[scanx] /= (255. * (CGFloat)(bm_compare_w * bm_compare_h)); 

      // If the image is very dark, it matched well. If the average pixel value is < 0.07, we consider this 
      // a presumptive match. Mark it as such, but continue looking to see if there's an even better match. 
      if (meanValues[scanx] < 0.07) { 
       if (meanValues[scanx] < presumptiveMatchMeanVal) { 
        presumptiveMatchMeanVal = meanValues[scanx]; 
        presumptiveMatchIdx = scanx; 
       } 
      } 

      CGImageRelease(displayCrop); 
      CGContextRelease(compareCxt); 

     } 
    } 


    // After we're done scanning the whole menubar (or we bailed because we found a good match), 
    // return the origin point. 
    // If we didn't match well enough, return NSZeroPoint 
    if (presumptiveMatchIdx >= 0) { 
     ret = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame)); 
     ret.x -= (IMG.size.width + presumptiveMatchIdx); 
     ret.y -= 22; 
    } 


    CGImageRelease(displayImg); 
    CGImageRelease(compareImg); 
    CGColorSpaceRelease(csK); 

    if (bm_bar) free(bm_bar); 
    if (bm_compare) free(bm_compare); 
    if (meanValues) free(meanValues); 

    return ret; 
} 

@end 
Cuestiones relacionadas