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/ui/view/OutlinerIterator.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream/4%7.4.7.tar.xz libreoffice-upstream/4%7.4.7.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 '')
-rw-r--r-- | sd/source/ui/view/OutlinerIterator.cxx | 798 |
1 files changed, 798 insertions, 0 deletions
diff --git a/sd/source/ui/view/OutlinerIterator.cxx b/sd/source/ui/view/OutlinerIterator.cxx new file mode 100644 index 000000000..8cdb29330 --- /dev/null +++ b/sd/source/ui/view/OutlinerIterator.cxx @@ -0,0 +1,798 @@ +/* -*- 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 <OutlinerIterator.hxx> +#include <OutlinerIteratorImpl.hxx> +#include <svx/svditer.hxx> +#include <tools/debug.hxx> +#include <osl/diagnose.h> +#include <Outliner.hxx> + +#include <drawdoc.hxx> +#include <DrawViewShell.hxx> +#include <sdpage.hxx> + +namespace sd::outliner { + +//===== IteratorPosition ====================================================== + +IteratorPosition::IteratorPosition() +: mnText(0) +, mnPageIndex(-1) +, mePageKind(PageKind::Standard) +, meEditMode(EditMode::Page) +{ +} + +bool IteratorPosition::operator== (const IteratorPosition& aPosition) const +{ + return mxObject.get() == aPosition.mxObject.get() + && mnText == aPosition.mnText + && mnPageIndex == aPosition.mnPageIndex + && mePageKind == aPosition.mePageKind + && meEditMode == aPosition.meEditMode; +} + +//===== Iterator ============================================================== + +Iterator::Iterator() +{ +} + +Iterator::Iterator (const Iterator& rIterator) + : mxIterator(rIterator.mxIterator ? rIterator.mxIterator->Clone() : nullptr) +{ +} + +Iterator::Iterator(Iterator&& rIterator) noexcept + : mxIterator(std::move(rIterator.mxIterator)) +{ +} + +Iterator::Iterator (std::unique_ptr<IteratorImplBase> pObject) + : mxIterator(std::move(pObject)) +{ +} + +Iterator::~Iterator() +{ +} + +Iterator& Iterator::operator= (const Iterator& rIterator) +{ + if (this != &rIterator) + { + if (rIterator.mxIterator) + mxIterator.reset(rIterator.mxIterator->Clone()); + else + mxIterator.reset(); + } + return *this; +} + +Iterator& Iterator::operator=(Iterator&& rIterator) noexcept +{ + mxIterator = std::move(rIterator.mxIterator); + return *this; +} + +const IteratorPosition& Iterator::operator* () const +{ + DBG_ASSERT (mxIterator, "::sd::outliner::Iterator::operator* : missing implementation object"); + return mxIterator->GetPosition(); +} + +Iterator& Iterator::operator++ () +{ + if (mxIterator) + mxIterator->GotoNextText(); + return *this; +} + +bool Iterator::operator== (const Iterator& rIterator) const +{ + if (!mxIterator || !rIterator.mxIterator) + return mxIterator.get() == rIterator.mxIterator.get(); + else + return *mxIterator == *rIterator.mxIterator; +} + +bool Iterator::operator!= (const Iterator& rIterator) const +{ + return ! operator==(rIterator); +} + +void Iterator::Reverse() +{ + if (mxIterator) + mxIterator->Reverse(); +} + +//===== IteratorFactory ======================================================= + +OutlinerContainer::OutlinerContainer (SdOutliner* pOutliner) +: mpOutliner(pOutliner) +{ +} + +Iterator OutlinerContainer::begin() +{ + return CreateIterator (BEGIN); +} + +Iterator OutlinerContainer::end() +{ + return CreateIterator (END); +} + +Iterator OutlinerContainer::current() +{ + return CreateIterator (CURRENT); +} + +Iterator OutlinerContainer::CreateIterator (IteratorLocation aLocation) +{ + // Decide on certain features of the outliner which kind of iterator to + // use. + if (mpOutliner->mbRestrictSearchToSelection) + // There is a selection. Search only in this. + return CreateSelectionIterator ( + mpOutliner->maMarkListCopy, + mpOutliner->mpDrawDocument, + mpOutliner->mpWeakViewShell.lock(), + mpOutliner->mbDirectionIsForward, + aLocation); + else + // Search in the whole document. + return CreateDocumentIterator ( + mpOutliner->mpDrawDocument, + mpOutliner->mpWeakViewShell.lock(), + mpOutliner->mbDirectionIsForward, + aLocation); +} + +Iterator OutlinerContainer::CreateSelectionIterator ( + const ::std::vector<::tools::WeakReference<SdrObject>>& rObjectList, + SdDrawDocument* pDocument, + const std::shared_ptr<ViewShell>& rpViewShell, + bool bDirectionIsForward, + IteratorLocation aLocation) +{ + OSL_ASSERT(rpViewShell); + + sal_Int32 nObjectIndex; + + if (bDirectionIsForward) + switch (aLocation) + { + case CURRENT: + case BEGIN: + default: + nObjectIndex = 0; + break; + case END: + nObjectIndex = rObjectList.size(); + break; + } + else + switch (aLocation) + { + case CURRENT: + case BEGIN: + default: + nObjectIndex = rObjectList.size()-1; + break; + case END: + nObjectIndex = -1; + break; + } + + return Iterator (std::make_unique<SelectionIteratorImpl> ( + rObjectList, nObjectIndex, pDocument, rpViewShell, bDirectionIsForward)); +} + +Iterator OutlinerContainer::CreateDocumentIterator ( + SdDrawDocument* pDocument, + const std::shared_ptr<ViewShell>& rpViewShell, + bool bDirectionIsForward, + IteratorLocation aLocation) +{ + OSL_ASSERT(rpViewShell); + + PageKind ePageKind; + EditMode eEditMode; + + switch (aLocation) + { + case BEGIN: + default: + if (bDirectionIsForward) + { + ePageKind = PageKind::Standard; + eEditMode = EditMode::Page; + } + else + { + ePageKind = PageKind::Handout; + eEditMode = EditMode::MasterPage; + } + break; + + case END: + if (bDirectionIsForward) + { + ePageKind = PageKind::Handout; + eEditMode = EditMode::MasterPage; + } + else + { + ePageKind = PageKind::Standard; + eEditMode = EditMode::Page; + } + break; + + case CURRENT: + const std::shared_ptr<DrawViewShell> pDrawViewShell( + std::dynamic_pointer_cast<DrawViewShell>(rpViewShell)); + if (pDrawViewShell) + { + ePageKind = pDrawViewShell->GetPageKind(); + eEditMode = pDrawViewShell->GetEditMode(); + } + else + { + ePageKind = PageKind::Standard; + eEditMode = EditMode::Page; + } + break; + } + + sal_Int32 nPageIndex = GetPageIndex (pDocument, rpViewShell, + ePageKind, eEditMode, bDirectionIsForward, aLocation); + + return Iterator ( + std::make_unique<DocumentIteratorImpl> (nPageIndex, ePageKind, eEditMode, + pDocument, rpViewShell, bDirectionIsForward)); +} + +sal_Int32 OutlinerContainer::GetPageIndex ( + SdDrawDocument const * pDocument, + const std::shared_ptr<ViewShell>& rpViewShell, + PageKind ePageKind, + EditMode eEditMode, + bool bDirectionIsForward, + IteratorLocation aLocation) +{ + OSL_ASSERT(rpViewShell); + + sal_Int32 nPageIndex; + sal_Int32 nPageCount; + + const std::shared_ptr<DrawViewShell> pDrawViewShell( + std::dynamic_pointer_cast<DrawViewShell>(rpViewShell)); + + switch (eEditMode) + { + case EditMode::Page: + nPageCount = pDocument->GetSdPageCount (ePageKind); + break; + case EditMode::MasterPage: + nPageCount = pDocument->GetMasterSdPageCount(ePageKind); + break; + default: + nPageCount = 0; + } + + switch (aLocation) + { + case CURRENT: + if (pDrawViewShell) + nPageIndex = pDrawViewShell->GetCurPagePos(); + else + { + const SdPage* pPage = rpViewShell->GetActualPage(); + if (pPage != nullptr) + nPageIndex = (pPage->GetPageNum()-1)/2; + else + nPageIndex = 0; + } + break; + + case BEGIN: + default: + if (bDirectionIsForward) + nPageIndex = 0; + else + nPageIndex = nPageCount-1; + break; + + case END: + if (bDirectionIsForward) + nPageIndex = nPageCount; + else + nPageIndex = -1; + break; + } + + return nPageIndex; +} + +//===== IteratorImplBase ==================================================== + +IteratorImplBase::IteratorImplBase(SdDrawDocument* pDocument, + const std::weak_ptr<ViewShell>& rpViewShellWeak, + bool bDirectionIsForward) +: mpDocument (pDocument) +, mpViewShellWeak (rpViewShellWeak) +, mbDirectionIsForward (bDirectionIsForward) +{ + std::shared_ptr<DrawViewShell> pDrawViewShell; + if ( ! mpViewShellWeak.expired()) + pDrawViewShell = std::dynamic_pointer_cast<DrawViewShell>(rpViewShellWeak.lock()); + + if (pDrawViewShell) + { + maPosition.mePageKind = pDrawViewShell->GetPageKind(); + maPosition.meEditMode = pDrawViewShell->GetEditMode(); + } + else + { + maPosition.mePageKind = PageKind::Standard; + maPosition.meEditMode = EditMode::Page; + } +} + +IteratorImplBase::IteratorImplBase( SdDrawDocument* pDocument, + const std::weak_ptr<ViewShell>& rpViewShellWeak, + bool bDirectionIsForward, PageKind ePageKind, EditMode eEditMode) +: mpDocument (pDocument) +, mpViewShellWeak (rpViewShellWeak) +, mbDirectionIsForward (bDirectionIsForward) +{ + maPosition.mePageKind = ePageKind; + maPosition.meEditMode = eEditMode; +} + +IteratorImplBase::~IteratorImplBase() +{} + +bool IteratorImplBase::operator== (const IteratorImplBase& rIterator) const +{ + return maPosition == rIterator.maPosition; +} + +bool IteratorImplBase::IsEqualSelection(const IteratorImplBase& rIterator) const +{ + // When this method is executed instead of the ones from derived classes + // then the argument is of another type then the object itself. In this + // just compare the position objects. + return maPosition == rIterator.maPosition; +} + +const IteratorPosition& IteratorImplBase::GetPosition() +{ + return maPosition; +} + +IteratorImplBase* IteratorImplBase::Clone (IteratorImplBase* pObject) const +{ + if (pObject != nullptr) + { + pObject->maPosition = maPosition; + pObject->mpDocument = mpDocument; + pObject->mpViewShellWeak = mpViewShellWeak; + pObject->mbDirectionIsForward = mbDirectionIsForward; + } + return pObject; +} + +void IteratorImplBase::Reverse() +{ + mbDirectionIsForward = ! mbDirectionIsForward; +} + +//===== SelectionIteratorImpl =========================================== + +SelectionIteratorImpl::SelectionIteratorImpl ( + const ::std::vector<::tools::WeakReference<SdrObject>>& rObjectList, + sal_Int32 nObjectIndex, + SdDrawDocument* pDocument, + const std::weak_ptr<ViewShell>& rpViewShellWeak, + bool bDirectionIsForward) + : IteratorImplBase (pDocument, rpViewShellWeak, bDirectionIsForward), + mrObjectList(rObjectList), + mnObjectIndex(nObjectIndex) +{ +} + +SelectionIteratorImpl::~SelectionIteratorImpl() +{} + +IteratorImplBase* SelectionIteratorImpl::Clone (IteratorImplBase* pObject) const +{ + SelectionIteratorImpl* pIterator = static_cast<SelectionIteratorImpl*>(pObject); + if (pIterator == nullptr) + pIterator = new SelectionIteratorImpl ( + mrObjectList, mnObjectIndex, mpDocument, mpViewShellWeak, mbDirectionIsForward); + return pIterator; +} + +void SelectionIteratorImpl::GotoNextText() +{ + SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >( mrObjectList.at(mnObjectIndex).get() ); + if (mbDirectionIsForward) + { + if( pTextObj ) + { + ++maPosition.mnText; + if( maPosition.mnText >= pTextObj->getTextCount() ) + { + maPosition.mnText = 0; + ++mnObjectIndex; + } + } + else + { + ++mnObjectIndex; + } + } + else + { + if( pTextObj ) + { + --maPosition.mnText; + if( maPosition.mnText < 0 ) + { + maPosition.mnText = -1; + --mnObjectIndex; + } + } + else + { + --mnObjectIndex; + maPosition.mnText = -1; + } + + if( (maPosition.mnText == -1) && (mnObjectIndex >= 0) ) + { + pTextObj = dynamic_cast< SdrTextObj* >( mrObjectList.at(mnObjectIndex).get() ); + if( pTextObj ) + maPosition.mnText = pTextObj->getTextCount() - 1; + } + + if( maPosition.mnText == -1 ) + maPosition.mnText = 0; + } +} + +const IteratorPosition& SelectionIteratorImpl::GetPosition() +{ + maPosition.mxObject = mrObjectList.at(mnObjectIndex); + + return maPosition; +} + +bool SelectionIteratorImpl::operator== (const IteratorImplBase& rIterator) const +{ + return rIterator.IsEqualSelection(*this); +} + +bool SelectionIteratorImpl::IsEqualSelection(const IteratorImplBase& rIterator) const +{ + const SelectionIteratorImpl* pSelectionIterator = + static_cast<const SelectionIteratorImpl*>(&rIterator); + return mpDocument == pSelectionIterator->mpDocument + && mnObjectIndex == pSelectionIterator->mnObjectIndex; +} + +//===== ViewIteratorImpl ================================================ + +ViewIteratorImpl::ViewIteratorImpl ( + sal_Int32 nPageIndex, + SdDrawDocument* pDocument, + const std::weak_ptr<ViewShell>& rpViewShellWeak, + bool bDirectionIsForward) + : IteratorImplBase (pDocument, rpViewShellWeak, bDirectionIsForward), + mbPageChangeOccurred(false), + mpPage(nullptr) +{ + SetPage (nPageIndex); +} + +ViewIteratorImpl::ViewIteratorImpl ( + sal_Int32 nPageIndex, + SdDrawDocument* pDocument, + const std::weak_ptr<ViewShell>& rpViewShellWeak, + bool bDirectionIsForward, + PageKind ePageKind, + EditMode eEditMode) + : IteratorImplBase (pDocument, rpViewShellWeak, bDirectionIsForward, ePageKind, eEditMode), + mbPageChangeOccurred(false), + mpPage(nullptr) +{ + SetPage (nPageIndex); +} + +ViewIteratorImpl::~ViewIteratorImpl() +{ +} + +IteratorImplBase* ViewIteratorImpl::Clone (IteratorImplBase* pObject) const +{ + + ViewIteratorImpl* pIterator = static_cast<ViewIteratorImpl*>(pObject); + if (pIterator == nullptr) + pIterator = new ViewIteratorImpl ( + maPosition.mnPageIndex, mpDocument, mpViewShellWeak, mbDirectionIsForward); + + IteratorImplBase::Clone (pObject); + + if (mpObjectIterator != nullptr) + { + pIterator->mpObjectIterator.reset( new SdrObjListIter(mpPage, SdrIterMode::DeepNoGroups, !mbDirectionIsForward) ); + + // No direct way to set the object iterator to the current object. + pIterator->maPosition.mxObject.reset(nullptr); + while (pIterator->mpObjectIterator->IsMore() && pIterator->maPosition.mxObject!=maPosition.mxObject) + pIterator->maPosition.mxObject.reset(pIterator->mpObjectIterator->Next()); + } + else + pIterator->mpObjectIterator.reset(); + + return pIterator; +} + +void ViewIteratorImpl::GotoNextText() +{ + SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >( maPosition.mxObject.get() ); + if( pTextObj ) + { + if (mbDirectionIsForward) + { + ++maPosition.mnText; + if( maPosition.mnText < pTextObj->getTextCount() ) + return; + } + else + { + --maPosition.mnText; + if( maPosition.mnText >= 0 ) + return; + } + } + + if (mpObjectIterator != nullptr && mpObjectIterator->IsMore()) + maPosition.mxObject.reset(mpObjectIterator->Next()); + else + maPosition.mxObject.reset(nullptr); + + if (!maPosition.mxObject.is() ) + { + if (mbDirectionIsForward) + SetPage (maPosition.mnPageIndex+1); + else + SetPage (maPosition.mnPageIndex-1); + + if (mpPage != nullptr) + mpObjectIterator.reset( new SdrObjListIter(mpPage, SdrIterMode::DeepNoGroups, !mbDirectionIsForward) ); + if (mpObjectIterator!=nullptr && mpObjectIterator->IsMore()) + maPosition.mxObject.reset(mpObjectIterator->Next()); + else + maPosition.mxObject.reset(nullptr); + } + + maPosition.mnText = 0; + if( !mbDirectionIsForward && maPosition.mxObject.is() ) + { + pTextObj = dynamic_cast< SdrTextObj* >( maPosition.mxObject.get() ); + if( pTextObj ) + maPosition.mnText = pTextObj->getTextCount() - 1; + } +} + +void ViewIteratorImpl::SetPage (sal_Int32 nPageIndex) +{ + mbPageChangeOccurred = (maPosition.mnPageIndex!=nPageIndex); + if (mbPageChangeOccurred) + { + maPosition.mnPageIndex = nPageIndex; + + sal_Int32 nPageCount; + if (maPosition.meEditMode == EditMode::Page) + nPageCount = mpDocument->GetSdPageCount(maPosition.mePageKind); + else + nPageCount = mpDocument->GetMasterSdPageCount( + maPosition.mePageKind); + + // Get page pointer. Here we have three cases: regular pages, + // master pages and invalid page indices. The later ones are not + // errors but the effect of the iterator advancing to the next page + // and going past the last one. This dropping of the rim at the far + // side is detected here and has to be reacted to by the caller. + if (nPageIndex>=0 && nPageIndex < nPageCount) + { + if (maPosition.meEditMode == EditMode::Page) + mpPage = mpDocument->GetSdPage ( + static_cast<sal_uInt16>(nPageIndex), + maPosition.mePageKind); + else + mpPage = mpDocument->GetMasterSdPage ( + static_cast<sal_uInt16>(nPageIndex), + maPosition.mePageKind); + } + else + mpPage = nullptr; + } + + // Set up object list iterator. + if (mpPage != nullptr) + mpObjectIterator.reset( new SdrObjListIter(mpPage, SdrIterMode::DeepNoGroups, ! mbDirectionIsForward) ); + else + mpObjectIterator.reset(); + + // Get object pointer. + if (mpObjectIterator!=nullptr && mpObjectIterator->IsMore()) + maPosition.mxObject.reset( mpObjectIterator->Next() ); + else + maPosition.mxObject.reset(nullptr); + + maPosition.mnText = 0; + if( !mbDirectionIsForward && maPosition.mxObject.is() ) + { + SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >( maPosition.mxObject.get() ); + if( pTextObj ) + maPosition.mnText = pTextObj->getTextCount() - 1; + } + +} + +void ViewIteratorImpl::Reverse() +{ + IteratorImplBase::Reverse (); + + // Create reversed object list iterator. + if (mpPage != nullptr) + mpObjectIterator.reset( new SdrObjListIter(mpPage, SdrIterMode::DeepNoGroups, ! mbDirectionIsForward) ); + else + mpObjectIterator.reset(); + + // Move iterator to the current object. + ::tools::WeakReference<SdrObject> xObject = std::move(maPosition.mxObject); + + if (!mpObjectIterator) + return; + + while (mpObjectIterator->IsMore() && maPosition.mxObject != xObject) + maPosition.mxObject.reset(mpObjectIterator->Next()); +} + +//===== DocumentIteratorImpl ============================================ + +DocumentIteratorImpl::DocumentIteratorImpl ( + sal_Int32 nPageIndex, + PageKind ePageKind, EditMode eEditMode, + SdDrawDocument* pDocument, + const std::weak_ptr<ViewShell>& rpViewShellWeak, + bool bDirectionIsForward) + : ViewIteratorImpl (nPageIndex, pDocument, rpViewShellWeak, bDirectionIsForward, + ePageKind, eEditMode) +{ + if (eEditMode == EditMode::Page) + mnPageCount = pDocument->GetSdPageCount (ePageKind); + else + mnPageCount = pDocument->GetMasterSdPageCount(ePageKind); +} + +DocumentIteratorImpl::~DocumentIteratorImpl() +{} + +IteratorImplBase* DocumentIteratorImpl::Clone (IteratorImplBase* pObject) const +{ + DocumentIteratorImpl* pIterator = static_cast<DocumentIteratorImpl*>(pObject); + if (pIterator == nullptr) + pIterator = new DocumentIteratorImpl ( + maPosition.mnPageIndex, maPosition.mePageKind, maPosition.meEditMode, + mpDocument, mpViewShellWeak, mbDirectionIsForward); + // Finish the cloning. + return ViewIteratorImpl::Clone (pIterator); +} + +void DocumentIteratorImpl::GotoNextText() +{ + bool bSetToOnePastLastPage = false; + bool bViewChanged = false; + + ViewIteratorImpl::GotoNextText(); + + if (mbDirectionIsForward) + { + if (maPosition.mnPageIndex >= mnPageCount) + { + // Switch to master page. + if (maPosition.meEditMode == EditMode::Page) + { + maPosition.meEditMode = EditMode::MasterPage; + SetPage (0); + } + + // Switch to next view mode. + else + { + if (maPosition.mePageKind == PageKind::Handout) + // Not really necessary but makes things more clear. + bSetToOnePastLastPage = true; + else + { + maPosition.meEditMode = EditMode::Page; + if (maPosition.mePageKind == PageKind::Standard) + maPosition.mePageKind = PageKind::Notes; + else if (maPosition.mePageKind == PageKind::Notes) + maPosition.mePageKind = PageKind::Handout; + SetPage (0); + } + } + bViewChanged = true; + } + } + else + if (maPosition.mnPageIndex < 0) + { + // Switch from master pages to draw pages. + if (maPosition.meEditMode == EditMode::MasterPage) + { + maPosition.meEditMode = EditMode::Page; + bSetToOnePastLastPage = true; + } + + // Switch to previous view mode. + else + { + if (maPosition.mePageKind == PageKind::Standard) + SetPage (-1); + else + { + maPosition.meEditMode = EditMode::MasterPage; + if (maPosition.mePageKind == PageKind::Handout) + maPosition.mePageKind = PageKind::Notes; + else if (maPosition.mePageKind == PageKind::Notes) + maPosition.mePageKind = PageKind::Standard; + bSetToOnePastLastPage = true; + } + } + bViewChanged = true; + } + + if (!bViewChanged) + return; + + // Get new page count; + sal_Int32 nPageCount; + if (maPosition.meEditMode == EditMode::Page) + nPageCount = mpDocument->GetSdPageCount (maPosition.mePageKind); + else + nPageCount = mpDocument->GetMasterSdPageCount(maPosition.mePageKind); + + // Now that we know the number of pages we can set the current page index. + if (bSetToOnePastLastPage) + SetPage (nPageCount); +} + +} // end of namespace ::sd::outliner + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |