4

I have a document-based application. I override NSDocument's makeWindowControllers to instantiate a custom window controller. Its initializer calls init(window: NSWindow?) of its superclass, i.e. it does not use any of the initializers that involve a nib file.

How do I make cascading work (see shouldCascadeWindows)? At the moment each window is opening in the same position on the 1st screen.

Can I somehow reuse the existing cascading logic, maybe by calling something on NSWindowController?

If I have to implement it myself manually, how should I best get the position of the top-most document window? And which of the potentially many windows of a document should be the window from which to compute the offset?

mistercake
  • 692
  • 5
  • 14
  • How is the window instantiated? What is the position of the first window? Which window is the main window of the document? From which window do you want to cascade? Is the frame of the window autosaved? Did you search for a cascading method? – Willeke Aug 19 '19 at 01:07
  • I want the default behavior, i.e. what you get when you create a new document-based application in Xcode. I can pick a position for the first window if there is no default or recommended position. For now the window of the first window controller is the main window. I want to cascade from the top-most document window I guess, whatever's the default or recommended practice. Not sure how autosaving is relevant, it's a new document after all, there's no previous record of it. I know of `cascadeTopLeft`, but that just does the offsetting math. I have not found anything else. – mistercake Aug 19 '19 at 08:46
  • With "Is the frame of the window autosaved?" I mean [frameAutosaveName](https://developer.apple.com/documentation/appkit/nswindow/1419362-frameautosavename). – Willeke Aug 19 '19 at 08:59
  • It's an empty string at the moment. – mistercake Aug 19 '19 at 09:12
  • The window controller calls `cascadeTopLeft(from:)` after loading the window from the nib. If you provide the window you have to call `cascadeTopLeft(from:)`. – Willeke Aug 19 '19 at 10:08
  • So you're saying I have to implement it manually? As I have said, I know about this method. What about the third part of my question? Also, can you elaborate as to how your answer depended on `frameAutosaveName`? – mistercake Aug 19 '19 at 10:30
  • `frameAutosaveName` has nothing to do with it if it's not set. – Willeke Aug 19 '19 at 10:57

1 Answers1

5

func cascadeTopLeft(from topLeftPoint: NSPoint) -> NSPoint

Positions the window's top left to a given point.

Parameters

topLeftPoint The new top-left point, in screen coordinates, for the window. When NSZeroPoint, the window is not moved, except as needed to constrain to the visible screen

NSZeroPoint is the starting point for the first window.

Return Value The point shifted from top left of the window in screen coordinates.

Discussion

The returned point can be passed to a subsequent invocation of cascadeTopLeft(from:) to position the next window so the title bars of both windows are fully visible.

The returned point is the starting point for the next window.

Example (like TextEdit):

static var cascadingPoint = NSZeroPoint

override func makeWindowControllers() {
    let window = NSWindow(contentRect: NSMakeRect(100, 100, 500, 500), styleMask: .titled, backing: .buffered, defer: true)
    Document.cascadingPoint = window.cascadeTopLeft(from: Document.cascadingPoint)
    let windowController = NSWindowController(window: window)
    addWindowController(windowController)
}

Another example (like Safari)

override func makeWindowControllers() {
    let window = NSWindow(contentRect: NSMakeRect(100, 100, 500, 500), styleMask: .titled, backing: .buffered, defer: true)
    let cascadingPoint = NSApp.mainWindow?.cascadeTopLeft(from: NSZeroPoint) ?? NSZeroPoint
    window.cascadeTopLeft(from: cascadingPoint)
    let windowController = NSWindowController(window: window)
    addWindowController(windowController)
}
Willeke
  • 14,578
  • 4
  • 19
  • 47
  • Like the questioner, I'm looking for a much simpler means if any. Because when using a XIB for the window-controller, it seems to work automatically. All the code that goes into `NSDocument.makeWindowControllers` is just a call to `NSWindowController.initWithWindowNibName` followed by `NSDocument.addWindowController:` and it just works. But I guess figuring internals of API implementation is a wild goose chase that's not worth it. – trss Jan 09 '23 at 09:54
  • @trss figuring internals of API implementation is what I did. Read the comments please. – Willeke Jan 09 '23 at 11:07
  • Continued my search and found NSWindowController.h in [10.15](https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.15.sdk/System/Library/Frameworks/AppKit.framework/Versions/C/Headers/NSWindowController.h#L52) and [11.3](https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX11.3.sdk/System/Library/Frameworks/AppKit.framework/Versions/C/Headers/NSWindowController.h#L53) mention, "If this is set to YES then new windows **loaded from nibs (only windows from nibs)** will be cascaded based on the original frame of the window from the nib" which means we don't have access to the working. – trss Jan 09 '23 at 14:07
  • However [12.3](https://github.com/realjf/MacOSX-SDKs/blob/master/MacOSX12.3.sdk/System/Library/Frameworks/AppKit.framework/Versions/C/Headers/NSWindowController.h#L53) mentions "If this is set to YES then new windows will be cascaded based on the original frame of the window." where the clause **loaded from nibs (only windows from nibs)** has been removed! Haven't verified what actually happens for windows without nibs in 12.3. @Willeke I agree yours is as close to an answer as we can get given all this. Just stating it's hard to go further than that. – trss Jan 09 '23 at 14:15