I have got a problem with my WindowController for an App that's basically a list of items, sitting on a panel that can be opened from the menu bar.
To open the panel the user has to click on the App's Status item in the menubar. The app loads fine and everything works ok if the panel is open from the start (and you don't click the status item). However, if its hidden at launch and one clicks the statusitem to open the panel, the App starts eating away huge chunks of RAM without ever getting out of the loop that is creating this effect.
I have worked with some breakpoints to try to find the problem. It seems that some methods within my Window Controller keep calling each other creating an endless loop. But I can't figure out why.
Maybe one of you guys can spot the problem. If any other lines of code are of interest I will try to get them to you. However, for now this is the heart of the application. The rest does not contain that much logic (apart from the AppDelegate).
Here's the Controller's .m-file:
//
// UIController.m
// hourglass
//
// Created by Matze on 24/04/14.
// Copyright (c) 2014 hourglass. All rights reserved.
//
#import "UIController.h"
#implementation UIController
- (id)initWithDelegate:(id<UIControllerDelegate>)delegate {
self = [super initWithWindowNibName:#"UI"];
if (self != nil) {
_delegate = delegate;
}
return self;
}
- (void)deleteSelectedObjects {
NSArray *objectArray = [_Tasks selectedObjects];
for (int i = 0; i < [objectArray count]; i++) {
NSManagedObject *mo2delete = [objectArray objectAtIndex:i];
[[[NSApp delegate] managedObjectContext] deleteObject: mo2delete];
}
}
#pragma Actions
- (IBAction)buttonDelete:(id)sender {
NSUInteger row = [TableView rowForView:sender];
[TableView selectRowIndexes:[[NSIndexSet alloc] initWithIndex:row] byExtendingSelection:NO];
[self deleteSelectedObjects];
}
- (void)keyDown:(NSEvent *)event {
unichar key = [[event charactersIgnoringModifiers] characterAtIndex:0];
if(key == NSDeleteCharacter)
{
if([TableView selectedRow] == -1)
{
NSBeep();
}
BOOL isEditing = [[NSApp delegate]isEditing];
if (!isEditing)
{
[self deleteSelectedObjects];
return;
}
}
[super keyDown:event];
}
#pragma UI Drawing & Animation
- (void)awakeFromNib
{
[super awakeFromNib];
// Create Panel
NSPanel *panel = (id)[self window];
[panel setAcceptsMouseMovedEvents:YES];
[panel setLevel:NSPopUpMenuWindowLevel];
[panel setOpaque:NO];
[panel setBackgroundColor:[NSColor clearColor]];
// Resize Panel
NSRect panelRect = [[self window] frame];
panelRect.size.height = POPUP_HEIGHT;
[[self window] setFrame:panelRect display:NO];
}
- (NSRect)statusRectForWindow:(NSWindow *)window {
NSRect screenRect = [[[NSScreen screens] objectAtIndex:0] frame];
NSRect statusRect = NSZeroRect;
statusItemView = nil;
if ([[self delegate] respondsToSelector:#selector(statusItemViewForUIController:)]) {
statusItemView = [[self delegate] statusItemViewForUIController:self];
}
if (statusItemView) {
statusRect = [statusItemView globalRect];
statusRect.origin.y = NSMinY(statusRect) - NSHeight(statusRect) - 2 ;
} else {
statusRect.size = NSMakeSize(STATUS_ITEM_VIEW_WIDTH, [[NSStatusBar systemStatusBar] thickness]);
statusRect.origin.x = roundf((NSWidth(screenRect) - NSWidth(statusRect)) / 2);
statusRect.origin.y = NSHeight(screenRect) - NSHeight(statusRect) * 2;
}
return statusRect;
}
- (void)setHasActivePanel:(BOOL)flag
{
if (_hasActivePanel != flag)
{
_hasActivePanel = flag;
if (_hasActivePanel)
{
[self openPanel];
}
else
{
[self closePanel];
}
}
}
- (void)windowWillClose:(NSNotification *)notification {
[self setHasActivePanel:NO];
}
- (void)windowDidResignKey:(NSNotification *)notification {
if ([[self window] isVisible]) {
[self setHasActivePanel:NO];
}
}
- (void)windowDidResize:(NSNotification *)notification {
NSWindow *panel = [self window];
NSRect statusRect = [self statusRectForWindow:panel];
NSRect panelRect = [panel frame];
CGFloat statusX = roundf(NSMidX(statusRect));
CGFloat panelX = statusX - NSMinX(panelRect);
CGFloat maxX = NSMaxX([[self backgroundView] bounds]);
CGFloat maxY = NSMaxY([[self backgroundView] bounds]);
self.backgroundView.triangle = panelX; // #Jan: why is a setter method not possible (i.e. setTriangle)
NSRect buttonRect = [[self buttonadd] frame];
buttonRect.size.width = BUTTON_SIZE;
buttonRect.size.height = buttonRect.size.width;
buttonRect.origin.x = maxX - buttonRect.size.width;// * 1.5;
buttonRect.origin.y = maxY - TRIANGLE_HEIGHT - buttonRect.size.height;// * 1.5;
[[self buttonadd] setFrame:buttonRect];
// NSRect listButtonRect = NSMakeRect(NSMinX([[self backgroundView] bounds]), buttonRect.origin.y, buttonRect.size.width, buttonRect.size.height) ;
// [[self buttonlist] setFrame:listButtonRect];
NSRect tableRect = [[self tableScrollView] frame];
tableRect.size.width = maxX;
tableRect.size.height = maxY - TRIANGLE_HEIGHT - buttonRect.size.height;
tableRect.origin.x = self.backgroundView.frame.origin.x;
tableRect.origin.y = maxY - POPUP_HEIGHT;
[[self tableScrollView] setFrame:tableRect];
}
- (void)cancelOperation:(id)sender {
[self setHasActivePanel:NO];
}
- (void)openPanel {
NSWindow *panel = [self window];
NSRect screenRect = [[[NSScreen screens] objectAtIndex:0] frame];
NSRect statusRect = [self statusRectForWindow:panel];
NSRect panelRect = [panel frame];
panelRect.size.width = PANEL_WIDTH;
panelRect.origin.x = roundf(NSMidX(statusRect) - NSWidth(panelRect) / 2);
panelRect.origin.y = NSMaxY(statusRect) - NSHeight(panelRect);
[panel setAlphaValue:0];
[panel setFrame:statusRect display:YES];
[panel makeKeyAndOrderFront:nil];
NSTimeInterval openDuration = OPEN_DURATION;
// For testing
NSEvent *currentEvent = [NSApp currentEvent];
if ([currentEvent type] == NSLeftMouseDown) {
NSUInteger clearFlags = ([currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); // #Jan
BOOL shiftPressed = (clearFlags == NSShiftKeyMask);
BOOL shiftOptionPressed = (clearFlags == (NSShiftKeyMask | NSAlternateKeyMask));
if (shiftPressed || shiftOptionPressed) {
openDuration *= 10;
if (shiftOptionPressed)
NSLog(#"Icon is at %#\n\tMenu is on screen %#\n\tWill be animated to %#",
NSStringFromRect(statusRect), NSStringFromRect(screenRect), NSStringFromRect(panelRect));
}
}
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:openDuration];
[[panel animator] setFrame:panelRect display:YES];
[[panel animator] setAlphaValue:1];
[NSAnimationContext endGrouping];
}
- (void)closePanel {
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:CLOSE_DURATION];
[[[self window] animator] setAlphaValue:0];
[NSAnimationContext endGrouping];
dispatch_after(dispatch_walltime(NULL, NSEC_PER_SEC * CLOSE_DURATION * 2), dispatch_get_main_queue(), ^{
[[self window] orderOut:nil];
});
}
- (NSColor*)colorForIndex:(NSInteger)index {
NSManagedObjectContext *context = [[NSApp delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Task"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSInteger itemCount = [context countForFetchRequest:fetchRequest error:&error];
float val = 0;
float brightness = 0.63;
if( itemCount < 11 )
{
val = brightness + ((0.1/10)*(index*3));
}
else
{
val = brightness + ((0.1/itemCount)*(index*(3)));
}
return [NSColor colorWithDeviceHue: 0.57
saturation: 0.67
brightness: val
alpha: 1
];
}
- (void)tableView:(NSTableView *)tableView
didAddRowView:(NSTableRowView *)rowView
forRow:(NSInteger)row {
rowView.backgroundColor = [self colorForIndex:row];
}
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
[statusItemView setStatusContent:[object valueForKeyPath:keyPath]];
}
#end
and here's the .h file:
//
// UIController.h
// hourglass
//
// Created by Matze on 24/04/14.
// Copyright (c) 2014 hourglass. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "GlobalProperties.h"
#import "StatusItemView.h"
#import "BackgroundView.h"
#import "RFOverlayScrollView.h"
#class UIController;
#protocol UIControllerDelegate <NSObject>
#optional
- (StatusItemView *)statusItemViewForUIController:(UIController *)controller;
#end
#interface UIController : NSWindowController <NSWindowDelegate> {
IBOutlet NSTableView *TableView;
StatusItemView *statusItemView;
}
//TODO: Split Controller function for views into separate Controller Classes (i.e. BackgroundViewController)
#property (nonatomic, unsafe_unretained) IBOutlet BackgroundView *backgroundView;
#property (nonatomic, unsafe_unretained) IBOutlet RFOverlayScrollView *tableScrollView;
#property (strong) IBOutlet NSButton *buttonadd;
#property (strong) IBOutlet NSArrayController *Tasks;
#property (nonatomic) BOOL hasActivePanel;
#property (nonatomic, unsafe_unretained, readonly) id<UIControllerDelegate> delegate;
- (id) initWithDelegate:(id<UIControllerDelegate>)delegate;
- (void)openPanel;
- (void)closePanel;
- (void)deleteSelectedObjects;
- (NSColor*)colorForIndex:(NSInteger)index;
- (IBAction)buttonDelete:(id)sender;
#end
Any help is much appreciated!
Related
This is in a ModalViewController using StoryBoard and ARC
When I dismiss the view I see this line...
NSLog(#"%d %#",[imgArray count],fileName); ... being printed over and over again.
How do I kill the functions transitionWithView/animateWithDuration when the view is dismissed?
Missing something quite crucial!
.h
#import <UIKit/UIKit.h>
#interface InfoVC : UIViewController
#property (weak, nonatomic) IBOutlet UIImageView *imageview;
#property (weak, nonatomic) IBOutlet UIScrollView *scrollview;
#end
.m
#import "InfoVC.h"
#import <QuartzCore/QuartzCore.h>
#interface InfoVC ()
#end
#implementation InfoVC {
int randomInt;
NSArray *imgs;
NSMutableArray *imgArray;
NSTimer *myTimer;
NSTimer *myTimer2;
NSString *currentImg;
}
- (void)viewDidLoad
{
[super viewDidLoad];
imgs = [NSArray arrayWithObjects:
#"01.jpg",
#"02.jpg",
#"03.jpg",
nil];
imgArray = [imgs mutableCopy];
[self initialImage];
}
- (void)initialImage
{
_imageview.contentMode = UIViewContentModeLeft;
int rnd = [imgArray count];
randomInt = (arc4random()%rnd);
currentImg = [imgArray objectAtIndex:randomInt];
UIImage *image = [UIImage imageNamed:currentImg];
[_imageview setImage:image];
_imageview.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
_scrollview.contentSize = image.size;
_scrollview.contentOffset = CGPointMake(-150.0, 0.0);
[imgArray removeObjectAtIndex: randomInt];
[self animate];
}
- (void)randomImage
{
if ([imgArray count]==0) {
imgArray = [imgs mutableCopy];
}
int rnd = [imgArray count];
randomInt = (arc4random()%rnd);
NSString *fileName = [imgArray objectAtIndex:randomInt];
NSLog(#"%d %#",[imgArray count],fileName);
[imgArray removeObjectAtIndex: randomInt];
UIImage * toImage = [UIImage imageNamed:fileName];
void (^completion)(void) = ^{
[self animate];
};
[UIView transitionWithView:self.view
duration:1.75f
options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction
animations:^
{
_imageview.image = toImage;
}
completion:^(BOOL finished)
{
completion();
}
];
}
- (void)animate{
CGPoint offset = _scrollview.contentOffset;
float flt = 150.0;
if (_scrollview.contentOffset.x == flt)
{
offset.x = 0.0 ;
}
else
{
offset.x = flt ;
}
void (^completion2)(void) = ^{
[self randomImage];
};
[UIView animateWithDuration:5.0 delay:0 options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn
animations:^{
_scrollview.contentOffset = offset;
}completion:^(BOOL completed){
completion2();
}
];
}
- (void)viewDidUnload
{
[super viewDidUnload];
[_scrollview.layer removeAllAnimations];
[_imageview.layer removeAllAnimations];
}
- (IBAction)dismissInfo:(id)sender
{
[_scrollview.layer removeAllAnimations];
[_imageview.layer removeAllAnimations];
[self dismissModalViewControllerAnimated:YES];
}
I tried using a gazillion things retro fitting transitionWithView with CABasicAnimation, using NSTimer etc.
In the end I just did:
[_imageview removeFromSuperview];
when dismissing the view and all was great.
But from other posts on here it appears there is a bug hen when applying an alpha transition to a UIScrollview - the reference to the view is lost when it's transparent.
My inspiration for this project is the Droplr and CloudApp mac menubar applications. I have been attempting to implement the code explained here.
However, when I implement the code, the menubar images disappear. Here is my code to create the status item:
- (id)init {
self = [super init];
if (self != nil) {
// Install status item into the menu bar
NSStatusItem *statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:STATUS_ITEM_VIEW_WIDTH];
_statusItemView = [[StatusItemView alloc] initWithStatusItem:statusItem];
_statusItemView.image = [NSImage imageNamed:#"Status"];
_statusItemView.alternateImage = [NSImage imageNamed:#"StatusHighlighted"];
_statusItemView.action = #selector(togglePanel:);
StatusItemView* dragView = [[StatusItemView alloc] initWithFrame:NSMakeRect(0, 0, 24, 24)];
[statusItem setView:dragView];
[dragView release];
}
return self;
}
This is my view file:
#import "StatusItemView.h"
#implementation StatusItemView
#synthesize statusItem = _statusItem;
#synthesize image = _image;
#synthesize alternateImage = _alternateImage;
#synthesize isHighlighted = _isHighlighted;
#synthesize action = _action;
#synthesize target = _target;
#pragma mark -
- (id)initWithStatusItem:(NSStatusItem *)statusItem {
CGFloat itemWidth = [statusItem length];
CGFloat itemHeight = [[NSStatusBar systemStatusBar] thickness];
NSRect itemRect = NSMakeRect(0.0, 0.0, itemWidth, itemHeight);
self = [super initWithFrame:itemRect];
if (self != nil) {
_statusItem = statusItem;
_statusItem.view = self;
}
return self;
}
#pragma mark -
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
//register for drags
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
}
return self;
}
//we want to copy the files
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
return NSDragOperationCopy;
}
perform the drag and log the files that are dropped
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"Files: %#",files);
}
return YES;
}
#pragma mark -
- (void)drawRect:(NSRect)dirtyRect {
[self.statusItem drawStatusBarBackgroundInRect:dirtyRect withHighlight:self.isHighlighted];
NSImage *icon = self.isHighlighted ? self.alternateImage : self.image;
NSSize iconSize = [icon size];
NSRect bounds = self.bounds;
CGFloat iconX = roundf((NSWidth(bounds) - iconSize.width) / 2);
CGFloat iconY = roundf((NSHeight(bounds) - iconSize.height) / 2);
NSPoint iconPoint = NSMakePoint(iconX, iconY);
[icon compositeToPoint:iconPoint operation:NSCompositeSourceOver];
}
#pragma mark -
#pragma mark Mouse tracking
- (void)mouseDown:(NSEvent *)theEvent {
[NSApp sendAction:self.action to:self.target from:self];
}
#pragma mark -
#pragma mark Accessors
- (void)setHighlighted:(BOOL)newFlag {
if (_isHighlighted == newFlag) return;
_isHighlighted = newFlag;
[self setNeedsDisplay:YES];
}
#pragma mark -
- (void)setImage:(NSImage *)newImage {
if (_image != newImage) {
_image = newImage;
[self setNeedsDisplay:YES];
}
}
- (void)setAlternateImage:(NSImage *)newImage {
if (_alternateImage != newImage) {
_alternateImage = newImage;
if (self.isHighlighted) {
[self setNeedsDisplay:YES];
}
}
}
#pragma mark -
- (NSRect)globalRect {
NSRect frame = [self frame];
frame.origin = [self.window convertBaseToScreen:frame.origin];
return frame;
}
#end
Thanks everyone!
I know this is an old question but perhaps this might help:
Try setting the NSStatusItem *statusItem as a #propery of the class. If you have ARC, the menubar might be getting destroyed right after the init function finishes.
I'm working on a soundboard app, that has several pages of buttons to play sound effects with a stop button on every page should the user wish to manually interrupt the clip. I'm using avaudioplayer in each view to play the sound upon pressing the button for that clip. It works fine until the view is changed. If a user jumps to a new page the sound keeps playing and the stop button stops working even if they return to the original view. Pressing a sound button no longer interrupts the running sound resulting in two sounds over each other.
From googling and searching this site, I know the issue is that each view change creates a new instance of the player and the remedy is to create a singleton class. Unfortunately I have yet to find any further examples of how to actually do this. If someone could provide or point the way to a beginners guide for creating an avaudioplayer singleton I would really appreciate it. All I need to be able to do is pass the file name to the shared player and start play with a sound clip button and have the stop button stop sounds no matter what view the user is on. I am using the ios 5.1 sdk with storyboards and ARC enabled.
My solution, as used in one of my own projects, is posted beneath. Feel free to copy-and-paste, I intend to open-source this project once it's finished :)
A preview of the player can be seen on YouTube: http://www.youtube.com/watch?v=Q98DQ6iNTYM
AudioPlayer.h
#protocol AudioPlayerDelegate;
#interface AudioPlayer : NSObject
#property (nonatomic, assign, readonly) BOOL isPlaying;
#property (nonatomic, assign) id <AudioPlayerDelegate> delegate;
+ (AudioPlayer *)sharedAudioPlayer;
- (void)playAudioAtURL:(NSURL *)URL;
- (void)play;
- (void)pause;
#end
#protocol AudioPlayerDelegate <NSObject>
#optional
- (void)audioPlayerDidStartPlaying;
- (void)audioPlayerDidStartBuffering;
- (void)audioPlayerDidPause;
- (void)audioPlayerDidFinishPlaying;
#end
AudioPlayer.m
// import AVPlayer.h & AVPlayerItem.h
#interface AudioPlayer ()
- (void)playerItemDidFinishPlaying:(id)sender;
#end
#implementation AudioPlayer
{
AVPlayer *player;
}
#synthesize isPlaying, delegate;
+ (AudioPlayer *)sharedAudioPlayer
{
static dispatch_once_t pred;
static AudioPlayer *sharedAudioPlayer = nil;
dispatch_once(&pred, ^
{
sharedAudioPlayer = [[self alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:sharedAudioPlayer selector:#selector(playerItemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
});
return sharedAudioPlayer;
}
- (void)playAudioAtURL:(NSURL *)URL
{
if (player)
{
[player removeObserver:self forKeyPath:#"status"];
[player pause];
}
player = [AVPlayer playerWithURL:URL];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
if (delegate && [delegate respondsToSelector:#selector(audioPlayerDidStartBuffering)])
[delegate audioPlayerDidStartBuffering];
}
- (void)play
{
if (player)
{
[player play];
if (delegate && [delegate respondsToSelector:#selector(audioPlayerDidStartPlaying)])
[delegate audioPlayerDidStartPlaying];
}
}
- (void)pause
{
if (player)
{
[player pause];
if (delegate && [delegate respondsToSelector:#selector(audioPlayerDidPause)])
[delegate audioPlayerDidPause];
}
}
- (BOOL)isPlaying
{
DLog(#"%f", player.rate);
return (player.rate > 0);
}
#pragma mark - AV player
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == player && [keyPath isEqualToString:#"status"])
{
if (player.status == AVPlayerStatusReadyToPlay)
{
[self play];
}
}
}
#pragma mark - Private methods
- (void)playerItemDidFinishPlaying:(id)sender
{
DLog(#"%#", sender);
if (delegate && [delegate respondsToSelector:#selector(audioPlayerDidFinishPlaying)])
[delegate audioPlayerDidFinishPlaying];
}
#end
AudioPlayerViewController.h
extern NSString *const kAudioPlayerWillShowNotification;
extern NSString *const kAudioPlayerWillHideNotification;
#interface AudioPlayerViewController : UIViewController
#property (nonatomic, assign, readonly) BOOL isPlaying;
#property (nonatomic, assign, readonly) BOOL isPlayerVisible;
- (void)playAudioAtURL:(NSURL *)URL withTitle:(NSString *)title;
- (void)pause;
#end
AudioPlayerViewController.m
NSString *const kAudioPlayerWillShowNotification = #"kAudioPlayerWillShowNotification";
NSString *const kAudioPlayerWillHideNotification = #"kAudioPlayerWillHideNotification";
#interface AudioPlayerViewController () <AudioPlayerDelegate>
#property (nonatomic, strong) AudioPlayerView *playerView;
- (void)playButtonTouched:(id)sender;
- (void)closeButtonTouched:(id)sender;
- (void)hidePlayer;
#end
#implementation AudioPlayerViewController
#synthesize playerView, isPlaying, isPlayerVisible;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
playerView = [[AudioPlayerView alloc] initWithFrame:CGRectZero];
[AudioPlayer sharedAudioPlayer].delegate = self;
}
return self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
self.view = playerView;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
[playerView.playButton addTarget:self action:#selector(playButtonTouched:) forControlEvents:UIControlEventTouchUpInside];
[playerView.closeButton addTarget:self action:#selector(closeButtonTouched:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Private methods
- (AudioPlayerView *)playerView
{
return (AudioPlayerView *)self.view;
}
- (void)hidePlayer
{
[[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillHideNotification object:nil];
[self.playerView hidePlayer];
}
- (void)playButtonTouched:(id)sender
{
DLog(#"play / pause");
if ([AudioPlayer sharedAudioPlayer].isPlaying)
{
[[AudioPlayer sharedAudioPlayer] pause];
}
else
{
[[AudioPlayer sharedAudioPlayer] play];
}
[self.playerView showPlayer];
}
- (void)closeButtonTouched:(id)sender
{
DLog(#"close");
if ([AudioPlayer sharedAudioPlayer].isPlaying)
[[AudioPlayer sharedAudioPlayer] pause];
[self hidePlayer];
}
#pragma mark - Instance methods
- (void)playAudioAtURL:(NSURL *)URL withTitle:(NSString *)title
{
playerView.titleLabel.text = title;
[[AudioPlayer sharedAudioPlayer] playAudioAtURL:URL];
[[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillShowNotification object:nil];
[playerView showPlayer];
}
- (void)pause
{
[[AudioPlayer sharedAudioPlayer] pause];
[[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillHideNotification object:nil];
[playerView hidePlayer];
}
#pragma mark - Audio player delegate
- (void)audioPlayerDidStartPlaying
{
DLog(#"did start playing");
playerView.playButtonStyle = PlayButtonStylePause;
}
- (void)audioPlayerDidStartBuffering
{
DLog(#"did start buffering");
playerView.playButtonStyle = PlayButtonStyleActivity;
}
- (void)audioPlayerDidPause
{
DLog(#"did pause");
playerView.playButtonStyle = PlayButtonStylePlay;
}
- (void)audioPlayerDidFinishPlaying
{
[self hidePlayer];
}
#pragma mark - Properties
- (BOOL)isPlaying
{
return [AudioPlayer sharedAudioPlayer].isPlaying;
}
- (BOOL)isPlayerVisible
{
return !playerView.isPlayerHidden;
}
#end
AudioPlayerView.h
typedef enum
{
PlayButtonStylePlay = 0,
PlayButtonStylePause,
PlayButtonStyleActivity,
} PlayButtonStyle;
#interface AudioPlayerView : UIView
#property (nonatomic, strong) UIButton *playButton;
#property (nonatomic, strong) UIButton *closeButton;
#property (nonatomic, strong) UILabel *titleLabel;
#property (nonatomic, strong) UIActivityIndicatorView *activityView;
#property (nonatomic, assign) PlayButtonStyle playButtonStyle;
#property (nonatomic, assign, readonly) BOOL isPlayerHidden;
- (void)showPlayer;
- (void)hidePlayer;
#end
AudioPlayerView.m
#implementation AudioPlayerView
{
BOOL _isAnimating;
}
#synthesize playButton, closeButton, titleLabel, playButtonStyle, activityView, isPlayerHidden = _playerHidden;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"musicplayer_background.png"]];
_playerHidden = YES;
activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
activityView.frame = CGRectMake(0.0f, 0.0f, 30.0f, 30.0f);
[self addSubview:activityView];
playButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 30.0f, 30.0f)];
[playButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[playButton setBackgroundImage:[UIImage imageNamed:#"button_pause.png"] forState:UIControlStateNormal];
playButton.titleLabel.textAlignment = UITextAlignmentCenter;
[self addSubview:playButton];
closeButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 30.0f, 30.0f)];
[closeButton setBackgroundImage:[UIImage imageNamed:#"button_close.png"] forState:UIControlStateNormal];
[closeButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
closeButton.titleLabel.textAlignment = UITextAlignmentCenter;
[self addSubview:closeButton];
titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 240.0f, 30.0f)];
titleLabel.text = nil;
titleLabel.textAlignment = UITextAlignmentCenter;
titleLabel.font = [UIFont boldSystemFontOfSize:13.0f];
titleLabel.numberOfLines = 2;
titleLabel.textColor = [UIColor whiteColor];
titleLabel.backgroundColor = [UIColor clearColor];
[self addSubview:titleLabel];
}
return self;
}
- (void)layoutSubviews
{
#define PADDING 5.0f
DLog(#"%#", NSStringFromCGRect(self.bounds));
CGRect frame = self.bounds;
CGFloat y = frame.size.height / 2;
titleLabel.center = CGPointMake(frame.size.width / 2, y);
CGFloat x = titleLabel.frame.origin.x - (playButton.frame.size.width / 2) - PADDING;
playButton.center = CGPointMake(x, y);
activityView.center = CGPointMake(x, y);
x = titleLabel.frame.origin.x + titleLabel.frame.size.width + (closeButton.frame.size.width / 2) + PADDING;
closeButton.center = CGPointMake(x, y);
}
#pragma mark - Instance methods
- (void)showPlayer
{
if (_isAnimating || _playerHidden == NO)
return;
_isAnimating = YES;
[UIView
animateWithDuration:0.5f
animations:^
{
CGRect frame = self.frame;
frame.origin.y -= 40.0f;
self.frame = frame;
}
completion:^ (BOOL finished)
{
_isAnimating = NO;
_playerHidden = NO;
}];
}
- (void)hidePlayer
{
if (_isAnimating || _playerHidden)
return;
_isAnimating = YES;
[UIView
animateWithDuration:0.5f
animations:^
{
CGRect frame = self.frame;
frame.origin.y += 40.0f;
self.frame = frame;
}
completion:^ (BOOL finished)
{
_isAnimating = NO;
_playerHidden = YES;
}];
}
- (void)setPlayButtonStyle:(PlayButtonStyle)style
{
playButton.hidden = (style == PlayButtonStyleActivity);
activityView.hidden = (style != PlayButtonStyleActivity);
switch (style)
{
case PlayButtonStyleActivity:
{
[activityView startAnimating];
}
break;
case PlayButtonStylePause:
{
[activityView stopAnimating];
[playButton setBackgroundImage:[UIImage imageNamed:#"button_pause.png"] forState:UIControlStateNormal];
}
break;
case PlayButtonStylePlay:
default:
{
[activityView stopAnimating];
[playButton setBackgroundImage:[UIImage imageNamed:#"button_play.png"] forState:UIControlStateNormal];
}
break;
}
[self setNeedsLayout];
}
#end
AppDelegate - didFinishLaunching
// setup audio player
audioPlayer = [[AudioPlayerViewController alloc] init]; // public property ...
CGRect frame = self.window.rootViewController.view.frame;
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
CGFloat tabBarHeight = tabBarController.tabBar.frame.size.height;
audioPlayer.view.frame = CGRectMake(0.0f, frame.size.height - tabBarHeight, 320.0f, 40.0f);
[self.window.rootViewController.view insertSubview:audioPlayer.view belowSubview:tabBarController.tabBar];
From any view controller inside the app I start audio with the following code:
- (void)playAudioWithURL:(NSURL *)URL title:(NSString *)title
{
OnsNieuwsAppDelegate *appDelegate = (OnsNieuwsAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.audioPlayer playAudioAtURL:URL withTitle:title];
}
Assets
For the above example, the following assets can be used (button images are white, so hard to see against background):
Buttons:
Background:
There's a lot of discussion (and links to blogs, etc.) about singletons over at What should my Objective-C singleton look like?, and I see a fair number of tutorials as a result of this Google search: http://www.google.com/search?q=+cocoa+touch+singleton+tutorial, but the real answer to your question, I believe, is that you should do one of two things:
If you do want the sound for a particular view to continue playing when the user switches, create the player as you're doing now, but when the view (re)appears, check that a player exists, and don't make a new one.
If you want the sound to stop, then stop the sound when the view changes (i.e., in viewWillDisappear:).
I have a custom view that I'm using in a xib file. I load the view and add it to a window. It adds the view just fine as I can see the default text of the labels in the view, but when I try to change the label with a method call, it doesn't change the text.
The custom view isn't anything to fancy, just draws a rounded, transparent background.
NotificationView.h
#import <Cocoa/Cocoa.h>
#interface NotificationView : NSView
#property (weak) IBOutlet NSTextField *primaryLabel;
#property (weak) IBOutlet NSTextField *secondaryLabel;
#property (weak) IBOutlet NSTextField *identifierLabel;
#end
NotificationView.m
#implementation NotificationView
#synthesize primaryLabel;
#synthesize secondaryLabel;
#synthesize identifierLabel;
- (id) initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (self)
{
return self;
}
return nil;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSColor *bgColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.6];
NSRect rect = NSMakeRect([self bounds].origin.x + 3, [self bounds].origin.y + 3, [self bounds].size.width - 6, [self bounds].size.height - 6);
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:5.0 yRadius:5.0];
[path addClip];
NSShadow *shadow = [[NSShadow alloc] init];
[shadow setShadowColor:[NSColor redColor]];
[shadow setShadowBlurRadius:2.0f];
[shadow setShadowOffset:NSMakeSize(0.f, -1.f)];
[shadow set];
[bgColor set];
NSRectFill(rect);
[super drawRect:dirtyRect];
}
#end
In the xib I have a custom view set to the type NotificationView. I've added 3 labels to the view and connected them to the above IBOutlets. (I ctrl-click & drag from the label to the .h file to make the connection.)
I'm loading the view and adding it to a window with the following method. It looks through an array of windows, if an existing match is found it used that window, if not it creates a new window.
- (void) popupNotificationWithTag:(NSString *)tag fade:(double)msFade lineOne:(NSString *)lineOneText lineTwo:(NSString *)lineTwoText
{
NotificationWindow *notificationWindow;
NotificationWindow *tmpWindow;
NSEnumerator *enumerator;
// Walk the notification windows in the array
enumerator = [self.notificationWindows objectEnumerator];
if(enumerator)
{
while((tmpWindow = [enumerator nextObject]))
{
if([tmpWindow.tag isEqualToString:tag])
{
notificationWindow = tmpWindow;
}
}
}
// Make a new notification window
if (!notificationWindow)
{
int width = [[NSScreen mainScreen] frame].size.width;
int height = [[NSScreen mainScreen] frame].size.height;
notificationWindow = [[NotificationWindow alloc] initWithRect:NSMakeRect(width - 420, height - 130, 400, 100)];
NSNib *nib = [[NSNib alloc] initWithNibNamed:#"Notification" bundle: nil];
NSArray *objects;
[nib instantiateNibWithOwner:self topLevelObjects:&objects];
for (id obj in objects) {
if ([[obj class] isSubclassOfClass:[NSView class]])
[notificationWindow setContentView:obj];
}
[notificationWindow setTag:tag];
[self.notificationWindows addObject:notificationWindow];
}
// Display window
[notificationWindow makeKeyAndOrderFront:nil];
[notificationWindow display];
notificationWindow.fadeOut = msFade;
[notificationWindow setPrimaryText:lineOneText];
[notificationWindow setSecondaryText:lineTwoText];
[notificationWindow setIdentifierText:tag];
}
The window class is NotificationWindow.h
#import <Foundation/Foundation.h>
#interface NotificationWindow : NSWindow
#property (nonatomic, strong) NSString *tag;
#property (nonatomic) double fadeOut;
- (id)initWithRect:(NSRect)contentRect;
- (void) setPrimaryText:(NSString *)text;
- (void) setSecondaryText:(NSString *)text;
- (void) setIdentifierText:(NSString *)text;
#end
NotificationWindow.m
#import "NotificationWindow.h"
#import "NotificationView.h"
//===========================================================================================================================
// Private call properties and methods
//===========================================================================================================================
#interface NotificationWindow()
#property (nonatomic,strong) NSTimer *timerFade;
- (void) timerFadeFired;
#end
//===========================================================================================================================
//===========================================================================================================================
#implementation NotificationWindow
//===========================================================================================================================
// Property Getters and Setters
//===========================================================================================================================
#synthesize tag = _tag;
#synthesize fadeOut = _fadeOut;
#synthesize timerFade = _timerFade;
//===========================================================================================================================
// Public methods
//===========================================================================================================================
- (id)initWithRect:(NSRect)contentRect
{
if (self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]) {
[self setLevel: NSScreenSaverWindowLevel];
[self setBackgroundColor: [NSColor clearColor]];
[self setAlphaValue: 1.0];
[self setOpaque: NO];
[self setHasShadow: NO];
[self setIgnoresMouseEvents: YES];
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
[self orderFront: NSApp];
self.fadeOut = -1;
// Start our timer to deal with fadeing the window
self.timerFade = [NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:#selector(timerFadeFired)
userInfo:nil
repeats:YES];
return self;
}
return nil;
}
- (BOOL) canBecomeKeyWindow
{
return YES;
}
- (void) display
{
[super display];
[self setAlphaValue:1.0];
}
- (void) setPrimaryText:(NSString *)text
{
NotificationView *view = self.contentView;
view.primaryLabel.stringValue = text;
}
- (void) setSecondaryText:(NSString *)text
{
NotificationView *view = self.contentView;
view.secondaryLabel.stringValue = text;
}
- (void) setIdentifierText:(NSString *)text
{
NotificationView *view = self.contentView;
view.identifierLabel.stringValue = text;
}
//===========================================================================================================================
// Private methods
//===========================================================================================================================
- (void) timerFadeFired
{
[self orderFront:NSApp];
if (self.fadeOut > 0)
{
self.fadeOut--;
}
else if (self.fadeOut == 0)
{
if (self.alphaValue > 0)
self.alphaValue -= 0.002;
else
self.fadeOut = -1;
}
}
#end
So I assume I'm doing something wrong connecting the labels to the IBOutlets, but I can't figure out what. I suppose I could create the view in code, but I was trying to be good and use the interface builder.
I'm in XCode 4.2.1.
I'm trying to re-write an application I have for Windows in Objective-C for my Mac, and I want to be able to do something like Mac's hot corners. If I move my mouse to the left side of the screen it will make a window visible, if I move it outside of the window location the window will hide again. (window would be pushed up to the left side of screen).
Does anyone know where I can find some demo code (or reference) on how to do this, or at least how to tell where the mouse is at, even if the current application is not on top. (not sure how to word this, too used to Windows world).
Thank you
-Brad
You're going to want to implement an invisible window on the edge of the screen with the window order set so it's always on top. Then, you can listen for mouse-moved events in this window.
To set the window to be invisible and on top, make a window subclass use calls like:
[self setBackgroundColor:[NSColor clearColor]];
[self setExcludedFromWindowsMenu:YES];
[self setCanHide:NO];
[self setLevel:NSScreenSaverWindowLevel];
[self setAlphaValue:0.0f];
[self setOpaque:NO];
[self orderFrontRegardless];
then, to turn on mouse moved events,
[self setAcceptsMouseMovedEvents:YES];
will cause the window to get calls to:
- (void)mouseMoved:(NSEvent *)theEvent
{
NSLog(#"mouse moved into invisible window.");
}
So hopefully that's enough to give you a start.
-Ken
Here's AutoHidingWindow - a subclass of SlidingWindow which pops up when the mouse hits the edge of the screen. Feedback welcome.
#interface ActivationWindow : NSWindow
{
AutoHidingWindow *_activationDelegate;
NSTrackingArea *_trackingArea;
}
- (ActivationWindow*)initWithDelegate:(AutoHidingWindow*)activationDelegate;
#property (assign) AutoHidingWindow *activationDelegate;
#property (retain) NSTrackingArea *trackingArea;
- (void)adjustWindowFrame;
- (void)adjustTrackingArea;
#end
#interface AutoHidingWindow ()
- (void)autoShow;
- (void)autoHide;
#end
#implementation AutoHidingWindow
- (id)initWithContentRect:(NSRect) contentRect
styleMask:(unsigned int) styleMask
backing:(NSBackingStoreType) backingType
defer:(BOOL) flag
{
if ((self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:backingType
defer:flag])) {
_activationWindow = [[ActivationWindow alloc] initWithDelegate:self];
}
return self;
}
#synthesize activationWindow = _activationWindow;
- (void)dealloc
{
[_activationWindow release], _activationWindow = nil;
[super dealloc];
}
- (void)makeKeyAndOrderFront:(id)sender
{
[super makeKeyAndOrderFront:sender];
}
- (void)autoShow
{
[self makeKeyAndOrderFront:self];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(autoHide) object:nil];
[self performSelector:#selector(autoHide) withObject:nil afterDelay:2];
}
- (void)autoHide
{
NSPoint mouseLocation = [NSEvent mouseLocation];
NSRect windowFrame = [self frame];
if (NSPointInRect(mouseLocation, windowFrame)) {
[self performSelector:#selector(autoHide) withObject:nil afterDelay:2];
}
else {
[self orderOut:self];
}
}
#end
#implementation ActivationWindow
- (ActivationWindow*)initWithDelegate:(AutoHidingWindow*)activationDelegate
{
if ((self = [super initWithContentRect:[[NSScreen mainScreen] frame]
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]) != nil) {
_activationDelegate = activationDelegate;
[self setBackgroundColor:[NSColor clearColor]];
[self setExcludedFromWindowsMenu:YES];
[self setCanHide:NO];
[self setHasShadow:NO];
[self setLevel:NSScreenSaverWindowLevel];
[self setAlphaValue:0.0];
[self setIgnoresMouseEvents:YES];
[self setOpaque:NO];
[self orderFrontRegardless];
[self adjustWindowFrame];
[self.activationDelegate addObserver:self
forKeyPath:#"slidingEdge"
options:0
context:#"slidingEdge"];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(screenParametersChanged:)
name:NSApplicationDidChangeScreenParametersNotification
object:nil];
}
return self;
}
#synthesize activationDelegate = _activationDelegate;
#synthesize trackingArea = _trackingArea;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([#"slidingEdge" isEqual:context]) {
[self adjustTrackingArea];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self.activationDelegate removeObserver:self forKeyPath:#"slidingEdge"];
_activationDelegate = nil;
[_trackingArea release], _trackingArea = nil;
[super dealloc];
}
- (void)screenParametersChanged:(NSNotification *)notification
{
[self adjustWindowFrame];
}
- (void)adjustWindowFrame
{
NSScreen *mainScreen = [NSScreen mainScreen];
CGFloat menuBarHeight = [NSMenuView menuBarHeight];
NSRect windowFrame = [mainScreen frame];
windowFrame.size.height -= menuBarHeight;
[self setFrame:windowFrame display:NO];
[self adjustTrackingArea];
}
- (void)adjustTrackingArea
{
NSView *contentView = [self contentView];
NSRect trackingRect = contentView.bounds;
CGRectEdge slidingEdge = self.activationDelegate.slidingEdge;
CGFloat trackingRectSize = 2.0;
switch (slidingEdge) {
case CGRectMaxXEdge:
trackingRect.origin.x = trackingRect.origin.x + trackingRect.size.width - trackingRectSize;
trackingRect.size.width = trackingRectSize;
break;
case CGRectMaxYEdge:
trackingRect.origin.y = trackingRect.origin.y + trackingRect.size.height - trackingRectSize;
trackingRect.size.height = trackingRectSize;
break;
case CGRectMinXEdge:
trackingRect.origin.x = 0;
trackingRect.size.width = trackingRectSize;
break;
case CGRectMinYEdge:
default:
trackingRect.origin.y = 0;
trackingRect.size.height = trackingRectSize;
}
NSTrackingAreaOptions options =
NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
NSTrackingActiveAlways |
NSTrackingEnabledDuringMouseDrag;
NSTrackingArea *trackingArea = self.trackingArea;
if (trackingArea != nil) {
[contentView removeTrackingArea:trackingArea];
}
trackingArea = [[NSTrackingArea alloc] initWithRect:trackingRect
options:options
owner:self
userInfo:nil];
[contentView addTrackingArea:trackingArea];
self.trackingArea = [trackingArea autorelease];
}
- (void)mouseEntered:(NSEvent *)theEvent
{
[self.activationDelegate autoShow];
}
- (void)mouseMoved:(NSEvent *)theEvent
{
[self.activationDelegate autoShow];
}
- (void)mouseExited:(NSEvent *)theEvent
{
}
#end
you may look how we did it in the Visor project:
http://github.com/binaryage/visor/blob/master/src/Visor.m#L1025-1063
Here's what I came up with. Thanks to Peter for the above tips.
#interface SlidingWindow : NSWindow
{
CGRectEdge _slidingEdge;
NSView *_wrapperView;
}
#property (nonatomic, assign) CGRectEdge slidingEdge;
#property (nonatomic, retain) NSView *wrapperView;
-(id)initWithContentRect:(NSRect) contentRect
styleMask:(unsigned int) styleMask
backing:(NSBackingStoreType) backingType
defer:(BOOL) flag;
- (NSView*)wrapperViewWithFrame:(NSRect)bounds;
- (BOOL)mayOrderOut;
#end
#interface SlidingWindow ()
- (void)adjustWrapperView;
- (void)setWindowWidth:(NSNumber*)width;
- (void)setWindowHeight:(NSNumber*)height;
#end
#implementation SlidingWindow
#synthesize slidingEdge = _slidingEdge;
#synthesize wrapperView = _wrapperView;
- (id)initWithContentRect:(NSRect) contentRect
styleMask:(unsigned int) styleMask
backing:(NSBackingStoreType) backingType
defer:(BOOL) flag
{
if ((self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:backingType
defer:flag])) {
/* May want to setup some other options,
like transparent background or something */
[self setSlidingEdge:CGRectMaxYEdge];
[self setHidesOnDeactivate:YES];
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
}
return self;
}
- (NSView*)wrapperViewWithFrame:(NSRect)bounds
{
return [[[NSView alloc] initWithFrame:bounds] autorelease];
}
- (void)adjustWrapperView
{
if (self.wrapperView == nil) {
NSRect frame = [self frame];
NSRect bounds = NSMakeRect(0, 0, frame.size.width, frame.size.height);
NSView *wrapperView = [self wrapperViewWithFrame:bounds];
NSArray *subviews = [[[[self contentView] subviews] copy] autorelease];
for (NSView *view in subviews) {
[wrapperView addSubview:view];
}
[wrapperView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[[self contentView] addSubview:wrapperView];
self.wrapperView = wrapperView;
}
switch (self.slidingEdge) {
case CGRectMaxXEdge:
[self.wrapperView setAutoresizingMask:(NSViewHeightSizable | NSViewMaxXMargin)];
break;
case CGRectMaxYEdge:
[self.wrapperView setAutoresizingMask:(NSViewWidthSizable | NSViewMaxYMargin)];
break;
case CGRectMinXEdge:
[self.wrapperView setAutoresizingMask:(NSViewHeightSizable | NSViewMinXMargin)];
break;
case CGRectMinYEdge:
default:
[self.wrapperView setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
}
}
- (void)makeKeyAndOrderFront:(id)sender
{
[self adjustWrapperView];
if ([self isVisible]) {
[super makeKeyAndOrderFront:sender];
}
else {
NSRect screenRect = [[NSScreen menubarScreen] visibleFrame];
NSRect windowRect = [self frame];
CGFloat x;
CGFloat y;
NSRect startWindowRect;
NSRect endWindowRect;
switch (self.slidingEdge) {
case CGRectMinXEdge:
x = 0;
y = (screenRect.size.height - windowRect.size.height) / 2 + screenRect.origin.y;
startWindowRect = NSMakeRect(x - windowRect.size.width, y, 0, windowRect.size.height);
break;
case CGRectMinYEdge:
x = (screenRect.size.width - windowRect.size.width) / 2 + screenRect.origin.x;
y = 0;
startWindowRect = NSMakeRect(x, y - windowRect.size.height, windowRect.size.width, 0);
break;
case CGRectMaxXEdge:
x = screenRect.size.width - windowRect.size.width + screenRect.origin.x;
y = (screenRect.size.height - windowRect.size.height) / 2 + screenRect.origin.y;
startWindowRect = NSMakeRect(x + windowRect.size.width, y, 0, windowRect.size.height);
break;
case CGRectMaxYEdge:
default:
x = (screenRect.size.width - windowRect.size.width) / 2 + screenRect.origin.x;
y = screenRect.size.height - windowRect.size.height + screenRect.origin.y;
startWindowRect = NSMakeRect(x, y + windowRect.size.height, windowRect.size.width, 0);
}
endWindowRect = NSMakeRect(x, y, windowRect.size.width, windowRect.size.height);
[self setFrame:startWindowRect display:NO animate:NO];
[super makeKeyAndOrderFront:sender];
[self setFrame:endWindowRect display:YES animate:YES];
[self performSelector:#selector(makeResizable)
withObject:nil
afterDelay:1];
}
}
- (void)makeResizable
{
NSView *wrapperView = self.wrapperView;
NSRect frame = [self frame];
NSRect bounds = NSMakeRect(0, 0, frame.size.width, frame.size.height);
[wrapperView setFrame:bounds];
[wrapperView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
}
- (void)orderOut:(id)sender
{
[self adjustWrapperView];
NSRect startWindowRect = [self frame];
NSRect endWindowRect;
switch (self.slidingEdge) {
case CGRectMinXEdge:
endWindowRect = NSMakeRect(startWindowRect.origin.x,
startWindowRect.origin.y,
0,
startWindowRect.size.height);
break;
case CGRectMinYEdge:
endWindowRect = NSMakeRect(startWindowRect.origin.x,
startWindowRect.origin.y,
startWindowRect.size.width,
0);
break;
case CGRectMaxXEdge:
endWindowRect = NSMakeRect(startWindowRect.origin.x + startWindowRect.size.width,
startWindowRect.origin.y,
0,
startWindowRect.size.height);
break;
case CGRectMaxYEdge:
default:
endWindowRect = NSMakeRect(startWindowRect.origin.x,
startWindowRect.origin.y + startWindowRect.size.height,
startWindowRect.size.width,
0);
}
[self setFrame:endWindowRect display:YES animate:YES];
switch (self.slidingEdge) {
case CGRectMaxXEdge:
case CGRectMinXEdge:
if (startWindowRect.size.width > 0) {
[self performSelector:#selector(setWindowWidth:)
withObject:[NSNumber numberWithDouble:startWindowRect.size.width]
afterDelay:0];
}
break;
case CGRectMaxYEdge:
case CGRectMinYEdge:
default:
if (startWindowRect.size.height > 0) {
[self performSelector:#selector(setWindowHeight:)
withObject:[NSNumber numberWithDouble:startWindowRect.size.height]
afterDelay:0];
}
}
[super orderOut:sender];
}
- (void)setWindowWidth:(NSNumber*)width
{
NSRect startWindowRect = [self frame];
NSRect endWindowRect = NSMakeRect(startWindowRect.origin.x,
startWindowRect.origin.y,
[width doubleValue],
startWindowRect.size.height);
[self setFrame:endWindowRect display:NO animate:NO];
}
- (void)setWindowHeight:(NSNumber*)height
{
NSRect startWindowRect = [self frame];
NSRect endWindowRect = NSMakeRect(startWindowRect.origin.x,
startWindowRect.origin.y,
startWindowRect.size.width,
[height doubleValue]);
[self setFrame:endWindowRect display:NO animate:NO];
}
- (void)resignKeyWindow
{
[self orderOut:self];
[super resignKeyWindow];
}
- (BOOL)canBecomeKeyWindow
{
return YES;
}
- (void)performClose:(id)sender
{
[self close];
}
- (void)dealloc
{
[_wrapperView release], _wrapperView = nil;
[super dealloc];
}
#end
#implementation NSScreen (MenubarScreen)
+ (NSScreen*)menubarScreen
{
NSArray *screens = [self screens];
if ([screens count] > 0) {
return [screens objectAtIndex:0];
}
return nil;
}
#end