NSDocument -canCloseDocumentWithDelegate::: not called when terminating

  • Hello,

      when I close my document window without terminating the application,
    -canCloseDocumentWithDelegate::: is called, independent of whether the document
    is dirty.

    When I quit the application, -canCloseDocumentWithDelegate::: is only called if
    the document reports it is dirty. If it is not dirty, AppKit takes a shortcut
    calls into -close directly. NSDocumentController doesn't seem to be involved either.

    Why do I need this?

    My document is stored in a package that contains a preferences file. When the
    document is saved, the preferences file is saved as part of the package. So, if
    it is dirty at the time of closing it's going to be saved. If the document isn't
    dirty, I'd like to update the preference file only.

    A good place would have been -canCloseDocumentWithDelegate::: because I can
    check whether the document is dirty and save the preferences file if necessary.
    That doesn't work if it's not getting called of course. -close is called after
    the document is saved, so the dirty flag there isn't helpful.

    Are there better options to do a last-minute save? I do not want to change the
    design, meaning I don't want preferences to contribute to the change count state
    or even worse be undoable. Plus I do want the preferences to be stored in the
    package, not someplace else (restoration state for example).

    Thanks for any pointers!

    Regards
    Markus
    --
    __________________________________________
    Markus Spoettl
  • Perhaps I'm missing something — is writing the preferences file very time-consuming? If not, why not write it whether you need to or not? Or whenever a preferences value changes, which is what you'd do with NSUserDefaults?

    — F

    On 23 Jul 2012, at 10:12 AM, Markus Spoettl wrote:

    > My document is stored in a package that contains a preferences file. When the document is saved, the preferences file is saved as part of the package. So, if it is dirty at the time of closing it's going to be saved. If the document isn't dirty, I'd like to update the preference file only.
    >
    > A good place would have been -canCloseDocumentWithDelegate::: because I can check whether the document is dirty and save the preferences file if necessary. That doesn't work if it's not getting called of course. -close is called after the document is saved, so the dirty flag there isn't helpful.
    >
    > Are there better options to do a last-minute save? I do not want to change the design, meaning I don't want preferences to contribute to the change count state or even worse be undoable. Plus I do want the preferences to be stored in the package, not someplace else (restoration state for example).

    --
    Fritz Anderson -- Xcode 4 Unleashed: Now in stores! -- <http://x4u.manoverboard.org/>
  • I can't really recommend an approach that essentially lies about the "dirty" state of a document. If you have something to write into the document bundle, then the document is dirty and it should be declared so with the available NSDocument APIs. If the existing APIs don't meet your needs for whatever reason, file an enhancement request.

    Also, NSDocument and NSFileWrapper (assuming you're using it) might not take it very kindly if you modify the package outside of the normal -save… and -write… APIs, especially if/when you adopt Autosave and iCloud.

    To answer your question more directly—there is no public callback that is invoked in every case on Quit when the document returns NO from -isDocumentEdited or -hasUnautosavedChanges. In fact, sometimes nothing gets invoked at all because when -isDocumentEdited returns NO, NSDocument enables sudden termination, which allows -[NSApplication terminate:] to take shortcuts and Shutdown/Restart/Logout to kill the app.

    -KP

    On Jul 23, 2012, at 8:12 AM, Markus Spoettl <ms_lists...> wrote:

    > Hello,
    >
    > when I close my document window without terminating the application, -canCloseDocumentWithDelegate::: is called, independent of whether the document is dirty.
    >
    > When I quit the application, -canCloseDocumentWithDelegate::: is only called if the document reports it is dirty. If it is not dirty, AppKit takes a shortcut calls into -close directly. NSDocumentController doesn't seem to be involved either.
    >
    > Why do I need this?
    >
    > My document is stored in a package that contains a preferences file. When the document is saved, the preferences file is saved as part of the package. So, if it is dirty at the time of closing it's going to be saved. If the document isn't dirty, I'd like to update the preference file only.
    >
    > A good place would have been -canCloseDocumentWithDelegate::: because I can check whether the document is dirty and save the preferences file if necessary. That doesn't work if it's not getting called of course. -close is called after the document is saved, so the dirty flag there isn't helpful.
    >
    > Are there better options to do a last-minute save? I do not want to change the design, meaning I don't want preferences to contribute to the change count state or even worse be undoable. Plus I do want the preferences to be stored in the package, not someplace else (restoration state for example).
    >
    > Thanks for any pointers!
    >
    > Regards
    > Markus
    > --
    > __________________________________________
    > Markus Spoettl
  • On Jul 23, 2012, at 8:12 AM, Markus Spoettl <ms_lists...> wrote:

    > Are there better options to do a last-minute save? I do not want to change the design, meaning I don't want preferences to contribute to the change count state or even worse be undoable. Plus I do want the preferences to be stored in the package, not someplace else (restoration state for example).

    I don't have my crazy autosave flowchart handy, but I *think* you should be able to simply override -hasUnautosavedChanges to return YES if either super returns YES or if your preferences file needs to be saved. -autosaveWithImplicitCancellability:: is documented to check -hasUnautosavedChanges, so I would assume that it is called unconditionally at all places where AppKit wants to ensure the document is written to disk if necessary.

    Of course, this assumes you are returning YES from +autosavesInPlace.

    --Kyle Sluder
  • On 7/23/12 5:42 PM, Fritz Anderson wrote:
    > Perhaps I'm missing something — is writing the preferences file very
    > time-consuming? If not, why not write it whether you need to or not? Or
    > whenever a preferences value changes, which is what you'd do with
    > NSUserDefaults?

    Thanks for your suggestion. Writing is fast, so I guess I could write in -close.
    I'd prefer not to write if it's not necessary and it seems I can detect that
    case reliably because -canCloseDocumentWithDelegate::: will be called if there
    are unsaved changes and -close is called from within
    -canCloseDocumentWithDelegate::: in that case. That could break in the future,
    though.

    Regards
    Markus
    --
    __________________________________________
    Markus Spoettl
  • On Jul 23, 2012, at 9:06 AM, Kevin Perry <kperry...> wrote:

    > I can't really recommend an approach that essentially lies about the "dirty" state of a document. If you have something to write into the document bundle, then the document is dirty and it should be declared so with the available NSDocument APIs. If the existing APIs don't meet your needs for whatever reason, file an enhancement request.

    What about overriding -hasUnautosavedChanges to return YES, and also calling -scheduleAutosaving whenever the preference state changes?

    Marking the document edited just because the user has scrolled the content area is a poor user experience, and making such a change undoable is even worse.

    We're hoping to move as much of this editor state information as possible to use NSWindowRestoration, but there are still a few places where the line between discardable visual configuration and semantically meaningful state is blurry. Is the currently-selected document in an OmniGraffle document meaningful? After all, you can configure shapes to switch canvases when you click on them.

    >
    > To answer your question more directly—there is no public callback that is invoked in every case on Quit when the document returns NO from -isDocumentEdited or -hasUnautosavedChanges. In fact, sometimes nothing gets invoked at all because when -isDocumentEdited returns NO, NSDocument enables sudden termination, which allows -[NSApplication terminate:] to take shortcuts and Shutdown/Restart/Logout to kill the app.

    Perhaps in addition to the above, Markus could also disable sudden termination before calling -scheduleAutosaving, and re-enable it in an override of one of the save methods.

    --Kyle Sluder
  • On 7/23/12 6:06 PM, Kevin Perry wrote:
    > I can't really recommend an approach that essentially lies about the "dirty"
    > state of a document. If you have something to write into the document bundle,
    > then the document is dirty and it should be declared so with the available
    > NSDocument APIs. If the existing APIs don't meet your needs for whatever
    > reason, file an enhancement request.
    >
    > Also, NSDocument and NSFileWrapper (assuming you're using it) might not take
    > it very kindly if you modify the package outside of the normal -save… and
    > -write… APIs, especially if/when you adopt Autosave and iCloud.
    >
    > To answer your question more directly—there is no public callback that is
    > invoked in every case on Quit when the document returns NO from
    > -isDocumentEdited or -hasUnautosavedChanges. In fact, sometimes nothing gets
    > invoked at all because when -isDocumentEdited returns NO, NSDocument enables
    > sudden termination, which allows -[NSApplication terminate:] to take
    > shortcuts and Shutdown/Restart/Logout to kill the app.

    OK, I get your point and I'm not insisting on asking for trouble, so:

    If there would be a way to tell NSDocument to forgo asking whether to save a
    document if -isDocumentEdited returned YES, and instead just save it, that would
    be a solution. All I want is a silent save, when only preferences need to be
    saved (since I'm using file wrappers it would be quick too because I'm only
    replacing the wrapper for my preferences).

    I don't see how I could do that. Is there a way?

    Regards
    Markus
    --
    __________________________________________
    Markus Spoettl
  • On 7/23/12 6:22 PM, Kyle Sluder wrote:
    > I don't have my crazy autosave flowchart handy, but I *think* you should be
    > able to simply override -hasUnautosavedChanges to return YES if either super
    > returns YES or if your preferences file needs to be saved.
    > -autosaveWithImplicitCancellability:: is documented to check
    > -hasUnautosavedChanges, so I would assume that it is called unconditionally
    > at all places where AppKit wants to ensure the document is written to disk if
    > necessary.
    >
    > Of course, this assumes you are returning YES from +autosavesInPlace.

    Thanks for the idea, I'll think about that. Not using autosavesInPlace right
    now, but if it's the ticket to making this work, it may be worth reconsidering.
    I'm not a big fan of the feature, though.

    Regards
    Markus
    --
    __________________________________________
    Markus Spoettl
  • On Jul 23, 2012, at 10:51 AM, Markus Spoettl <ms_lists...> wrote:

    > On 7/23/12 6:22 PM, Kyle Sluder wrote:
    >> I don't have my crazy autosave flowchart handy, but I *think* you should be
    >> able to simply override -hasUnautosavedChanges to return YES if either super
    >> returns YES or if your preferences file needs to be saved.
    >> -autosaveWithImplicitCancellability:: is documented to check
    >> -hasUnautosavedChanges, so I would assume that it is called unconditionally
    >> at all places where AppKit wants to ensure the document is written to disk if
    >> necessary.
    >>
    >> Of course, this assumes you are returning YES from +autosavesInPlace.
    >

    -autosaveWithImplicitCancellability: does check -hasUnautosavedChanges, but the quit procedure doesn't—it actually checks -isDocumentEdited. And when autosavesInPlace returns NO, overriding -isDocumentEdited to return YES will still result in the dirty dot in the close button, unless you go to great lengths to prevent the state from bubbling up to -[NSWindow setDocumentEdited:].

    > If there would be a way to tell NSDocument to forgo asking whether to save a document if -isDocumentEdited returned YES, and instead just save it, that would be a solution. All I want is a silent save, when only preferences need to be saved (since I'm using file wrappers it would be quick too because I'm only replacing the wrapper for my preferences).
    >
    > I don't see how I could do that. Is there a way?

    You're talking about the "Do you want to save changes to this document" dialog when clicking the close button or quitting? The only way to bypass that is to override canCloseDocumentWithDelegate:… check for your special "-isDocumentEdited==YES, but don't show a panel" case, and invoke the shouldCloseSelector (after writing the preferences file) without calling super.

    But really, if you and other developers feel there is a need for functionality to mark a document as needing saving, but without ever informing the user about it or giving them a chance to cancel it, then file an enhancement request for such an API.

    -KP
  • On 7/23/12 9:34 PM, Kevin Perry wrote:
    >> If there would be a way to tell NSDocument to forgo asking whether to save
    >> a document if -isDocumentEdited returned YES, and instead just save it,
    >> that would be a solution. All I want is a silent save, when only
    >> preferences need to be saved (since I'm using file wrappers it would be
    >> quick too because I'm only replacing the wrapper for my preferences).
    >>
    >> I don't see how I could do that. Is there a way?
    >
    > You're talking about the "Do you want to save changes to this document"
    > dialog when clicking the close button or quitting? The only way to bypass
    > that is to override canCloseDocumentWithDelegate:… check for your special
    > "-isDocumentEdited==YES, but don't show a panel" case, and invoke the
    > shouldCloseSelector (after writing the preferences file) without calling
    > super.

    Thanks, I'll see if I can make this work, I'll report back.

    Regards
    Markus
    --
    __________________________________________
    Markus Spoettl
  • On 7/23/12 6:27 PM, Markus Spoettl wrote:
    > On 7/23/12 5:42 PM, Fritz Anderson wrote:
    >> Perhaps I'm missing something — is writing the preferences file very
    >> time-consuming? If not, why not write it whether you need to or not? Or
    >> whenever a preferences value changes, which is what you'd do with
    >> NSUserDefaults?
    >
    > Thanks for your suggestion. Writing is fast, so I guess I could write in -close.
    > I'd prefer not to write if it's not necessary and it seems I can detect that
    > case reliably because -canCloseDocumentWithDelegate::: will be called if there
    > are unsaved changes and -close is called from within
    > -canCloseDocumentWithDelegate::: in that case. That could break in the future,
    > though.

    Despite the future breakage risk I've decided to go with this solution. I'm not
    using versions, autosave, sudden termination or iCloud at this point, so
    modifing the package at the time the document is guaranteed to be finished with
    its wrapper seems reasonably safe. Autosave has some nice features but overall
    it doesn't seem worth the unavoidable trouble. My documents can get huge, saving
    may take long, even making snapshots can take long (I do use asynchronous saving
    and loading). Saving cancelability gives me a headache.

    Thanks for the suggestion, guys!

    Regards
    Markus
    --
    __________________________________________
    Markus Spoettl
previous month july 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today