From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- sd/source/ui/view/sdview.cxx | 1395 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1395 insertions(+) create mode 100644 sd/source/ui/view/sdview.cxx (limited to 'sd/source/ui/view/sdview.cxx') diff --git a/sd/source/ui/view/sdview.cxx b/sd/source/ui/view/sdview.cxx new file mode 100644 index 000000000..f27622fd1 --- /dev/null +++ b/sd/source/ui/view/sdview.cxx @@ -0,0 +1,1395 @@ +/* -*- 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace sdr::table; +namespace sd { + +View::View( + SdDrawDocument& rDrawDoc, + OutputDevice* pOutDev, + ViewShell* pViewShell) +: FmFormView(rDrawDoc, pOutDev), + mrDoc(rDrawDoc), + mpDocSh(rDrawDoc.GetDocSh()), + mpViewSh(pViewShell), + mpDropMarkerObj(nullptr), + mnDragSrcPgNum(SDRPAGE_NOTFOUND), + mnAction(DND_ACTION_NONE), + maDropErrorIdle("sd View DropError"), + maDropInsertFileIdle("sd View DropInsertFile"), + mnLockRedrawSmph(0), + mbIsDropAllowed(true), + maSmartTags(*this), + mpClipboard (new ViewClipboard (*this)) +{ + // #i73602# Use default from the configuration + SetBufferedOverlayAllowed(SvtOptionsDrawinglayer::IsOverlayBuffer_DrawImpress()); + + // #i74769#, #i75172# Use default from the configuration + SetBufferedOutputAllowed(SvtOptionsDrawinglayer::IsPaintBuffer_DrawImpress()); + + EnableExtendedKeyInputDispatcher(false); + EnableExtendedMouseEventDispatcher(false); + + SetUseIncompatiblePathCreateInterface(false); + + SetMinMoveDistancePixel(2); + SetHitTolerancePixel(2); + SetMeasureLayer(sUNO_LayerName_measurelines); + + // Timer for delayed drop (has to be for MAC) + maDropErrorIdle.SetInvokeHandler( LINK(this, View, DropErrorHdl) ); + maDropInsertFileIdle.SetInvokeHandler( LINK(this, View, DropInsertFileHdl) ); +} + +void View::ImplClearDrawDropMarker() +{ + mpDropMarker.reset(); +} + +View::~View() +{ + maSmartTags.Dispose(); + + // release content of selection clipboard, if we own the content + ClearSelectionClipboard(); + + if (mxDropMediaSizeListener) + { + suppress_fun_call_w_exception(mxDropMediaSizeListener->dispose()); + mxDropMediaSizeListener.clear(); + } + + maDropErrorIdle.Stop(); + maDropInsertFileIdle.Stop(); + + ImplClearDrawDropMarker(); + + while(PaintWindowCount()) + { + // remove all registered OutDevs + suppress_fun_call_w_exception(DeleteWindowFromPaintView(GetFirstOutputDevice())); + } +} + +namespace { + +class ViewRedirector : public sdr::contact::ViewObjectContactRedirector +{ +public: + ViewRedirector(); + + // all default implementations just call the same methods at the original. To do something + // different, override the method and at least do what the method does. + virtual void createRedirectedPrimitive2DSequence( + const sdr::contact::ViewObjectContact& rOriginal, + const sdr::contact::DisplayInfo& rDisplayInfo, + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) override; +}; + +} + +ViewRedirector::ViewRedirector() +{ +} + +void ViewRedirector::createRedirectedPrimitive2DSequence( + const sdr::contact::ViewObjectContact& rOriginal, + const sdr::contact::DisplayInfo& rDisplayInfo, + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) +{ + SdrObject* pObject = rOriginal.GetViewContact().TryToGetSdrObject(); + SdrPage* pSdrPage = pObject ? pObject->getSdrPageFromSdrObject() : nullptr; + if(!pObject || !pSdrPage) + { + // not a SdrObject visualisation (maybe e.g. page) or no page + sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence(rOriginal, rDisplayInfo, rVisitor); + return; + } + + const bool bDoCreateGeometry(pSdrPage->checkVisibility( rOriginal, rDisplayInfo, true )); + + if(!bDoCreateGeometry && + (( pObject->GetObjInventor() != SdrInventor::Default ) || ( pObject->GetObjIdentifier() != SdrObjKind::Page )) ) + return; + + PresObjKind eKind(PresObjKind::NONE); + const bool bSubContentProcessing(rDisplayInfo.GetSubContentActive()); + const bool bIsMasterPageObject(pSdrPage->IsMasterPage()); + const bool bIsPrinting(rOriginal.GetObjectContact().isOutputToPrinter()); + const SdrPageView* pPageView = rOriginal.GetObjectContact().TryToGetSdrPageView(); + const SdrPage* pVisualizedPage = GetSdrPageFromXDrawPage(rOriginal.GetObjectContact().getViewInformation2D().getVisualizedPage()); + const SdPage* pObjectsSdPage = dynamic_cast< SdPage* >(pSdrPage); + const bool bIsInsidePageObj(pPageView && pPageView->GetPage() != pVisualizedPage); + + // check if we need to draw a placeholder border. Never do it for + // objects inside a SdrPageObj and never when printing + if(!bIsInsidePageObj && !bIsPrinting) + { + bool bCreateOutline(false); + + if( pObject->IsEmptyPresObj() && dynamic_cast< SdrTextObj *>( pObject ) != nullptr ) + { + if( !bSubContentProcessing || !pObject->IsNotVisibleAsMaster() ) + { + eKind = pObjectsSdPage ? pObjectsSdPage->GetPresObjKind(pObject) : PresObjKind::NONE; + bCreateOutline = true; + } + } + else if( ( pObject->GetObjInventor() == SdrInventor::Default ) && ( pObject->GetObjIdentifier() == SdrObjKind::Text ) ) + { + if( pObjectsSdPage ) + { + eKind = pObjectsSdPage->GetPresObjKind(pObject); + + if((eKind == PresObjKind::Footer) || (eKind == PresObjKind::Header) || (eKind == PresObjKind::DateTime) || (eKind == PresObjKind::SlideNumber) ) + { + if( !bSubContentProcessing ) + { + // only draw a boundary for header&footer objects on the masterpage itself + bCreateOutline = true; + } + } + } + } + else if( ( pObject->GetObjInventor() == SdrInventor::Default ) && ( pObject->GetObjIdentifier() == SdrObjKind::Page ) ) + { + // only for handout page, else this frame will be created for each + // page preview object in SlideSorter and PagePane + if(pObjectsSdPage && PageKind::Handout == pObjectsSdPage->GetPageKind()) + { + bCreateOutline = true; + } + } + + if(bCreateOutline) + { + // empty presentation objects get a gray frame + const svtools::ColorConfig aColorConfig; + const svtools::ColorConfigValue aColor( aColorConfig.GetColorValue( svtools::OBJECTBOUNDARIES ) ); + + if( aColor.bIsVisible ) + { + // get basic object transformation + const basegfx::BColor aRGBColor(aColor.nColor.getBColor()); + basegfx::B2DHomMatrix aObjectMatrix; + basegfx::B2DPolyPolygon aObjectPolyPolygon; + pObject->TRGetBaseGeometry(aObjectMatrix, aObjectPolyPolygon); + + // create dashed border + { + // create object polygon + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(aObjectMatrix); + + // create line and stroke attribute + ::std::vector< double > aDotDashArray { 160.0, 80.0 }; + + const double fFullDotDashLen(::std::accumulate(aDotDashArray.begin(), aDotDashArray.end(), 0.0)); + const drawinglayer::attribute::LineAttribute aLine(aRGBColor); + const drawinglayer::attribute::StrokeAttribute aStroke(std::move(aDotDashArray), fFullDotDashLen); + + // create primitive and add + const drawinglayer::primitive2d::Primitive2DReference xRef(new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + aPolygon, + aLine, + aStroke)); + rVisitor.visit(xRef); + } + + // now paint the placeholder description, but only when masterpage + // is displayed as page directly (MasterPage view) + if(!bSubContentProcessing && bIsMasterPageObject) + { + OUString aObjectString; + + switch( eKind ) + { + case PresObjKind::Title: + { + if(pObjectsSdPage && pObjectsSdPage->GetPageKind() == PageKind::Standard) + { + static OUString aTitleAreaStr(SdResId(STR_PLACEHOLDER_DESCRIPTION_TITLE)); + aObjectString = aTitleAreaStr; + } + + break; + } + case PresObjKind::Outline: + { + static OUString aOutlineAreaStr(SdResId(STR_PLACEHOLDER_DESCRIPTION_OUTLINE)); + aObjectString = aOutlineAreaStr; + break; + } + case PresObjKind::Footer: + { + static OUString aFooterAreaStr(SdResId(STR_PLACEHOLDER_DESCRIPTION_FOOTER)); + aObjectString = aFooterAreaStr; + break; + } + case PresObjKind::Header: + { + static OUString aHeaderAreaStr(SdResId(STR_PLACEHOLDER_DESCRIPTION_HEADER)); + aObjectString = aHeaderAreaStr; + break; + } + case PresObjKind::DateTime: + { + static OUString aDateTimeStr(SdResId(STR_PLACEHOLDER_DESCRIPTION_DATETIME)); + aObjectString = aDateTimeStr; + break; + } + case PresObjKind::Notes: + { + static OUString aDateTimeStr(SdResId(STR_PLACEHOLDER_DESCRIPTION_NOTES)); + aObjectString = aDateTimeStr; + break; + } + case PresObjKind::SlideNumber: + { + if(pObjectsSdPage && pObjectsSdPage->GetPageKind() == PageKind::Standard) + { + static OUString aSlideAreaStr(SdResId(STR_PLACEHOLDER_DESCRIPTION_SLIDE)); + aObjectString = aSlideAreaStr; + } + else + { + static OUString aNumberAreaStr(SdResId(STR_PLACEHOLDER_DESCRIPTION_NUMBER)); + aObjectString = aNumberAreaStr; + } + break; + } + default: + { + break; + } + } + + if( !aObjectString.isEmpty() ) + { + // decompose object matrix to be able to place text correctly + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate, fShearX; + aObjectMatrix.decompose(aScale, aTranslate, fRotate, fShearX); + + // create font + SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >( pObject ); + const SdrTextVertAdjust eTVA(pTextObj ? pTextObj->GetTextVerticalAdjust() : SDRTEXTVERTADJUST_CENTER); + vcl::Font aScaledVclFont; + + // use a text size factor to get more reliable text sizes from the text layouter + // (and from vcl), tipp from HDU + static const sal_uInt32 nTextSizeFactor(100); + + // use a factor to get more linear text size calculations + aScaledVclFont.SetFontHeight( 500 * nTextSizeFactor ); + + // get basic geometry and get text size + drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; + aTextLayouter.setFont(aScaledVclFont); + const sal_Int32 nTextLength(aObjectString.getLength()); + + // do not forget to use the factor again to get the width for the 500 + const double fTextWidth(aTextLayouter.getTextWidth(aObjectString, 0, nTextLength) * (1.0 / nTextSizeFactor)); + const double fTextHeight(aTextLayouter.getTextHeight() * (1.0 / nTextSizeFactor)); + + // calculate text primitive position. If text is at bottom, use top for + // the extra text and vice versa + const double fHorDist(125); + const double fVerDist(125); + const double fPosX((aTranslate.getX() + aScale.getX()) - fTextWidth - fHorDist); + const double fPosY((SDRTEXTVERTADJUST_BOTTOM == eTVA) + ? aTranslate.getY() - fVerDist + fTextHeight + : (aTranslate.getY() + aScale.getY()) - fVerDist); + + // get font attributes; use normally scaled font + vcl::Font aVclFont; + basegfx::B2DVector aTextSizeAttribute; + + aVclFont.SetFontHeight( 500 ); + + const drawinglayer::attribute::FontAttribute aFontAttribute( + drawinglayer::primitive2d::getFontAttributeFromVclFont( + aTextSizeAttribute, + aVclFont, + false, + false)); + + // fill text matrix + const basegfx::B2DHomMatrix aTextMatrix(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aTextSizeAttribute.getX(), aTextSizeAttribute.getY(), + fShearX, + fRotate, + fPosX, fPosY)); + + // create DXTextArray (can be empty one) + ::std::vector< double > aDXArray{}; + + // create locale; this may need some more information in the future + const css::lang::Locale aLocale; + + // create primitive and add + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, + aObjectString, + 0, + nTextLength, + std::move(aDXArray), + aFontAttribute, + aLocale, + aRGBColor)); + rVisitor.visit(xRef); + } + } + } + } + } + + if(bDoCreateGeometry) + { + sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence( + rOriginal, + rDisplayInfo, rVisitor); + } +} + +namespace +{ + void setOutlinerBgFromPage(::Outliner& rOutl, SdrPageView& rPgView, bool bScreenDisplay) + { + SdPage* pPage = static_cast(rPgView.GetPage()); + if (pPage) + { + // #i75566# Name change GetBackgroundColor -> GetPageBackgroundColor and + // hint value if screen display. Only then the AutoColor mechanisms shall be applied + rOutl.SetBackgroundColor(pPage->GetPageBackgroundColor(&rPgView, bScreenDisplay)); + } + } +} + +/** + * The event will be forwarded to the View + */ +void View::CompleteRedraw(OutputDevice* pOutDev, const vcl::Region& rReg, sdr::contact::ViewObjectContactRedirector* pRedirector /*=0*/) +{ + // execute ?? + if (mnLockRedrawSmph != 0) + return; + + SdrPageView* pPgView = GetSdrPageView(); + + if (pPgView) + { + SdPage* pPage = static_cast( pPgView->GetPage() ); + if( pPage ) + { + SdrOutliner& rOutl = mrDoc.GetDrawOutliner(); + bool bScreenDisplay(true); + + // #i75566# printing; suppress AutoColor BackgroundColor generation + // for visibility reasons by giving GetPageBackgroundColor() + // the needed hint + // #i75566# PDF export; suppress AutoColor BackgroundColor generation (see printing) + if (pOutDev && ((OUTDEV_PRINTER == pOutDev->GetOutDevType()) + || (OUTDEV_PDF == pOutDev->GetOutDevType()))) + bScreenDisplay = false; + + setOutlinerBgFromPage(rOutl, *pPgView, bScreenDisplay); + } + } + + ViewRedirector aViewRedirector; + FmFormView::CompleteRedraw(pOutDev, rReg, pRedirector ? pRedirector : &aViewRedirector); +} + +void View::MarkListHasChanged() +{ + FmFormView::MarkListHasChanged(); + + if( GetMarkedObjectCount() > 0 ) + maSmartTags.deselect(); +} + +bool View::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll, bool /*bSlide*/, bool /*bMaster*/) +{ + bool bOk = FmFormView::SetAttributes(rSet, bReplaceAll); + return bOk; +} + +void View::GetAttributes( SfxItemSet& rTargetSet, bool bOnlyHardAttr ) const +{ + FmFormView::GetAttributes( rTargetSet, bOnlyHardAttr ); +} + +/** + * Is a presentation object selected? + */ +bool View::IsPresObjSelected(bool bOnPage, bool bOnMasterPage, bool bCheckPresObjListOnly, bool bCheckLayoutOnly) const +{ + SdrMarkList* pMarkList; + + if (mnDragSrcPgNum != SDRPAGE_NOTFOUND && + mnDragSrcPgNum != GetSdrPageView()->GetPage()->GetPageNum()) + { + /* Drag&Drop is in progress + Source and destination page are different: + we use the saved mark list */ + pMarkList = mpDragSrcMarkList.get(); + } + else + { + // We use the current mark list + pMarkList = new SdrMarkList(GetMarkedObjectList()); + } + + SdrMark* pMark; + SdPage* pPage; + + bool bSelected = false; + bool bMasterPage = false; + + for (size_t nMark = pMarkList->GetMarkCount(); nMark && !bSelected; ) + { + --nMark; + // Backwards through mark list + pMark = pMarkList->GetMark(nMark); + SdrObject* pObj = pMark->GetMarkedSdrObj(); + + if ( pObj && ( bCheckPresObjListOnly || pObj->IsEmptyPresObj() || pObj->GetUserCall() ) ) + { + pPage = static_cast( pObj->getSdrPageFromSdrObject() ); + bMasterPage = pPage && pPage->IsMasterPage(); + + if ( (bMasterPage && bOnMasterPage) || (!bMasterPage && bOnPage) ) + { + if ( pPage && pPage->IsPresObj(pObj) ) + { + if( bCheckLayoutOnly ) + { + PresObjKind eKind = pPage->GetPresObjKind(pObj); + + if((eKind != PresObjKind::Footer) && (eKind != PresObjKind::Header) && (eKind != PresObjKind::DateTime) && (eKind != PresObjKind::SlideNumber) ) + bSelected = true; + } + else + { + bSelected = true; + } + } + } + } + } + + if (pMarkList != mpDragSrcMarkList.get()) + { + delete pMarkList; + } + + return bSelected; +} + +void View::SelectAll() +{ + if ( IsTextEdit() ) + { + OutlinerView* pOLV = GetTextEditOutlinerView(); + const ::Outliner* pOutliner = GetTextEditOutliner(); + pOLV->SelectRange( 0, pOutliner->GetParagraphCount() ); + } + else + { + MarkAll(); + } +} + +bool View::SetStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr) +{ + // forward to SdrView + FmFormView::SetStyleSheet(pStyleSheet, bDontRemoveHardAttr); + return true; +} + +/** + * Start text input + */ +static void SetSpellOptions( const SdDrawDocument& rDoc, EEControlBits& rCntrl ) +{ + bool bOnlineSpell = rDoc.GetOnlineSpell(); + + if( bOnlineSpell ) + rCntrl |= EEControlBits::ONLINESPELLING; + else + rCntrl &= ~EEControlBits::ONLINESPELLING; +} + +void OutlinerMasterViewFilter::Start(SdrOutliner *pOutl) +{ + m_pOutl = pOutl; + OutlinerView* pOutlView = m_pOutl->GetView(0); + m_bReadOnly = pOutlView->IsReadOnly(); + pOutlView->SetReadOnly(true); +} + +void OutlinerMasterViewFilter::End() +{ + if (m_pOutl) + { + OutlinerView* pOutlView = m_pOutl->GetView(0); + pOutlView->SetReadOnly(m_bReadOnly); + m_pOutl = nullptr; + } +} + +SfxViewShell* View::GetSfxViewShell() const +{ + SfxViewShell* pRet = nullptr; + + if (mpViewSh) + pRet = &mpViewSh->GetViewShellBase(); + + return pRet; +} + +// Create a new view-local UndoManager manager for Impress/Draw +std::unique_ptr View::createLocalTextUndoManager() +{ + std::unique_ptr pUndoManager(new sd::UndoManager); + pUndoManager->SetDocShell(mpDocSh); + return pUndoManager; +} + +bool View::SdrBeginTextEdit( + SdrObject* pObj, SdrPageView* pPV, vcl::Window* pWin, + bool bIsNewObj, + SdrOutliner* pOutl, OutlinerView* pGivenOutlinerView, + bool bDontDeleteOutliner, bool bOnlyOneView, bool bGrabFocus ) +{ + SdrPage* pPage = pObj ? pObj->getSdrPageFromSdrObject() : nullptr; + bool bMasterPage = pPage && pPage->IsMasterPage(); + + GetViewShell()->GetViewShellBase().GetEventMultiplexer()->MultiplexEvent( + EventMultiplexerEventId::BeginTextEdit, static_cast(pObj) ); + + if( pOutl==nullptr && pObj ) + pOutl = SdrMakeOutliner(OutlinerMode::TextObject, pObj->getSdrModelFromSdrObject()).release(); + + // make draw&impress specific initialisations + if( pOutl ) + { + pOutl->SetStyleSheetPool(static_cast( mrDoc.GetStyleSheetPool() )); + pOutl->SetCalcFieldValueHdl(LINK(SD_MOD(), SdModule, CalcFieldValueHdl)); + EEControlBits nCntrl = pOutl->GetControlWord(); + nCntrl |= EEControlBits::ALLOWBIGOBJS; + nCntrl |= EEControlBits::MARKFIELDS; + nCntrl |= EEControlBits::AUTOCORRECT; + + nCntrl &= ~EEControlBits::ULSPACESUMMATION; + if ( mrDoc.IsSummationOfParagraphs() ) + nCntrl |= EEControlBits::ULSPACESUMMATION; + + SetSpellOptions( mrDoc, nCntrl ); + + pOutl->SetControlWord(nCntrl); + + Reference< linguistic2::XSpellChecker1 > xSpellChecker( LinguMgr::GetSpellChecker() ); + if ( xSpellChecker.is() ) + pOutl->SetSpeller( xSpellChecker ); + + Reference< linguistic2::XHyphenator > xHyphenator( LinguMgr::GetHyphenator() ); + if( xHyphenator.is() ) + pOutl->SetHyphenator( xHyphenator ); + + pOutl->SetDefaultLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() ); + } + + bool bReturn = FmFormView::SdrBeginTextEdit( + pObj, pPV, pWin, bIsNewObj, pOutl, + pGivenOutlinerView, bDontDeleteOutliner, + bOnlyOneView, bGrabFocus); + + if ( mpViewSh ) + { + mpViewSh->GetViewShellBase().GetDrawController().FireSelectionChangeListener(); + + if (pObj && pObj->GetObjIdentifier() == SdrObjKind::Table) + mpViewSh->UpdateScrollBars(); + + if (comphelper::LibreOfficeKit::isActive()) + { + if (OutlinerView* pView = GetTextEditOutlinerView()) + { + ::tools::Rectangle aRectangle = pView->GetOutputArea(); + if (pWin && pWin->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + { + aRectangle = o3tl::convert(aRectangle, o3tl::Length::mm100, o3tl::Length::twip); + } + OString sRectangle = aRectangle.toString(); + SfxLokHelper::notifyOtherViews(&mpViewSh->GetViewShellBase(), LOK_CALLBACK_VIEW_LOCK, "rectangle", sRectangle); + } + } + } + + if (::Outliner* pOL = bReturn ? GetTextEditOutliner() : nullptr) + { + if (pObj) + { + if( pObj->GetObjInventor() == SdrInventor::Default && pObj->GetObjIdentifier() == SdrObjKind::Table ) + { + Color aBackground = GetTextEditBackgroundColor(*this); + pOL->SetBackgroundColor( aBackground ); + } + else + { + // tdf#148140 Set the background to determine autocolor. + // Use any explicit bg with fallback to underlying page if + // none found + if (!pObj->setSuitableOutlinerBg(*pOL) && pPV) + setOutlinerBgFromPage(*pOL, *pPV, true); + } + } + + pOL->SetParaInsertedHdl(LINK(this, View, OnParagraphInsertedHdl)); + pOL->SetParaRemovingHdl(LINK(this, View, OnParagraphRemovingHdl)); + } + + if (bMasterPage && bReturn && pOutl) + { + const SdrTextObj* pTextObj = pOutl->GetTextObj(); + const SdPage* pSdPage = pTextObj ? static_cast(pTextObj->getSdrPageFromSdrObject()) : nullptr; + const PresObjKind eKind = pSdPage ? pSdPage->GetPresObjKind(const_cast(pTextObj)) : PresObjKind::NONE; + switch (eKind) + { + case PresObjKind::Title: + case PresObjKind::Outline: + case PresObjKind::Text: + maMasterViewFilter.Start(pOutl); + break; + default: + break; + } + } + + return bReturn; +} + +/** ends current text editing */ +SdrEndTextEditKind View::SdrEndTextEdit(bool bDontDeleteReally) +{ + maMasterViewFilter.End(); + + ::tools::WeakReference xObj( GetTextEditObject() ); + + bool bDefaultTextRestored = RestoreDefaultText( xObj.get() ); + + SdrEndTextEditKind eKind = FmFormView::SdrEndTextEdit(bDontDeleteReally); + + if( bDefaultTextRestored ) + { + if( xObj.is() && !xObj->IsEmptyPresObj() ) + { + xObj->SetEmptyPresObj( true ); + } + else + { + eKind = SdrEndTextEditKind::Unchanged; + } + } + else if( xObj.is() && xObj->IsEmptyPresObj() ) + { + SdrTextObj* pObj = xObj.get(); + if( pObj && pObj->HasText() ) + { + SdrPage* pPage = pObj->getSdrPageFromSdrObject(); + if( !pPage || !pPage->IsMasterPage() ) + pObj->SetEmptyPresObj( false ); + } + } + + GetViewShell()->GetViewShellBase().GetEventMultiplexer()->MultiplexEvent( + EventMultiplexerEventId::EndTextEdit, + static_cast(xObj.get()) ); + + if( xObj.is() ) + { + if ( mpViewSh ) + { + mpViewSh->GetViewShellBase().GetDrawController().FireSelectionChangeListener(); + + if (comphelper::LibreOfficeKit::isActive()) + SfxLokHelper::notifyOtherViews(&mpViewSh->GetViewShellBase(), LOK_CALLBACK_VIEW_LOCK, "rectangle", "EMPTY"); + + } + + SdPage* pPage = dynamic_cast< SdPage* >( xObj->getSdrPageFromSdrObject() ); + if( pPage ) + pPage->onEndTextEdit( xObj.get() ); + } + + return eKind; +} + +/** restores the default text if the given text object is currently in edit mode and + no text has been entered already. Is only useful just before text edit ends. */ +bool View::RestoreDefaultText( SdrTextObj* pTextObj ) +{ + bool bRestored = false; + + if( pTextObj && (pTextObj == GetTextEditObject()) ) + { + if( !pTextObj->HasText() ) + { + SdPage* pPage = dynamic_cast< SdPage* >( pTextObj->getSdrPageFromSdrObject() ); + + if(pPage) + { + bRestored = pPage->RestoreDefaultText( pTextObj ); + if( bRestored ) + { + SdrOutliner* pOutliner = GetTextEditOutliner(); + pTextObj->SetTextEditOutliner( pOutliner ); + OutlinerParaObject* pParaObj = pTextObj->GetOutlinerParaObject(); + if (pOutliner) + pOutliner->SetText(*pParaObj); + } + } + } + } + + return bRestored; +} + +/** + * Sets the original size of the marked objects. + */ +void View::SetMarkedOriginalSize() +{ + std::unique_ptr pUndoGroup(new SdrUndoGroup(mrDoc)); + const size_t nCount = GetMarkedObjectCount(); + bool bOK = false; + + for( size_t i = 0; i < nCount; ++i ) + { + SdrObject* pObj = GetMarkedObjectByIndex(i); + + if( pObj->GetObjInventor() == SdrInventor::Default ) + { + if( pObj->GetObjIdentifier() == SdrObjKind::OLE2 ) + { + uno::Reference < embed::XEmbeddedObject > xObj = static_cast(pObj)->GetObjRef(); + if( xObj.is() ) + { + // TODO/LEAN: working with VisualArea can switch object to running state + + sal_Int64 nAspect = static_cast(pObj)->GetAspect(); + Size aOleSize; + + if ( nAspect == embed::Aspects::MSOLE_ICON ) + { + MapMode aMap100( MapUnit::Map100thMM ); + aOleSize = static_cast(pObj)->GetOrigObjSize( &aMap100 ); + bOK = true; + } + else + { + MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( nAspect ) ); + try + { + awt::Size aSz = xObj->getVisualAreaSize( nAspect ); + aOleSize = OutputDevice::LogicToLogic(Size(aSz.Width, aSz.Height), MapMode(aUnit), MapMode(MapUnit::Map100thMM)); + bOK = true; + } + catch( embed::NoVisualAreaSizeException& ) + {} + } + + if ( bOK ) + { + ::tools::Rectangle aDrawRect( pObj->GetLogicRect() ); + + pUndoGroup->AddAction( mrDoc.GetSdrUndoFactory().CreateUndoGeoObject( *pObj ) ); + pObj->Resize( aDrawRect.TopLeft(), Fraction( aOleSize.Width(), aDrawRect.GetWidth() ), + Fraction( aOleSize.Height(), aDrawRect.GetHeight() ) ); + } + } + } + else if( pObj->GetObjIdentifier() == SdrObjKind::Graphic ) + { + const SdrGrafObj* pSdrGrafObj = static_cast< const SdrGrafObj* >(pObj); + const Size aSize = pSdrGrafObj->getOriginalSize( ); + pUndoGroup->AddAction( GetModel()->GetSdrUndoFactory().CreateUndoGeoObject(*pObj ) ); + ::tools::Rectangle aRect( pObj->GetLogicRect() ); + aRect.SetSize( aSize ); + pObj->SetLogicRect( aRect ); + bOK = true; + } + } + } + + if( bOK ) + { + pUndoGroup->SetComment(SdResId(STR_UNDO_ORIGINALSIZE)); + mpDocSh->GetUndoManager()->AddUndoAction(std::move(pUndoGroup)); + } +} + +/** + * Connect OLE object to client. + */ +void View::DoConnect(SdrOle2Obj* pObj) +{ + if (!mpViewSh) + return; + + uno::Reference < embed::XEmbeddedObject > xObj( pObj->GetObjRef() ); + if( !xObj.is() ) + return; + + ::sd::Window* pWindow = mpViewSh->GetActiveWindow(); + SfxInPlaceClient* pSdClient = mpViewSh-> GetViewShellBase().FindIPClient( xObj, pWindow ); + if ( pSdClient ) + return; + + pSdClient = new Client(pObj, mpViewSh, pWindow); + ::tools::Rectangle aRect = pObj->GetLogicRect(); + { + // TODO/LEAN: working with visual area can switch object to running state + Size aDrawSize = aRect.GetSize(); + + MapMode aMapMode( mrDoc.GetScaleUnit() ); + Size aObjAreaSize = pObj->GetOrigObjSize( &aMapMode ); + + Fraction aScaleWidth (aDrawSize.Width(), aObjAreaSize.Width() ); + Fraction aScaleHeight(aDrawSize.Height(), aObjAreaSize.Height() ); + aScaleWidth.ReduceInaccurate(10); // compatible to SdrOle2Obj + aScaleHeight.ReduceInaccurate(10); + pSdClient->SetSizeScale(aScaleWidth, aScaleHeight); + + // visible area is only changed in-place! + // the object area must be set after the scaling, since it triggers resize + aRect.SetSize(aObjAreaSize); + pSdClient->SetObjArea(aRect); + } +} + +bool View::IsMorphingAllowed() const +{ + const SdrMarkList& rMarkList = GetMarkedObjectList(); + bool bRet = false; + + if ( rMarkList.GetMarkCount() == 2 ) + { + const SdrObject* pObj1 = rMarkList.GetMark( 0 )->GetMarkedSdrObj(); + const SdrObject* pObj2 = rMarkList.GetMark( 1 )->GetMarkedSdrObj(); + const SdrObjKind nKind1 = pObj1->GetObjIdentifier(); + const SdrObjKind nKind2 = pObj2->GetObjIdentifier(); + + if ( ( nKind1 != SdrObjKind::Text && nKind2 != SdrObjKind::Text ) && + ( nKind1 != SdrObjKind::TitleText && nKind2 != SdrObjKind::TitleText ) && + ( nKind1 != SdrObjKind::OutlineText && nKind2 != SdrObjKind::OutlineText ) && + ( nKind1 != SdrObjKind::Group && nKind2 != SdrObjKind::Group ) && + ( nKind1 != SdrObjKind::Line && nKind2 != SdrObjKind::Line ) && + ( nKind1 != SdrObjKind::PolyLine && nKind2 != SdrObjKind::PolyLine ) && + ( nKind1 != SdrObjKind::PathLine && nKind2 != SdrObjKind::PathLine ) && + ( nKind1 != SdrObjKind::FreehandLine && nKind2 != SdrObjKind::FreehandLine ) && + ( nKind1 != SdrObjKind::PathPolyLine && nKind2 != SdrObjKind::PathPolyLine ) && + ( nKind1 != SdrObjKind::Measure && nKind2 != SdrObjKind::Measure ) && + ( nKind1 != SdrObjKind::Edge && nKind2 != SdrObjKind::Edge ) && + ( nKind1 != SdrObjKind::Graphic && nKind2 != SdrObjKind::Graphic ) && + ( nKind1 != SdrObjKind::OLE2 && nKind2 != SdrObjKind::OLE2 ) && + ( nKind1 != SdrObjKind::Caption && nKind2 != SdrObjKind::Caption ) && + dynamic_cast< const E3dObject *>( pObj1 ) == nullptr && dynamic_cast< const E3dObject *>( pObj2 ) == nullptr ) + { + SfxItemSetFixed aSet1( mrDoc.GetPool() ); + SfxItemSetFixed aSet2( mrDoc.GetPool() ); + + aSet1.Put(pObj1->GetMergedItemSet()); + aSet2.Put(pObj2->GetMergedItemSet()); + + const drawing::FillStyle eFillStyle1 = aSet1.Get( XATTR_FILLSTYLE ).GetValue(); + const drawing::FillStyle eFillStyle2 = aSet2.Get( XATTR_FILLSTYLE ).GetValue(); + + if( ( eFillStyle1 == drawing::FillStyle_NONE || eFillStyle1 == drawing::FillStyle_SOLID ) && + ( eFillStyle2 == drawing::FillStyle_NONE || eFillStyle2 == drawing::FillStyle_SOLID ) ) + bRet = true; + } + } + + return bRet; +} + +bool View::IsVectorizeAllowed() const +{ + const SdrMarkList& rMarkList = GetMarkedObjectList(); + bool bRet = false; + + if( rMarkList.GetMarkCount() == 1 ) + { + const SdrGrafObj* pObj = dynamic_cast< const SdrGrafObj* >(rMarkList.GetMark( 0 )->GetMarkedSdrObj()); + + if(pObj) + { + if(GraphicType::Bitmap == pObj->GetGraphicType() && !pObj->isEmbeddedVectorGraphicData()) + { + bRet = true; + } + } + } + + return bRet; +} + +void View::onAccessibilityOptionsChanged() +{ + if( !mpViewSh ) + return; + + ::sd::Window* pWindow = mpViewSh->GetActiveWindow(); + if( !pWindow ) + return; + + const StyleSettings& rStyleSettings = pWindow->GetSettings().GetStyleSettings(); + + if( mpViewSh->GetViewFrame() && mpViewSh->GetViewFrame()->GetDispatcher() ) + { + sal_uInt16 nOutputSlot, nPreviewSlot; + + if( rStyleSettings.GetHighContrastMode() ) + { + nOutputSlot = SID_OUTPUT_QUALITY_CONTRAST; + } + else + { + nOutputSlot = SID_OUTPUT_QUALITY_COLOR; + } + + if( rStyleSettings.GetHighContrastMode() + && officecfg::Office::Common::Accessibility::IsForPagePreviews::get() ) + { + nPreviewSlot = SID_PREVIEW_QUALITY_CONTRAST; + } + else + { + nPreviewSlot = SID_PREVIEW_QUALITY_COLOR; + } + + mpViewSh->GetViewFrame()->GetDispatcher()->Execute( nOutputSlot, SfxCallMode::ASYNCHRON ); + mpViewSh->GetViewFrame()->GetDispatcher()->Execute( nPreviewSlot, SfxCallMode::ASYNCHRON ); + } + + mpViewSh->Invalidate(); +} + +IMPL_LINK( View, OnParagraphInsertedHdl, ::Outliner::ParagraphHdlParam, aParam, void ) +{ + SdrObject* pObj = GetTextEditObject(); + + if( aParam.pPara && pObj ) + { + SdPage* pPage = dynamic_cast< SdPage* >( pObj->getSdrPageFromSdrObject() ); + if( pPage ) + pPage->onParagraphInserted( aParam.pOutliner, aParam.pPara, pObj ); + } +} + +/** + * Handler for the deletion of the pages (paragraphs). + */ +IMPL_LINK( View, OnParagraphRemovingHdl, ::Outliner::ParagraphHdlParam, aParam, void ) +{ + SdrObject* pObj = GetTextEditObject(); + + if( aParam.pPara && pObj ) + { + SdPage* pPage = dynamic_cast< SdPage* >( pObj->getSdrPageFromSdrObject() ); + if( pPage ) + pPage->onParagraphRemoving( aParam.pOutliner, aParam.pPara, pObj ); + } +} + +bool View::isRecordingUndo() const +{ + if( mrDoc.IsUndoEnabled() ) + { + sd::UndoManager* pUndoManager = mrDoc.GetUndoManager(); + return pUndoManager && pUndoManager->IsInListAction(); + } + else + { + return false; + } +} + +void View::AddCustomHdl() +{ + maSmartTags.addCustomHandles( maHdlList ); +} + +void View::updateHandles() +{ + AdjustMarkHdl(); +} + +SdrViewContext View::GetContext() const +{ + SdrViewContext eContext = SdrViewContext::Standard; + if( maSmartTags.getContext( eContext ) ) + return eContext; + else + return FmFormView::GetContext(); +} + +bool View::HasMarkablePoints() const +{ + if( maSmartTags.HasMarkablePoints() ) + return true; + else + return FmFormView::HasMarkablePoints(); +} + +sal_Int32 View::GetMarkablePointCount() const +{ + sal_Int32 nCount = FmFormView::GetMarkablePointCount(); + nCount += maSmartTags.GetMarkablePointCount(); + return nCount; +} + +bool View::HasMarkedPoints() const +{ + if( maSmartTags.HasMarkedPoints() ) + return true; + else + return FmFormView::HasMarkedPoints(); +} + +bool View::MarkPoint(SdrHdl& rHdl, bool bUnmark ) +{ + if( maSmartTags.MarkPoint( rHdl, bUnmark ) ) + return true; + else + return FmFormView::MarkPoint( rHdl, bUnmark ); +} + +bool View::MarkPoints(const ::tools::Rectangle* pRect, bool bUnmark) +{ + if( maSmartTags.MarkPoints( pRect, bUnmark ) ) + return true; + else + return FmFormView::MarkPoints( pRect, bUnmark ); +} + +void View::CheckPossibilities() +{ + FmFormView::CheckPossibilities(); + maSmartTags.CheckPossibilities(); +} + +void View::OnBeginPasteOrDrop( PasteOrDropInfos* pInfo ) +{ + SdrOutliner* pOutliner = GetTextEditOutliner(); + if (!pOutliner) + return; + + // Turn character attributes of the paragraph of the insert position into + // character-level attributes, so they are not lost when OnEndPasteOrDrop() + // sets the paragraph stylesheet. + SfxItemSet aSet(pOutliner->GetParaAttribs(pInfo->nStartPara)); + pOutliner->SetCharAttribs(pInfo->nStartPara, aSet); +} + +/** this is called after a paste or drop operation, make sure that the newly inserted paragraphs + get the correct style sheet. */ +void View::OnEndPasteOrDrop( PasteOrDropInfos* pInfo ) +{ + /* Style Sheet handling */ + SdrTextObj* pTextObj = GetTextEditObject(); + SdrOutliner* pOutliner = GetTextEditOutliner(); + if( !pOutliner || !pTextObj || !pTextObj->getSdrPageFromSdrObject() ) + return; + + SdPage* pPage = static_cast< SdPage* >( pTextObj->getSdrPageFromSdrObject() ); + const PresObjKind eKind = pPage->GetPresObjKind(pTextObj); + + // outline kinds are taken care of in Outliner::ImplSetLevelDependentStyleSheet + if( eKind == PresObjKind::Outline ) + return; + + SfxStyleSheet* pStyleSheet = nullptr; + if( eKind != PresObjKind::NONE ) + pStyleSheet = pPage->GetStyleSheetForPresObj(eKind); + else + pStyleSheet = pTextObj->GetStyleSheet(); + // just put the object style on each new paragraph + for ( sal_Int32 nPara = pInfo->nStartPara; nPara <= pInfo->nEndPara; nPara++ ) + { + pOutliner->SetStyleSheet( nPara, pStyleSheet ); + } +} + +bool View::ShouldToggleOn( + const bool bBulletOnOffMode, + const bool bNormalBullet) +{ + // If setting bullets/numbering by the dialog, always should toggle on. + if (!bBulletOnOffMode) + return true; + SdrModel* pSdrModel = GetModel(); + if (!pSdrModel) + return false; + + bool bToggleOn = false; + std::unique_ptr pOutliner(SdrMakeOutliner(OutlinerMode::TextObject, *pSdrModel)); + const size_t nMarkCount = GetMarkedObjectCount(); + for (size_t nIndex = 0; nIndex < nMarkCount && !bToggleOn; ++nIndex) + { + SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >(GetMarkedObjectByIndex(nIndex)); + if (!pTextObj || pTextObj->IsTextEditActive()) + continue; + if( dynamic_cast< const SdrTableObj *>( pTextObj ) != nullptr) + { + SdrTableObj* pTableObj = dynamic_cast< SdrTableObj* >(pTextObj); + if (!pTableObj) + continue; + CellPos aStart, aEnd; + SvxTableController* pTableController = dynamic_cast< SvxTableController* >(getSelectionController().get()); + if (pTableController) + { + pTableController->getSelectedCells(aStart, aEnd); + } + else + { + aStart = SdrTableObj::getFirstCell(); + aEnd = pTableObj->getLastCell(); + } + sal_Int32 nColCount = pTableObj->getColumnCount(); + for (sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow && !bToggleOn; nRow++) + { + for (sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol && !bToggleOn; nCol++) + { + sal_Int32 nCellIndex = nRow * nColCount + nCol; + SdrText* pText = pTableObj->getText(nCellIndex); + if (!pText || !pText->GetOutlinerParaObject()) + continue; + pOutliner->SetText(*(pText->GetOutlinerParaObject())); + sal_Int16 nStatus = pOutliner->GetBulletsNumberingStatus(); + bToggleOn = (bNormalBullet && nStatus != 0) || (!bNormalBullet && nStatus != 1); + pOutliner->Clear(); + } + } + } + else + { + OutlinerParaObject* pParaObj = pTextObj->GetOutlinerParaObject(); + if (!pParaObj) + continue; + pOutliner->SetText(*pParaObj); + sal_Int16 nStatus = pOutliner->GetBulletsNumberingStatus(); + bToggleOn = (bNormalBullet && nStatus != 0) || (!bNormalBullet && nStatus != 1); + pOutliner->Clear(); + } + } + return bToggleOn; +} + +void View::ChangeMarkedObjectsBulletsNumbering( + const bool bToggle, + const bool bHandleBullets, + const SvxNumRule* pNumRule ) +{ + SdrModel* pSdrModel = GetModel(); + OutputDevice* pOut = GetFirstOutputDevice(); + vcl::Window* pWindow = pOut ? pOut->GetOwnerWindow() : nullptr; + if (!pSdrModel || !pWindow) + return; + + const bool bUndoEnabled = pSdrModel->IsUndoEnabled(); + std::unique_ptr pUndoGroup(bUndoEnabled ? new SdrUndoGroup(*pSdrModel) : nullptr); + + const bool bToggleOn = ShouldToggleOn( bToggle, bHandleBullets ); + + std::unique_ptr pOutliner(SdrMakeOutliner(OutlinerMode::TextObject, *pSdrModel)); + OutlinerView aOutlinerView(pOutliner.get(), pWindow); + + const size_t nMarkCount = GetMarkedObjectCount(); + for (size_t nIndex = 0; nIndex < nMarkCount; ++nIndex) + { + SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >(GetMarkedObjectByIndex(nIndex)); + if (!pTextObj || pTextObj->IsTextEditActive()) + continue; + if( dynamic_cast< SdrTableObj *>( pTextObj ) != nullptr) + { + SdrTableObj* pTableObj = dynamic_cast< SdrTableObj* >(pTextObj); + if (!pTableObj) + continue; + CellPos aStart, aEnd; + SvxTableController* pTableController = dynamic_cast< SvxTableController* >(getSelectionController().get()); + if (pTableController) + { + pTableController->getSelectedCells(aStart, aEnd); + } + else + { + aStart = SdrTableObj::getFirstCell(); + aEnd = pTableObj->getLastCell(); + } + sal_Int32 nColCount = pTableObj->getColumnCount(); + for (sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++) + { + for (sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++) + { + sal_Int32 nCellIndex = nRow * nColCount + nCol; + SdrText* pText = pTableObj->getText(nCellIndex); + if (!pText || !pText->GetOutlinerParaObject()) + continue; + + pOutliner->SetText(*(pText->GetOutlinerParaObject())); + if (bUndoEnabled) + { + pUndoGroup->AddAction(pSdrModel->GetSdrUndoFactory().CreateUndoObjectSetText(*pTextObj, nCellIndex)); + } + if ( !bToggleOn ) + { + aOutlinerView.SwitchOffBulletsNumbering(); + } + else + { + aOutlinerView.ApplyBulletsNumbering( bHandleBullets, pNumRule, bToggle ); + } + sal_uInt32 nParaCount = pOutliner->GetParagraphCount(); + pText->SetOutlinerParaObject(pOutliner->CreateParaObject(0, static_cast(nParaCount))); + pOutliner->Clear(); + } + } + // Broadcast the object change event. + if (!pTextObj->AdjustTextFrameWidthAndHeight()) + { + pTextObj->SetChanged(); + pTextObj->BroadcastObjectChange(); + } + } + else + { + OutlinerParaObject* pParaObj = pTextObj->GetOutlinerParaObject(); + if (!pParaObj) + continue; + pOutliner->SetText(*pParaObj); + if (bUndoEnabled) + { + pUndoGroup->AddAction( + pSdrModel->GetSdrUndoFactory().CreateUndoObjectSetText(*pTextObj, 0)); + } + if ( !bToggleOn ) + { + aOutlinerView.SwitchOffBulletsNumbering(); + } + else + { + aOutlinerView.ApplyBulletsNumbering( bHandleBullets, pNumRule, bToggle ); + } + sal_uInt32 nParaCount = pOutliner->GetParagraphCount(); + pTextObj->SetOutlinerParaObject(pOutliner->CreateParaObject(0, static_cast(nParaCount))); + pOutliner->Clear(); + } + } + + if ( bUndoEnabled && pUndoGroup->GetActionCount() > 0 ) + { + pSdrModel->BegUndo(); + pSdrModel->AddUndo(std::move(pUndoGroup)); + pSdrModel->EndUndo(); + } +} + +} // end of namespace sd + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3