diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sd/source/core/drawdoc2.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sd/source/core/drawdoc2.cxx')
-rw-r--r-- | sd/source/core/drawdoc2.cxx | 1382 |
1 files changed, 1382 insertions, 0 deletions
diff --git a/sd/source/core/drawdoc2.cxx b/sd/source/core/drawdoc2.cxx new file mode 100644 index 000000000..d0187bab0 --- /dev/null +++ b/sd/source/core/drawdoc2.cxx @@ -0,0 +1,1382 @@ +/* -*- 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 <vcl/settings.hxx> + +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <sfx2/printer.hxx> +#include <editeng/paperinf.hxx> +#include <svx/svdopage.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdundo.hxx> +#include <vcl/svapp.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/langitem.hxx> +#include <svl/itempool.hxx> +#include <editeng/flditem.hxx> + +#include <sfx2/linkmgr.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdlayer.hxx> + +#include <svx/svditer.hxx> +#include <comphelper/lok.hxx> +#include <xmloff/autolayout.hxx> + +#include <sdresid.hxx> +#include <drawdoc.hxx> +#include <sdpage.hxx> +#include <strings.hrc> +#include <glob.hxx> +#include <stlpool.hxx> +#include <anminfo.hxx> +#include <undo/undomanager.hxx> +#include <sfx2/lokhelper.hxx> +#include <unomodel.hxx> + +#include <DrawDocShell.hxx> + +#include "PageListWatcher.hxx" +#include <unokywds.hxx> + +using namespace ::sd; + +const ::tools::Long PRINT_OFFSET = 30; // see /svx/source/dialog/page.cxx + +using namespace com::sun::star; + +// Looks up an object by name +SdrObject* SdDrawDocument::GetObj(std::u16string_view rObjName) const +{ + SdrObject* pObj = nullptr; + SdrObject* pObjFound = nullptr; + const SdPage* pPage = nullptr; + + // First search in all pages + sal_uInt16 nPage = 0; + const sal_uInt16 nMaxPages = GetPageCount(); + + while (nPage < nMaxPages && !pObjFound) + { + pPage = static_cast<const SdPage*>( GetPage(nPage) ); + SdrObjListIter aIter(pPage, SdrIterMode::DeepWithGroups); + + while (aIter.IsMore() && !pObjFound) + { + pObj = aIter.Next(); + + if( ( pObj->GetName() == rObjName ) || + ( SdrInventor::Default == pObj->GetObjInventor() && + SdrObjKind::OLE2 == pObj->GetObjIdentifier() && + rObjName == static_cast< SdrOle2Obj* >( pObj )->GetPersistName() ) ) + { + pObjFound = pObj; + } + } + + nPage++; + } + + // If it couldn't be found, look through all master pages + nPage = 0; + const sal_uInt16 nMaxMasterPages = GetMasterPageCount(); + + while (nPage < nMaxMasterPages && !pObjFound) + { + pPage = static_cast<const SdPage*>( GetMasterPage(nPage) ); + SdrObjListIter aIter(pPage, SdrIterMode::DeepWithGroups); + + while (aIter.IsMore() && !pObjFound) + { + pObj = aIter.Next(); + + if( ( pObj->GetName() == rObjName ) || + ( SdrInventor::Default == pObj->GetObjInventor() && + SdrObjKind::OLE2 == pObj->GetObjIdentifier() && + rObjName == static_cast< SdrOle2Obj* >( pObj )->GetPersistName() ) ) + { + pObjFound = pObj; + } + } + + nPage++; + } + + return pObjFound; +} + +// Find SdPage by name +sal_uInt16 SdDrawDocument::GetPageByName(std::u16string_view rPgName, bool& rbIsMasterPage) const +{ + SdPage* pPage = nullptr; + sal_uInt16 nPage = 0; + const sal_uInt16 nMaxPages = GetPageCount(); + sal_uInt16 nPageNum = SDRPAGE_NOTFOUND; + + rbIsMasterPage = false; + + // Search all regular pages and all notes pages (handout pages are + // ignored) + while (nPage < nMaxPages && nPageNum == SDRPAGE_NOTFOUND) + { + pPage = const_cast<SdPage*>(static_cast<const SdPage*>( + GetPage(nPage))); + + if (pPage != nullptr + && pPage->GetPageKind() != PageKind::Handout + && pPage->GetName() == rPgName) + { + nPageNum = nPage; + } + + nPage++; + } + + // Search all master pages when not found among non-master pages + const sal_uInt16 nMaxMasterPages = GetMasterPageCount(); + nPage = 0; + + while (nPage < nMaxMasterPages && nPageNum == SDRPAGE_NOTFOUND) + { + pPage = const_cast<SdPage*>(static_cast<const SdPage*>( + GetMasterPage(nPage))); + + if (pPage && pPage->GetName() == rPgName) + { + nPageNum = nPage; + rbIsMasterPage = true; + } + + nPage++; + } + + return nPageNum; +} + +bool SdDrawDocument::IsPageNameUnique( std::u16string_view rPgName ) const +{ + sal_uInt16 nCount = 0; + SdPage* pPage = nullptr; + + // Search all regular pages and all notes pages (handout pages are ignored) + sal_uInt16 nPage = 0; + sal_uInt16 nMaxPages = GetPageCount(); + while (nPage < nMaxPages) + { + pPage = const_cast<SdPage*>(static_cast<const SdPage*>(GetPage(nPage))); + + if (pPage && pPage->GetName() == rPgName && pPage->GetPageKind() != PageKind::Handout) + nCount++; + + nPage++; + } + + // Search all master pages + nPage = 0; + nMaxPages = GetMasterPageCount(); + while (nPage < nMaxPages) + { + pPage = const_cast<SdPage*>(static_cast<const SdPage*>(GetMasterPage(nPage))); + + if (pPage && pPage->GetName() == rPgName) + nCount++; + + nPage++; + } + + return nCount == 1; +} + +SdPage* SdDrawDocument::GetSdPage(sal_uInt16 nPgNum, PageKind ePgKind) const +{ + return mpDrawPageListWatcher->GetSdPage(ePgKind, sal_uInt32(nPgNum)); +} + +sal_uInt16 SdDrawDocument::GetSdPageCount(PageKind ePgKind) const +{ + return static_cast<sal_uInt16>(mpDrawPageListWatcher->GetSdPageCount(ePgKind)); +} + +SdPage* SdDrawDocument::GetMasterSdPage(sal_uInt16 nPgNum, PageKind ePgKind) +{ + return mpMasterPageListWatcher->GetSdPage(ePgKind, sal_uInt32(nPgNum)); +} + +sal_uInt16 SdDrawDocument::GetMasterSdPageCount(PageKind ePgKind) const +{ + return static_cast<sal_uInt16>(mpMasterPageListWatcher->GetSdPageCount(ePgKind)); +} + +sal_uInt16 SdDrawDocument::GetActiveSdPageCount() const +{ + return static_cast<sal_uInt16>(mpDrawPageListWatcher->GetVisibleSdPageCount()); +} + +// Adapt the page numbers that are registered in the page objects of the notes +// pages +void SdDrawDocument::UpdatePageObjectsInNotes(sal_uInt16 nStartPos) +{ + sal_uInt16 nPageCount = GetPageCount(); + SdPage* pPage = nullptr; + + for (sal_uInt16 nPage = nStartPos; nPage < nPageCount; nPage++) + { + pPage = static_cast<SdPage*>( GetPage(nPage) ); + + // If this is a notes page, find its page object and correct the page + // number + if (pPage && pPage->GetPageKind() == PageKind::Notes) + { + const size_t nObjCount = pPage->GetObjCount(); + for (size_t nObj = 0; nObj < nObjCount; ++nObj) + { + SdrObject* pObj = pPage->GetObj(nObj); + if (pObj->GetObjIdentifier() == SdrObjKind::Page && + pObj->GetObjInventor() == SdrInventor::Default) + { + // The page object is the preceding page (drawing page) + SAL_WARN_IF(!nStartPos, "sd", "Position of notes page must not be 0."); + + SAL_WARN_IF(nPage <= 1, "sd", "Page object must not be a handout."); + + if (nStartPos > 0 && nPage > 1) + static_cast<SdrPageObj*>(pObj)->SetReferencedPage(GetPage(nPage - 1)); + } + } + } + } +} + +void SdDrawDocument::UpdatePageRelativeURLs(const OUString& rOldName, std::u16string_view rNewName) +{ + if (rNewName.empty()) + return; + + SfxItemPool& rPool(GetPool()); + for (const SfxPoolItem* pItem : rPool.GetItemSurrogates(EE_FEATURE_FIELD)) + { + const SvxFieldItem* pFldItem = dynamic_cast< const SvxFieldItem * > (pItem); + + if(pFldItem) + { + SvxURLField* pURLField = const_cast< SvxURLField* >( dynamic_cast<const SvxURLField*>( pFldItem->GetField() ) ); + + if(pURLField) + { + OUString aURL = pURLField->GetURL(); + + if (!aURL.isEmpty() && (aURL[0] == 35) && (aURL.indexOf(rOldName, 1) == 1)) + { + if (aURL.getLength() == rOldName.getLength() + 1) // standard page name + { + aURL = aURL.replaceAt(1, aURL.getLength() - 1, u"") + + rNewName; + pURLField->SetURL(aURL); + } + else + { + const OUString sNotes(SdResId(STR_NOTES)); + if (aURL.getLength() == rOldName.getLength() + 2 + sNotes.getLength() + && aURL.indexOf(sNotes, rOldName.getLength() + 2) == rOldName.getLength() + 2) + { + aURL = aURL.replaceAt(1, aURL.getLength() - 1, u"") + + rNewName + " " + sNotes; + pURLField->SetURL(aURL); + } + } + } + } + } + } +} + +void SdDrawDocument::UpdatePageRelativeURLs(SdPage const * pPage, sal_uInt16 nPos, sal_Int32 nIncrement) +{ + bool bNotes = (pPage->GetPageKind() == PageKind::Notes); + + SfxItemPool& rPool(GetPool()); + for (const SfxPoolItem* pItem : rPool.GetItemSurrogates(EE_FEATURE_FIELD)) + { + const SvxFieldItem* pFldItem; + + if ((pFldItem = dynamic_cast< const SvxFieldItem * > (pItem)) != nullptr) + { + SvxURLField* pURLField = const_cast< SvxURLField* >( dynamic_cast<const SvxURLField*>( pFldItem->GetField() ) ); + + if(pURLField) + { + OUString aURL = pURLField->GetURL(); + + if (!aURL.isEmpty() && (aURL[0] == 35)) + { + OUString aHashSlide = "#" + SdResId(STR_PAGE); + + if (aURL.startsWith(aHashSlide)) + { + OUString aURLCopy = aURL; + const OUString sNotes(SdResId(STR_NOTES)); + + aURLCopy = aURLCopy.replaceAt(0, aHashSlide.getLength(), u""); + + bool bNotesLink = ( aURLCopy.getLength() >= sNotes.getLength() + 3 + && aURLCopy.endsWith(sNotes) ); + + if (bNotesLink != bNotes) + continue; // no compatible link and page + + if (bNotes) + aURLCopy = aURLCopy.replaceAt(aURLCopy.getLength() - sNotes.getLength(), sNotes.getLength(), u""); + + sal_Int32 number = aURLCopy.toInt32(); + sal_uInt16 realPageNumber = (nPos + 1)/ 2; + + if ( number >= realPageNumber ) + { + // update link page number + number += nIncrement; + aURL = aURL.replaceAt(aHashSlide.getLength() + 1, aURL.getLength() - aHashSlide.getLength() - 1, u"") + + OUString::number(number); + if (bNotes) + { + aURL += " " + sNotes; + } + pURLField->SetURL(aURL); + } + } + } + } + } + } +} + +// Move page +void SdDrawDocument::MovePage(sal_uInt16 nPgNum, sal_uInt16 nNewPos) +{ + FmFormModel::MovePage(nPgNum, nNewPos); + + sal_uInt16 nMin = std::min(nPgNum, nNewPos); + + UpdatePageObjectsInNotes(nMin); +} + +// Insert page +void SdDrawDocument::InsertPage(SdrPage* pPage, sal_uInt16 nPos) +{ + bool bLast = (nPos == GetPageCount()); + + FmFormModel::InsertPage(pPage, nPos); + + static_cast<SdPage*>(pPage)->ConnectLink(); + + UpdatePageObjectsInNotes(nPos); + + if (!bLast) + UpdatePageRelativeURLs(static_cast<SdPage*>( pPage ), nPos, 1); + + if (comphelper::LibreOfficeKit::isActive() && static_cast<SdPage*>(pPage)->GetPageKind() == PageKind::Standard) + { + SdXImpressDocument* pDoc = comphelper::getFromUnoTunnel<SdXImpressDocument>(this->getUnoModel()); + SfxLokHelper::notifyDocumentSizeChangedAllViews(pDoc); + } +} + +// Delete page +void SdDrawDocument::DeletePage(sal_uInt16 nPgNum) +{ + FmFormModel::DeletePage(nPgNum); + + UpdatePageObjectsInNotes(nPgNum); +} + +// Remove page +rtl::Reference<SdrPage> SdDrawDocument::RemovePage(sal_uInt16 nPgNum) +{ + rtl::Reference<SdrPage> pPage = FmFormModel::RemovePage(nPgNum); + + bool bLast = ((nPgNum+1)/2 == (GetPageCount()+1)/2); + + auto pSdPage = static_cast<SdPage*>(pPage.get()); + pSdPage->DisconnectLink(); + ReplacePageInCustomShows( pSdPage, nullptr ); + UpdatePageObjectsInNotes(nPgNum); + + if (!bLast) + UpdatePageRelativeURLs(pSdPage, nPgNum, -1); + + if (comphelper::LibreOfficeKit::isActive() && pSdPage->GetPageKind() == PageKind::Standard) + { + SdXImpressDocument* pDoc = comphelper::getFromUnoTunnel<SdXImpressDocument>(this->getUnoModel()); + SfxLokHelper::notifyDocumentSizeChangedAllViews(pDoc); + } + + return pPage; +} + +// Warning: This is not called for new master pages created from SdrModel::Merge, +// you also have to modify code in SdDrawDocument::Merge! +void SdDrawDocument::InsertMasterPage(SdrPage* pPage, sal_uInt16 nPos ) +{ + FmFormModel::InsertMasterPage( pPage, nPos ); + if( pPage->IsMasterPage() && (static_cast<SdPage*>(pPage)->GetPageKind() == PageKind::Standard) ) + { + // new master page created, add its style family + SdStyleSheetPool* pStylePool = static_cast<SdStyleSheetPool*>( GetStyleSheetPool() ); + if( pStylePool ) + pStylePool->AddStyleFamily( static_cast<SdPage*>(pPage) ); + } +} + +rtl::Reference<SdrPage> SdDrawDocument::RemoveMasterPage(sal_uInt16 nPgNum) +{ + SdPage* pPage = static_cast<SdPage*>(GetMasterPage(nPgNum )); + if( pPage && pPage->IsMasterPage() && (pPage->GetPageKind() == PageKind::Standard) ) + { + // master page removed, remove its style family + SdStyleSheetPool* pStylePool = static_cast<SdStyleSheetPool*>( GetStyleSheetPool() ); + if( pStylePool ) + pStylePool->RemoveStyleFamily( pPage ); + } + + return FmFormModel::RemoveMasterPage(nPgNum); +} + +//Select pages +void SdDrawDocument::SetSelected(SdPage* pPage, bool bSelect) +{ + PageKind ePageKind = pPage->GetPageKind(); + + if (ePageKind == PageKind::Standard) + { + pPage->SetSelected(bSelect); + + const sal_uInt16 nDestPageNum(pPage->GetPageNum() + 1); + SdPage* pNotesPage = nullptr; + + if(nDestPageNum < GetPageCount()) + { + pNotesPage = static_cast<SdPage*>(GetPage(nDestPageNum)); + } + + if (pNotesPage && pNotesPage->GetPageKind() == PageKind::Notes) + { + pNotesPage->SetSelected(bSelect); + } + } + else if (ePageKind == PageKind::Notes) + { + pPage->SetSelected(bSelect); + SdPage* pStandardPage = static_cast<SdPage*>( GetPage( pPage->GetPageNum() - 1 ) ); + + if (pStandardPage && pStandardPage->GetPageKind() == PageKind::Standard) + pStandardPage->SetSelected(bSelect); + } +} + +// If no pages exist yet, create them now +void SdDrawDocument::CreateFirstPages( SdDrawDocument const * pRefDocument /* = 0 */ ) +{ + // If no page exists yet in the model, (File -> New), insert a page + sal_uInt16 nPageCount = GetPageCount(); + + if (nPageCount > 1) + return; + + // #i57181# Paper size depends on Language, like in Writer + Size aDefSize = SvxPaperInfo::GetDefaultPaperSize( MapUnit::Map100thMM ); + + // Insert handout page + rtl::Reference<SdPage> pHandoutPage = AllocSdPage(false); + + SdPage* pRefPage = nullptr; + + if( pRefDocument ) + pRefPage = pRefDocument->GetSdPage( 0, PageKind::Handout ); + + if( pRefPage ) + { + pHandoutPage->SetSize(pRefPage->GetSize()); + pHandoutPage->SetBorder( pRefPage->GetLeftBorder(), pRefPage->GetUpperBorder(), pRefPage->GetRightBorder(), pRefPage->GetLowerBorder() ); + } + else + { + pHandoutPage->SetSize(aDefSize); + pHandoutPage->SetBorder(0, 0, 0, 0); + } + + pHandoutPage->SetPageKind(PageKind::Handout); + pHandoutPage->SetName( SdResId(STR_HANDOUT) ); + InsertPage(pHandoutPage.get(), 0); + + // Insert master page and register this with the handout page + rtl::Reference<SdPage> pHandoutMPage = AllocSdPage(true); + pHandoutMPage->SetSize( pHandoutPage->GetSize() ); + pHandoutMPage->SetPageKind(PageKind::Handout); + pHandoutMPage->SetBorder( pHandoutPage->GetLeftBorder(), + pHandoutPage->GetUpperBorder(), + pHandoutPage->GetRightBorder(), + pHandoutPage->GetLowerBorder() ); + InsertMasterPage(pHandoutMPage.get(), 0); + pHandoutPage->TRG_SetMasterPage( *pHandoutMPage ); + + // Insert page + // If nPageCount==1 is, the model for the clipboard was created, thus a + // default page must already exist + rtl::Reference<SdPage> pPage; + bool bClipboard = false; + + if( pRefDocument ) + pRefPage = pRefDocument->GetSdPage( 0, PageKind::Standard ); + + if (nPageCount == 0) + { + pPage = AllocSdPage(false); + + if( pRefPage ) + { + pPage->SetSize( pRefPage->GetSize() ); + pPage->SetBorder( pRefPage->GetLeftBorder(), pRefPage->GetUpperBorder(), pRefPage->GetRightBorder(), pRefPage->GetLowerBorder() ); + } + else if (meDocType == DocumentType::Draw) + { + // Draw: always use default size with margins + pPage->SetSize(aDefSize); + + SfxPrinter* pPrinter = mpDocSh->GetPrinter(false); + if (pPrinter && pPrinter->IsValid()) + { + Size aOutSize(pPrinter->GetOutputSize()); + Point aPageOffset(pPrinter->GetPageOffset()); + aPageOffset -= pPrinter->PixelToLogic( Point() ); + ::tools::Long nOffset = !aPageOffset.X() && !aPageOffset.Y() ? 0 : PRINT_OFFSET; + + sal_uLong nTop = aPageOffset.Y(); + sal_uLong nLeft = aPageOffset.X(); + sal_uLong nBottom = std::max(::tools::Long(aDefSize.Height() - aOutSize.Height() - nTop + nOffset), ::tools::Long(0)); + sal_uLong nRight = std::max(::tools::Long(aDefSize.Width() - aOutSize.Width() - nLeft + nOffset), ::tools::Long(0)); + + pPage->SetBorder(nLeft, nTop, nRight, nBottom); + } + else + { + // The printer is not available. Use a border of 10mm + // on each side instead. + // This has to be kept synchronized with the border + // width set in the + // SvxPageDescPage::PaperSizeSelect_Impl callback. + pPage->SetBorder(1000, 1000, 1000, 1000); + } + } + else + { + // Impress: always use screen format, landscape. + Size aSz( SvxPaperInfo::GetPaperSize(PAPER_SCREEN_16_9, MapUnit::Map100thMM) ); + pPage->SetSize( Size( aSz.Height(), aSz.Width() ) ); + pPage->SetBorder(0, 0, 0, 0); + } + + InsertPage(pPage.get(), 1); + } + else + { + bClipboard = true; + pPage = static_cast<SdPage*>( GetPage(1) ); + } + + // Insert master page, then register this with the page + rtl::Reference<SdPage> pMPage = AllocSdPage(true); + pMPage->SetSize( pPage->GetSize() ); + pMPage->SetBorder( pPage->GetLeftBorder(), + pPage->GetUpperBorder(), + pPage->GetRightBorder(), + pPage->GetLowerBorder() ); + InsertMasterPage(pMPage.get(), 1); + pPage->TRG_SetMasterPage( *pMPage ); + if( bClipboard ) + pMPage->SetLayoutName( pPage->GetLayoutName() ); + + // Insert notes page + rtl::Reference<SdPage> pNotesPage = AllocSdPage(false); + + if( pRefDocument ) + pRefPage = pRefDocument->GetSdPage( 0, PageKind::Notes ); + + if( pRefPage ) + { + pNotesPage->SetSize( pRefPage->GetSize() ); + pNotesPage->SetBorder( pRefPage->GetLeftBorder(), pRefPage->GetUpperBorder(), pRefPage->GetRightBorder(), pRefPage->GetLowerBorder() ); + } + else + { + // Always use portrait format + if (aDefSize.Height() >= aDefSize.Width()) + { + pNotesPage->SetSize(aDefSize); + } + else + { + pNotesPage->SetSize( Size(aDefSize.Height(), aDefSize.Width()) ); + } + + pNotesPage->SetBorder(0, 0, 0, 0); + } + pNotesPage->SetPageKind(PageKind::Notes); + InsertPage(pNotesPage.get(), 2); + if( bClipboard ) + pNotesPage->SetLayoutName( pPage->GetLayoutName() ); + + // Insert master page, then register this with the notes page + rtl::Reference<SdPage> pNotesMPage = AllocSdPage(true); + pNotesMPage->SetSize( pNotesPage->GetSize() ); + pNotesMPage->SetPageKind(PageKind::Notes); + pNotesMPage->SetBorder( pNotesPage->GetLeftBorder(), + pNotesPage->GetUpperBorder(), + pNotesPage->GetRightBorder(), + pNotesPage->GetLowerBorder() ); + InsertMasterPage(pNotesMPage.get(), 2); + pNotesPage->TRG_SetMasterPage( *pNotesMPage ); + if( bClipboard ) + pNotesMPage->SetLayoutName( pPage->GetLayoutName() ); + + if( !pRefPage && (meDocType != DocumentType::Draw) ) + pPage->SetAutoLayout( AUTOLAYOUT_TITLE, true, true ); + + mpWorkStartupTimer.reset( new Timer("DrawWorkStartupTimer") ); + mpWorkStartupTimer->SetInvokeHandler( LINK(this, SdDrawDocument, WorkStartupHdl) ); + mpWorkStartupTimer->SetTimeout(2000); + mpWorkStartupTimer->Start(); + + SetChanged(false); +} + +// Creates missing notes and handout pages (after PowerPoint import). +// We assume that at least one default page and one default master page exist. + +bool SdDrawDocument::CreateMissingNotesAndHandoutPages() +{ + bool bOK = false; + sal_uInt16 nPageCount = GetPageCount(); + + if (nPageCount != 0) + { + // Set PageKind + SdPage* pHandoutMPage = static_cast<SdPage*>( GetMasterPage(0) ); + pHandoutMPage->SetPageKind(PageKind::Handout); + + SdPage* pHandoutPage = static_cast<SdPage*>( GetPage(0) ); + pHandoutPage->SetPageKind(PageKind::Handout); + pHandoutPage->TRG_SetMasterPage( *pHandoutMPage ); + + for (sal_uInt16 i = 1; i < nPageCount; i = i + 2) + { + SdPage* pPage = static_cast<SdPage*>( GetPage(i) ); + + if(!pPage->TRG_HasMasterPage()) + { + // No master page set -> use first default master page + // (If there was no default page in the PPT) + pPage->TRG_SetMasterPage(*GetMasterPage(1)); + } + + SdPage* pNotesPage = static_cast<SdPage*>( GetPage(i+1) ); + pNotesPage->SetPageKind(PageKind::Notes); + + // Set notes master page + sal_uInt16 nMasterPageAfterPagesMasterPage = pPage->TRG_GetMasterPage().GetPageNum() + 1; + pNotesPage->TRG_SetMasterPage(*GetMasterPage(nMasterPageAfterPagesMasterPage)); + } + + bOK = true; + StopWorkStartupDelay(); + SetChanged(false); + } + + return bOK; +} + +void SdDrawDocument::UnselectAllPages() +{ + sal_uInt16 nNoOfPages = GetSdPageCount(PageKind::Standard); + for (sal_uInt16 nPage = 0; nPage < nNoOfPages; ++nPage) + { + SdPage* pPage = GetSdPage(nPage, PageKind::Standard); + pPage->SetSelected(false); + } +} + +// + Move selected pages after said page +// (nTargetPage = (sal_uInt16)-1 --> move before first page) +// + Returns sal_True when the page has been moved +bool SdDrawDocument::MovePages(sal_uInt16 nTargetPage) +{ + SdPage* pPage = nullptr; + sal_uInt16 nPage; + sal_uInt16 nNoOfPages = GetSdPageCount(PageKind::Standard); + bool bSomethingHappened = false; + + const bool bUndo = IsUndoEnabled(); + + if( bUndo ) + BegUndo(SdResId(STR_UNDO_MOVEPAGES)); + + // List of selected pages + std::vector<SdPage*> aPageList; + for (nPage = 0; nPage < nNoOfPages; nPage++) + { + pPage = GetSdPage(nPage, PageKind::Standard); + + if (pPage->IsSelected()) { + aPageList.push_back(pPage); + } + } + + // If necessary, look backwards, until we find a page that wasn't selected + nPage = nTargetPage; + + if (nPage != sal_uInt16(-1)) + { + pPage = GetSdPage(nPage, PageKind::Standard); + while (nPage > 0 && pPage->IsSelected()) + { + nPage--; + pPage = GetSdPage(nPage, PageKind::Standard); + } + + if (pPage->IsSelected()) + { + nPage = sal_uInt16(-1); + } + } + + // Insert before the first page + if (nPage == sal_uInt16(-1)) + { + std::vector<SdPage*>::reverse_iterator iter; + for (iter = aPageList.rbegin(); iter != aPageList.rend(); ++iter) + { + nPage = (*iter)->GetPageNum(); + if (nPage != 0) + { + SdrPage* pPg = GetPage(nPage); + if( bUndo ) + AddUndo(GetSdrUndoFactory().CreateUndoSetPageNum(*pPg, nPage, 1)); + MovePage(nPage, 1); + pPg = GetPage(nPage+1); + if( bUndo ) + AddUndo(GetSdrUndoFactory().CreateUndoSetPageNum(*pPg, nPage+1, 2)); + MovePage(nPage+1, 2); + bSomethingHappened = true; + } + } + } + // Insert after <nPage> + else + { + nTargetPage = 2 * nPage + 1; // PageKind::Standard --> absolute + + for (const auto& rpPage : aPageList) + { + nPage = rpPage->GetPageNum(); + if (nPage > nTargetPage) + { + nTargetPage += 2; // Insert _after_ the page + + if (nPage != nTargetPage) + { + SdrPage* pPg = GetPage(nPage); + if( bUndo ) + AddUndo(GetSdrUndoFactory().CreateUndoSetPageNum(*pPg, nPage, nTargetPage)); + MovePage(nPage, nTargetPage); + pPg = GetPage(nPage+1); + if( bUndo ) + AddUndo(GetSdrUndoFactory().CreateUndoSetPageNum(*pPg, nPage+1, nTargetPage+1)); + MovePage(nPage+1, nTargetPage+1); + bSomethingHappened = true; + } + } + else + { + if (nPage != nTargetPage) + { + SdrPage* pPg = GetPage(nPage+1); + if( bUndo ) + AddUndo(GetSdrUndoFactory().CreateUndoSetPageNum(*pPg, nPage+1, nTargetPage+1)); + MovePage(nPage+1, nTargetPage+1); + pPg = GetPage(nPage); + if( bUndo ) + AddUndo(GetSdrUndoFactory().CreateUndoSetPageNum(*pPg, nPage, nTargetPage)); + MovePage(nPage, nTargetPage); + bSomethingHappened = true; + } + } + nTargetPage = rpPage->GetPageNum(); + } + } + + if( bUndo ) + EndUndo(); + + return bSomethingHappened; +} + +// Return number of links in sfx2::LinkManager +sal_uLong SdDrawDocument::GetLinkCount() const +{ + return m_pLinkManager->GetLinks().size(); +} + +// Set Language +void SdDrawDocument::SetLanguage( const LanguageType eLang, const sal_uInt16 nId ) +{ + bool bChanged = false; + + if( nId == EE_CHAR_LANGUAGE && meLanguage != eLang ) + { + meLanguage = eLang; + bChanged = true; + } + else if( nId == EE_CHAR_LANGUAGE_CJK && meLanguageCJK != eLang ) + { + meLanguageCJK = eLang; + bChanged = true; + } + else if( nId == EE_CHAR_LANGUAGE_CTL && meLanguageCTL != eLang ) + { + meLanguageCTL = eLang; + bChanged = true; + } + + if( bChanged ) + { + GetDrawOutliner().SetDefaultLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() ); + m_pHitTestOutliner->SetDefaultLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() ); + m_pItemPool->SetPoolDefaultItem( SvxLanguageItem( eLang, nId ) ); + SetChanged( bChanged ); + } +} + +// Return language +LanguageType SdDrawDocument::GetLanguage( const sal_uInt16 nId ) const +{ + LanguageType eLangType = meLanguage; + + if( nId == EE_CHAR_LANGUAGE_CJK ) + eLangType = meLanguageCJK; + else if( nId == EE_CHAR_LANGUAGE_CTL ) + eLangType = meLanguageCTL; + + return eLangType; +} + +// Initiate WorkStartup +IMPL_LINK_NOARG(SdDrawDocument, WorkStartupHdl, Timer *, void) +{ + if (IsTransportContainer()) + return; + + if( mpDocSh ) + mpDocSh->SetWaitCursor( true ); + + bool bChanged = IsChanged(); // remember this + + // Initialize Autolayouts + SdPage* pHandoutMPage = GetMasterSdPage(0, PageKind::Handout); + + if (pHandoutMPage->GetAutoLayout() == AUTOLAYOUT_NONE) + { + // No AutoLayout yet -> initialize + pHandoutMPage->SetAutoLayout(AUTOLAYOUT_HANDOUT6, true, true); + } + + SdPage* pPage = GetSdPage(0, PageKind::Standard); + + if (pPage->GetAutoLayout() == AUTOLAYOUT_NONE) + { + // No AutoLayout yet -> initialize + pPage->SetAutoLayout(AUTOLAYOUT_NONE, true, true); + } + + SdPage* pNotesPage = GetSdPage(0, PageKind::Notes); + + if (pNotesPage->GetAutoLayout() == AUTOLAYOUT_NONE) + { + // No AutoLayout yet -> initialize + pNotesPage->SetAutoLayout(AUTOLAYOUT_NOTES, true, true); + } + + SetChanged(bChanged); + + if( mpDocSh ) + mpDocSh->SetWaitCursor( false ); +} + +// When the WorkStartupTimer has been created (this only happens in +// SdDrawViewShell::Construct() ), the timer may be stopped and the WorkStartup +// may be initiated. +void SdDrawDocument::StopWorkStartupDelay() +{ + if (mpWorkStartupTimer) + { + if ( mpWorkStartupTimer->IsActive() ) + { + // Timer not yet expired -> initiate WorkStartup + mpWorkStartupTimer->Stop(); + WorkStartupHdl(nullptr); + } + + mpWorkStartupTimer.reset(); + } +} + +// When the WorkStartupTimer has been created (this only happens in +// SdDrawViewShell::Construct() ), the timer may be stopped and the WorkStartup +// may be initiated. +SdAnimationInfo* SdDrawDocument::GetAnimationInfo(SdrObject* pObject) +{ + DBG_ASSERT(pObject, "sd::SdDrawDocument::GetAnimationInfo(), invalid argument!"); + if( pObject ) + return GetShapeUserData( *pObject ); + else + return nullptr; +} + +SdAnimationInfo* SdDrawDocument::GetShapeUserData(SdrObject& rObject, bool bCreate /* = false */ ) +{ + sal_uInt16 nUD = 0; + sal_uInt16 nUDCount = rObject.GetUserDataCount(); + SdAnimationInfo* pRet = nullptr; + + // Can we find animation information within the user data? + for (nUD = 0; nUD < nUDCount; nUD++) + { + SdrObjUserData* pUD = rObject.GetUserData(nUD); + if((pUD->GetInventor() == SdrInventor::StarDrawUserData) && (pUD->GetId() == SD_ANIMATIONINFO_ID)) + { + pRet = dynamic_cast<SdAnimationInfo*>(pUD); + break; + } + } + + if( (pRet == nullptr) && bCreate ) + { + pRet = new SdAnimationInfo( rObject ); + rObject.AppendUserData( std::unique_ptr<SdrObjUserData>(pRet) ); + } + + return pRet; +} + +/** this method enforces that the masterpages are in the correct order, + that is at position 1 is a PageKind::Standard masterpage followed by a + PageKind::Notes masterpage and so on. # +*/ +void SdDrawDocument::CheckMasterPages() +{ + sal_uInt16 nMaxPages = GetMasterPageCount(); + + // we need at least a handout master and one master page + if( nMaxPages < 2 ) + { + return; + } + + SdPage* pPage = nullptr; + + sal_uInt16 nPage; + + // first see if the page order is correct + for( nPage = 1; nPage < nMaxPages; nPage++ ) + { + pPage = static_cast<SdPage*> (GetMasterPage( nPage )); + // if an odd page is not a standard page or an even page is not a notes page + if( ((1 == (nPage & 1)) && (pPage->GetPageKind() != PageKind::Standard) ) || + ((0 == (nPage & 1)) && (pPage->GetPageKind() != PageKind::Notes) ) ) + break; // then we have a fatal error + } + + if( nPage >= nMaxPages ) + return; + + SdPage* pNotesPage = nullptr; + + // there is a fatal error in the master page order, + // we need to repair the document + bool bChanged = false; + + nPage = 1; + while( nPage < nMaxPages ) + { + pPage = static_cast<SdPage*> (GetMasterPage( nPage )); + if( pPage->GetPageKind() != PageKind::Standard ) + { + bChanged = true; + sal_uInt16 nFound = nPage + 1; + while( nFound < nMaxPages ) + { + pPage = static_cast<SdPage*>(GetMasterPage( nFound )); + if( PageKind::Standard == pPage->GetPageKind() ) + { + MoveMasterPage( nFound, nPage ); + pPage->SetInserted(); + break; + + } + + nFound++; + } + + // if we don't have any more standard pages, were done + if( nMaxPages == nFound ) + break; + } + + nPage++; + + if( nPage < nMaxPages ) + pNotesPage = static_cast<SdPage*>(GetMasterPage( nPage )); + else + pNotesPage = nullptr; + + if( (nullptr == pNotesPage) || (pNotesPage->GetPageKind() != PageKind::Notes) || ( pPage->GetLayoutName() != pNotesPage->GetLayoutName() ) ) + { + bChanged = true; + + sal_uInt16 nFound = nPage + 1; + while( nFound < nMaxPages ) + { + pNotesPage = static_cast<SdPage*>(GetMasterPage( nFound )); + if( (PageKind::Notes == pNotesPage->GetPageKind()) && ( pPage->GetLayoutName() == pNotesPage->GetLayoutName() ) ) + { + MoveMasterPage( nFound, nPage ); + pNotesPage->SetInserted(); + break; + } + + nFound++; + } + + // looks like we lost a notes page + if( nMaxPages == nFound ) + { + // so create one + + // first find a reference notes page for size + SdPage* pRefNotesPage = nullptr; + nFound = 0; + while( nFound < nMaxPages ) + { + pRefNotesPage = static_cast<SdPage*>(GetMasterPage( nFound )); + if( PageKind::Notes == pRefNotesPage->GetPageKind() ) + break; + nFound++; + } + if( nFound == nMaxPages ) + pRefNotesPage = nullptr; + + rtl::Reference<SdPage> pNewNotesPage = AllocSdPage(true); + pNewNotesPage->SetPageKind(PageKind::Notes); + if( pRefNotesPage ) + { + pNewNotesPage->SetSize( pRefNotesPage->GetSize() ); + pNewNotesPage->SetBorder( pRefNotesPage->GetLeftBorder(), + pRefNotesPage->GetUpperBorder(), + pRefNotesPage->GetRightBorder(), + pRefNotesPage->GetLowerBorder() ); + } + InsertMasterPage(pNewNotesPage.get(), nPage ); + pNewNotesPage->SetLayoutName( pPage->GetLayoutName() ); + pNewNotesPage->SetAutoLayout(AUTOLAYOUT_NOTES, true, true ); + nMaxPages++; + } + } + + nPage++; + } + + // now remove all remaining and unused non PageKind::Standard slides + while( nPage < nMaxPages ) + { + bChanged = true; + + RemoveMasterPage( nPage ); + nMaxPages--; + } + + if( bChanged ) + { + OSL_FAIL( "master pages where in a wrong order" ); + RecalcPageNums( true); + } +} + +sal_uInt16 SdDrawDocument::CreatePage ( + SdPage* pActualPage, + PageKind ePageKind, + const OUString& sStandardPageName, + const OUString& sNotesPageName, + AutoLayout eStandardLayout, + AutoLayout eNotesLayout, + bool bIsPageBack, + bool bIsPageObj, + const sal_Int32 nInsertPosition) +{ + SdPage* pPreviousStandardPage; + SdPage* pPreviousNotesPage; + rtl::Reference<SdPage> pStandardPage; + rtl::Reference<SdPage> pNotesPage; + + // From the given page determine the standard page and notes page of which + // to take the layout and the position where to insert the new pages. + if (ePageKind == PageKind::Notes) + { + pPreviousNotesPage = pActualPage; + sal_uInt16 nNotesPageNum = pPreviousNotesPage->GetPageNum() + 2; + pPreviousStandardPage = static_cast<SdPage*>( GetPage(nNotesPageNum - 3) ); + eStandardLayout = pPreviousStandardPage->GetAutoLayout(); + } + else + { + pPreviousStandardPage = pActualPage; + sal_uInt16 nStandardPageNum = pPreviousStandardPage->GetPageNum() + 2; + pPreviousNotesPage = static_cast<SdPage*>( GetPage(nStandardPageNum - 1) ); + eNotesLayout = pPreviousNotesPage->GetAutoLayout(); + } + + // Create new standard page and set it up + pStandardPage = AllocSdPage(false); + + // Set the size here since else the presobj autolayout + // will be wrong. + pStandardPage->SetSize( pPreviousStandardPage->GetSize() ); + pStandardPage->SetBorder( pPreviousStandardPage->GetLeftBorder(), + pPreviousStandardPage->GetUpperBorder(), + pPreviousStandardPage->GetRightBorder(), + pPreviousStandardPage->GetLowerBorder() ); + + // Use master page of current page. + pStandardPage->TRG_SetMasterPage(pPreviousStandardPage->TRG_GetMasterPage()); + + // User layout of current standard page + pStandardPage->SetLayoutName( pPreviousStandardPage->GetLayoutName() ); + pStandardPage->SetAutoLayout(eStandardLayout, true); + pStandardPage->setHeaderFooterSettings( pPreviousStandardPage->getHeaderFooterSettings() ); + + // transition settings of current page + pStandardPage->setTransitionType( pPreviousStandardPage->getTransitionType() ); + pStandardPage->setTransitionSubtype( pPreviousStandardPage->getTransitionSubtype() ); + pStandardPage->setTransitionDirection( pPreviousStandardPage->getTransitionDirection() ); + pStandardPage->setTransitionFadeColor( pPreviousStandardPage->getTransitionFadeColor() ); + pStandardPage->setTransitionDuration( pPreviousStandardPage->getTransitionDuration() ); + + // apply previous animation timing + pStandardPage->SetPresChange( pPreviousStandardPage->GetPresChange() ); + pStandardPage->SetTime( pPreviousStandardPage->GetTime() ); + + // Create new notes page and set it up + pNotesPage = AllocSdPage(false); + pNotesPage->SetPageKind(PageKind::Notes); + + // Use master page of current page + pNotesPage->TRG_SetMasterPage(pPreviousNotesPage->TRG_GetMasterPage()); + + // Use layout of current notes page + pNotesPage->SetLayoutName( pPreviousNotesPage->GetLayoutName() ); + pNotesPage->SetAutoLayout(eNotesLayout, true); + pNotesPage->setHeaderFooterSettings( pPreviousNotesPage->getHeaderFooterSettings() ); + + return InsertPageSet ( + pActualPage, + ePageKind, + sStandardPageName, + sNotesPageName, + bIsPageBack, + bIsPageObj, + pStandardPage.get(), + pNotesPage.get(), + nInsertPosition); +} + +sal_uInt16 SdDrawDocument::DuplicatePage (sal_uInt16 nPageNum) +{ + PageKind ePageKind = PageKind::Standard; + + // Get current page + SdPage* pActualPage = GetSdPage(nPageNum, ePageKind); + + // Get background flags + SdrLayerAdmin& rLayerAdmin = GetLayerAdmin(); + SdrLayerID aBckgrnd = rLayerAdmin.GetLayerID(sUNO_LayerName_background); + SdrLayerID aBckgrndObj = rLayerAdmin.GetLayerID(sUNO_LayerName_background_objects); + SdrLayerIDSet aVisibleLayers = pActualPage->TRG_GetMasterPageVisibleLayers(); + + return DuplicatePage ( + pActualPage, ePageKind, + // No names for the new slides + OUString(), OUString(), + aVisibleLayers.IsSet(aBckgrnd), + aVisibleLayers.IsSet(aBckgrndObj), -1); +} + +sal_uInt16 SdDrawDocument::DuplicatePage ( + SdPage* pActualPage, + PageKind ePageKind, + const OUString& sStandardPageName, + const OUString& sNotesPageName, + bool bIsPageBack, + bool bIsPageObj, + const sal_Int32 nInsertPosition) +{ + SdPage* pPreviousStandardPage; + SdPage* pPreviousNotesPage; + rtl::Reference<SdPage> pStandardPage; + rtl::Reference<SdPage> pNotesPage; + + // From the given page determine the standard page and the notes page + // of which to make copies. + if (ePageKind == PageKind::Notes) + { + pPreviousNotesPage = pActualPage; + sal_uInt16 nNotesPageNum = pPreviousNotesPage->GetPageNum() + 2; + pPreviousStandardPage = static_cast<SdPage*>( GetPage(nNotesPageNum - 3) ); + } + else + { + pPreviousStandardPage = pActualPage; + sal_uInt16 nStandardPageNum = pPreviousStandardPage->GetPageNum() + 2; + pPreviousNotesPage = static_cast<SdPage*>( GetPage(nStandardPageNum - 1) ); + } + + // Create duplicates of a standard page and the associated notes page + pStandardPage = static_cast<SdPage*>( pPreviousStandardPage->CloneSdrPage(*this).get() ); + pNotesPage = static_cast<SdPage*>( pPreviousNotesPage->CloneSdrPage(*this).get() ); + + return InsertPageSet ( + pActualPage, + ePageKind, + sStandardPageName, + sNotesPageName, + bIsPageBack, + bIsPageObj, + pStandardPage.get(), + pNotesPage.get(), + nInsertPosition); +} + +sal_uInt16 SdDrawDocument::InsertPageSet ( + SdPage* pActualPage, + PageKind ePageKind, + const OUString& sStandardPageName, + const OUString& sNotesPageName, + bool bIsPageBack, + bool bIsPageObj, + SdPage* pStandardPage, + SdPage* pNotesPage, + sal_Int32 nInsertPosition) +{ + SdPage* pPreviousStandardPage; + SdPage* pPreviousNotesPage; + sal_uInt16 nStandardPageNum; + sal_uInt16 nNotesPageNum; + OUString aNotesPageName(sNotesPageName); + + // Gather some information about the standard page and the notes page + // that are to be inserted. This makes sure that there is always one + // standard page followed by one notes page. + if (ePageKind == PageKind::Notes) + { + pPreviousNotesPage = pActualPage; + nNotesPageNum = pPreviousNotesPage->GetPageNum() + 2; + pPreviousStandardPage = static_cast<SdPage*>( GetPage(nNotesPageNum - 3) ); + nStandardPageNum = nNotesPageNum - 1; + } + else + { + pPreviousStandardPage = pActualPage; + nStandardPageNum = pPreviousStandardPage->GetPageNum() + 2; + pPreviousNotesPage = static_cast<SdPage*>( GetPage(nStandardPageNum - 1) ); + nNotesPageNum = nStandardPageNum + 1; + aNotesPageName = sStandardPageName; + } + + OSL_ASSERT(nNotesPageNum==nStandardPageNum+1); + if (nInsertPosition < 0) + nInsertPosition = nStandardPageNum; + + // Set up and insert the standard page + SetupNewPage ( + pPreviousStandardPage, + pStandardPage, + sStandardPageName, + nInsertPosition, + bIsPageBack, + bIsPageObj); + + // Set up and insert the notes page + pNotesPage->SetPageKind(PageKind::Notes); + SetupNewPage ( + pPreviousNotesPage, + pNotesPage, + aNotesPageName, + nInsertPosition+1, + bIsPageBack, + bIsPageObj); + + // Return an index that allows the caller to access the newly inserted + // pages by using GetSdPage() + return pStandardPage->GetPageNum() / 2; +} + +void SdDrawDocument::SetupNewPage ( + SdPage const * pPreviousPage, + SdPage* pPage, + const OUString& sPageName, + sal_uInt16 nInsertionPoint, + bool bIsPageBack, + bool bIsPageObj) +{ + if (pPreviousPage != nullptr) + { + pPage->SetSize( pPreviousPage->GetSize() ); + pPage->SetBorder( pPreviousPage->GetLeftBorder(), + pPreviousPage->GetUpperBorder(), + pPreviousPage->GetRightBorder(), + pPreviousPage->GetLowerBorder() ); + } + pPage->SetName(sPageName); + + InsertPage(pPage, nInsertionPoint); + + if (pPreviousPage != nullptr) + { + SdrLayerAdmin& rLayerAdmin = GetLayerAdmin(); + SdrLayerID aBckgrnd = rLayerAdmin.GetLayerID(sUNO_LayerName_background); + SdrLayerID aBckgrndObj = rLayerAdmin.GetLayerID(sUNO_LayerName_background_objects); + SdrLayerIDSet aVisibleLayers = pPreviousPage->TRG_GetMasterPageVisibleLayers(); + aVisibleLayers.Set(aBckgrnd, bIsPageBack); + aVisibleLayers.Set(aBckgrndObj, bIsPageObj); + pPage->TRG_SetMasterPageVisibleLayers(aVisibleLayers); + } +} + +sd::UndoManager* SdDrawDocument::GetUndoManager() const +{ + return mpDocSh ? dynamic_cast< sd::UndoManager* >(mpDocSh->GetUndoManager()) : nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |