How to detect if NSDrawer will open off screen - objective-c

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.

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.

Make NSWindow with transparent titlebar partly unmovable

I have a NSWindow with a splitted screen like in Reminders. Therefore I use this code:
self.window.titlebarAppearsTransparent = true
self.window.styleMask |= NSFullSizeContentViewWindowMask
This works perfectly. But inside the window I have a SplitView (like in the Reminders App) and a NSOutlineView at the right side. The OutlineView goes up to the top of the window corner.
The problem now is: Clicking and dragging at the top of the OutlineView makes the window movable. Any way, I can disable this but still keeping the moving ability at the left side of the app?
Ok, there are two things you need to do:
First you need to set your window to be not movable. To do so, subclass your Window and override isMovable and return no. Or you call setMovable: and set it to no.
After that, you have to manually reenable dragging by adding a view that has the exact size and position of the area you want to draggable. Alternatively you can set up a NSTrackingArea.
Either way, you need to override mouseDown: and insert some code to move the window.
My words in code:
Objective-C
[self.window setMovable:false];
// OR (in NSWindow subclass)
- (BOOL)isMovable {
return false;
}
//Mouse Down
- (void)mouseDown:(NSEvent *)theEvent {
_initialLocation = [theEvent locationInWindow];
NSPoint point;
while (1) {
theEvent = [[self window] nextEventMatchingMask: (NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
point =[theEvent locationInWindow];
NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
NSRect windowFrame = [self.window frame];
NSPoint newOrigin = windowFrame.origin;
// Get the mouse location in window coordinates.
NSPoint currentLocation = point;
// Update the origin with the difference between the new mouse location and the old mouse location.
newOrigin.x += (currentLocation.x - _initialLocation.x);
newOrigin.y += (currentLocation.y - _initialLocation.y);
// Don't let window get dragged up under the menu bar
if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height)) {
newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
}
// Move the window to the new location
[self.window setFrameOrigin:newOrigin];
if ([theEvent type] == NSLeftMouseUp) {
break;
}
}
}
initialLocation is a NSPoint property
Note: I looked up some things here and here

How to handle double monitors in ObjectiveC?

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.

Dragging a NSWindow higher than the menubar

I made an app called Uberlayer. Users can use this single-purpose Mac app to overlay images on top of other applications.
I am using setMovableByWindowBackground to enable dragging the whole window by it's background.
There is a little improvement I would love to make, as the application is now you can't drag the window higher than the menubar, which can be little bit anoying. With normal window I can imagine it's a good thing to have it lock to the Menubar, but for this use case it would be a good thing to be able to drag it higher.
Anyone knows how you can change this default window behavior?
Instead using -setMovableByWindowBackground: override -mouseDown: event like this:
- (void)mouseDown:(NSEvent *)event
{
NSPoint originalMouseLocation = [window convertBaseToScreen:[event locationInWindow]];
NSRect originalFrame = [window frame];
while (YES)
{
NSEvent *newEvent = [window nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
if ([newEvent type] == NSLeftMouseUp)
{
break;
}
NSPoint newMouseLocation = [window convertBaseToScreen:[newEvent locationInWindow]];
NSPoint delta = NSMakePoint(
newMouseLocation.x - originalMouseLocation.x,
newMouseLocation.y - originalMouseLocation.y);
NSRect newFrame = originalFrame;
newFrame.origin.x += delta.x;
newFrame.origin.y += delta.y;
[window setFrame:newFrame display:YES animate:NO];
}
}
** Do not forget to hide window's title bar (select window -> go to Attributes Inspector -> uncheck Title Bar ) because with it window can't go higher than menubar. Or other solution would be to create borderless window.
I am just adding to Justin's Answer. You need to set its level while using Justin's -mouseDown: code.
[window setLevel:NSMainMenuWindowLevel];
If the window's level is below to the NSMainMenuWindowLevel it will not work.
You can set it to NSStatusWindowLevel if you want the window above the status bar.

HowTo show/hide a second vertical-splitted view in Cocoa?

let me introduce. I'm an enthusiast programmer (not professional) with c, c++, java experience and now starting with Objective-C and Cocoa on MacOsx.
In my first program I would like to create two vertical-splitted views, having the left one (main) always-on and the right one to show/hide as per button press (its use will be for debugging output).
I've seen exactly what I want under Xcode 4.2 where we can Hide/Show navigator/debug/utilities. I'm looking for the "utilities" behavior, that's exactly what I want. The usage of that vertical view is to output "debugging" text from my program, I'm thinking on using the NSTextView in a NSScrollView to simulate a "Console". I know I can just use a Terminal or the Debug view of Xcode, and that's what is working now. I need this just for learning how to do it and to improve my program.
I've google a lot and read similar request but I couldn't find exactly how to do this one.
Thanks in advance for your help.
Luis
as promised, here is what I finally did to solve my problem.
The objective: I want two vertical-splitted views:
Button to SHOW/HIDE the right view and window resize accordingly.
Left one (main) always-on and NON resizable in width (it can resize in height)
Right one show/hide, can resize in width/heigh, must have always a min width.
When Right is Hidden, Main Window min width/height equals the Left view
I created a NSSplitView (vertical) with 2 Custom View's with adequate autosizing restrictions in Interface Builder ('springs'/'struts'). Then I did the following:
Controller.h
:
#interface Controller : NSWindowController <NSSplitViewDelegate, NSWindowDelegate> {
:
Controller.m
:
// To control the Splitter (next 3 methods)
// =======================================
// The splitter cannot be moved. I always return "widthViewLeft" which is "fixed static sized to the left view width"
// I return NO to resize the left panel and YES to the right panel.
-(CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex {
return (widthViewLeft);
}
-(CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex {
return (widthViewLeft);
}
-(BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview {
return subview != splitViewLeft;
}
// To control the Main Window resize
// =======================================
// I allow to resize if the Right panel is open.
// I restrict to a fixed size if the Right panel is closed(hidden), so I don't allow to resize.
- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
{
if ( [[leftViewController view] isHidden ] ) {
proposedFrameSize.width = widthViewLeft + 2;
proposedFrameSize.height = heightViewLeft + titleBarHeight + 2;
}
return proposedFrameSize;
}
// To HIDE the right panel
// =======================================
//
-(void)handleNotificationHideConsola:(NSNotification *)pNotification
{
NSRect newFrame;
NSSize newMinSize;
NSSize newMaxSize;
// Hide the right panel
[[rightViewController view] setHidden:TRUE];
// Values that do not change
newMinSize.height = [theWindow minSize].height;
newMaxSize.height = [theWindow maxSize].height;
newFrame.origin.x = [theWindow frame].origin.x;
//newFrame.origin.y = [theWindow frame].origin.y;
// Values that change
newMinSize.width = widthViewLeft;
newMaxSize.width = widthViewLeft;
newFrame.size.width = widthViewLeft + 2;
newFrame.size.height = heightViewLeft + titleBarHeight + 2;
newFrame.origin.y = [theWindow frame].origin.y + ([theWindow frame].size.height - newFrame.size.height) ;
// Perform the change
[theWindow setMinSize:newMinSize];
[theWindow setFrame:newFrame display:YES animate:YES];
}
// To SHOW the right panel
// =======================================
//
-(void)handleNotificationShowConsola:(NSNotification *)pNotification
{
if ( [[rightViewController view] isHidden] ) {
NSRect newFrame;
NSSize newMinSize;
// Show the right panel
[[rightViewController view] setHidden:FALSE];
// Values that do not change
newMinSize.height = [theWindow minSize].height;
newFrame.origin.x = [theWindow frame].origin.x;
newFrame.origin.y = [theWindow frame].origin.y ;
// Values that change
newMinSize.width = widthViewLeft + widthViewRigth;
newFrame.size.width = widthViewLeft + widthViewRigth;
newFrame.size.height = newMinSize.height + titleBarHeight;
newFrame.origin.y = [theWindow frame].origin.y - (newFrame.size.height - [theWindow frame].size.height);
// Perform the change
[theWindow setMinSize:newMinSize];
[theWindow setFrame:newFrame display:YES animate:YES];
}
}
Thank you again #markhunte for the idea and hope the above sample helps someone else.
Luis
A very rough idea. Change the width of the view with setPosition:ofDividerAtIndex:
setPosition:(CGFloat)position ofDividerAtIndex:(NSInteger)dividerIndex
Declare a CGFloat splitterlength.
And Put this in applicationDidFinishLaunching.
splitterlength = splitView.bounds.size.width;
[splitView setPosition:splitterlength ofDividerAtIndex:0];
Then use this Action
- (IBAction)moveSplitter:(id)sender {
NSArray *splitterViews =splitView.subviews;
CGFloat splitterCheckLength =[[splitterViews objectAtIndex:0]bounds].size.width;
CGFloat openSplitter=splitterlength/2;
if (splitterCheckLength ==openSplitter) {
[splitView setPosition:splitterlength ofDividerAtIndex:0];
}else {
[splitView setPosition:openSplitter ofDividerAtIndex:0];
}
}
Use what ever length you want.
Saying this though. I would use normal customViews and adjust them. That way I do not have to worry about the dragging of the splitter by the user.