Kaydet (Commit) b6828ca1 authored tarafından Michael Stahl's avatar Michael Stahl

rhbz#680365: SwRenderData: do not store SwPageFrm*

Currently when printing via SwXTextDocument::getRendererCount()
two STL containers in class SwRenderData are filled with bare pointers
to page frames from the layout.
These pointers are then used by SwXTextDocument::render(), which takes
one page out of the map and prints it via this non-ref-counted pointer.

This bizarre design will not be tolerated; instead, when printing a page,
iterate over SwRootFrm::GetLower() until the requested page is found.
This is slower, i.e. O(n^2), but at least it does not crash.
üst 96be522e
...@@ -38,7 +38,6 @@ ...@@ -38,7 +38,6 @@
#include <vector> #include <vector>
#include <utility> #include <utility>
class SwPageFrm;
class SwDoc; class SwDoc;
class SwDocShell; class SwDocShell;
class ViewShell; class ViewShell;
...@@ -226,20 +225,17 @@ public: ...@@ -226,20 +225,17 @@ public:
class SwRenderData class SwRenderData
{ {
// pages valid for printing (according to the current settings) // pages valid for printing (according to the current settings)
// and their respective start frames (see getRendererCount in unotxdoc.cxx)
// This set of pages does NOT depend on the 'PageRange' that is used as a printing option! // This set of pages does NOT depend on the 'PageRange' that is used as a printing option!
std::set< sal_Int32 > m_aValidPages; // the set of possible pages (see StringRangeEnumerator::getRangesFromString ) std::set< sal_Int32 > m_aValidPages; // the set of possible pages (see StringRangeEnumerator::getRangesFromString )
std::map< sal_Int32, const SwPageFrm * > m_aValidStartFrames; // the map of start frames for those pages
// printer paper tray to use for each of the m_aValidPages above // printer paper tray to use for each of the m_aValidPages above
std::map< sal_Int32, sal_Int32 > m_aPrinterPaperTrays; std::map< sal_Int32, sal_Int32 > m_aPrinterPaperTrays;
// vector of pages and their order to be printed (duplicates and any order allowed!) // vector of pages and their order to be printed (duplicates and any order allowed!)
// (see 'render' in unotxdoc.cxx) // (see 'render' in unotxdoc.cxx)
// negative entry indicates the page to be printed is from the post-it doc
std::vector< sal_Int32 > m_aPagesToPrint; std::vector< sal_Int32 > m_aPagesToPrint;
std::vector< const SwPageFrm * > m_aPostItStartFrames;
// for prospect printing: the pairs of pages to be printed together on a single prospect page. // for prospect printing: the pairs of pages to be printed together on a single prospect page.
// -1 indicates a half page to be left empty. // -1 indicates a half page to be left empty.
std::vector< std::pair< sal_Int32, sal_Int32 > > m_aPagePairs; std::vector< std::pair< sal_Int32, sal_Int32 > > m_aPagePairs;
...@@ -281,13 +277,10 @@ public: ...@@ -281,13 +277,10 @@ public:
const SwPrintUIOptions *pOpt, const SwRenderData *pData, bool bIsPDFExport ); const SwPrintUIOptions *pOpt, const SwRenderData *pData, bool bIsPDFExport );
typedef std::map< sal_Int32, const SwPageFrm * > ValidStartFramesMap_t;
typedef std::vector< std::pair< sal_Int32, sal_Int32 > > PagePairsVec_t; typedef std::vector< std::pair< sal_Int32, sal_Int32 > > PagePairsVec_t;
std::set< sal_Int32 > & GetValidPagesSet() { return m_aValidPages; } std::set< sal_Int32 > & GetValidPagesSet() { return m_aValidPages; }
const std::set< sal_Int32 > & GetValidPagesSet() const { return m_aValidPages; } const std::set< sal_Int32 > & GetValidPagesSet() const { return m_aValidPages; }
ValidStartFramesMap_t & GetValidStartFrames() { return m_aValidStartFrames; }
const ValidStartFramesMap_t & GetValidStartFrames() const { return m_aValidStartFrames; }
// a map for printer paper tray numbers to use for each document page // a map for printer paper tray numbers to use for each document page
// a value of -1 for the tray means that there is no specific tray defined // a value of -1 for the tray means that there is no specific tray defined
...@@ -300,16 +293,6 @@ public: ...@@ -300,16 +293,6 @@ public:
std::vector< sal_Int32 > & GetPagesToPrint() { return m_aPagesToPrint; } std::vector< sal_Int32 > & GetPagesToPrint() { return m_aPagesToPrint; }
const std::vector< sal_Int32 > & GetPagesToPrint() const { return m_aPagesToPrint; } const std::vector< sal_Int32 > & GetPagesToPrint() const { return m_aPagesToPrint; }
// used for 'normal' printing with post-its
// - if the map entry will be NULL then the respective page to be printed is from
// the document. In that case use the value from GetPagesToPrint at the same index to
// get the phys. page number to be printed, and then retrieve the start frame to use
// from GetValidStartFrms.
// - If the entry is not NULL it is the start frame of the page from the post-it document
// that is to be printed
std::vector< const SwPageFrm * > & GetPostItStartFrames() { return m_aPostItStartFrames; }
const std::vector< const SwPageFrm * > & GetPostItStartFrames() const { return m_aPostItStartFrames; }
// used for prospect printing only // used for prospect printing only
PagePairsVec_t & GetPagePairsForProspectPrinting() { return m_aPagePairs; } PagePairsVec_t & GetPagePairsForProspectPrinting() { return m_aPagePairs; }
const PagePairsVec_t & GetPagePairsForProspectPrinting() const { return m_aPagePairs; } const PagePairsVec_t & GetPagePairsForProspectPrinting() const { return m_aPagePairs; }
......
...@@ -1309,9 +1309,7 @@ void SwDoc::CalculatePagesForPrinting( ...@@ -1309,9 +1309,7 @@ void SwDoc::CalculatePagesForPrinting(
std::map< sal_Int32, sal_Int32 > &rPrinterPaperTrays = rData.GetPrinterPaperTrays(); std::map< sal_Int32, sal_Int32 > &rPrinterPaperTrays = rData.GetPrinterPaperTrays();
std::set< sal_Int32 > &rValidPages = rData.GetValidPagesSet(); std::set< sal_Int32 > &rValidPages = rData.GetValidPagesSet();
std::map< sal_Int32, const SwPageFrm * > &rValidStartFrms = rData.GetValidStartFrames();
rValidPages.clear(); rValidPages.clear();
rValidStartFrms.clear();
while ( pStPage ) while ( pStPage )
{ {
const sal_Bool bRightPg = pStPage->OnRightPage(); const sal_Bool bRightPg = pStPage->OnRightPage();
...@@ -1323,8 +1321,6 @@ void SwDoc::CalculatePagesForPrinting( ...@@ -1323,8 +1321,6 @@ void SwDoc::CalculatePagesForPrinting(
if ( bPrintEmptyPages || pStPage->Frm().Height() ) if ( bPrintEmptyPages || pStPage->Frm().Height() )
{ {
rValidPages.insert( nPageNo ); rValidPages.insert( nPageNo );
rValidStartFrms[ nPageNo ] = pStPage;
rPrinterPaperTrays[ nPageNo ] = lcl_GetPaperBin( pStPage ); rPrinterPaperTrays[ nPageNo ] = lcl_GetPaperBin( pStPage );
} }
} }
...@@ -1459,104 +1455,55 @@ void SwDoc::UpdatePagesForPrintingWithPostItData( ...@@ -1459,104 +1455,55 @@ void SwDoc::UpdatePagesForPrintingWithPostItData(
// now add those post-it pages to the vector of pages to print // now add those post-it pages to the vector of pages to print
// or replace them if only post-its should be printed // or replace them if only post-its should be printed
rData.GetPostItStartFrames().clear(); if (nPostItMode == POSTITS_ONLY)
if (nPostItMode == POSTITS_ENDDOC)
{
// set all values up to number of pages to print currently known to NULL,
// meaning none of the pages currently in the vector is from the
// post-it document, they are the documents pages.
rData.GetPostItStartFrames().resize( rData.GetPagesToPrint().size() );
}
else if (nPostItMode == POSTITS_ONLY)
{ {
// no document page to be printed // no document page to be printed
rData.GetPagesToPrint().clear(); rData.GetPagesToPrint().clear();
} }
// now we just need to add the post-it pages to be printed to the end // now we just need to add the post-it pages to be printed to the
// of the vector of pages to print and keep the GetValidStartFrames // end of the vector of pages to print
// data conform with it
sal_Int32 nPageNum = 0; sal_Int32 nPageNum = 0;
const SwPageFrm * pPageFrm = (SwPageFrm*)rData.m_pPostItShell->GetLayout()->Lower(); const SwPageFrm * pPageFrm = (SwPageFrm*)rData.m_pPostItShell->GetLayout()->Lower();
while( pPageFrm && nPageNum < nPostItDocPageCount ) while( pPageFrm && nPageNum < nPostItDocPageCount )
{ {
OSL_ENSURE( pPageFrm, "Empty page frame. How are we going to print this?" ); OSL_ENSURE( pPageFrm, "Empty page frame. How are we going to print this?" );
++nPageNum; ++nPageNum;
rData.GetPagesToPrint().push_back( 0 ); // a page number of 0 indicates this page is from the post-it doc // negative page number indicates page is from the post-it doc
rData.GetPagesToPrint().push_back( -nPageNum );
OSL_ENSURE( pPageFrm, "pPageFrm is NULL!" ); OSL_ENSURE( pPageFrm, "pPageFrm is NULL!" );
rData.GetPostItStartFrames().push_back( pPageFrm );
pPageFrm = (SwPageFrm*)pPageFrm->GetNext(); pPageFrm = (SwPageFrm*)pPageFrm->GetNext();
} }
OSL_ENSURE( nPageNum == nPostItDocPageCount, "unexpected number of pages" ); OSL_ENSURE( nPageNum == nPostItDocPageCount, "unexpected number of pages" );
} }
else if (nPostItMode == POSTITS_ENDPAGE) else if (nPostItMode == POSTITS_ENDPAGE)
{ {
// the next step is to find all the start frames from the post-it // the next step is to find all the pages from the post-it
// document that should be printed for a given physical page of the document // document that should be printed for a given physical page
std::map< sal_Int32, std::vector< const SwPageFrm * > > aPhysPageToPostItFrames; // of the document
// ... thus, first collect all post-it doc start frames in a vector
sal_Int32 nPostItPageNum = 0;
std::vector< const SwPageFrm * > aAllPostItStartFrames;
const SwPageFrm * pPageFrm = (SwPageFrm*)rData.m_pPostItShell->GetLayout()->Lower();
while( pPageFrm && sal_Int32(aAllPostItStartFrames.size()) < nPostItDocPageCount )
{
OSL_ENSURE( pPageFrm, "Empty page frame. How are we going to print this?" );
++nPostItPageNum;
aAllPostItStartFrames.push_back( pPageFrm );
pPageFrm = (SwPageFrm*)pPageFrm->GetNext();
}
OSL_ENSURE( sal_Int32(aAllPostItStartFrames.size()) == nPostItDocPageCount,
"unexpected number of frames; does not match number of pages" );
// get a map that holds all post-it frames to be printed for a
// given physical page from the document
sal_Int32 nLastStartPageNum = 0;
std::map< sal_Int32, sal_Int32 >::const_iterator aIt;
for (aIt = aPostItLastStartPageNum.begin(); aIt != aPostItLastStartPageNum.end(); ++aIt)
{
const sal_Int32 nFrames = aIt->second - nLastStartPageNum;
const sal_Int32 nFirstStartPageNum = aIt == aPostItLastStartPageNum.begin() ?
1 : aIt->second - nFrames + 1;
OSL_ENSURE( 1 <= nFirstStartPageNum && nFirstStartPageNum <= nPostItDocPageCount,
"page number for first frame out of range" );
std::vector< const SwPageFrm * > aStartFrames;
for (sal_Int32 i = 0; i < nFrames; ++i)
{
const sal_Int32 nIdx = nFirstStartPageNum - 1 + i; // -1 because lowest page num is 1
OSL_ENSURE( 0 <= nIdx && nIdx < sal_Int32(aAllPostItStartFrames.size()),
"index out of range" );
aStartFrames.push_back( aAllPostItStartFrames[ nIdx ] );
}
aPhysPageToPostItFrames[ aIt->first /* phys page num */ ] = aStartFrames;
nLastStartPageNum = aIt->second;
}
// ok, now that aPhysPageToPostItFrames can give the start frames for all
// post-it pages to be printed we need to merge those at the correct
// position into the GetPagesToPrint vector and build and maintain the
// GetValidStartFrames vector as well.
// Since inserting a larger number of entries in the middle of a vector
// isn't that efficient we will create new vectors by copying the required data
std::vector< sal_Int32 > aTmpPagesToPrint; std::vector< sal_Int32 > aTmpPagesToPrint;
std::vector< const SwPageFrm * > aTmpPostItStartFrames; sal_Int32 nLastPostItPage(0);
const size_t nNum = rData.GetPagesToPrint().size(); const size_t nNum = rData.GetPagesToPrint().size();
for (size_t i = 0 ; i < nNum; ++i) for (size_t i = 0 ; i < nNum; ++i)
{ {
// add the physical page to print from the document // add the physical page to print from the document
const sal_Int32 nPhysPage = rData.GetPagesToPrint()[i]; const sal_Int32 nPhysPage = rData.GetPagesToPrint()[i];
aTmpPagesToPrint.push_back( nPhysPage ); aTmpPagesToPrint.push_back( nPhysPage );
aTmpPostItStartFrames.push_back( NULL );
// add the post-it document pages to print, i.e those // add the post-it document pages to print, i.e those
// post-it pages that have the data for the above physical page // post-it pages that have the data for the above physical page
const std::vector< const SwPageFrm * > &rPostItFrames = aPhysPageToPostItFrames[ nPhysPage ]; ::std::map<sal_Int32, sal_Int32>::const_iterator const iter(
const size_t nPostItFrames = rPostItFrames.size(); aPostItLastStartPageNum.find(nPhysPage));
for (size_t k = 0; k < nPostItFrames; ++k) if (iter != aPostItLastStartPageNum.end())
{ {
aTmpPagesToPrint.push_back( 0 ); for (sal_Int32 j = nLastPostItPage + 1;
aTmpPostItStartFrames.push_back( rPostItFrames[k] ); j <= iter->second; ++j)
{
// negative page number indicates page is from the
aTmpPagesToPrint.push_back(-j); // post-it document
}
nLastPostItPage = iter->second;
} }
} }
...@@ -1564,7 +1511,6 @@ void SwDoc::UpdatePagesForPrintingWithPostItData( ...@@ -1564,7 +1511,6 @@ void SwDoc::UpdatePagesForPrintingWithPostItData(
// swapping the data should be more efficient than assigning since // swapping the data should be more efficient than assigning since
// we won't need the temporary vectors anymore // we won't need the temporary vectors anymore
rData.GetPagesToPrint().swap( aTmpPagesToPrint ); rData.GetPagesToPrint().swap( aTmpPagesToPrint );
rData.GetPostItStartFrames().swap( aTmpPostItStartFrames );
} }
} }
} }
...@@ -1577,12 +1523,11 @@ void SwDoc::CalculatePagePairsForProspectPrinting( ...@@ -1577,12 +1523,11 @@ void SwDoc::CalculatePagePairsForProspectPrinting(
{ {
std::map< sal_Int32, sal_Int32 > &rPrinterPaperTrays = rData.GetPrinterPaperTrays(); std::map< sal_Int32, sal_Int32 > &rPrinterPaperTrays = rData.GetPrinterPaperTrays();
std::set< sal_Int32 > &rValidPagesSet = rData.GetValidPagesSet(); std::set< sal_Int32 > &rValidPagesSet = rData.GetValidPagesSet();
std::map< sal_Int32, const SwPageFrm * > &rValidStartFrms = rData.GetValidStartFrames();
std::vector< std::pair< sal_Int32, sal_Int32 > > &rPagePairs = rData.GetPagePairsForProspectPrinting(); std::vector< std::pair< sal_Int32, sal_Int32 > > &rPagePairs = rData.GetPagePairsForProspectPrinting();
std::map< sal_Int32, const SwPageFrm * > validStartFrms;
rPagePairs.clear(); rPagePairs.clear();
rValidPagesSet.clear(); rValidPagesSet.clear();
rValidStartFrms.clear();
rtl::OUString aPageRange; rtl::OUString aPageRange;
// PageContent : // PageContent :
...@@ -1620,7 +1565,7 @@ void SwDoc::CalculatePagePairsForProspectPrinting( ...@@ -1620,7 +1565,7 @@ void SwDoc::CalculatePagePairsForProspectPrinting(
OSL_ENSURE( pPageFrm, "Empty page frame. How are we going to print this?" ); OSL_ENSURE( pPageFrm, "Empty page frame. How are we going to print this?" );
++nPageNum; ++nPageNum;
rValidPagesSet.insert( nPageNum ); rValidPagesSet.insert( nPageNum );
rValidStartFrms[ nPageNum ] = pPageFrm; validStartFrms[ nPageNum ] = pPageFrm;
pPageFrm = (SwPageFrm*)pPageFrm->GetNext(); pPageFrm = (SwPageFrm*)pPageFrm->GetNext();
rPrinterPaperTrays[ nPageNum ] = lcl_GetPaperBin( pStPage ); rPrinterPaperTrays[ nPageNum ] = lcl_GetPaperBin( pStPage );
...@@ -1649,7 +1594,7 @@ void SwDoc::CalculatePagePairsForProspectPrinting( ...@@ -1649,7 +1594,7 @@ void SwDoc::CalculatePagePairsForProspectPrinting(
for ( i = 0; i < sal_Int32(aPagesToPrint.size()); ++i) for ( i = 0; i < sal_Int32(aPagesToPrint.size()); ++i)
{ {
const sal_Int32 nPage = aPagesToPrint[i]; const sal_Int32 nPage = aPagesToPrint[i];
const SwPageFrm *pFrm = rValidStartFrms[ nPage ]; const SwPageFrm *pFrm = validStartFrms[ nPage ];
aVec.push_back( pFrm ); aVec.push_back( pFrm );
} }
......
...@@ -59,6 +59,9 @@ ...@@ -59,6 +59,9 @@
using namespace ::com::sun::star; using namespace ::com::sun::star;
SwPageFrm const*
lcl_getPage(SwRootFrm const& rLayout, sal_Int32 const nPage); // vprint.cxx
SwPagePreviewLayout* ViewShell::PagePreviewLayout() SwPagePreviewLayout* ViewShell::PagePreviewLayout()
{ {
return Imp()->PagePreviewLayout(); return Imp()->PagePreviewLayout();
...@@ -125,18 +128,13 @@ void ViewShell::PrintProspect( ...@@ -125,18 +128,13 @@ void ViewShell::PrintProspect(
const SwPageFrm *pStPage = 0; const SwPageFrm *pStPage = 0;
const SwPageFrm *pNxtPage = 0; const SwPageFrm *pNxtPage = 0;
const SwRenderData::ValidStartFramesMap_t &rFrms = rPrintData.GetRenderData().GetValidStartFrames();
if (rPagesToPrint.first > 0) if (rPagesToPrint.first > 0)
{ {
SwRenderData::ValidStartFramesMap_t::const_iterator aIt( rFrms.find( rPagesToPrint.first ) ); pStPage = lcl_getPage(*aShell.GetLayout(), rPagesToPrint.first);
OSL_ENSURE( aIt != rFrms.end(), "failed to find start frame" );
pStPage = aIt->second;
} }
if (rPagesToPrint.second > 0) if (rPagesToPrint.second > 0)
{ {
SwRenderData::ValidStartFramesMap_t::const_iterator aIt( rFrms.find( rPagesToPrint.second ) ); pNxtPage = lcl_getPage(*aShell.GetLayout(), rPagesToPrint.second);
OSL_ENSURE( aIt != rFrms.end(), "failed to find start frame" );
pNxtPage = aIt->second;
} }
//#i14016# - consider empty pages on calculation of page size, used for calculation of scaling. //#i14016# - consider empty pages on calculation of page size, used for calculation of scaling.
......
...@@ -437,6 +437,24 @@ SwDoc * ViewShell::FillPrtDoc( SwDoc *pPrtDoc, const SfxPrinter* pPrt) ...@@ -437,6 +437,24 @@ SwDoc * ViewShell::FillPrtDoc( SwDoc *pPrtDoc, const SfxPrinter* pPrt)
return pPrtDoc; return pPrtDoc;
} }
// TODO: there is already a GetPageByPageNum, but it checks some physical page
// number; unsure if we want that here, should find out what that is...
SwPageFrm const*
lcl_getPage(SwRootFrm const& rLayout, sal_Int32 const nPage)
{
// yes this is O(n^2) but at least it does not crash...
SwPageFrm const* pPage = dynamic_cast<const SwPageFrm*>(rLayout.Lower());
for (sal_Int32 i = nPage; pPage && (i > 0); --i)
{
if (1 == i) { // note: nPage is 1-based, i.e. 0 is invalid!
return pPage;
}
pPage = dynamic_cast<SwPageFrm const*>(pPage->GetNext());
}
OSL_ENSURE(pPage, "ERROR: SwPageFrm expected");
OSL_FAIL("non-existent page requested");
return 0;
}
sal_Bool ViewShell::PrintOrPDFExport( sal_Bool ViewShell::PrintOrPDFExport(
OutputDevice *pOutDev, OutputDevice *pOutDev,
...@@ -483,30 +501,23 @@ sal_Bool ViewShell::PrintOrPDFExport( ...@@ -483,30 +501,23 @@ sal_Bool ViewShell::PrintOrPDFExport(
pShell->PrepareForPrint( rPrintData ); pShell->PrepareForPrint( rPrintData );
const sal_Int32 nPage = rPrintData.GetRenderData().GetPagesToPrint()[ nRenderer ]; const sal_Int32 nPage = rPrintData.GetRenderData().GetPagesToPrint()[ nRenderer ];
#if OSL_DEBUG_LEVEL > 1 OSL_ENSURE( nPage < 0 ||
OSL_ENSURE( nPage == 0 || rPrintData.GetRenderData().GetValidPagesSet().count( nPage ) == 1, "nPage not valid" ); rPrintData.GetRenderData().GetValidPagesSet().count( nPage ) == 1,
#endif "ViewShell::PrintOrPDFExport: nPage not valid" );
const SwPageFrm *pStPage = 0; ViewShell *const pViewSh2 = (nPage < 0)
if (nPage > 0) // a 'regular' page, not one from the post-it document ? rPrintData.GetRenderData().m_pPostItShell // post-it page
{ : pShell; // a 'regular' page, not one from the post-it doc
const SwRenderData::ValidStartFramesMap_t &rFrms = rPrintData.GetRenderData().GetValidStartFrames();
SwRenderData::ValidStartFramesMap_t::const_iterator aIt( rFrms.find( nPage ) ); SwPageFrm const*const pStPage =
OSL_ENSURE( aIt != rFrms.end(), "failed to find start frame" ); lcl_getPage(*pViewSh2->GetLayout(), abs(nPage));
if (aIt == rFrms.end()) OSL_ENSURE( pStPage, "failed to get start page" );
return sal_False; if (!pStPage)
pStPage = aIt->second;
}
else // a page from the post-its document ...
{ {
OSL_ENSURE( nPage == 0, "unexpected page number. 0 for post-it pages expected" ); return sal_False;
pStPage = rPrintData.GetRenderData().GetPostItStartFrames()[ nRenderer ];
} }
OSL_ENSURE( pStPage, "failed to get start page" );
//!! applying view options and formatting the dcoument should now only be done in getRendererCount! //!! applying view options and formatting the dcoument should now only be done in getRendererCount!
ViewShell *pViewSh2 = nPage == 0 ? /* post-it page? */
rPrintData.GetRenderData().m_pPostItShell : pShell;
::SetSwVisArea( pViewSh2, pStPage->Frm() ); ::SetSwVisArea( pViewSh2, pStPage->Frm() );
// FIXME disabled because rPrintData.aOffset is always (0,0) // FIXME disabled because rPrintData.aOffset is always (0,0)
...@@ -523,8 +534,6 @@ sal_Bool ViewShell::PrintOrPDFExport( ...@@ -523,8 +534,6 @@ sal_Bool ViewShell::PrintOrPDFExport(
pShell->InitPrt( pOutDev ); pShell->InitPrt( pOutDev );
pViewSh2 = nPage == 0 ? /* post-it page? */
rPrintData.GetRenderData().m_pPostItShell : pShell;
::SetSwVisArea( pViewSh2, pStPage->Frm() ); ::SetSwVisArea( pViewSh2, pStPage->Frm() );
pStPage->GetUpper()->Paint( pStPage->Frm(), &rPrintData ); pStPage->GetUpper()->Paint( pStPage->Frm(), &rPrintData );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment