2009-08-19 6 views
12

Ho una domanda sullo NSStatusItem per il cacao in mac osx. Se guardi l'app mac chiamata snippet (guarda il film allo http://snippetsapp.com/). vedrai che una volta che hai fatto clic sull'icona della tua barra di stato, una vista/pannello perfettamente allineato o forse anche una finestra appare proprio sotto l'icona.Come ottenere la posizione sullo schermo di un NSStatusItem

La mia domanda è ... Come calcolare la posizione in cui posizionare il proprio NSWindow proprio come fa questa app?

Ho provato quanto segue:

  1. sottoclasse NSMenu
  2. Impostare il papismo vista per la prima voce del menu (ha funzionato, ma abbastanza)
  3. Utilizzando addSubview invece di icona per NSStatusItem questo ha funzionato, ma non è possibile ottenere più in alto di 20px
+0

discusso anche qui: http://stackoverflow.com/questions/5413784/how-to-get-frame-for-nsstatusitem/10375784 –

risposta

0

Se si è disposti a utilizzare l'analisi delle immagini per trovare l'elemento di stato su una barra dei menu, ecco una categoria per NSScreen che fa esattamente questo.

Potrebbe sembrare strano farlo in questo modo, ma è veloce, relativamente piccolo, ed è il modo solo di trovare un elemento di stato senza API non documentata.

Se si passa all'immagine corrente per la voce di stato, questo metodo dovrebbe trovarla.

@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 
10

Dare all'oggetto NSStatus una vista, quindi ottenere la cornice della finestra di quella vista. Questo tecnicamente conta come UndocumentedGoodness, quindi non sorprenderti se si rompe un giorno (ad esempio, se iniziano a tenere la finestra fuori dallo schermo).

Non so cosa intendi con "non potrei arrivare più in alto di 20px".

+0

w00t !! che ha aiutato a guardare @ questo messaggio di debug 01/0835: 22: 15: 43.199 PasteBin [14430: a0f] X: 1118.000000 - Y: 1028.000000 2009-08-19 22: 15: 43.203 PasteBin [14430: a0f] X: 1118,000000 - Y: 1028.000000 ------ Codice qui --- - (void) drawRect: (NSRect) dirtyRect { // Codice di disegno qui. \t NSLog (@ "X:% f - Y:% f", self.window.frame.origin.x, self.window.frame.origin.y); } - --- - - - - - Grazie mille penso che tu abbia risolto il mistero per milioni di sviluppatori adesso grazie! –

+0

Sia chiaro che questa soluzione non funziona correttamente, specialmente con più schermi. Come ha detto Peter, non è documentato e attualmente lo rimuovo dalla prossima versione della mia app. – Mazyod

+0

Sembra che non sia necessario aggiungere una vista personalizzata a NSStatusItem per ottenere la sua posizione. Vedi http://stackoverflow.com/a/10375784/279024 – rubiii

1

Sembra che questa app utilizzi Matt's MAAttachedWindow. C'è un'applicazione di esempio con lo stesso layout & posizione.

0

da Apple NSStatusItem Class Reference:

L'impostazione di una vista personalizzata ignora tutte le altre impostazioni di aspetto e comportamento definiti da NSStatusItem. La visualizzazione personalizzata è responsabile del disegno stesso e dei propri comportamenti, come l'elaborazione dei clic del mouse e l'invio di messaggi di azione.

7

Per fare ciò senza il fastidio di una visualizzazione personalizzata, ho provato quanto segue (che funziona). Nel metodo che è impostato come il ricorso per l'elemento dello stato cioè il metodo che viene chiamato quando l'utente sceglie la voce di stato, il telaio dell'elemento di stato possono essere recuperate da:

[[[NSApp currentEvent] window] frame] 

Lavori un piacere per me

+0

Come ottenere la posizione dell'elemento di stato senza fare clic su di esso? –

+0

Questa risposta soffre dello stesso problema che sto vedendo con la risposta di Peter ... Cercando il modo 'Dropbox'. – Mazyod

+0

Questo non funziona bene con i tasti di scelta rapida, ad esempio. Se non si utilizza un NSStatusItem con una visualizzazione personalizzata, è possibile provare questa soluzione: http://stackoverflow.com/a/10375784/279024 – rubiii