Fix MusicVideo not showing non-first notifications on displays that don't have the menu bar on them.
The problem was that GrowlDisplayWindowController and GrowlPositionController were both trying to get the screen of a window that wasn't on a screen (yet|anymore), both before and after displaying the notification. The solution is to get the screen from the display window controller, not from the window.
2 // GrowlPositionController.m
5 // Created by Ofri Wolfus on 31/08/05.
6 // Copyright 2004-2006 The Growl Project. All rights reserved.
8 // This file is under the BSD License, refer to License.txt for details
11 #import "GrowlPositionController.h"
12 #import "GrowlDisplayWindowController.h"
13 #import "GrowlPreferencesController.h"
14 #import "NSMutableStringAdditions.h"
15 #import "GrowlDefines.h"
16 #import "GrowlTicketController.h"
20 @interface GrowlPositionController (PRIVATE)
21 - (NSMutableSet *)reservedRectsForScreen:(NSScreen *)inScreen;
22 - (NSRectArray)copyRectsInSet:(NSSet *)rectSet count:(int *)outCount padding:(float)padding excludingDisplayController:(GrowlDisplayWindowController *)displayController;
25 @implementation GrowlPositionController
28 - (id) initSingleton {
29 if ((self = [super initSingleton])) {
30 reservedRects = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
31 reservedRectsByController = [[NSMutableDictionary alloc] init];
39 CFRelease(reservedRects);
42 //Read in the stored selection from picker and translate to a properly returned GrowlPosition.
43 + (enum GrowlPosition)selectedOriginPosition
45 enum GrowlPositionOrigin globalSelectedPosition = (enum GrowlPositionOrigin)[[GrowlPreferencesController sharedController] integerForKey:GROWL_POSITION_PREFERENCE_KEY];
46 enum GrowlPosition translatedPosition;
48 switch(globalSelectedPosition){
51 //Default to middle of the screen if no origin is set, though this case shouldn't be hit.
52 translatedPosition = GrowlMiddleColumnPosition;
54 case GrowlTopLeftCorner:
55 translatedPosition = GrowlTopLeftPosition;
57 case GrowlBottomRightCorner:
58 translatedPosition = GrowlBottomRightPosition;
60 case GrowlTopRightCorner:
61 translatedPosition = GrowlTopRightPosition;
63 case GrowlBottomLeftCorner:
64 translatedPosition = GrowlBottomLeftPosition;
68 return translatedPosition;
71 //Return a rect suitable for the position and screen.
72 + (NSRect) rectForPosition:(enum GrowlPosition)position inScreen:(NSScreen *)screen {
75 NSRect result = NSZeroRect;
77 //Treat nil as the main screen
79 screen = [NSScreen mainScreen];
81 screenFrame = [screen visibleFrame];
82 areaSize = NSMakeSize(screenFrame.size.width / 3.0f, screenFrame.size.height / 3.0f); //We have 9 identical areas on each screen
86 case GrowlTopLeftPosition:
87 result = NSMakeRect(screenFrame.origin.x,
88 screenFrame.origin.y + areaSize.height + areaSize.height,
94 case GrowlTopMiddlePosition:
95 result = NSMakeRect(screenFrame.origin.x + areaSize.width,
96 screenFrame.origin.y + areaSize.height + areaSize.height,
102 case GrowlTopRightPosition:
103 result = NSMakeRect(screenFrame.origin.x + areaSize.width + areaSize.width,
104 screenFrame.origin.y + areaSize.height + areaSize.height,
110 case GrowlCenterLeftPosition:
111 result = NSMakeRect(screenFrame.origin.x,
112 screenFrame.origin.y + areaSize.height,
118 case GrowlCenterMiddlePosition:
119 result = NSMakeRect(screenFrame.origin.x + areaSize.width,
120 screenFrame.origin.y + areaSize.height,
126 case GrowlCenterRightPosition:
127 result = NSMakeRect(screenFrame.origin.x + areaSize.width + areaSize.width,
128 screenFrame.origin.y + areaSize.height,
134 case GrowlBottomLeftPosition:
135 result = NSMakeRect(screenFrame.origin.x,
136 screenFrame.origin.y,
142 case GrowlBottomMiddlePosition:
143 result = NSMakeRect(screenFrame.origin.x + areaSize.width,
144 screenFrame.origin.y,
150 case GrowlBottomRightPosition:
151 result = NSMakeRect(screenFrame.origin.x + areaSize.width + areaSize.width,
152 screenFrame.origin.y,
158 case GrowlTopRowPosition:
159 result = NSMakeRect(screenFrame.origin.x,
160 screenFrame.origin.y + areaSize.height + areaSize.height,
161 screenFrame.size.width,
166 case GrowlCenterRowPosition:
167 result = NSMakeRect(screenFrame.origin.x,
168 screenFrame.origin.y + areaSize.height,
169 screenFrame.size.width,
174 case GrowlBottomRowPosition:
175 result = NSMakeRect(screenFrame.origin.x,
176 screenFrame.origin.y,
177 screenFrame.size.width,
182 case GrowlLeftColumnPosition:
183 result = NSMakeRect(screenFrame.origin.x,
184 screenFrame.origin.y,
186 screenFrame.size.height);
190 case GrowlMiddleColumnPosition:
191 result = NSMakeRect(screenFrame.origin.x + areaSize.width,
192 screenFrame.origin.y,
194 screenFrame.size.height);
198 case GrowlRightColumnPosition:
199 result = NSMakeRect(screenFrame.origin.x + areaSize.width + areaSize.width,
200 screenFrame.origin.y,
202 screenFrame.size.height);
209 - (BOOL) positionDisplay:(GrowlDisplayWindowController *)displayController {
210 GrowlLog *growlLog = [GrowlLog sharedController];
212 GrowlApplicationTicket *displayTicket = [[GrowlTicketController sharedController] ticketForApplicationName:[[displayController notification] applicationName]];
213 selectedPositionType = [displayTicket positionType];
214 selectedCustomPosition = (enum GrowlPositionOrigin)[displayTicket selectedPosition];
216 NSScreen *preferredScreen = [displayController screen];
217 NSRect screenFrame = [preferredScreen visibleFrame];
218 NSSize displaySize = [[displayController window] frame].size;
219 float padding = [displayController requiredDistanceFromExistingDisplays];
221 // Ask the display where it wants to be displayed in the first instance....
225 enum GrowlExpansionDirection primaryDirection = [displayController primaryExpansionDirection];
226 enum GrowlExpansionDirection secondaryDirection = [displayController secondaryExpansionDirection];
228 if ([reservedRectsByController objectForKey:[NSValue valueWithPointer:displayController]]) {
229 NSRect currentlyReservedRect = [[reservedRectsByController objectForKey:[NSValue valueWithPointer:displayController]] rectValue];
230 idealOrigin = currentlyReservedRect.origin;
232 //The expansion direction determines which origins should be kept constant
233 switch (primaryDirection) {
234 case GrowlDownExpansionDirection:
235 idealOrigin.y += (currentlyReservedRect.size.height - displaySize.height);
237 case GrowlUpExpansionDirection:
239 case GrowlLeftExpansionDirection:
240 idealOrigin.x += (currentlyReservedRect.size.width - displaySize.width);
242 case GrowlRightExpansionDirection:
244 case GrowlNoExpansionDirection:
248 idealFrame = NSMakeRect(idealOrigin.x, idealOrigin.y,
249 displaySize.width, displaySize.height);
251 if (!NSContainsRect(screenFrame,idealFrame)) {
252 idealOrigin = [displayController idealOriginInRect:screenFrame];
253 idealFrame = NSMakeRect(idealOrigin.x,idealOrigin.y,displaySize.width,displaySize.height);
256 idealOrigin = [displayController idealOriginInRect:screenFrame];
257 idealFrame = NSMakeRect(idealOrigin.x,idealOrigin.y,displaySize.width,displaySize.height);
260 // Try and reserve the rect
261 NSRect displayFrame = idealFrame;
262 if ([self reserveRect:displayFrame inScreen:preferredScreen forDisplayController:displayController]) {
263 [[displayController window] setFrame:displayFrame display:YES animate:YES];
267 // Something was blocking the display...try to find the next position for the display.
269 [growlLog writeToLog:@"---"];
270 [growlLog writeToLog:@"positionDisplay: could not reserve initial rect; looking for another one"];
271 [growlLog writeToLog:@"primaryDirection: %@", NSStringFromGrowlExpansionDirection(primaryDirection)];
272 [growlLog writeToLog:@"secondaryDirection: %@", NSStringFromGrowlExpansionDirection(secondaryDirection)];
275 NSRectArray usedRects = [self copyRectsInSet:[self reservedRectsForScreen:preferredScreen] count:&numberOfRects padding:padding excludingDisplayController:displayController];
277 /* This will loop until the display is placed or we run off the screen entirely
278 * A more 'efficient' implementation might sort all of the usedRects, then look at them iteratively. I (evands) found it to be
279 * thoroughly nontrivial to do such a sort in a robust fashion. While the below code does loop more than an 'efficient' search,
280 * it ends up being a whole bunch of simple float comparisons in the worst case, which any modern computer can handle with ease. Let's
281 * not over-optimize unless this is an actual bottleneck. :)
284 BOOL haveBestSecondaryOrigin = NO;
285 float bestSecondaryOrigin = 0;
287 while (NSContainsRect(screenFrame,displayFrame)) {
288 //Adjust in our primary direction
289 switch (primaryDirection) {
290 case GrowlDownExpansionDirection:
291 displayFrame.origin.y -= 1;
293 case GrowlUpExpansionDirection:
294 displayFrame.origin.y += 1;
296 case GrowlLeftExpansionDirection:
297 displayFrame.origin.x -= 1;
299 case GrowlRightExpansionDirection:
300 displayFrame.origin.x += 1;
302 case GrowlNoExpansionDirection:
303 NSLog(@"This should never happen");
309 BOOL intersects = NO;
310 //Check to see if the proposed displayFrame intersects with any used rect
311 for (int i = 0; i < numberOfRects; i++) {
312 if (NSIntersectsRect(displayFrame, usedRects[i])) {
313 //We intersected. Sadness.
316 /* Determine, based on this intersection, how far we should shift if we end up moving in
317 * our secondary direction.
319 switch (secondaryDirection) {
320 case GrowlDownExpansionDirection:
322 if (!haveBestSecondaryOrigin ||
323 NSMinY(usedRects[i]) > bestSecondaryOrigin) {
324 haveBestSecondaryOrigin = YES;
325 bestSecondaryOrigin = NSMinY(usedRects[i]) - NSHeight(displayFrame);
329 case GrowlUpExpansionDirection:
331 if (!haveBestSecondaryOrigin ||
332 NSMaxY(usedRects[i]) < bestSecondaryOrigin) {
333 haveBestSecondaryOrigin = YES;
334 bestSecondaryOrigin = NSMaxY(usedRects[i]);
338 case GrowlLeftExpansionDirection:
340 if (!haveBestSecondaryOrigin ||
341 NSMinX(usedRects[i]) < bestSecondaryOrigin) {
342 haveBestSecondaryOrigin = YES;
343 bestSecondaryOrigin = NSMinX(usedRects[i]) - NSWidth(displayFrame);
347 case GrowlRightExpansionDirection:
349 if (!haveBestSecondaryOrigin ||
350 NSMaxX(usedRects[i]) < bestSecondaryOrigin) {
351 haveBestSecondaryOrigin = YES;
352 bestSecondaryOrigin = NSMaxX(usedRects[i]);
356 case GrowlNoExpansionDirection:
357 NSLog(@"This should never happen");
365 if (!intersects) break;
368 if (NSContainsRect(screenFrame,displayFrame)) {
369 //The rect is on the screen! Try to reserve it.
370 if ([self reserveRect:displayFrame inScreen:preferredScreen forDisplayController:displayController]) {
371 [[displayController window] setFrame:displayFrame display:YES animate:YES];
376 // If we've run offscreen or couldn't reserve that rect, use the secondary direction after resetting from our previous efforts
377 switch (primaryDirection) {
378 case GrowlDownExpansionDirection:
379 case GrowlUpExpansionDirection:
380 displayFrame.origin.y = idealFrame.origin.y;
382 case GrowlLeftExpansionDirection:
383 case GrowlRightExpansionDirection:
384 displayFrame.origin.x = idealFrame.origin.x;
386 case GrowlNoExpansionDirection:
387 NSLog(@"This should never happen");
393 switch (secondaryDirection) {
394 case GrowlDownExpansionDirection:
395 case GrowlUpExpansionDirection:
396 displayFrame.origin.y = bestSecondaryOrigin;
398 case GrowlLeftExpansionDirection:
399 case GrowlRightExpansionDirection:
400 displayFrame.origin.x = bestSecondaryOrigin;
402 case GrowlNoExpansionDirection:
403 NSLog(@"This should never happen");
409 if (!NSContainsRect(screenFrame,displayFrame)) {
410 NSLog(@"Could not display Growl notification; no screen space available.");
420 //Reserve a rect in a specific screen.
421 - (BOOL) reserveRect:(NSRect)inRect inScreen:(NSScreen *)inScreen forDisplayController:(GrowlDisplayWindowController *)displayController {
423 NSValue *displayControllerValue = (displayController ? [NSValue valueWithPointer:displayController] : nil);
425 //Treat nil as the main screen
426 if (!inScreen) inScreen = [NSScreen mainScreen];
428 NSMutableSet *reservedRectsOfScreen = [self reservedRectsForScreen:inScreen];
429 NSValue *newRectValue = [NSValue valueWithRect:inRect];
430 NSEnumerator *rectValuesEnumerator;
433 //Make sure the rect is not already reserved. However, if it is reserved by displayController, that's fine (it is just rerequesting its current space).
434 if ([reservedRectsOfScreen member:newRectValue] &&
435 (!displayController || (![[reservedRectsByController objectForKey:displayControllerValue] isEqual:newRectValue]))) {
438 rectValuesEnumerator = [reservedRectsOfScreen objectEnumerator];
440 // Loop through all the values in reservedRects and make sure
441 // that the new rect does not intersect with any of the already
442 // reserved rects, excepting if the displayController itself is reserving the rect.
443 while ((value = [rectValuesEnumerator nextObject])) {
444 if ((NSIntersectsRect(inRect, [value rectValue])) &&
445 (!displayController || (![[reservedRectsByController objectForKey:displayControllerValue] isEqual:value]))) {
452 // Add the new rect if it passed the intersection test
454 [self clearReservedRectForDisplayController:displayController];
455 [reservedRectsByController setObject:[NSValue valueWithRect:inRect]
456 forKey:displayControllerValue];
457 [reservedRectsOfScreen addObject:newRectValue];
463 - (BOOL) reserveRect:(NSRect)inRect forDisplayController:(GrowlDisplayWindowController *)displayController {
464 return [self reserveRect:inRect inScreen:[displayController screen] forDisplayController:displayController];
467 - (void) clearReservedRectForDisplayController:(GrowlDisplayWindowController *)displayController
469 NSValue *controllerKey = [NSValue valueWithPointer:displayController];
470 NSMutableSet *reservedRectsOfScreen = [self reservedRectsForScreen:[displayController screen]];
471 NSValue *value = [reservedRectsByController objectForKey:controllerKey];
474 [reservedRectsOfScreen removeObject:value];
475 [reservedRectsByController removeObjectForKey:controllerKey];
480 * @method copyRectsInSet:count:padding
481 * @brief Returns a malloc'd array of NSRect structs which were contained as values in rectSet
483 * @param rectSet An NSSet which must contain only NSValues representing rects via -[NSValue rectValue]
484 * @param outCount If non-NULL, on return will have the number of rects in the returned array
485 * @param padding Padding to add to each returned rect in the rect array
486 * @param displayController A display controller whose rect(s) should not be included. Pass nil to include all rects.
487 * @result A malloc'd NSRectArray. This value should be freed after use.
489 - (NSRectArray)copyRectsInSet:(NSSet *)rectSet count:(int *)outCount padding:(float)padding excludingDisplayController:(GrowlDisplayWindowController *)displayController
491 NSEnumerator *enumerator = [rectSet objectEnumerator];
493 NSValue *displayControllerValue = [NSValue valueWithPointer:displayController];
494 int count = [rectSet count];
496 if (outCount) *outCount = count;
498 NSRectArray gridRects = (NSRectArray)malloc(sizeof(NSRect) * count);
500 while ((value = [enumerator nextObject])) {
501 if (!displayController || (![[reservedRectsByController objectForKey:displayControllerValue] isEqual:value])) {
502 gridRects[i++] = NSInsetRect([value rectValue], -padding, -padding);
509 //Returns the set of reserved rect for a specific screen. The return value *is* the storage!
510 - (NSMutableSet *)reservedRectsForScreen:(NSScreen *)screen {
511 NSMutableSet *result = nil;
513 //Treat nil as the main screen
515 screen = [NSScreen mainScreen];
517 //Get the set of reserved rects for our screen
518 result = (NSMutableSet *)CFDictionaryGetValue(reservedRects, screen);
520 //Make sure the set exists. If not, create it.
522 result = [[NSMutableSet alloc] init];
523 CFDictionarySetValue(reservedRects, screen, result);
530 - (enum GrowlPosition) originPosition {
531 if (selectedPositionType == 1) {
532 enum GrowlPosition translatedPosition;
533 switch (selectedCustomPosition) {
536 //Default to middle of the screen if no origin is set, though this case shouldn't be hit.
537 translatedPosition = GrowlMiddleColumnPosition;
539 case GrowlTopLeftCorner:
540 translatedPosition = GrowlTopLeftPosition;
542 case GrowlBottomRightCorner:
543 translatedPosition = GrowlBottomRightPosition;
545 case GrowlTopRightCorner:
546 translatedPosition = GrowlTopRightPosition;
548 case GrowlBottomLeftCorner:
549 translatedPosition = GrowlBottomLeftPosition;
552 return translatedPosition;
554 return [GrowlPositionController selectedOriginPosition];
559 NSString *NSStringFromGrowlPosition(enum GrowlPosition pos) {
564 case GrowlTopLeftPosition:
565 case GrowlTopMiddlePosition:
566 case GrowlTopRightPosition:
567 case GrowlTopRowPosition:
571 case GrowlCenterLeftPosition:
572 case GrowlCenterMiddlePosition:
573 case GrowlCenterRightPosition:
574 case GrowlCenterRowPosition:
578 case GrowlBottomLeftPosition:
579 case GrowlBottomMiddlePosition:
580 case GrowlBottomRightPosition:
581 case GrowlBottomRowPosition:
585 case GrowlLeftColumnPosition:
589 case GrowlMiddleColumnPosition:
593 case GrowlRightColumnPosition:
603 case GrowlTopLeftPosition:
604 case GrowlCenterLeftPosition:
605 case GrowlBottomLeftPosition:
609 case GrowlTopMiddlePosition:
610 case GrowlBottomMiddlePosition:
614 case GrowlCenterMiddlePosition:
619 case GrowlTopRightPosition:
620 case GrowlCenterRightPosition:
621 case GrowlBottomRightPosition:
625 case GrowlTopRowPosition:
626 case GrowlCenterRowPosition:
627 case GrowlBottomRowPosition:
631 case GrowlLeftColumnPosition:
632 case GrowlMiddleColumnPosition:
633 case GrowlRightColumnPosition:
640 if (first && second) {
641 unsigned firstLength = [first length];
642 unsigned secondLength = [second length];
644 if (firstLength && secondLength) {
645 unsigned capacity = firstLength + secondLength + 1U;
646 NSMutableString *mutable = [[NSMutableString alloc] initWithCapacity:capacity];
648 [mutable appendString:first];
649 [mutable appendCharacter:'-'];
650 [mutable appendString:second];
652 str = [mutable autorelease];
653 } else if (firstLength || secondLength) {
654 str = firstLength ? first : second;
661 NSString *NSStringFromGrowlExpansionDirection(enum GrowlExpansionDirection dir) {
663 case GrowlNoExpansionDirection:
665 case GrowlDownExpansionDirection:
667 case GrowlUpExpansionDirection:
669 case GrowlLeftExpansionDirection:
671 case GrowlRightExpansionDirection: