/* -*- 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/. */ #include <strings.hrc> #include <doc.hxx> #include <drawdoc.hxx> #include <cmdid.h> #include <DashedLine.hxx> #include <docsh.hxx> #include <edtwin.hxx> #include <fmthdft.hxx> #include <HeaderFooterWin.hxx> #include <pagedesc.hxx> #include <pagefrm.hxx> #include <view.hxx> #include <viewopt.hxx> #include <wrtsh.hxx> #include <IDocumentDrawModelAccess.hxx> #include <basegfx/color/bcolortools.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/range/b2drectangle.hxx> #include <basegfx/vector/b2dsize.hxx> #include <drawinglayer/attribute/fillgradientattribute.hxx> #include <drawinglayer/attribute/fontattribute.hxx> #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> #include <drawinglayer/primitive2d/polygonprimitive2d.hxx> #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> #include <drawinglayer/primitive2d/textlayoutdevice.hxx> #include <drawinglayer/primitive2d/textprimitive2d.hxx> #include <editeng/boxitem.hxx> #include <svx/hdft.hxx> #include <sfx2/bindings.hxx> #include <sfx2/viewfrm.hxx> #include <drawinglayer/processor2d/baseprocessor2d.hxx> #include <drawinglayer/processor2d/processorfromoutputdevice.hxx> #include <vcl/canvastools.hxx> #include <vcl/metric.hxx> #include <vcl/menubtn.hxx> #include <vcl/svapp.hxx> #include <vcl/settings.hxx> #include <memory> #define TEXT_PADDING 5 #define BOX_DISTANCE 10 #define BUTTON_WIDTH 18 using namespace basegfx; using namespace basegfx::utils; using namespace drawinglayer::attribute; namespace { basegfx::BColor lcl_GetFillColor(const basegfx::BColor& rLineColor) { basegfx::BColor aHslLine = basegfx::utils::rgb2hsl(rLineColor); double nLuminance = aHslLine.getZ() * 2.5; if ( nLuminance == 0 ) nLuminance = 0.5; else if ( nLuminance >= 1.0 ) nLuminance = aHslLine.getZ() * 0.4; aHslLine.setZ( nLuminance ); return basegfx::utils::hsl2rgb( aHslLine ); } basegfx::BColor lcl_GetLighterGradientColor(const basegfx::BColor& rDarkColor) { basegfx::BColor aHslDark = basegfx::utils::rgb2hsl(rDarkColor); double nLuminance = aHslDark.getZ() * 255 + 20; aHslDark.setZ( nLuminance / 255.0 ); return basegfx::utils::hsl2rgb( aHslDark ); } B2DPolygon lcl_GetPolygon( const ::tools::Rectangle& rRect, bool bOnTop ) { const double nRadius = 3; const double nKappa((M_SQRT2 - 1.0) * 4.0 / 3.0); B2DPolygon aPolygon; aPolygon.append( B2DPoint( rRect.Left(), rRect.Top() ) ); { B2DPoint aCorner( rRect.Left(), rRect.Bottom() ); B2DPoint aStart( rRect.Left(), rRect.Bottom() - nRadius ); B2DPoint aEnd( rRect.Left() + nRadius, rRect.Bottom() ); aPolygon.append( aStart ); aPolygon.appendBezierSegment( interpolate( aStart, aCorner, nKappa ), interpolate( aEnd, aCorner, nKappa ), aEnd ); } { B2DPoint aCorner( rRect.Right(), rRect.Bottom() ); B2DPoint aStart( rRect.Right() - nRadius, rRect.Bottom() ); B2DPoint aEnd( rRect.Right(), rRect.Bottom() - nRadius ); aPolygon.append( aStart ); aPolygon.appendBezierSegment( interpolate( aStart, aCorner, nKappa ), interpolate( aEnd, aCorner, nKappa ), aEnd ); } aPolygon.append( B2DPoint( rRect.Right(), rRect.Top() ) ); if ( !bOnTop ) { B2DRectangle aBRect = vcl::unotools::b2DRectangleFromRectangle(rRect); B2DHomMatrix aRotation = createRotateAroundPoint( aBRect.getCenterX(), aBRect.getCenterY(), M_PI ); aPolygon.transform( aRotation ); } return aPolygon; } } void SwFrameButtonPainter::PaintButton(drawinglayer::primitive2d::Primitive2DContainer& rSeq, const tools::Rectangle& rRect, bool bOnTop) { rSeq.clear(); B2DPolygon aPolygon = lcl_GetPolygon(rRect, bOnTop); // Colors basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor(); basegfx::BColor aFillColor = lcl_GetFillColor(aLineColor); basegfx::BColor aLighterColor = lcl_GetLighterGradientColor(aFillColor); const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); if (rSettings.GetHighContrastMode()) { aFillColor = rSettings.GetDialogColor().getBColor(); aLineColor = rSettings.GetDialogTextColor().getBColor(); rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), aFillColor))); } else { B2DRectangle aGradientRect = vcl::unotools::b2DRectangleFromRectangle(rRect); double nAngle = M_PI; if (bOnTop) nAngle = 0; FillGradientAttribute aFillAttrs(drawinglayer::attribute::GradientStyle::Linear, 0.0, 0.0, 0.0, nAngle, aLighterColor, aFillColor, 10); rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::FillGradientPrimitive2D(aGradientRect, aFillAttrs))); } // Create the border lines primitive rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(aPolygon, aLineColor))); } SwHeaderFooterWin::SwHeaderFooterWin( SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader ) : SwFrameMenuButtonBase( pEditWin, pFrame ), m_aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), "modules/swriter/ui/headerfootermenu.ui", ""), m_bIsHeader( bHeader ), m_pPopupMenu(m_aBuilder.get_menu("menu")), m_pLine( nullptr ), m_bIsAppearing( false ), m_nFadeRate( 100 ), m_aFadeTimer( ) { //FIXME RenderContext // Get the font and configure it vcl::Font aFont = Application::GetSettings().GetStyleSettings().GetToolFont(); SetZoomedPointFont(*this, aFont); // Create the line control m_pLine = VclPtr<SwDashedLine>::Create(GetEditWin(), &SwViewOption::GetHeaderFooterMarkColor); m_pLine->SetZOrder(this, ZOrderFlags::Before); // set the PopupMenu // Rewrite the menu entries' text if (m_bIsHeader) { m_pPopupMenu->SetItemText(m_pPopupMenu->GetItemId("edit"), SwResId(STR_FORMAT_HEADER)); m_pPopupMenu->SetItemText(m_pPopupMenu->GetItemId("delete"), SwResId(STR_DELETE_HEADER)); } else { m_pPopupMenu->SetItemText(m_pPopupMenu->GetItemId("edit"), SwResId(STR_FORMAT_FOOTER)); m_pPopupMenu->SetItemText(m_pPopupMenu->GetItemId("delete"), SwResId(STR_DELETE_FOOTER)); } SetPopupMenu(m_pPopupMenu); m_aFadeTimer.SetTimeout(50); m_aFadeTimer.SetInvokeHandler(LINK(this, SwHeaderFooterWin, FadeHandler)); } SwHeaderFooterWin::~SwHeaderFooterWin( ) { disposeOnce(); } void SwHeaderFooterWin::dispose() { m_pPopupMenu.clear(); m_aBuilder.disposeBuilder(); m_pLine.disposeAndClear(); SwFrameMenuButtonBase::dispose(); } void SwHeaderFooterWin::SetOffset(Point aOffset, long nXLineStart, long nXLineEnd) { // Compute the text to show const SwPageDesc* pDesc = GetPageFrame()->GetPageDesc(); bool bIsFirst = !pDesc->IsFirstShared() && GetPageFrame()->OnFirstPage(); bool bIsLeft = !pDesc->IsHeaderShared() && !GetPageFrame()->OnRightPage(); bool bIsRight = !pDesc->IsHeaderShared() && GetPageFrame()->OnRightPage(); m_sLabel = SwResId(STR_HEADER_TITLE); if (!m_bIsHeader) m_sLabel = bIsFirst ? SwResId(STR_FIRST_FOOTER_TITLE) : bIsLeft ? SwResId(STR_LEFT_FOOTER_TITLE) : bIsRight ? SwResId(STR_RIGHT_FOOTER_TITLE) : SwResId(STR_FOOTER_TITLE ); else m_sLabel = bIsFirst ? SwResId(STR_FIRST_HEADER_TITLE) : bIsLeft ? SwResId(STR_LEFT_HEADER_TITLE) : bIsRight ? SwResId(STR_RIGHT_HEADER_TITLE) : SwResId(STR_HEADER_TITLE); sal_Int32 nPos = m_sLabel.lastIndexOf("%1"); m_sLabel = m_sLabel.replaceAt(nPos, 2, pDesc->GetName()); // Compute the text size and get the box position & size from it ::tools::Rectangle aTextRect; GetTextBoundRect(aTextRect, m_sLabel); ::tools::Rectangle aTextPxRect = LogicToPixel(aTextRect); FontMetric aFontMetric = GetFontMetric(GetFont()); Size aBoxSize (aTextPxRect.GetWidth() + BUTTON_WIDTH + TEXT_PADDING * 2, aFontMetric.GetLineHeight() + TEXT_PADDING * 2 ); long nYFooterOff = 0; if (!m_bIsHeader) nYFooterOff = aBoxSize.Height(); Point aBoxPos(aOffset.X() - aBoxSize.Width() - BOX_DISTANCE, aOffset.Y() - nYFooterOff); if (AllSettings::GetLayoutRTL()) { aBoxPos.setX( aOffset.X() + BOX_DISTANCE ); } // Set the position & Size of the window SetPosSizePixel(aBoxPos, aBoxSize); double nYLinePos = aBoxPos.Y(); if (!m_bIsHeader) nYLinePos += aBoxSize.Height(); Point aLinePos(nXLineStart, nYLinePos); Size aLineSize(nXLineEnd - nXLineStart, 1); m_pLine->SetPosSizePixel(aLinePos, aLineSize); } void SwHeaderFooterWin::ShowAll(bool bShow) { if (!PopupMenu::IsInExecute()) { m_bIsAppearing = bShow; if (m_aFadeTimer.IsActive()) m_aFadeTimer.Stop(); m_aFadeTimer.Start(); } } bool SwHeaderFooterWin::Contains( const Point &rDocPt ) const { ::tools::Rectangle aRect(GetPosPixel(), GetSizePixel()); if (aRect.IsInside(rDocPt)) return true; ::tools::Rectangle aLineRect(m_pLine->GetPosPixel(), m_pLine->GetSizePixel()); return aLineRect.IsInside(rDocPt); } void SwHeaderFooterWin::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle&) { // Use pixels for the rest of the drawing SetMapMode(MapMode(MapUnit::MapPixel)); drawinglayer::primitive2d::Primitive2DContainer aSeq; const ::tools::Rectangle aRect(::tools::Rectangle(Point(0, 0), rRenderContext.PixelToLogic(GetSizePixel()))); SwFrameButtonPainter::PaintButton(aSeq, aRect, m_bIsHeader); // Create the text primitive basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor(); B2DVector aFontSize; FontAttribute aFontAttr = drawinglayer::primitive2d::getFontAttributeFromVclFont(aFontSize, rRenderContext.GetFont(), false, false); FontMetric aFontMetric = rRenderContext.GetFontMetric(rRenderContext.GetFont()); double nTextOffsetY = aFontMetric.GetAscent() + TEXT_PADDING; Point aTextPos(TEXT_PADDING, nTextOffsetY); basegfx::B2DHomMatrix aTextMatrix(createScaleTranslateB2DHomMatrix( aFontSize.getX(), aFontSize.getY(), double(aTextPos.X()), double(aTextPos.Y()))); aSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( aTextMatrix, m_sLabel, 0, m_sLabel.getLength(), std::vector<double>(), aFontAttr, css::lang::Locale(), aLineColor))); // Create the 'plus' or 'arrow' primitive B2DRectangle aSignArea(B2DPoint(aRect.Right() - BUTTON_WIDTH, 0.0), B2DSize(aRect.Right(), aRect.getHeight())); B2DPolygon aSign; if (IsEmptyHeaderFooter()) { // Create the + polygon double nLeft = aSignArea.getMinX() + TEXT_PADDING; double nRight = aSignArea.getMaxX() - TEXT_PADDING; double nHalfW = ( nRight - nLeft ) / 2.0; double nTop = aSignArea.getCenterY() - nHalfW; double nBottom = aSignArea.getCenterY() + nHalfW; aSign.append(B2DPoint(nLeft, aSignArea.getCenterY() - 1.0)); aSign.append(B2DPoint(aSignArea.getCenterX() - 1.0, aSignArea.getCenterY() - 1.0)); aSign.append(B2DPoint(aSignArea.getCenterX() - 1.0, nTop)); aSign.append(B2DPoint(aSignArea.getCenterX() + 1.0, nTop)); aSign.append(B2DPoint(aSignArea.getCenterX() + 1.0, aSignArea.getCenterY() - 1.0)); aSign.append(B2DPoint(nRight, aSignArea.getCenterY() - 1.0)); aSign.append(B2DPoint(nRight, aSignArea.getCenterY() + 1.0)); aSign.append(B2DPoint(aSignArea.getCenterX() + 1.0, aSignArea.getCenterY() + 1.0)); aSign.append(B2DPoint(aSignArea.getCenterX() + 1.0, nBottom)); aSign.append(B2DPoint(aSignArea.getCenterX() - 1.0, nBottom)); aSign.append(B2DPoint(aSignArea.getCenterX() - 1.0, aSignArea.getCenterY() + 1.0)); aSign.append(B2DPoint(nLeft, aSignArea.getCenterY() + 1.0)); aSign.setClosed(true); } else { // Create the v polygon B2DPoint aLeft(aSignArea.getMinX() + TEXT_PADDING, aSignArea.getCenterY()); B2DPoint aRight(aSignArea.getMaxX() - TEXT_PADDING, aSignArea.getCenterY()); B2DPoint aBottom((aLeft.getX() + aRight.getX()) / 2.0, aLeft.getY() + 4.0); aSign.append(aLeft); aSign.append(aRight); aSign.append(aBottom); aSign.setClosed(true); } BColor aSignColor = COL_BLACK.getBColor(); if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) aSignColor = COL_WHITE.getBColor(); aSeq.push_back( drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( B2DPolyPolygon(aSign), aSignColor)) ); // Create the processor and process the primitives const drawinglayer::geometry::ViewInformation2D aNewViewInfos; std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(rRenderContext, aNewViewInfos)); // TODO Ghost it all if needed drawinglayer::primitive2d::Primitive2DContainer aGhostedSeq(1); double nFadeRate = double(m_nFadeRate) / 100.0; const basegfx::BColorModifierSharedPtr aBColorModifier = std::make_shared<basegfx::BColorModifier_interpolate>(COL_WHITE.getBColor(), 1.0 - nFadeRate); aGhostedSeq[0] = drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::ModifiedColorPrimitive2D(aSeq, aBColorModifier)); pProcessor->process(aGhostedSeq); } bool SwHeaderFooterWin::IsEmptyHeaderFooter( ) const { bool bResult = true; // Actually check it const SwPageDesc* pDesc = GetPageFrame()->GetPageDesc(); bool const bFirst(GetPageFrame()->OnFirstPage()); const SwFrameFormat *const pFormat = (GetPageFrame()->OnRightPage()) ? pDesc->GetRightFormat(bFirst) : pDesc->GetLeftFormat(bFirst); if ( pFormat ) { if ( m_bIsHeader ) bResult = !pFormat->GetHeader().IsActive(); else bResult = !pFormat->GetFooter().IsActive(); } return bResult; } void SwHeaderFooterWin::ExecuteCommand(const OString& rIdent) { SwView& rView = GetEditWin()->GetView(); SwWrtShell& rSh = rView.GetWrtShell(); const OUString& rStyleName = GetPageFrame()->GetPageDesc()->GetName(); if (rIdent == "edit") { OString sPageId = m_bIsHeader ? OString("header") : OString("footer"); rView.GetDocShell()->FormatPage(rStyleName, sPageId, rSh); } else if (rIdent == "borderback") { const SwPageDesc* pDesc = GetPageFrame()->GetPageDesc(); const SwFrameFormat& rMaster = pDesc->GetMaster(); SwFrameFormat* pHFFormat = const_cast< SwFrameFormat* >( rMaster.GetFooter().GetFooterFormat() ); if ( m_bIsHeader ) pHFFormat = const_cast< SwFrameFormat* >( rMaster.GetHeader().GetHeaderFormat() ); SfxItemSet aSet( pHFFormat->GetAttrSet() ); // Items to hand over XPropertyList things like XColorList, // XHatchList, XGradientList, and XBitmapList to the Area TabPage: aSet.MergeRange( SID_COLOR_TABLE, SID_PATTERN_LIST ); // create needed items for XPropertyList entries from the DrawModel so that // the Area TabPage can access them rSh.GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->PutAreaListItems( aSet ); aSet.MergeRange(SID_ATTR_BORDER_INNER, SID_ATTR_BORDER_INNER); // Create a box info item... needed by the dialog std::shared_ptr<SvxBoxInfoItem> aBoxInfo(std::make_shared<SvxBoxInfoItem>(SID_ATTR_BORDER_INNER)); const SfxPoolItem *pBoxInfo; if (SfxItemState::SET == pHFFormat->GetAttrSet().GetItemState(SID_ATTR_BORDER_INNER, true, &pBoxInfo)) aBoxInfo.reset(static_cast<SvxBoxInfoItem*>(pBoxInfo->Clone())); aBoxInfo->SetTable(false); aBoxInfo->SetDist(true); aBoxInfo->SetMinDist(false); aBoxInfo->SetDefDist(MIN_BORDER_DIST); aBoxInfo->SetValid(SvxBoxInfoItemValidFlags::DISABLE); aSet.Put(*aBoxInfo); if (svx::ShowBorderBackgroundDlg( GetFrameWeld(), &aSet ) ) { pHFFormat->SetFormatAttr( aSet ); rView.GetDocShell()->SetModified(); } } else if (rIdent == "delete") { rSh.ChangeHeaderOrFooter( rStyleName, m_bIsHeader, false, true ); // warning: "this" may be disposed now rSh.GetWin()->GrabFocusToDocument(); } else if (rIdent == "insert_pagenumber") { SfxViewFrame* pVFrame = rSh.GetView().GetViewFrame(); pVFrame->GetBindings().Execute(FN_INSERT_FLD_PGNUMBER); } else if (rIdent == "insert_pagecount") { SfxViewFrame* pVFrame = rSh.GetView().GetViewFrame(); pVFrame->GetBindings().Execute(FN_INSERT_FLD_PGCOUNT); } } void SwHeaderFooterWin::SetReadonly( bool bReadonly ) { ShowAll( !bReadonly ); } void SwHeaderFooterWin::MouseButtonDown( const MouseEvent& rMEvt ) { if (IsEmptyHeaderFooter()) { SwView& rView = GetEditWin()->GetView(); SwWrtShell& rSh = rView.GetWrtShell(); const OUString& rStyleName = GetPageFrame()->GetPageDesc()->GetName(); rSh.ChangeHeaderOrFooter( rStyleName, m_bIsHeader, true, false ); } else MenuButton::MouseButtonDown( rMEvt ); } void SwHeaderFooterWin::Select() { ExecuteCommand(GetCurItemIdent()); } IMPL_LINK_NOARG(SwHeaderFooterWin, FadeHandler, Timer *, void) { if (m_bIsAppearing && m_nFadeRate > 0) m_nFadeRate -= 25; else if (!m_bIsAppearing && m_nFadeRate < 100) m_nFadeRate += 25; if (m_nFadeRate != 100 && !IsVisible()) { Show(); m_pLine->Show(); } else if (m_nFadeRate == 100 && IsVisible()) { Show(false); m_pLine->Show(false); } else Invalidate(); if (IsVisible() && m_nFadeRate > 0 && m_nFadeRate < 100) m_aFadeTimer.Start(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */