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
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.
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
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.
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.
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.