diff --git a/docs/changes.txt b/docs/changes.txt index 81d3454843..250db8a239 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -147,6 +147,7 @@ All (GUI): - Fix position of the rectangle returned by wxDataViewCtrl::GetItemRect(). - Add wxDataViewRenderer::GetAccessibleDescription(). - Add wxDataViewCheckIconTextRenderer class. +- Implement persistence support for wxDataViewCtrl (iwbnwif). - Improve wxImage::Scale() handling of pixels with alpha channel (Tim Kosse). - Fix parsing of RGBA strings in wxColour (Laurent Poujoulat). - Refactor code in wxQuantize() for MSVC to avoid crash. diff --git a/docs/doxygen/overviews/persistence.h b/docs/doxygen/overviews/persistence.h index 396dddd587..c055ccfc27 100644 --- a/docs/doxygen/overviews/persistence.h +++ b/docs/doxygen/overviews/persistence.h @@ -50,6 +50,7 @@ Currently the following classes are supported: - wxTopLevelWindow (and hence wxFrame and wxDialog) - wxBookCtrlBase (i.e. wxNotebook, wxListbook, wxToolbook and wxChoicebook) +- wxDataViewCtrl (and derivatives such as wxDataViewListCtrl) - wxTreebook To automatically save and restore the properties of the windows of classes diff --git a/include/wx/persist/dataview.h b/include/wx/persist/dataview.h new file mode 100644 index 0000000000..5be0feaa5f --- /dev/null +++ b/include/wx/persist/dataview.h @@ -0,0 +1,175 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/persist/dataview.h +// Purpose: Persistence support for wxDataViewCtrl and its derivatives +// Author: wxWidgets Team +// Created: 2017-08-21 +// Copyright: (c) 2017 wxWidgets.org +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_PERSIST_DATAVIEW_H_ +#define _WX_PERSIST_DATAVIEW_H_ + +#include "wx/persist/window.h" + +#include "wx/dataview.h" + +// ---------------------------------------------------------------------------- +// String constants used by wxPersistentDataViewCtrl. +// ---------------------------------------------------------------------------- + +#define wxPERSIST_DVC_KIND "DataView" + +#define wxPERSIST_DVC_COLUMNS "Columns" + +#define wxPERSIST_DVC_HIDDEN "Hidden" +#define wxPERSIST_DVC_POS "Position" +#define wxPERSIST_DVC_TITLE "Title" +#define wxPERSIST_DVC_WIDTH "Width" + +#define wxPERSIST_DVC_SORT_KEY "Sorting/Column" +#define wxPERSIST_DVC_SORT_ASC "Sorting/Asc" + +// ---------------------------------------------------------------------------- +// Helper function to search for a column by its title. +// ---------------------------------------------------------------------------- + +namespace wxPrivate +{ + +inline +wxDataViewColumn* +GetColumnByTitle(wxDataViewCtrl* control, const wxString& name) +{ + for ( unsigned int col = 0; col < control->GetColumnCount(); col++) + { + if ( control->GetColumn(col)->GetTitle() == name ) + return control->GetColumn(col); + } + + return NULL; +} + +} // namespace wxPrivate + +// ---------------------------------------------------------------------------- +// wxPersistentDataViewCtrl: Saves and restores user modified column widths +// and single column sort order. +// +// Future improvements could be to save and restore column order if the user +// has changed it and multicolumn sorts. +// ---------------------------------------------------------------------------- + +class wxPersistentDataViewCtrl : public wxPersistentWindow +{ +public: + wxPersistentDataViewCtrl(wxDataViewCtrl* control) + : wxPersistentWindow(control) + { + } + + virtual void Save() const wxOVERRIDE + { + wxDataViewCtrl* const control = Get(); + + wxDataViewColumn* sortColumn = NULL; + + for ( unsigned int col = 0; col < control->GetColumnCount(); col++ ) + { + wxDataViewColumn* column = control->GetColumn(col); + wxASSERT(column); + + // Create a prefix string to identify each column. + wxString columnPrefix; + columnPrefix.Printf("/%s/%s/", wxPERSIST_DVC_COLUMNS, + column->GetTitle()); + + // Save the column attributes. + SaveValue(columnPrefix + wxPERSIST_DVC_HIDDEN, column->IsHidden()); + SaveValue(columnPrefix + wxPERSIST_DVC_POS, + control->GetColumnPosition(column)); + SaveValue(columnPrefix + wxPERSIST_DVC_WIDTH, column->GetWidth()); + + // Check if this column is the current sort key. + if ( column->IsSortKey() ) + sortColumn = column; + } + + // Note: The current implementation does not save and restore multi- + // column sort keys. + if (control->IsMultiColumnSortAllowed()) + return; + + // Save the sort key and direction if there is a valid sort. + if ( sortColumn ) + { + SaveValue(wxPERSIST_DVC_SORT_KEY, sortColumn->GetTitle()); + SaveValue(wxPERSIST_DVC_SORT_ASC, + sortColumn->IsSortOrderAscending()); + } + } + + virtual bool Restore() wxOVERRIDE + { + wxDataViewCtrl* const control = Get(); + wxDataViewColumn* column; + + for ( unsigned int col = 0; col < control->GetColumnCount(); col++ ) + { + column = control->GetColumn(col); + wxASSERT(column); + + // Create a prefix string to identify each column within the + // persistence store (columns are stored by title). The persistence + // store benignly handles cases where the title is not found. + wxString columnPrefix; + columnPrefix.Printf("/%s/%s/", wxPERSIST_DVC_COLUMNS, + column->GetTitle()); + + // Restore column hidden status. + bool hidden; + if ( RestoreValue(columnPrefix + wxPERSIST_DVC_HIDDEN, &hidden) ) + column->SetHidden(hidden); + + // Restore the column width. + int width; + if ( RestoreValue(columnPrefix + wxPERSIST_DVC_WIDTH, &width) ) + column->SetWidth(width); + + // TODO: Set the column's view position. + } + + // Restore the sort key and order if there is a valid model and sort + // criteria. + wxString sortColumn; + if ( control->GetModel() && + RestoreValue(wxPERSIST_DVC_SORT_KEY, &sortColumn) && + sortColumn != "" ) + { + bool sortAsc = true; + column = wxPrivate::GetColumnByTitle(control, sortColumn); + + if ( column ) + { + RestoreValue(wxPERSIST_DVC_SORT_ASC, &sortAsc); + column->SetSortOrder(sortAsc); + + // Resort the control based on the new sort criteria. + control->GetModel()->Resort(); + } + } + return true; + } + + virtual wxString GetKind() const wxOVERRIDE + { + return wxPERSIST_DVC_KIND; + } +}; + +inline wxPersistentObject *wxCreatePersistentObject(wxDataViewCtrl* control) +{ + return new wxPersistentDataViewCtrl(control); +} + +#endif // _WX_PERSIST_DATAVIEW_H_ diff --git a/interface/wx/persist.h b/interface/wx/persist.h index a60fd1f42c..f9c75920fe 100644 --- a/interface/wx/persist.h +++ b/interface/wx/persist.h @@ -204,6 +204,9 @@ protected: /** Base class for persistent object adapters. + See @ref overview_persistence for an overview of persistent objects within + wxWidgets. + wxWidgets persistence framework is non-intrusive, i.e. can work with the classes which have no relationship to nor knowledge of it. To allow this, an intermediate persistence adapter is used: this is just a simple object @@ -292,13 +295,14 @@ protected: bool SaveValue(const wxString& name, T value) const; /** - Restore the value saved by Save(). + Restore a value saved by SaveValue(). @param name - The same name as was used by Save(). + The same name as was used by SaveValue(). @param value - Non-@NULL pointer which will be filled with the value if it was - read successfully or not modified if it wasn't. + Non-@NULL pointer to the same type that was passed to SaveValue(). + The pointed to object will be filled with the saved value if it + was read successfully or not modified otherwise. @return @true if the value was successfully read or @false if it was not found or an error occurred. diff --git a/tests/persist/persistence.cpp b/tests/persist/persistence.cpp new file mode 100644 index 0000000000..4382379a48 --- /dev/null +++ b/tests/persist/persistence.cpp @@ -0,0 +1,229 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: tests/persistence/persistence.cpp +// Purpose: wxPersistentObjects unit test +// Author: wxWidgets Team +// Created: 2017-08-23 +// Copyright: (c) 2017 wxWidgets Team +/////////////////////////////////////////////////////////////////////////////// + +// Note: The wxDataViewCtrl test currently uses the derivative class +// wxDataViewListCtrl for convenience. + +// ---------------------------------------------------------------------------- +// headers +// ---------------------------------------------------------------------------- + +#include "testprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#ifndef WX_PRECOMP + #include "wx/app.h" + #include "wx/config.h" + #include "wx/dataview.h" + #include "wx/frame.h" + #include "wx/persist/dataview.h" + #include "wx/persist/toplevel.h" +#endif // WX_PRECOMP + +// ---------------------------------------------------------------------------- +// macros +// ---------------------------------------------------------------------------- + +#define APP_NAME "cpptest" +#define PO_PREFIX "/Persistent_Options" +#define DVLC_PREFIX PO_PREFIX "/DataView/dvlc" +#define DVLC_COL "Column #" +#define DVLC_COL_PREFIX DVLC_PREFIX "/Columns/" DVLC_COL +#define DVLC_SORT_PREFIX DVLC_PREFIX "/Sorting" +#define DVLC_FRAME_PREFIX PO_PREFIX "/Window/frame" + +// -------------------------------------------------------------------------- +// test class +// -------------------------------------------------------------------------- + +class PersistenceTestCase : public CppUnit::TestCase +{ +public: + PersistenceTestCase() + { + wxTheApp->SetAppName("PersistTest"); + wxConfig::Get()->DeleteGroup("/Persistent_Options"); + wxConfig::Get()->Flush(); + } + + virtual void setUp(); + +private: + CPPUNIT_TEST_SUITE( PersistenceTestCase ); + CPPUNIT_TEST( FrameSaveTest ); + CPPUNIT_TEST( FrameRestoreTest ); + CPPUNIT_TEST( DVLCSaveTest ); + CPPUNIT_TEST( DVLCRestoreTest ); + CPPUNIT_TEST_SUITE_END(); + + void FrameSaveTest(); + void FrameRestoreTest(); + void DVLCSaveTest(); + void DVLCRestoreTest(); + + wxDataViewListCtrl* m_list; + wxFrame* m_frame; + + wxDECLARE_NO_COPY_CLASS(PersistenceTestCase); +}; + +// register in the unnamed registry so that these tests are run by default +CPPUNIT_TEST_SUITE_REGISTRATION( PersistenceTestCase ); + +// also include in its own registry so that these tests can be run alone +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( PersistenceTestCase, "PersistenceTestCase" ); + +void PersistenceTestCase::setUp() +{ + // Create the objects to persist. + m_frame = new wxFrame(wxTheApp->GetTopWindow(), wxID_ANY, + "Persistence Test", + wxDefaultPosition, wxSize(400,400)); + + m_list = new wxDataViewListCtrl(m_frame, wxID_ANY, + wxDefaultPosition, + wxDLG_UNIT(m_frame, wxSize(-1,-1)), + wxDV_ROW_LINES|wxDV_SINGLE); + + // Define names so the objects can be found using wxConfig. + m_frame->SetName("frame"); + m_list->SetName("dvlc"); + + // Add some columns to the DVLC. + m_list->AppendTextColumn(DVLC_COL "1", + wxDATAVIEW_CELL_INERT, -1, wxALIGN_LEFT, + wxDATAVIEW_COL_RESIZABLE | + wxDATAVIEW_COL_REORDERABLE | + wxDATAVIEW_COL_SORTABLE); + m_list->AppendTextColumn(DVLC_COL "2", + wxDATAVIEW_CELL_INERT, -1, wxALIGN_LEFT, + wxDATAVIEW_COL_RESIZABLE | + wxDATAVIEW_COL_REORDERABLE | + wxDATAVIEW_COL_SORTABLE); + + // Populate with DVLC data. + wxVector data; + + data.push_back("AAAA"); + data.push_back("BBBB"); + m_list->AppendItem(data); + + data.clear(); + data.push_back("CCCC"); + data.push_back("DDDD"); + m_list->AppendItem(data); + + data.clear(); + data.push_back("EEEE"); + data.push_back("FFFF"); + m_list->AppendItem(data); +} + +void PersistenceTestCase::FrameSaveTest() +{ + // Adjust the initial settings. + m_frame->SetPosition(wxPoint(100, 150)); + m_frame->SetSize(wxSize(450, 350)); + + // Get default managers. + wxPersistenceManager& pm = wxPersistenceManager::Get(); + wxConfigBase* conf = wxConfig::Get(); + + pm.Register(m_frame); + + // Destroy the frame immediately. + m_frame->wxWindowBase::Destroy(); + + // Test that the relevant keys have been stored correctly. + int val; + + CPPUNIT_ASSERT(conf->Read(DVLC_FRAME_PREFIX "/x", &val)); + CPPUNIT_ASSERT_EQUAL(100, val); + + CPPUNIT_ASSERT(conf->Read(DVLC_FRAME_PREFIX "/y", &val)); + CPPUNIT_ASSERT_EQUAL(150, val); + + CPPUNIT_ASSERT(conf->Read(DVLC_FRAME_PREFIX "/h", &val)); + CPPUNIT_ASSERT_EQUAL(350, val); + + CPPUNIT_ASSERT(conf->Read(DVLC_FRAME_PREFIX "/w", &val)); + CPPUNIT_ASSERT_EQUAL(450, val); + + CPPUNIT_ASSERT(conf->Read(DVLC_FRAME_PREFIX "/Iconized", &val)); + CPPUNIT_ASSERT_EQUAL(0, val); + + CPPUNIT_ASSERT(conf->Read(DVLC_FRAME_PREFIX "/Maximized", &val)); + CPPUNIT_ASSERT_EQUAL(0, val); +} + +void PersistenceTestCase::FrameRestoreTest() +{ + // Get default manager. + wxPersistenceManager& pm = wxPersistenceManager::Get(); + + // Test that the objects are registered and can be restored. + CPPUNIT_ASSERT(pm.RegisterAndRestore(m_frame)); + + CPPUNIT_ASSERT_EQUAL(100, m_frame->GetPosition().x); + CPPUNIT_ASSERT_EQUAL(150, m_frame->GetPosition().y); + CPPUNIT_ASSERT_EQUAL(350, m_frame->GetSize().GetHeight()); + CPPUNIT_ASSERT_EQUAL(450, m_frame->GetSize().GetWidth()); + CPPUNIT_ASSERT(!m_frame->IsMaximized()); + CPPUNIT_ASSERT(!m_frame->IsIconized()); +} + +void PersistenceTestCase::DVLCSaveTest() +{ + // Adjust the initial settings. + m_list->GetColumn(0)->SetWidth(150); + m_list->GetColumn(1)->SetWidth(250); + m_list->GetColumn(1)->SetSortOrder(false); + + // Get default managers. + wxPersistenceManager& pm = wxPersistenceManager::Get(); + wxConfigBase* conf = wxConfig::Get(); + + pm.Register(m_list); + + // Destroy the frame containing the DVLC immediately. + m_frame->wxWindowBase::Destroy(); + + // Test that the relevant keys have been stored correctly. + int val; + wxString text; + + CPPUNIT_ASSERT(conf->Read(DVLC_COL_PREFIX "1/Width", &val)); + CPPUNIT_ASSERT_EQUAL(150, val); + + CPPUNIT_ASSERT(conf->Read(DVLC_COL_PREFIX "2/Width", &val)); + CPPUNIT_ASSERT_EQUAL(250, val); + + CPPUNIT_ASSERT(conf->Read(DVLC_SORT_PREFIX "/Column", &text)); + CPPUNIT_ASSERT_EQUAL("Column #2", text); + + CPPUNIT_ASSERT(conf->Read(DVLC_SORT_PREFIX "/Asc", &val)); + CPPUNIT_ASSERT_EQUAL(0, val); +} + +void PersistenceTestCase::DVLCRestoreTest() +{ + // Get default manager. + wxPersistenceManager& pm = wxPersistenceManager::Get(); + + // Test that the objects are registered and can be restored. + CPPUNIT_ASSERT(pm.RegisterAndRestore(m_list)); + + // Test that the correct values were restored. + CPPUNIT_ASSERT_EQUAL(150, m_list->GetColumn(0)->GetWidth()); + CPPUNIT_ASSERT_EQUAL(250, m_list->GetColumn(1)->GetWidth()); + CPPUNIT_ASSERT(m_list->GetColumn(1)->IsSortKey()); + CPPUNIT_ASSERT(!m_list->GetColumn(1)->IsSortOrderAscending()); +}