How to handle double monitors in ObjectiveC? - objective-c

I'm working with a Cordova Mac application and I can see weird behaviours when I have more than one monitor plugged in.
I'm using a Point to center the view which is on the Icon on the Menubar. However, when I work with two monitors, you have to click several times to actually get the right position.
This is the code to show the window:
- (void) showPanel
{
NSRect statusFrame = [[self.statusItem valueForKey:#"window"] frame];
NSRect winFrame = [self.window frame];
NSRect screenFrame = [[self.window screen] frame];
NSPoint p = NSMakePoint(statusFrame.origin.x, screenFrame.size.height - 32);
if (p.x > (screenFrame.size.width - winFrame.size.width)) {
p.x = screenFrame.size.width - winFrame.size.width - 30;
}
[self.window setFrameTopLeftPoint:p];
[self.window setIsVisible:YES];
[NSApp activateIgnoringOtherApps:YES];
[self.window makeKeyAndOrderFront:self];
}
I'm a total newbie when it comes to ObjectiveC so I'm not sure is there any considerations I should have in mind for this kind of scenarios.

Related

How do I do a window effect when closing the window in Objective C?

I have this neat bloom effect I've created with the window frame so that the window blooms open when the application launches. I just messed with frame origin and size, is all. Now I want to do the inverse when someone clicks the red X button on the window. Someone mentioned to subclass a window and then catch an event, but I don't know how to do that yet in Objective C. Also, I need it to not hide the window yet until this anti-bloom (wither?) effect has completed.
QUESTION: How do I subclass the window and fire this witherWindow function before the window stops being visible when they click the red X button on the window's titlebar?
Here's the effect. Note that my window is a fixed size of 1000w x 680h (but had to make it 700h because of titlebar). That's why I used static numbers instead of getting the current window width/height and storing it in a property for later use, and why I didn't do division math on those numbers when playing with the origin values.
- (void)bloomWindow:(NSWindow*)window
{
NSRect frame = [window frame];
frame.origin.y -= 350.0f;
frame.origin.x -= 500.0f;
frame.size.height = 700.0f;
frame.size.width = 1000.0f;
[window setFrame:frame display:YES animate:YES];
}
- (void)witherWindow:(NSWindow*)window
{
NSRect frame = [window frame];
frame.origin.y += 350.0f;
frame.origin.x += 500.0f;
frame.size.height = 0.0f;
frame.size.width = 0.0f;
[window setFrame:frame display:YES animate:YES];
}
It's much easier than subclassing the window.
In a default Cocoa application with the AppDelegate.m class, you'll have this event:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// add stuff here
}
Now, slightly following this step, we replace // add stuff here with this:
NSButton *closeButton = [self.window standardWindowButton:NSWindowCloseButton];
[closeButton setTarget:self.window.delegate];
[closeButton setAction:#selector(applicationWillClose)];
Then, underneath applicationDidFinishLaunching, add this:
- (void)applicationWillClose;
{
[self witherWindow:self.window];
[self.window close];
}
And ensure you have that witherWindow class method as posted in the question.

Draggable NSView returns to origin on mouseUp or when movement stops

I'm new to Cocoa development on the Mac, and I'm having issues creating a draggable NSView subclass, despite having followed many different guides, including the main Apple docs on custom views.
The code I've implemented so far is:
-(void)mouseDown:(NSEvent *) e {
[[NSCursor closedHandCursor] push];
self.lastDragLocation = [e locationInWindow];
}
-(void)mouseDragged:(NSEvent *)event {
NSView* superView = [self superview];
NSPoint currentOrigin = self.lastDragLocation;
NSRect currentFrame = [self frame];
NSPoint nextOrigin = NSMakePoint(currentOrigin.x + [event deltaX],
currentOrigin.y + ([event deltaY] *
([superView isFlipped] ? 1 : -1))
);
[self setFrameOrigin:nextOrigin];
[self autoscroll:event];
[superView setNeedsDisplayInRect:NSUnionRect(currentFrame, [self frame])];
self.lastDragLocation = nextOrigin;
}
-(void)mouseUp:(NSEvent*)event {
[NSCursor pop];
[[self window] invalidateCursorRectsForView:self];
}
Everything seems to work when I drag the view, but when I stop moving or release the mouse, the view snaps back to the original origin. When this happens during a drag, subsequent drag events set the view back to the right place, only to repeat the issue.
Incidentally, this is XCode 5.0 running on Mountain Lion.

Change cursor for full screen NSWindow

I am trying to make an overlay window that will allow drawing at the ShieldingWindowLevel, however when the window appears the cursor is still the default pointer. I would like to change it to the crosshairs. Having controller NSCursors before I am baffled why resetCursorRects is not ever called.
I manually create the window as follows (in my AppController class):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Create the window
NSRect frame = [[NSScreen mainScreen] frame];
// Provide a small area on the right to move the cursor in-and-out of the window.
frame.size.width = frame.size.width - 20;
self.window = [[NSWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[self.window setAcceptsMouseMovedEvents:YES];
[self.window setOpaque:NO];
[self.window setLevel:CGShieldingWindowLevel()];
[self.window setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:0.2]];
// Create the subview
ScreenOverlayView *subview = [[ScreenOverlayView alloc] initWithFrame:NSZeroRect];
[[self.window contentView] addSubview:subview];
// Add subview and show window
[self.window setContentView:subview];
[self.window makeFirstResponder:subview];
[self.window orderFrontRegardless];
}
With the following NSView subclass:
#implementation ScreenOverlayView
- (void) resetCursorRects {
[super resetCursorRects];
[self addCursorRect: [self bounds]
cursor: [NSCursor crosshairCursor]];
}
// ...
#end
I created a sample project to show this case and posted it to github, the most interesting files are ScreenOverlayView.m and AppDelegate.m.
I should point out that I have also spent a good deal of time trying to get this working with an NSTrackingArea, as you can see in the sample project. Tracking Area works if the mouse enters the view after it has appeared, but not if it is inside to start with. Using MouseEnter and MouseLeave would be fine if I had some way to set the initial cursor, but it will only change for a split second before changing back.
How can I get resetCursorRects to be invoked -OR- how can I set the cursor when I move it to the superview?
The key is that you really need to create a custom subclass of NSWindow, in order to counteract some of the default behavior that borderless windows (NSBorderlessWindowMask) have.
An updated version of your sample project is at http://www.markdouma.com/developer/full-screen-overlay.zip.
In it, I created a custom MDScreenOverlayWindow class that overrides NSWindow's canBecomeKeyWindow method like below:
// Windows created with NSBorderlessWindowMask normally can't be key,
but we want ours to be
- (BOOL)canBecomeKeyWindow {
return YES;
}
This will allow your view to become key and basically all your other stuff to work properly.
The other thing that may be of note is the drawRect: method. (It looks like you may be coming from iOS). You might want to look into NSBezierPath, as it could potentially simplify some of your drawing code. For example, I believe the drawing code you had could be consolidated into the following:
- (void)drawRect:(NSRect)rect {
// the color should probably be "pre-multiplied" by the alpha
// premultiplied version:
[[NSColor colorWithCalibratedRed:0.8 green:0.0 blue:0.0 alpha:0.8] set];
[NSBezierPath setDefaultLineWidth:2.0];
[NSBezierPath strokeLineFromPoint:currentLocation toPoint:downLocation];
}

Animating sibling NSViews

Background & Goal
I have a window and three sepearate views in a nib file in IB. The three views are used for the three 'parts' of the program: the first were the user drops a file, the second where it chooses some options and the third where it is presented with a progress bar as the program accomplishes a task.
I want to animate between those views as they change, and I'm doing a fade-in/out animation. The goal is to have the old view (the one that is going to disappear) fade out as the new view fades in. The views have different sizes, so I animate the window frame as well at the same time (those views fill up the window). All three views have Core Animation enabled in IB, but nothing else (the window's content view for example) has.
Code
At the start of the program, I register the views' sizes so I can use them later, add the views to the window and set the 2nd and 3rd view hidden.
- (void)awakeFromNib
{
// Register native views' size (when they get displayed again the window can be set to their bounds)
view1NativeSize = [firstView bounds].size;
view2NativeSize = [secondView bounds].size;
view3NativeSize = [thirdView bounds].size;
// Add views to the main window's content view
NSRect viewFrame = [[mainWindow contentView] bounds];
[firstView setFrame: viewFrame];
[secondView setFrame: viewFrame];
[thirdView setFrame: viewFrame];
[[mainWindow contentView] addSubview: firstView];
[[mainWindow contentView] addSubview: secondView];
[[mainWindow contentView] addSubview: thirdView];
// Set their attributes
[secondView setHidden: TRUE];
[thirdView setHidden: TRUE];
currView = 1;
}
Then I have three methods for swapping between views, where I calculate the new window frame and call a method for the animation.
- (IBAction)goToFirstView: (id)sender
{
NSRect newFrame = [mainWindow frame];
newFrame.size = view1NativeSize;
newFrame.size.height += [self titleBarHeight]; // Method that returns the title bar height
if (currView == 1) {
return;
}
else if (currView == 2) {
[self animateFromView: secondView toView: firstView andWindowFrame: newFrame];
currView--;
}
else if (currView == 3) {
[self animateFromView: thirdView toView: firstView andWindowFrame: newFrame];
currView -= 2;
}
}
- (IBAction)goToSecondView: (id)sender
{
NSRect newFrame = [mainWindow frame];
newFrame.size = view2NativeSize;
newFrame.size.height += [self titleBarHeight];
if (currView == 2) {
return;
}
else if (currView == 1) {
[self animateFromView: firstView toView: secondView andWindowFrame: newFrame];
currView++;
}
else if (currView == 3) {
[self animateFromView: thirdView toView: secondView andWindowFrame: newFrame];
currView--;
}
}
- (IBAction)goToThirdView: (id)sender
{
NSRect newFrame = [mainWindow frame];
newFrame.size = view3NativeSize;
newFrame.size.height += [self titleBarHeight];
if (currView == 3) {
return;
}
else if (currView == 1) {
[self animateFromView: firstView toView: thirdView andWindowFrame: newFrame];
currView += 2;
}
else if (currView == 2) {
[self animateFromView: secondView toView: thirdView andWindowFrame: newFrame];
currView++;
}
}
The method for the animation is pretty straightforward. I animate the views.
- (void)animateFromView: (NSView*)oldView
toView: (NSView*)newView
andWindowFrame: (NSRect)newWindowFrame
{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration: 0.5];
[[newView animator] setHidden: FALSE];
[[oldView animator] setHidden: TRUE];
//[[mainWindow animator] setFrame: newWindowFrame]; --Doesn't work??
[NSAnimationContext endGrouping];
[mainWindow setFrame: newWindowFrame display: YES animate: YES];
}
Questions
I have two problems with this (most important first):
On the third view there is a progress indicator and it never displays correctly. It doesn't update! I call -startAnimation: but it doesn't move at all. If i turn off CA on that third layer it won't fade in/out correctly but the progress bar starts to work (animate). No clue why...
In my numerous experiments trying to solve the first problem I tried turning on CA for the window's content view and turning off CA for the three subviews. The first problem wasn't solved, but I noticed something: that way the old view was fading out AS the new view faded in, while before that change (subviews with CA) I could only notice the new view fading in. I recorded it and watched it in sloooowww-moootion :) and found I was right: the old view magically disappears and all I see is the new view fading in. It's funny I only found that after watching the animation I really wanted. Am I supposed to turn CA on only for the content view or for all three subviews separately? (or is this problem from something else?)
I tried many things like turning on CA for the progress bar. I found also many questions here that are close to this one but don't specifically address the progress bar problem.
Good luck finding a solution :)
To handle your crossfade you could just use a CATransition as explained in this answer but for a crossfade you wouldn't need to set the transition type as it is the default.
I'm afraid I don't have an answer for the progress bar problem though, it may be something to do with the unorthodox way that you're doing the crossfade.

How to detect if NSDrawer will open off screen

I am working on an app whose main window has an NSDrawer. It is crucial that the drawer will always open on the right edge, and that is how I have coded it to work. What I would like to know is if there is a way to detect if the drawer will open "off the screen"... Is there a way that I can detect this? If so, how? And additionally how can I move the main window to adjust for the width of drawer that will open?
Thanks in advance.
Nick
EDIT:
Here is the solution thanks to Rob's suggestion.
-(IBAction)toggleDrawer:(id)sender
{
NSRect screenFrame = [[[NSScreen screens] objectAtIndex:0] visibleFrame];
NSRect windowFrame = [window frame];
NSRect drawerFrame = [[[drawer contentView] window] frame];
if ([drawer state] == NSDrawerOpenState)
{
[drawer close];
}
else
{
if (windowFrame.size.width +
windowFrame.origin.x +
drawerFrame.size.width > screenFrame.size.width)
{
NSLog(#"Will Open Off Screen");
float offset = (windowFrame.size.width +
windowFrame.origin.x +
drawerFrame.size.width) - screenFrame.size.width;
NSRect newRect = NSMakeRect(windowFrame.origin.x - offset,
windowFrame.origin.y,
windowFrame.size.width,
windowFrame.size.height);
[window setFrame:newRect display:YES animate:YES];
}
[drawer openOnEdge:NSMaxXEdge];
}
}
You could use the methods of NSScreen to calculate whether the frame of the expanded drawer would open off-screen, then use the -setFrame:display:animate: to move the window the requisite distance away from the screen edge before opening the drawer.