NSWindow setFrame | setPosition on the screen - objective-c

In my application, I need to display a multiple window of same NIB, at a time, and it will be closed/released on the timer basis,
everything is working fine, except the window position. My requirement is, it should display the window exactly below the previous window, I am able to calculate origin for the new window if any window of same NIB is present.
I am using this function to get the origin:
-(void)awakeFromNib{
NSString *eventMsg = [NSString stringWithFormat:#"%s is set “,eventName];
[pTextField setStringValue:eventMsg];
[pWindow setFrameOrigin:[self pointstoDisplayScreen]];
/* On timer callback , i will show the fadeout and fadein effects*/
[self startTimer];
}
/* This method returns the coordinate where to
draw the window
*/
-(NSPoint)pointstoDisplayScreen{
NSRect frame = [[NSScreen mainScreen] visibleFrame];
NSRect windowRect = [pWindow frame];
CGFloat yCoordinate = frame.size.height-windowRect.size.height;
NSPoint point = NSMakePoint(frame.size.width - windowRect.size.width, frame.size.height-windowRect.size.height);
/* Let there be some gap, if any window present and let it draw below to
the existing window
*/
point.y -= (windowRect.size.height + windowOffset)*noOfWindow;
NSLog([NSString stringWithFormat:#"PositionToDisplayScreen x = [%f] y = [%f]",point.x,point.y ]);
return point;
}
The problem is if if previous window is present it draws the new point, slightly below and toward right side of the existing window,
Is there any property i need set, the problem is, if the previous position is exactly on the top right, then it draw the new window on the opposite corner.

[NSWindowController setShouldCascadeWindows:NO];
By setting this, its working, Now all the notification drawing at the top right corner of the screen.
Thanks.

Your window location is "jumping" because you are not doing any bounds checking to determine that the calculated new origin is within the screen bounds.
The class NSScreen will give you details of the screen(s), for each on you cant get its frame.
Now you need to decide how you want a window origin to move; e.g. to the screen edge and no further, onto an adjacent screen if there is one, wrapping (off right means come on left), etc. The choice will depend on your application.
Armed with the screen frame(s) and the movement algorithm you can calculate your new origin.

Related

Get value of "Displays have separate spaces" option in Mavericks

Is it possible to determine if the "Displays have separate spaces" option is checked in OSX Mavericks? I found the option is stored in com.apple.spaces.plist with name "spans-displays" but this code doesn't work with sandboxing:
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
[userDefaults addSuiteNamed:#"com.apple.spaces"];
NSLog(#"%i", [[userDefaults objectForKey:#"spans-displays"] integerValue]);
[userDefaults release];
Thanks!
To my knowledge there is no simple API to discover this, Apple have never provided comprehensive API relating to Spaces.
However, with a bit of lateral thinking you can figure it out.
What is a distinctive feature of displays having separate spaces?
There are multiple menubars.
So "Are there multiple menubars?" has the same answer as "Do displays have separate spaces?"
Is there an API to tell you if a screen has a menubar?
Again, not to my knowledge, but can we figure it out?
NSWindow has an instance method constrainFrameRect:toScreen: which given a rectangle, representing a window frame, and a screen returns an adjusted rectangle where at least part of the rectangle is visible on the screen. Furthermore, by definition if the top edge of the rectangle is above the menubar area the rectangle will be adjusted so the top edge abuts the menubar...
Which means if we pass it a rectangle abutting the top edge of the screen the returned rectangle will abut the top edge of the menubar, provided there is a menubar. If there is no menubar then the returned rectangle will have the same top edge.
So we can determine if there is a menubar and its height. One small wrinkle, as constrainFrameRect:toScreen: is an instance method we need a window, any window, to make our code work.
Here is one way to code this as a function:
CGFloat menuBarHeight(NSScreen *screen)
{
// A dummy window so we can call constrainFrameRect:toScreen
NSWindow *dummy = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered defer:YES];
// create a small rectangle at the top left corner of the screen
NSRect screenFrame = screen.frame;
NSRect testFrame = NSMakeRect(NSMinX(screenFrame), NSMaxY(screenFrame)-30, 30, 30);
// constrain the rectangle to be visible
NSRect constrainedFrame = [dummy constrainFrameRect:testFrame toScreen:screen];
// did the top edge move? delta = 0 -> no, delta > 0 - yes and by the height of the menubar
CGFloat delta = NSMaxY(testFrame) - NSMaxY(constrainedFrame);
return delta;
}
So now we can tell if a particular screen has a menubar. How about more than one screen?
Well NSScreen's class method screens returns an array of all the available screens, so all we need to do is call our menuBarHeight function on each screen and see how many menubars we find.
If we find more than 1 then we've determined that displays have separate spaces.
Here is one way to code that, again as a function:
BOOL haveIndepenantScreens()
{
BOOL foundMenuBar = NO;
for (NSScreen *aScreen in NSScreen.screens)
{
if (menuBarHeight(aScreen) > 0)
{
if (foundMenuBar)
// second menu bar found
return YES;
else
// record found first menubar
foundMenuBar = YES;
}
}
return NO; // did not find multiple menubars
}
Job done :-)
use
NSScreen.screensHaveSeparateSpaces
Not sure when this appeared in the documents, but it is there as of 2021!
One advantage of this over #CRD's excellent answer is that it works even if the user has selected 'Automatically hide and show the menu bar'
credit for the answer to #chockenberry

CGRect not completely formed before arranging UI elements

I have the following code
- (void) setTargetGoalFrameToLeftOfWindow: (UIView*) goalView orientation: (UIInterfaceOrientation) orientation {
[goalView setTransform:CGAffineTransformMakeRotation(-M_PI_2)];
CGRect goalFrame = goalView.frame;
CGRect windowFrame = [self getWindowFrame];
CGFloat topMargin;
if (UIInterfaceOrientationIsLandscape(orientation)) {
topMargin = (windowFrame.size.width - goalFrame.size.height) / 2.0;
} else {
topMargin = (windowFrame.size.height - goalFrame.size.height) / 2.0;
}
goalView.frame = CGRectMake(GOAL_MARGIN, topMargin, goalFrame.size.width, goalFrame.size.height);
}
I am creating frame with CGRect and I am using it display my UI elements like labels, buttons etc. I am calculating position based on the window size so that they are in appropriate positions in different orientations. When my app is running, I click on home button. When I open the app again, my UI elements are messed up. They are not in proper positions. The method I mentioned above gets invoked every time I change the orientation and open the app. So, this is getting invoked when I reopen the app. But the problem is that, even before the frame is completely formed, it is taking the width and height at that particular point and calculating positions of my UI elements. This is leading to messed up UI. Is there any way where in I can restrict it to take width and height only after the frame is completely formed? Thanks!

Synchronised scrolling between two instances of NSScrollView

I have two instances of NSScrollView both presenting a view on the same content. The second scroll view however has a scaled down version of the document view presented in the first scroll view. Both width and height can be individually scaled and the original width - height constraints can be lost, but this is of no importance.
I have the synchronised scrolling working, even taking into account that the second scroll view needs to align its scrolling behaviour based on the scaling. There's one little snag I've been pulling my hairs out over:
As both views happily scroll along the smaller view needs to slowly catch up with the larger view, so that they both "arrive" at the end of their document at the same time. Right now this is not happening and the result is that the smaller view is at "end-of-document" before the larger view.
The code for synchronised scrolling is based on the example found in Apple's documentation titled "Synchronizing Scroll Views". I have adapted the synchronizedViewContentBoundsDidChange: to the following code:
- (void) synchronizedViewContentBoundsDidChange: (NSNotification *) notification {
// get the changed content view from the notification
NSClipView *changedContentView = [notification object];
// get the origin of the NSClipView of the scroll view that
// we're watching
NSPoint changedBoundsOrigin = [changedContentView documentVisibleRect].origin;;
// get our current origin
NSPoint curOffset = [[self contentView] bounds].origin;
NSPoint newOffset = curOffset;
// scrolling is synchronized in the horizontal plane
// so only modify the x component of the offset
// "scale" variable will correct for difference in size between views
NSSize ownSize = [[self documentView] frame].size;
NSSize otherSize = [[[self synchronizedScrollView] documentView] frame].size;
float scale = otherSize.width / ownSize.width;
newOffset.x = floor(changedBoundsOrigin.x / scale);
// if our synced position is different from our current
// position, reposition our content view
if (!NSEqualPoints(curOffset, changedBoundsOrigin)) {
// note that a scroll view watching this one will
// get notified here
[[self contentView] scrollToPoint:newOffset];
// we have to tell the NSScrollView to update its
// scrollers
[self reflectScrolledClipView:[self contentView]];
}
}
How would I need to change that code so that the required effect (both scroll bars arriving at an end of document) is achieved?
EDIT: Some clarification as it was confusing when I read it back myself: The smaller view needs to slow down when scrolling the first view reaches the end. This would probably mean re-evaluating that scaling factor... but how?
EDIT 2: I changed the method based on Alex's suggestion:
NSScroller *myScroll = [self horizontalScroller];
NSScroller *otherScroll = [[self synchronizedScrollView] horizontalScroller];
//[otherScroll setFloatValue: [myScroll floatValue]];
NSLog(#"My scroller value: %f", [myScroll floatValue]);
NSLog(#"Other scroller value: %f", [otherScroll floatValue]);
// Get the changed content view from the notification.
NSClipView *changedContentView = [notification object];
// Get the origin of the NSClipView of the scroll view that we're watching.
NSPoint changedBoundsOrigin = [changedContentView documentVisibleRect].origin;;
// Get our current origin.
NSPoint curOffset = [[self contentView] bounds].origin;
NSPoint newOffset = curOffset;
// Scrolling is synchronized in the horizontal plane so only modify the x component of the offset.
NSSize ownSize = [[self documentView] frame].size;
newOffset.x = floor(ownSize.width * [otherScroll floatValue]);
// If our synced position is different from our current position, reposition our content view.
if (!NSEqualPoints(curOffset, changedBoundsOrigin)) {
// Note that a scroll view watching this one will get notified here.
[[self contentView] scrollToPoint: newOffset];
// We have to tell the NSScrollView to update its scrollers.
[self reflectScrolledClipView:[self contentView]];
}
Using this method the smaller view is "overtaken" by the larger view when both scrollers reach a value of 0.7, which is not good. The larger view then scrolls past its end of document.
I think you might be approaching this in the wrong way. I think you should be getting a percentage of how far down each scroll be is scrolled in relation to itself and apply that to the other view. One example of how this could be done is this way using NSScroller's -floatValue:
NSScroller *myScroll = [self verticalScroller];
NSScroller *otherScroll = [otherScrollView verticalScroller];
[myScroll setFloatValue:otherScroll.floatValue];
I finally figured it out. The answer from Alex was a good hint but not the full solution as just setting the float value of a scroller doesn't do anything. That value needs translation to specific coordinates to which the scroll view needs to scroll its contents.
However, due to differences in size of the scrolled document view, you cannot just simply use this value, as the scaled down view will be overtaken by the "normal" view at some point. This will cause the normal view to scroll past its end of document.
The second part of the solution was to make the normal sized view wait with scrolling until the scaled down view has scrolled its own width.
The code:
// Scrolling is synchronized in the horizontal plane so only modify the x component of the offset.
NSSize ownSize = [[self documentView] frame].size;
newOffset.x = MAX(floor(ownSize.width * [otherScroll floatValue] - [self frame].size.width),0);
The waiting is achieved by subtracting the width of the scroll view from the width times the value of the scroller. When the scaled down version is still traversing its first scroll view width of pixels, this calculation will result in a negative offset. Using MAX will prevent strange effects and the original view will quietly wait until the value turns positive and then start its own scrolling. This solution also works when the user resizes the app window.

resizing an NSPanel to fit a dynamic NSMatrix and a button

I have an NSMatrix that is filled dynamically with some form items. Now, I can conveniently call [theMatrix sizeToCells] and then pass it into the panel to be displayed.
Now, I want the NSPanel object which contains this NSMatrix to resize to wrap nicely around it. The NSPanel also has a button at the bottom which should be under the NSMatrix.
I have been trying many things with getting bounds and setting frames and have been having much confusion.
Is there any standard or correct way of sizing a panel to it's contents?
As a side question: Does a frame's origin refer to it's top left or bottom left? Is it always consistent?
Thanks
The origin of an NSPanel/NSWindow frame is always the bottom-left corner, it's measured from the screen origin.
Whether the origin of a view is the top left or bottom left depends on whether or not its superview is flipped. Flipped views have their bounds origin in the top left.
To do what you want, you need to get the frame size of the NSMatrix, then recalculate the layout of the panel.
Something like this (written in here, untested!):
//NSMatrix* matrix;
//NSPanel* panel;
CGFloat panelMargin = 10.0;
CGFloat matrixBottomMargin = 30.0;
[matrix sizeToCells];
NSRect matrixFrame = [matrix frame];
NSRect panelFrame = [panel frame];
NSSize newPanelSize = NSMakeSize(NSWidth(matrixFrame) + 2.0 * panelMargin,
NSHeight(matrixFrame) + 2.0 * panelMargin + matrixBottomMargin);
CGFloat yDelta = newPanelSize.height - NSHeight(panelFrame);
panelFrame = NSMakeRect(panelFrame.origin.x,
panelFrame.origin.y - yDelta,
newPanelSize.width,
newPanelSize.height);
[panel setFrame:panelFrame display:YES];

How do I convert Cocoa co-ords from top left == origin to bottom left == origin

I use CGWindowListCopyWindowInfo to get a list of all windows. It gives me the co-ordinates of each window based upon the origin being the top-left of the screen.
If I use NSWindow's setFrame method, the co-ordinates on based upon the origin being the bottom-left of the screen.
What's a clean, reliable way to convert from one to the other?
Added: By clean and reliable, I mean, something sure to work regardless whether the user has multiple screens or is using Spaces. I figure there must be a known idiom using library APIs.
Math is quite reliable :-)
yFromBottom = screenHeight - windowHeight - yFromTop
Main screen height is
[[[NSScreen screens] objectAtIndex:0] frame].size.height
I would suggest using an NSAffineTransform. If you draw with respect to the default origin and then apply a transform to the view, you can essentially flip things around in one fell swoop.
Try something like this (from here):
NSRect boundsInWindow = [myView convertRect:[myView bounds] toView:nil];
NSRect visibleRectInWindow = [myView convertRect:[myView visibleRect] toView:nil];
// Flip Y to convert NSWindow coordinates to top-left-based window coordinates.
float borderViewHeight = [[myView window] frame].size.height;
boundsInWindow.origin.y = borderViewHeight - NSMaxY(boundsInWindow);
visibleRectInWindow.origin.y = borderViewHeight - NSMaxY(visibleRectInWindow);