Files
libreoffice/sw/source/core/layout/newfrm.cxx
Luke Deller 14bb680949 Insert blank page when first page number is even
When the page number is explicitly changed at a page break,
LibreOffice will insert a blank page if necessary to ensure that
even page numbers appear on "left" pages.

This commit fixes a case that was missed: the case where the page
number of the very first page in the document is explicitly set to
be an even number.

Also:

 - adjust a couple of unit tests which were referring to specific
   physical page numbers, that were not expecting this blank page to be
   there

 - enhance SwModelTestBase::parseDump to support xpath expressions
   evaluating to simple values rather than nodes, for use in a
   test case for this change

Change-Id: I1f41760c3bb17bdffb868cf32a1331de87d1d0e1
Reviewed-on: https://gerrit.libreoffice.org/39858
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
2017-09-01 10:45:43 +02:00

676 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <svx/svdmodel.hxx>
#include <svx/svdpage.hxx>
#include <drawdoc.hxx>
#include <fmtpdsc.hxx>
#include <swtable.hxx>
#include <rootfrm.hxx>
#include <pagefrm.hxx>
#include <dflyobj.hxx>
#include <frmtool.hxx>
#include <virtoutp.hxx>
#include <blink.hxx>
#include <sectfrm.hxx>
#include <notxtfrm.hxx>
#include <pagedesc.hxx>
#include "viewimp.hxx"
#include <hints.hxx>
#include <viewopt.hxx>
#include <set>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <DocumentLayoutManager.hxx>
SwLayVout *SwRootFrame::s_pVout = nullptr;
bool SwRootFrame::s_isInPaint = false;
bool SwRootFrame::s_isNoVirDev = false;
SwCache *SwFrame::mpCache = nullptr;
long FirstMinusSecond( long nFirst, long nSecond )
{ return nFirst - nSecond; }
long SecondMinusFirst( long nFirst, long nSecond )
{ return nSecond - nFirst; }
long SwIncrement( long nA, long nAdd )
{ return nA + nAdd; }
long SwDecrement( long nA, long nSub )
{ return nA - nSub; }
static SwRectFnCollection aHorizontal = {
/* fnRectGet */
&SwRect::Top_,
&SwRect::Bottom_,
&SwRect::Left_,
&SwRect::Rigth_,
&SwRect::Width_,
&SwRect::Height_,
&SwRect::TopLeft,
&SwRect::Size_,
/* fnRectSet */
&SwRect::Top_,
&SwRect::Bottom_,
&SwRect::Left_,
&SwRect::Rigth_,
&SwRect::Width_,
&SwRect::Height_,
&SwRect::SubTop,
&SwRect::AddBottom,
&SwRect::SubLeft,
&SwRect::AddRight,
&SwRect::AddWidth,
&SwRect::AddHeight,
&SwRect::SetPosX,
&SwRect::SetPosY,
&SwFrame::GetTopMargin,
&SwFrame::GetBottomMargin,
&SwFrame::GetLeftMargin,
&SwFrame::GetRightMargin,
&SwFrame::SetLeftRightMargins,
&SwFrame::SetTopBottomMargins,
&SwFrame::GetPrtTop,
&SwFrame::GetPrtBottom,
&SwFrame::GetPrtLeft,
&SwFrame::GetPrtRight,
&SwRect::GetTopDistance,
&SwRect::GetBottomDistance,
&SwRect::GetLeftDistance,
&SwRect::GetRightDistance,
&SwFrame::SetMaxBottom,
&SwRect::OverStepBottom,
&SwRect::SetUpperLeftCorner,
&SwFrame::MakeBelowPos,
&FirstMinusSecond,
&FirstMinusSecond,
&SwIncrement,
&SwIncrement,
&SwRect::SetLeftAndWidth,
&SwRect::SetTopAndHeight
};
static SwRectFnCollection aVertical = {
/* fnRectGet */
&SwRect::Rigth_,
&SwRect::Left_,
&SwRect::Top_,
&SwRect::Bottom_,
&SwRect::Height_,
&SwRect::Width_,
&SwRect::TopRight,
&SwRect::SwappedSize,
/* fnRectSet */
&SwRect::Rigth_,
&SwRect::Left_,
&SwRect::Top_,
&SwRect::Bottom_,
&SwRect::Height_,
&SwRect::Width_,
&SwRect::AddRight,
&SwRect::SubLeft,
&SwRect::SubTop,
&SwRect::AddBottom,
&SwRect::AddHeight,
&SwRect::AddWidth,
&SwRect::SetPosY,
&SwRect::SetPosX,
&SwFrame::GetRightMargin,
&SwFrame::GetLeftMargin,
&SwFrame::GetTopMargin,
&SwFrame::GetBottomMargin,
&SwFrame::SetTopBottomMargins,
&SwFrame::SetRightLeftMargins,
&SwFrame::GetPrtRight,
&SwFrame::GetPrtLeft,
&SwFrame::GetPrtTop,
&SwFrame::GetPrtBottom,
&SwRect::GetRightDistance,
&SwRect::GetLeftDistance,
&SwRect::GetTopDistance,
&SwRect::GetBottomDistance,
&SwFrame::SetMinLeft,
&SwRect::OverStepLeft,
&SwRect::SetUpperRightCorner,
&SwFrame::MakeLeftPos,
&FirstMinusSecond,
&SecondMinusFirst,
&SwIncrement,
&SwDecrement,
&SwRect::SetTopAndHeight,
&SwRect::SetRightAndWidth
};
static SwRectFnCollection aBottomToTop = {
/* fnRectGet */
&SwRect::Bottom_,
&SwRect::Top_,
&SwRect::Left_,
&SwRect::Rigth_,
&SwRect::Width_,
&SwRect::Height_,
&SwRect::BottomLeft,
&SwRect::Size_,
/* fnRectSet */
&SwRect::Bottom_,
&SwRect::Top_,
&SwRect::Left_,
&SwRect::Rigth_,
&SwRect::Width_,
&SwRect::Height_,
&SwRect::AddBottom,
&SwRect::SubTop,
&SwRect::SubLeft,
&SwRect::AddRight,
&SwRect::AddWidth,
&SwRect::AddHeight,
&SwRect::SetPosX,
&SwRect::SetPosY,
&SwFrame::GetBottomMargin,
&SwFrame::GetTopMargin,
&SwFrame::GetLeftMargin,
&SwFrame::GetRightMargin,
&SwFrame::SetLeftRightMargins,
&SwFrame::SetBottomTopMargins,
&SwFrame::GetPrtBottom,
&SwFrame::GetPrtTop,
&SwFrame::GetPrtLeft,
&SwFrame::GetPrtRight,
&SwRect::GetBottomDistance,
&SwRect::GetTopDistance,
&SwRect::GetLeftDistance,
&SwRect::GetRightDistance,
&SwFrame::SetMinTop,
&SwRect::OverStepTop,
&SwRect::SetLowerLeftCorner,
&SwFrame::MakeUpperPos,
&FirstMinusSecond,
&SecondMinusFirst,
&SwIncrement,
&SwDecrement,
&SwRect::SetLeftAndWidth,
&SwRect::SetBottomAndHeight
};
static SwRectFnCollection aVerticalRightToLeft = {
/* fnRectGet */
&SwRect::Left_,
&SwRect::Rigth_,
&SwRect::Top_,
&SwRect::Bottom_,
&SwRect::Height_,
&SwRect::Width_,
&SwRect::BottomRight,
&SwRect::SwappedSize,
/* fnRectSet */
&SwRect::Left_,
&SwRect::Rigth_,
&SwRect::Top_,
&SwRect::Bottom_,
&SwRect::Height_,
&SwRect::Width_,
&SwRect::SubLeft,
&SwRect::AddRight,
&SwRect::SubTop,
&SwRect::AddBottom,
&SwRect::AddHeight,
&SwRect::AddWidth,
&SwRect::SetPosY,
&SwRect::SetPosX,
&SwFrame::GetLeftMargin,
&SwFrame::GetRightMargin,
&SwFrame::GetTopMargin,
&SwFrame::GetBottomMargin,
&SwFrame::SetTopBottomMargins,
&SwFrame::SetLeftRightMargins,
&SwFrame::GetPrtLeft,
&SwFrame::GetPrtRight,
&SwFrame::GetPrtBottom,
&SwFrame::GetPrtTop,
&SwRect::GetLeftDistance,
&SwRect::GetRightDistance,
&SwRect::GetBottomDistance,
&SwRect::GetTopDistance,
&SwFrame::SetMaxRight,
&SwRect::OverStepRight,
&SwRect::SetLowerLeftCorner,
&SwFrame::MakeRightPos,
&FirstMinusSecond,
&FirstMinusSecond,
&SwDecrement,
&SwIncrement,
&SwRect::SetBottomAndHeight,
&SwRect::SetLeftAndWidth
};
static SwRectFnCollection aVerticalLeftToRight = {
/* fnRectGet */
&SwRect::Left_,
&SwRect::Rigth_,
&SwRect::Top_,
&SwRect::Bottom_,
&SwRect::Height_,
&SwRect::Width_,
&SwRect::TopLeft,
&SwRect::SwappedSize,
/* fnRectSet */
&SwRect::Left_,
&SwRect::Rigth_,
&SwRect::Top_,
&SwRect::Bottom_,
&SwRect::Height_,
&SwRect::Width_,
&SwRect::SubLeft,
&SwRect::AddRight,
&SwRect::SubTop,
&SwRect::AddBottom,
&SwRect::AddHeight,
&SwRect::AddWidth,
&SwRect::SetPosY,
&SwRect::SetPosX,
&SwFrame::GetLeftMargin,
&SwFrame::GetRightMargin,
&SwFrame::GetTopMargin,
&SwFrame::GetBottomMargin,
&SwFrame::SetTopBottomMargins,
&SwFrame::SetLeftRightMargins,
&SwFrame::GetPrtLeft,
&SwFrame::GetPrtRight,
&SwFrame::GetPrtTop,
&SwFrame::GetPrtBottom,
&SwRect::GetLeftDistance,
&SwRect::GetRightDistance,
&SwRect::GetTopDistance,
&SwRect::GetBottomDistance,
&SwFrame::SetMaxRight,
&SwRect::OverStepRight,
&SwRect::SetUpperLeftCorner,
&SwFrame::MakeRightPos,
&FirstMinusSecond,
&FirstMinusSecond,
&SwIncrement,
&SwIncrement,
&SwRect::SetTopAndHeight,
&SwRect::SetLeftAndWidth
};
SwRectFn fnRectHori = &aHorizontal;
SwRectFn fnRectVert = &aVertical;
SwRectFn fnRectVertL2R = &aVerticalLeftToRight;
SwRectFn fnRectB2T = &aBottomToTop;
SwRectFn fnRectVL2R = &aVerticalRightToLeft;
// #i65250#
sal_uInt32 SwFrame::mnLastFrameId=0;
void FrameInit()
{
SwRootFrame::s_pVout = new SwLayVout();
SwCache *pNew = new SwCache( 100
#ifdef DBG_UTIL
, "static SwBorderAttrs::pCache"
#endif
);
SwFrame::SetCache( pNew );
}
void FrameFinit()
{
#if OSL_DEBUG_LEVEL > 0
// The cache may only contain null pointers at this time.
for( size_t n = SwFrame::GetCachePtr()->size(); n; )
if( (*SwFrame::GetCachePtr())[ --n ] )
{
SwCacheObj* pObj = (*SwFrame::GetCachePtr())[ n ];
OSL_ENSURE( !pObj, "Who didn't deregister?");
}
#endif
delete SwRootFrame::s_pVout;
delete SwFrame::GetCachePtr();
}
// RootFrame::Everything that belongs to CurrShell
CurrShell::CurrShell( SwViewShell *pNew )
{
OSL_ENSURE( pNew, "insert 0-Shell?" );
pRoot = pNew->GetLayout();
if ( pRoot )
{
pPrev = pRoot->mpCurrShell;
pRoot->mpCurrShell = pNew;
pRoot->mpCurrShells->insert( this );
}
else
pPrev = nullptr;
}
CurrShell::~CurrShell()
{
if ( pRoot )
{
pRoot->mpCurrShells->erase( this );
if ( pPrev )
pRoot->mpCurrShell = pPrev;
if ( pRoot->mpCurrShells->empty() && pRoot->mpWaitingCurrShell )
{
pRoot->mpCurrShell = pRoot->mpWaitingCurrShell;
pRoot->mpWaitingCurrShell = nullptr;
}
}
}
void SetShell( SwViewShell *pSh )
{
SwRootFrame *pRoot = pSh->GetLayout();
if ( pRoot->mpCurrShells->empty() )
pRoot->mpCurrShell = pSh;
else
pRoot->mpWaitingCurrShell = pSh;
}
void SwRootFrame::DeRegisterShell( SwViewShell *pSh )
{
// Activate some shell if possible
if ( mpCurrShell == pSh )
{
mpCurrShell = nullptr;
for(SwViewShell& rShell : pSh->GetRingContainer())
{
if(&rShell != pSh)
{
mpCurrShell = &rShell;
break;
}
}
}
// Doesn't matter anymore
if ( mpWaitingCurrShell == pSh )
mpWaitingCurrShell = nullptr;
// Remove references
for ( SwCurrShells::iterator it = mpCurrShells->begin(); it != mpCurrShells->end(); ++it )
{
CurrShell *pC = *it;
if (pC->pPrev == pSh)
pC->pPrev = nullptr;
}
}
void InitCurrShells( SwRootFrame *pRoot )
{
pRoot->mpCurrShells = new SwCurrShells;
}
/*
|* The RootFrame requests an own FrameFormat from the document, which it is
|* going to delete again in the dtor. The own FrameFormat is derived from
|* the passed FrameFormat.
|*/
SwRootFrame::SwRootFrame( SwFrameFormat *pFormat, SwViewShell * pSh ) :
SwLayoutFrame( pFormat->GetDoc()->MakeFrameFormat(
"Root", pFormat ), nullptr ),
maPagesArea(),
mnViewWidth( -1 ),
mnColumns( 0 ),
mbBookMode( false ),
mbSidebarChanged( false ),
mbNeedGrammarCheck( false ),
mbCheckSuperfluous( false ),
mbIdleFormat( true ),
mbBrowseWidthValid( false ),
mbTurboAllowed( true ),
mbAssertFlyPages( true ),
mbIsVirtPageNum( false ),
mbIsNewLayout( true ),
mbCallbackActionEnabled ( false ),
mbLayoutFreezed ( false ),
mnBrowseWidth(MIN_BROWSE_WIDTH),
mpTurbo( nullptr ),
mpLastPage( nullptr ),
mpCurrShell( pSh ),
mpWaitingCurrShell( nullptr ),
mpCurrShells(nullptr),
mpDrawPage( nullptr ),
mpDestroy( nullptr ),
mnPhyPageNums( 0 ),
mnAccessibleShells( 0 )
{
mnFrameType = SwFrameType::Root;
setRootFrame( this );
}
void SwRootFrame::Init( SwFrameFormat* pFormat )
{
InitCurrShells( this );
IDocumentTimerAccess& rTimerAccess = pFormat->getIDocumentTimerAccess();
IDocumentLayoutAccess& rLayoutAccess = pFormat->getIDocumentLayoutAccess();
IDocumentFieldsAccess& rFieldsAccess = pFormat->getIDocumentFieldsAccess();
const IDocumentSettingAccess& rSettingAccess = pFormat->getIDocumentSettingAccess();
rTimerAccess.StopIdling();
// For creating the Flys by MakeFrames()
rLayoutAccess.SetCurrentViewShell( GetCurrShell() );
mbCallbackActionEnabled = false; // needs to be set to true before leaving!
SwDrawModel* pMd = pFormat->getIDocumentDrawModelAccess().GetDrawModel();
if ( pMd )
{
// Disable "multiple layout"
mpDrawPage = pMd->GetPage(0);
mpDrawPage->SetSize( Frame().SSize() );
}
// Initialize the layout: create pages, link content with Content etc.
// First, initialize some stuff, then get hold of the first
// node (which will be needed for the PageDesc).
SwDoc* pDoc = pFormat->GetDoc();
SwNodeIndex aIndex( *pDoc->GetNodes().GetEndOfContent().StartOfSectionNode() );
SwContentNode *pNode = pDoc->GetNodes().GoNextSection( &aIndex, true, false );
// #123067# pNode = 0 can really happen
SwTableNode *pTableNd= pNode ? pNode->FindTableNode() : nullptr;
// Get hold of PageDesc (either via FrameFormat of the first node or the initial one).
SwPageDesc *pDesc = nullptr;
::boost::optional<sal_uInt16> oPgNum;
if ( pTableNd )
{
const SwFormatPageDesc &rDesc = pTableNd->GetTable().GetFrameFormat()->GetPageDesc();
pDesc = const_cast<SwPageDesc*>(rDesc.GetPageDesc());
//#19104# respect the page number offset!!
oPgNum = rDesc.GetNumOffset();
if (oPgNum)
mbIsVirtPageNum = true;
}
else if ( pNode )
{
const SwFormatPageDesc &rDesc = pNode->GetSwAttrSet().GetPageDesc();
pDesc = const_cast<SwPageDesc*>(rDesc.GetPageDesc());
//#19104# respect the page number offset!!
oPgNum = rDesc.GetNumOffset();
if (oPgNum)
mbIsVirtPageNum = true;
}
else
mbIsVirtPageNum = false;
if ( !pDesc )
pDesc = &pDoc->GetPageDesc( 0 );
const bool bOdd = !oPgNum || 0 != ( oPgNum.get() % 2 );
const bool bFirst = true;
// Even page numbers are supposed to be printed as left pages. So if a
// page number has been explicitly set for this first page, then we must
// insert a blank page before it to make it a left page.
const bool bInsertEmpty = !bOdd;
// Create a page and put it in the layout
SwPageFrame *pPage = ::InsertNewPage( *pDesc, this, bOdd, bFirst, bInsertEmpty, false, nullptr );
// Find the first page in the Bodytext section.
SwLayoutFrame *pLay = pPage->FindBodyCont();
while( pLay->Lower() )
pLay = static_cast<SwLayoutFrame*>(pLay->Lower());
SwNodeIndex aTmp( *pDoc->GetNodes().GetEndOfContent().StartOfSectionNode(), 1 );
::InsertCnt_( pLay, pDoc, aTmp.GetIndex(), true );
//Remove masters that haven't been replaced yet from the list.
RemoveMasterObjs( mpDrawPage );
if( rSettingAccess.get(DocumentSettingId::GLOBAL_DOCUMENT) )
rFieldsAccess.UpdateRefFields();
//b6433357: Update page fields after loading
if ( !mpCurrShell || !mpCurrShell->Imp()->IsUpdateExpFields() )
{
SwDocPosUpdate aMsgHint( pPage->Frame().Top() );
rFieldsAccess.UpdatePageFields( &aMsgHint );
}
rTimerAccess.StartIdling();
mbCallbackActionEnabled = true;
SwViewShell *pViewSh = GetCurrShell();
if (pViewSh)
mbNeedGrammarCheck = pViewSh->GetViewOptions()->IsOnlineSpell();
}
void SwRootFrame::DestroyImpl()
{
mbTurboAllowed = false;
mpTurbo = nullptr;
if(pBlink)
pBlink->FrameDelete( this );
SwFrameFormat *pRegisteredInNonConst = static_cast<SwFrameFormat*>(GetRegisteredInNonConst());
if ( pRegisteredInNonConst )
{
SwDoc *pDoc = pRegisteredInNonConst->GetDoc();
pDoc->DelFrameFormat( pRegisteredInNonConst );
// do this before calling RemoveFootnotes() because footnotes
// can contain anchored objects
pDoc->GetDocumentLayoutManager().ClearSwLayouterEntries();
}
delete mpDestroy;
mpDestroy = nullptr;
// Remove references
for ( SwCurrShells::iterator it = mpCurrShells->begin(); it != mpCurrShells->end(); ++it )
(*it)->pRoot = nullptr;
delete mpCurrShells;
mpCurrShells = nullptr;
// Some accessible shells are left => problems on second SwFrame::Destroy call
assert(0 == mnAccessibleShells);
// fdo#39510 crash on document close with footnotes
// Object ownership in writer and esp. in layout are a mess: Before the
// document/layout split SwDoc and SwRootFrame were essentially one object
// and magically/uncleanly worked around their common destruction by call
// to SwDoc::IsInDtor() -- even from the layout. As of now destruction of
// the layout proceeds forward through the frames. Since SwTextFootnote::DelFrames
// also searches backwards to find the master of footnotes, they must be
// considered to be owned by the SwRootFrame and also be destroyed here,
// before tearing down the (now footnote free) rest of the layout.
RemoveFootnotes(nullptr, false, true);
SwLayoutFrame::DestroyImpl();
}
SwRootFrame::~SwRootFrame()
{
}
void SwRootFrame::RemoveMasterObjs( SdrPage *pPg )
{
// Remove all master objects from the Page. But don't delete!
for( size_t i = pPg ? pPg->GetObjCount() : 0; i; )
{
SdrObject* pObj = pPg->GetObj( --i );
if( dynamic_cast< const SwFlyDrawObj *>( pObj ) != nullptr )
pPg->RemoveObject( i );
}
}
void SwRootFrame::AllCheckPageDescs() const
{
if ( !IsLayoutFreezed() )
CheckPageDescs( const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower())) );
}
void SwRootFrame::AllInvalidateAutoCompleteWords() const
{
SwPageFrame *pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower()));
while ( pPage )
{
pPage->InvalidateAutoCompleteWords();
pPage = static_cast<SwPageFrame*>(pPage->GetNext());
}
}
void SwRootFrame::AllAddPaintRect() const
{
GetCurrShell()->AddPaintRect( Frame() );
}
void SwRootFrame::AllRemoveFootnotes()
{
RemoveFootnotes();
}
void SwRootFrame::AllInvalidateSmartTagsOrSpelling(bool bSmartTags) const
{
SwPageFrame *pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower()));
while ( pPage )
{
if ( bSmartTags )
pPage->InvalidateSmartTags();
pPage->InvalidateSpelling();
pPage = static_cast<SwPageFrame*>(pPage->GetNext());
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */