diff --git a/docs/changes.txt b/docs/changes.txt index 61dbacf733..57982bbed8 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -476,6 +476,7 @@ All (GUI): - wxHTML: render in RTL order inside RTL window (Richard Bullington-McGuire). - wxRibbon: added EVT_RIBBONGALLERY_CLICKED event (John Roberts). - wxRibbon: allow hiding the panels and showing tabs only (snowleopard). +- Add support for child documents to docview framework. - Add support for CP-866 encoding to wxEncodingConverter (madnut). - Consistency fixes for keyboard events across all major ports. - Added EVT_RIBBONBAR_TAB_LEFT_DCLICK event (snowleopard). diff --git a/include/wx/docview.h b/include/wx/docview.h index 1ed240f256..a6efd5dd10 100644 --- a/include/wx/docview.h +++ b/include/wx/docview.h @@ -17,6 +17,7 @@ #if wxUSE_DOC_VIEW_ARCHITECTURE #include "wx/list.h" +#include "wx/dlist.h" #include "wx/string.h" #include "wx/frame.h" #include "wx/filehistory.h" @@ -172,6 +173,10 @@ public: // dialogs. Override if necessary. virtual wxWindow *GetDocumentWindow() const; + // Returns true if this document is a child document corresponding to a + // part of the parent document and not a disk file as usual. + bool IsChildDocument() const { return m_documentParent != NULL; } + protected: wxList m_documentViews; wxString m_documentFile; @@ -179,7 +184,12 @@ protected: wxString m_documentTypeName; wxDocTemplate* m_documentTemplate; bool m_documentModified; + + // if the document parent is non-NULL, it's a pseudo-document corresponding + // to a part of the parent document which can't be saved or loaded + // independently of its parent and is always closed when its parent is wxDocument* m_documentParent; + wxCommandProcessor* m_commandProcessor; bool m_savedYet; @@ -193,6 +203,10 @@ protected: wxString DoGetUserReadableName() const; private: + // list of all documents whose m_documentParent is this one + typedef wxDList DocsList; + DocsList m_childDocuments; + DECLARE_ABSTRACT_CLASS(wxDocument) wxDECLARE_NO_COPY_CLASS(wxDocument); }; @@ -386,6 +400,7 @@ public: void OnUpdateFileRevert(wxUpdateUIEvent& event); void OnUpdateFileNew(wxUpdateUIEvent& event); void OnUpdateFileSave(wxUpdateUIEvent& event); + void OnUpdateFileSaveAs(wxUpdateUIEvent& event); void OnUpdateUndo(wxUpdateUIEvent& event); void OnUpdateRedo(wxUpdateUIEvent& event); diff --git a/interface/wx/docview.h b/interface/wx/docview.h index 4d2cd89bfe..c33bf8279e 100644 --- a/interface/wx/docview.h +++ b/interface/wx/docview.h @@ -1130,9 +1130,25 @@ public: @class wxDocument The document class can be used to model an application's file-based data. + It is part of the document/view framework supported by wxWidgets, and cooperates with the wxView, wxDocTemplate and wxDocManager classes. + A normal document is the one created without parent document and is + associated with a disk file. Since version 2.9.2 wxWidgets also supports a + special kind of documents called child documents which are virtual + in the sense that they do not correspond to a file but rather to a part of + their parent document. Because of this, the child documents can't be + created directly by user but can only be created by the parent document + (usually when it's being created itself). They also can't be independently + saved. A child document has its own view with the corresponding window. + This view can be closed by user but, importantly, is also automatically + closed when its parent document is closed. Thus, child documents may be + convenient for creating additional windows which need to be closed when the + main document is. The docview sample demonstrates this use of child + documents by creating a child document containing the information about the + parameters of the image opened in the main document. + @library{wxcore} @category{docview} @@ -1144,8 +1160,14 @@ public: /** Constructor. Define your own default constructor to initialize application-specific data. + + @param parent + Specifying a non-@c NULL parent document here makes this document a + special child document, see their description in the class + documentation. Notice that this parameter exists but is ignored in + wxWidgets versions prior to 2.9.1. */ - wxDocument(wxDocument* parent = 0); + wxDocument(wxDocument* parent = NULL); /** Destructor. Removes itself from the document manager. @@ -1278,6 +1300,18 @@ public: const wxList& GetViews() const; //@} + /** + Returns true if this document is a child document corresponding to a + part of the parent document and not a disk file as usual. + + This method can be used to check whether file-related operations make + sense for this document as they only apply to top-level documents and + not child ones. + + @since 2.9.2 + */ + bool IsChildDocument() const; + /** Returns @true if the document has been modified since the last save, @false otherwise. You may need to override this if your document view diff --git a/samples/docview/doc.cpp b/samples/docview/doc.cpp index 713a76d569..24f3984b16 100644 --- a/samples/docview/doc.cpp +++ b/samples/docview/doc.cpp @@ -250,7 +250,7 @@ wxTextCtrl* TextEditDocument::GetTextCtrl() const } // ---------------------------------------------------------------------------- -// ImageDocument and wxImageDetailsDocument implementation +// ImageDocument and ImageDetailsDocument implementation // ---------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(ImageDocument, wxDocument) @@ -260,3 +260,29 @@ bool ImageDocument::DoOpenDocument(const wxString& file) return m_image.LoadFile(file); } +bool ImageDocument::OnOpenDocument(const wxString& filename) +{ + if ( !wxDocument::OnOpenDocument(filename) ) + return false; + + // we don't have a wxDocTemplate for the image details document as it's + // never created by wxWidgets automatically, instead just do it manually + ImageDetailsDocument * const docDetails = new ImageDetailsDocument(this); + docDetails->SetFilename(filename); + + new ImageDetailsView(docDetails); + + return true; +} + +ImageDetailsDocument::ImageDetailsDocument(ImageDocument *parent) + : wxDocument(parent) +{ + const wxImage image = parent->GetImage(); + + m_size.x = image.GetWidth(); + m_size.y = image.GetHeight(); + m_numColours = image.CountColours(); + m_type = image.GetType(); + m_hasAlpha = image.HasAlpha(); +} diff --git a/samples/docview/doc.h b/samples/docview/doc.h index d79f8275de..c654398222 100644 --- a/samples/docview/doc.h +++ b/samples/docview/doc.h @@ -198,14 +198,19 @@ public: }; // ---------------------------------------------------------------------------- -// A document class representing an image +// Image and image details document classes (both are read-only for simplicity) // ---------------------------------------------------------------------------- +// This is a normal document containing an image, just like TextEditDocument +// above contains some text. It can be created from an image file on disk as +// usual. class ImageDocument : public wxDocument { public: ImageDocument() : wxDocument() { } + virtual bool OnOpenDocument(const wxString& file); + wxImage GetImage() const { return m_image; } protected: @@ -218,4 +223,28 @@ private: DECLARE_DYNAMIC_CLASS(ImageDocument) }; +// This is a child document of ImageDocument: this document doesn't +// correspond to any file on disk, it's part of ImageDocument and can't be +// instantiated independently of it. +class ImageDetailsDocument : public wxDocument +{ +public: + ImageDetailsDocument(ImageDocument *parent); + + // accessors for ImageDetailsView + wxSize GetSize() const { return m_size; } + unsigned long GetNumColours() const { return m_numColours; } + wxBitmapType GetType() const { return m_type; } + bool HasAlpha() const { return m_hasAlpha; } + +private: + // some information about the image we choose to show to the user + wxSize m_size; + unsigned long m_numColours; + wxBitmapType m_type; + bool m_hasAlpha; + + wxDECLARE_NO_COPY_CLASS(ImageDetailsDocument); +}; + #endif // _WX_SAMPLES_DOCVIEW_DOC_H_ diff --git a/samples/docview/view.cpp b/samples/docview/view.cpp index f4e4462f5a..555368aa7b 100644 --- a/samples/docview/view.cpp +++ b/samples/docview/view.cpp @@ -362,3 +362,76 @@ bool ImageView::OnClose(bool deleteWindow) return true; } +// ---------------------------------------------------------------------------- +// ImageDetailsView +// ---------------------------------------------------------------------------- + +ImageDetailsView::ImageDetailsView(ImageDetailsDocument *doc) + : wxView() +{ + SetDocument(doc); + + m_frame = wxGetApp().CreateChildFrame(this, false); + m_frame->SetTitle("Image Details"); + + wxPanel * const panel = new wxPanel(m_frame); + wxFlexGridSizer * const sizer = new wxFlexGridSizer(2, wxSize(5, 5)); + const wxSizerFlags + flags = wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border(); + + sizer->Add(new wxStaticText(panel, wxID_ANY, "Image &file:"), flags); + sizer->Add(new wxStaticText(panel, wxID_ANY, doc->GetFilename()), flags); + + sizer->Add(new wxStaticText(panel, wxID_ANY, "Image &type:"), flags); + wxString typeStr; + switch ( doc->GetType() ) + { + case wxBITMAP_TYPE_PNG: + typeStr = "PNG"; + break; + + case wxBITMAP_TYPE_JPEG: + typeStr = "JPEG"; + break; + + default: + typeStr = "Unknown"; + } + sizer->Add(new wxStaticText(panel, wxID_ANY, typeStr), flags); + + sizer->Add(new wxStaticText(panel, wxID_ANY, "Image &size:"), flags); + wxSize size = doc->GetSize(); + sizer->Add(new wxStaticText(panel, wxID_ANY, + wxString::Format("%d*%d", size.x, size.y)), + flags); + + sizer->Add(new wxStaticText(panel, wxID_ANY, "Number of unique &colours:"), + flags); + sizer->Add(new wxStaticText(panel, wxID_ANY, + wxString::Format("%lu", doc->GetNumColours())), + flags); + + sizer->Add(new wxStaticText(panel, wxID_ANY, "Uses &alpha:"), flags); + sizer->Add(new wxStaticText(panel, wxID_ANY, + doc->HasAlpha() ? "Yes" : "No"), flags); + + panel->SetSizer(sizer); + m_frame->SetClientSize(panel->GetBestSize()); + m_frame->Show(true); +} + +void ImageDetailsView::OnDraw(wxDC * WXUNUSED(dc)) +{ + // nothing to do here, we use controls to show our information +} + +bool ImageDetailsView::OnClose(bool deleteWindow) +{ + if ( wxGetApp().GetMode() != MyApp::Mode_Single && deleteWindow ) + { + delete m_frame; + m_frame = NULL; + } + + return true; +} diff --git a/samples/docview/view.h b/samples/docview/view.h index 8bbc37c2ba..3ba84d8847 100644 --- a/samples/docview/view.h +++ b/samples/docview/view.h @@ -148,4 +148,22 @@ private: DECLARE_DYNAMIC_CLASS(ImageView) }; +// ---------------------------------------------------------------------------- +// ImageDetailsView +// ---------------------------------------------------------------------------- + +class ImageDetailsView : public wxView +{ +public: + ImageDetailsView(ImageDetailsDocument *doc); + + virtual void OnDraw(wxDC *dc); + virtual bool OnClose(bool deleteWindow); + +private: + wxFrame *m_frame; + + wxDECLARE_NO_COPY_CLASS(ImageDetailsView); +}; + #endif // _WX_SAMPLES_DOCVIEW_VIEW_H_ diff --git a/src/common/docview.cpp b/src/common/docview.cpp index 34df0b1c27..6d6950b008 100644 --- a/src/common/docview.cpp +++ b/src/common/docview.cpp @@ -122,8 +122,12 @@ wxString FindExtension(const wxString& path) wxDocument::wxDocument(wxDocument *parent) { m_documentModified = false; - m_documentParent = parent; m_documentTemplate = NULL; + + m_documentParent = parent; + if ( parent ) + parent->m_childDocuments.push_back(this); + m_commandProcessor = NULL; m_savedYet = false; } @@ -140,6 +144,9 @@ wxDocument::~wxDocument() if (GetDocumentManager()) GetDocumentManager()->RemoveDocument(this); + if ( m_documentParent ) + m_documentParent->m_childDocuments.remove(this); + // Not safe to do here, since it'll invoke virtual view functions // expecting to see valid derived objects: and by the time we get here, // we've called destructors higher up. @@ -151,6 +158,40 @@ bool wxDocument::Close() if ( !OnSaveModified() ) return false; + // When the parent document closes, its children must be closed as well as + // they can't exist without the parent. + + // As usual, first check if all children can be closed. + DocsList::const_iterator it = m_childDocuments.begin(); + for ( DocsList::const_iterator end = m_childDocuments.end(); it != end; ++it ) + { + if ( !(*it)->OnSaveModified() ) + { + // Leave the parent document opened if a child can't close. + return false; + } + } + + // Now that they all did, do close them: as m_childDocuments is modified as + // we iterate over it, don't use the usual for-style iteration here. + while ( !m_childDocuments.empty() ) + { + wxDocument * const childDoc = m_childDocuments.front(); + + // This will call OnSaveModified() once again but it shouldn't do + // anything as the document was just saved or marked as not needing to + // be saved by the call to OnSaveModified() that returned true above. + if ( !childDoc->Close() ) + { + wxFAIL_MSG( "Closing the child document unexpectedly failed " + "after its OnSaveModified() returned true" ); + } + + // Delete the child document by deleting all its views. + childDoc->DeleteAllViews(); + } + + return OnCloseDocument(); } @@ -231,6 +272,12 @@ void wxDocument::Modify(bool mod) wxDocManager *wxDocument::GetDocumentManager() const { + // For child documents we use the same document manager as the parent, even + // though we don't have our own template (as children are not opened/saved + // directly). + if ( m_documentParent ) + return m_documentParent->GetDocumentManager(); + return m_documentTemplate ? m_documentTemplate->GetDocumentManager() : NULL; } @@ -895,7 +942,7 @@ BEGIN_EVENT_TABLE(wxDocManager, wxEvtHandler) EVT_UPDATE_UI(wxID_REVERT, wxDocManager::OnUpdateFileRevert) EVT_UPDATE_UI(wxID_NEW, wxDocManager::OnUpdateFileNew) EVT_UPDATE_UI(wxID_SAVE, wxDocManager::OnUpdateFileSave) - EVT_UPDATE_UI(wxID_SAVEAS, wxDocManager::OnUpdateDisableIfNoDoc) + EVT_UPDATE_UI(wxID_SAVEAS, wxDocManager::OnUpdateFileSaveAs) EVT_UPDATE_UI(wxID_UNDO, wxDocManager::OnUpdateUndo) EVT_UPDATE_UI(wxID_REDO, wxDocManager::OnUpdateRedo) @@ -1254,7 +1301,13 @@ void wxDocManager::OnUpdateFileNew(wxUpdateUIEvent& event) void wxDocManager::OnUpdateFileSave(wxUpdateUIEvent& event) { wxDocument * const doc = GetCurrentDocument(); - event.Enable( doc && !doc->AlreadySaved() ); + event.Enable( doc && !doc->IsChildDocument() && !doc->AlreadySaved() ); +} + +void wxDocManager::OnUpdateFileSaveAs(wxUpdateUIEvent& event) +{ + wxDocument * const doc = GetCurrentDocument(); + event.Enable( doc && !doc->IsChildDocument() ); } void wxDocManager::OnUpdateUndo(wxUpdateUIEvent& event)