From dcee1cd025d72ce560e0d171960b3c9aaf5caf93 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 15 Jul 2022 00:36:45 +0100 Subject: [PATCH] Improve behaviour of "force closing" wxDocuments When the document was forced to close, OnSaveModified() was still called and allowed the user to cancel closing the document -- but it was still closed after OnSaveModified() returned. Be more upfront about it and tell the user that the document will be closed anyhow, but still propose them to save it if necessary. An alternative solution might be to just deprecate "force closing" entirely, as this seems very user-unfriendly. --- include/wx/docview.h | 4 +++ interface/wx/docview.h | 13 ++++++++ samples/docview/docview.cpp | 9 ++++++ samples/docview/docview.h | 3 ++ src/common/docview.cpp | 63 ++++++++++++++++++++++++++++++++----- 5 files changed, 85 insertions(+), 7 deletions(-) diff --git a/include/wx/docview.h b/include/wx/docview.h index 1d7d329a3c..f14e71f18c 100644 --- a/include/wx/docview.h +++ b/include/wx/docview.h @@ -117,6 +117,10 @@ public: // modified to false) virtual bool OnSaveModified(); + // Similar to OnSaveModified() but doesn't allow the user to prevent the + // document from closing as it will be closed unconditionally. + virtual void OnSaveBeforeForceClose(); + // if you override, remember to call the default // implementation (wxDocument::OnChangeFilename) virtual void OnChangeFilename(bool notifyViews); diff --git a/interface/wx/docview.h b/interface/wx/docview.h index bd0a82618f..82cd6d4c64 100644 --- a/interface/wx/docview.h +++ b/interface/wx/docview.h @@ -1544,6 +1544,19 @@ public: */ virtual bool OnSaveModified(); + /** + This function is called when a document is forced to close. + + The default implementation asks the user whether to save the changes + but, unlike OnSaveModified(), does not allow to cancel closing. + + The document is force closed when wxDocManager::CloseDocument() is + called with its @c force argument set to @true. + + @since 3.3.0 + */ + virtual void OnSaveBeforeForceClose(); + /** Removes the view from the document's list of views. diff --git a/samples/docview/docview.cpp b/samples/docview/docview.cpp index bc05ed6b7a..b2aa4eafb0 100644 --- a/samples/docview/docview.cpp +++ b/samples/docview/docview.cpp @@ -75,6 +75,7 @@ wxIMPLEMENT_APP(MyApp); wxBEGIN_EVENT_TABLE(MyApp, wxApp) EVT_MENU(wxID_ABOUT, MyApp::OnAbout) + EVT_MENU(wxID_CLEAR, MyApp::OnForceCloseAll) wxEND_EVENT_TABLE() MyApp::MyApp() @@ -315,6 +316,7 @@ void MyApp::AppendDocumentFileCommands(wxMenu *menu, bool supportsPrinting) menu->Append(wxID_SAVE); menu->Append(wxID_SAVEAS); menu->Append(wxID_REVERT, _("Re&vert...")); + menu->Append(wxID_CLEAR, "&Force close all"); if ( supportsPrinting ) { @@ -437,6 +439,13 @@ wxFrame *MyApp::CreateChildFrame(wxView *view, bool isCanvas) return subframe; } +void MyApp::OnForceCloseAll(wxCommandEvent& WXUNUSED(event)) +{ + // Pass "true" here to force closing just for testing this functionality, + // there is no real reason to force the issue here. + wxDocManager::GetDocumentManager()->CloseDocuments(true); +} + void MyApp::OnAbout(wxCommandEvent& WXUNUSED(event)) { wxString modeName; diff --git a/samples/docview/docview.h b/samples/docview/docview.h index 27d5175f1f..008ec7fe76 100644 --- a/samples/docview/docview.h +++ b/samples/docview/docview.h @@ -71,6 +71,9 @@ private: void CreateMenuBarForFrame(wxFrame *frame, wxMenu *file, wxMenu *edit); + // force close all windows + void OnForceCloseAll(wxCommandEvent& event); + // show the about box: as we can have different frames it's more // convenient, even if somewhat less usual, to handle this in the // application object itself diff --git a/src/common/docview.cpp b/src/common/docview.cpp index fc6ecd445a..e871b5d95f 100644 --- a/src/common/docview.cpp +++ b/src/common/docview.cpp @@ -554,6 +554,50 @@ bool wxDocument::OnSaveModified() return true; } +void wxDocument::OnSaveBeforeForceClose() +{ + if ( !IsModified() ) + return; + + wxMessageDialog dialogSave + ( + GetDocumentWindow(), + wxString::Format + ( + _("Do you want to save changes to %s before closing it?"), + GetUserReadableName() + ), + wxTheApp->GetAppDisplayName(), + wxYES_NO | wxICON_QUESTION | wxCENTRE + ); + dialogSave.SetExtendedMessage(_("The document must be closed.")); + dialogSave.SetYesNoLabels(_("&Save"), _("&Discard changes")); + + if ( dialogSave.ShowModal() == wxID_YES ) + { + while ( !Save() ) + { + wxMessageDialog dialogRetry + ( + GetDocumentWindow(), + wxString::Format + ( + _("Saving %s failed, would you like to retry?"), + GetUserReadableName() + ), + wxTheApp->GetAppDisplayName(), + wxYES_NO | wxICON_ERROR | wxCENTRE + ); + dialogRetry.SetYesNoLabels(_("Retry"), _("Discard changes")); + + if ( dialogRetry.ShowModal() != wxID_YES ) + break; + } + } + + Modify(false); +} + bool wxDocument::Draw(wxDC& WXUNUSED(context)) { return true; @@ -1004,14 +1048,19 @@ wxDocManager::~wxDocManager() // closes the specified document bool wxDocManager::CloseDocument(wxDocument* doc, bool force) { - if ( !doc->CanClose() && !force ) - return false; + if ( force ) + { + // We need to close, but at least ask the user if the document should + // be saved before doing it. + doc->OnSaveBeforeForceClose(); + } + else // Allow the user to cancel closing too. + { + if ( !doc->CanClose() ) + return false; + } - // To really force the document to close, we must ensure that it isn't - // modified, otherwise it would ask the user about whether it should be - // destroyed (again, it had been already done by CanClose() above) and might - // not destroy it at all, while we must do it here. - doc->Modify(false); + // Note that by now the document is certain not to be modified any longer. // Implicitly deletes the document when // the last view is deleted