2009-08-19 10 views
12

Mam pytanie dotyczące NSStatusItem dla kakao w mac osx. Jeśli spojrzysz na aplikację mac o nazwie snippets (zobacz film pod numerem http://snippetsapp.com/). zobaczysz, że po kliknięciu ikony stanu, idealnie wyrównany widok/panel, a nawet okno pojawi się tuż pod ikoną.Jak uzyskać na ekranie lokalizację NSStatusItem

Moje pytanie brzmi ... Jak obliczyć pozycję, gdzie umieścić swoją NSWindow tak jak ta aplikacja?

Próbowałem następujący:

  1. podklasa NSMenu
  2. ustawić widok Popery na pierwszej pozycji menu (działało, ale wystarczająco)
  3. Korzystanie addSubview zamiast ikony do NSStatusItem to działało, ale nie można uzyskać więcej niż 20px
+0

omówiono także tutaj: http://stackoverflow.com/questions/5413784/how-to-get-frame-for-nsstatusitem/10375784 –

Odpowiedz

0

Jeśli chcesz użyć analizy obrazu, aby znaleźć pozycję statusu na pasku menu, oto kategoria dla NSScreen, która dokładnie to robi.

To może wydawać się szalone, aby zrobić to w ten sposób, ale jest szybkie, stosunkowo małe i jest to sposób na znalezienie elementu statusu bez nieudokumentowanego API.

Jeśli przekazujesz bieżący obraz dla pozycji statusu, ta metoda powinna go znaleźć.

@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

Podaj NSStatusItem widok, a następnie uzyskać ramkę okna tego widoku. Technicznie liczy się to jako UndocumentedGoodness, więc nie zdziw się, jeśli kiedyś się zepsuje (np. Jeśli zaczną trzymać okno poza ekranem).

Nie wiem, co masz na myśli przez "nie można uzyskać wagi poniżej 20 pikseli".

+0

w00t !! to pomogło spojrzeć @ ten komunikat debugowania 2009-08-19 22: 15: 43.199 PasteBin [14430: a0f] X: 1118.000000 - Y: 1028.000000 2009-08-19 22: 15: 43.203 PasteBin [14430: a0f] X: 1118000000 - Y: 1028.000000 ------ Kod tutaj --- - (void) drawRect: (NSRect) dirtyRect { // Kod rysunku tutaj. \t NSLog (@ "X:% f - Y:% f", self.window.frame.origin.x, self.window.frame.origin.y); } - --- - - - - - Wielkie dzięki, myślę, że rozwiązałeś zagadkę dla milionów programistów właśnie teraz! –

+0

Po prostu wyjaśnijmy, że to rozwiązanie nie działa poprawnie, szczególnie w przypadku wielu ekranów. Jak wspomniał Peter, nie jest to udokumentowane i obecnie usuwam go z następnej wersji mojej aplikacji. – Mazyod

+0

Wygląda na to, że nie trzeba dodawać niestandardowego widoku do NSStatusItem, aby uzyskać jego lokalizację. Zobacz http://stackoverflow.com/a/10375784/279024 – rubiii

1

Wygląda na to, że ta aplikacja używa Matt's MAAttachedWindow. Jest przykładowa aplikacja o tym samym układzie co pozycja &.

0

z Apple NSStatusItem Class Reference:

Ustawianie widoku niestandardowego zastępuje wszystkie inne ustawienia wyglądu i zachowania zdefiniowane przez NSStatusItem. Widok niestandardowy jest odpowiedzialny za rysowanie i dostarczanie własnych zachowań, takich jak przetwarzanie kliknięć myszy i wysyłanie komunikatów o akcji.

7

Aby to zrobić bez kłopotów z niestandardowego widoku, próbowałem następujące (to działa). W sposobie, który jest ustawiony jako działania dla elementu stanu czyli metody, która jest wywoływana, gdy użytkownik kliknie element stanu, ramka elementu stanu mogą być pobierane przez:

[[[NSApp currentEvent] window] frame] 

działa wspaniale dla mnie

+0

Jak uzyskać lokalizację elementu statusu bez kliknięcia? –

+0

Ta odpowiedź cierpi z powodu tego samego problemu, który widzę z odpowiedzią Piotra ... Poszukując sposobu "Dropbox". – Mazyod

+0

To nie działa dobrze na przykład w przypadku klawiszy skrótów. Jeśli nie używasz NSStatusItem z niestandardowym widokiem, możesz wypróbować to rozwiązanie: http://stackoverflow.com/a/10375784/279024 – rubiii

Powiązane problemy