/* -*- 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 #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 #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::GetCurrentViewOptions().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(css::awt::GradientStyle_LINEAR, 0.0, 0.0, 0.0, nAngle, basegfx::BColorStops(aLighterColor, aFillColor)); rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::FillGradientPrimitive2D(aGradientRect, std::move(aFillAttrs)))); } // Create the border lines primitive rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(std::move(aPolygon), aLineColor))); } SwHeaderFooterDashedLine::SwHeaderFooterDashedLine(SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader) : SwDashedLine(pEditWin, &SwViewOption::GetHeaderFooterMarkColor) , m_pEditWin(pEditWin) , m_pFrame(pFrame) , m_bIsHeader(bHeader) { } bool SwHeaderFooterDashedLine::IsOnScreen() { tools::Rectangle aBounds(GetPosPixel(), GetSizePixel()); tools::Rectangle aVisArea = GetEditWin()->LogicToPixel(GetEditWin()->GetView().GetVisArea()); return aBounds.Overlaps(aVisArea); } void SwHeaderFooterDashedLine::EnsureWin() { if (!m_pWin) { m_pWin = VclPtr::Create(m_pEditWin, m_pFrame, m_bIsHeader); m_pWin->SetZOrder(this, ZOrderFlags::Before); } } void SwHeaderFooterDashedLine::ShowAll(bool bShow) { Show(bShow); if (!m_pWin && IsOnScreen()) EnsureWin(); if (m_pWin) m_pWin->ShowAll(bShow); } void SwHeaderFooterDashedLine::SetReadonly(bool bReadonly) { ShowAll(!bReadonly); } bool SwHeaderFooterDashedLine::Contains(const Point &rDocPt) const { if (m_pWin && m_pWin->Contains(rDocPt)) return true; ::tools::Rectangle aLineRect(GetPosPixel(), GetSizePixel()); return aLineRect.Contains(rDocPt); } void SwHeaderFooterDashedLine::SetOffset(Point aOffset, tools::Long nXLineStart, tools::Long nXLineEnd) { Point aLinePos(nXLineStart, aOffset.Y()); Size aLineSize(nXLineEnd - nXLineStart, 1); SetPosSizePixel(aLinePos, aLineSize); bool bOnScreen = IsOnScreen(); if (!m_pWin && bOnScreen) { EnsureWin(); m_pWin->ShowAll(true); } else if (m_pWin && !bOnScreen) m_pWin.disposeAndClear(); if (m_pWin) m_pWin->SetOffset(aOffset); } SwHeaderFooterWin::SwHeaderFooterWin(SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader ) : InterimItemWindow(pEditWin, "modules/swriter/ui/hfmenubutton.ui", "HFMenuButton"), m_xMenuButton(m_xBuilder->weld_menu_button("menubutton")), m_xPushButton(m_xBuilder->weld_button("button")), m_pEditWin(pEditWin), m_pFrame(pFrame), m_bIsHeader( bHeader ), m_bIsAppearing( false ), m_nFadeRate( 100 ), m_aFadeTimer("SwHeaderFooterWin m_aFadeTimer") { m_xVirDev = m_xMenuButton->create_virtual_device(); SwFrameMenuButtonBase::SetVirDevFont(*m_xVirDev); m_xPushButton->connect_clicked(LINK(this, SwHeaderFooterWin, ClickHdl)); m_xMenuButton->connect_selected(LINK(this, SwHeaderFooterWin, SelectHdl)); // set the PopupMenu // Rewrite the menu entries' text if (m_bIsHeader) { m_xMenuButton->set_item_label("edit", SwResId(STR_FORMAT_HEADER)); m_xMenuButton->set_item_label("delete", SwResId(STR_DELETE_HEADER)); } else { m_xMenuButton->set_item_label("edit", SwResId(STR_FORMAT_FOOTER)); m_xMenuButton->set_item_label("delete", SwResId(STR_DELETE_FOOTER)); } m_aFadeTimer.SetTimeout(50); m_aFadeTimer.SetInvokeHandler(LINK(this, SwHeaderFooterWin, FadeHandler)); } SwHeaderFooterWin::~SwHeaderFooterWin( ) { disposeOnce(); } void SwHeaderFooterWin::dispose() { m_xPushButton.reset(); m_xMenuButton.reset(); m_pEditWin.clear(); m_xVirDev.disposeAndClear(); InterimItemWindow::dispose(); } void SwHeaderFooterWin::SetOffset(Point aOffset) { // Compute the text to show const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); const SwPageDesc* pDesc = pPageFrame->GetPageDesc(); bool bIsFirst = !pDesc->IsFirstShared() && pPageFrame->OnFirstPage(); bool bIsLeft = !pDesc->IsHeaderShared() && !pPageFrame->OnRightPage(); bool bIsRight = !pDesc->IsHeaderShared() && pPageFrame->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()); m_xMenuButton->set_accessible_name(m_sLabel); // Compute the text size and get the box position & size from it ::tools::Rectangle aTextRect; m_xVirDev->GetTextBoundRect(aTextRect, m_sLabel); ::tools::Rectangle aTextPxRect = m_xVirDev->LogicToPixel(aTextRect); FontMetric aFontMetric = m_xVirDev->GetFontMetric(m_xVirDev->GetFont()); Size aBoxSize (aTextPxRect.GetWidth() + BUTTON_WIDTH + TEXT_PADDING * 2, aFontMetric.GetLineHeight() + TEXT_PADDING * 2 ); tools::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); m_xVirDev->SetOutputSizePixel(aBoxSize); PaintButton(); } void SwHeaderFooterWin::ShowAll(bool bShow) { bool bIsEmptyHeaderFooter = IsEmptyHeaderFooter(); m_xMenuButton->set_visible(!bIsEmptyHeaderFooter); m_xPushButton->set_visible(bIsEmptyHeaderFooter); 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()); return aRect.Contains(rDocPt); } void SwHeaderFooterWin::PaintButton() { if (!m_xVirDev) return; // 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), m_xVirDev->PixelToLogic(GetSizePixel()))); SwFrameButtonPainter::PaintButton(aSeq, aRect, m_bIsHeader); // Create the text primitive basegfx::BColor aLineColor = SwViewOption::GetCurrentViewOptions().GetHeaderFooterMarkColor().getBColor(); B2DVector aFontSize; FontAttribute aFontAttr = drawinglayer::primitive2d::getFontAttributeFromVclFont(aFontSize, m_xVirDev->GetFont(), false, false); FontMetric aFontMetric = m_xVirDev->GetFontMetric(m_xVirDev->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(), {}, std::move(aFontAttr), css::lang::Locale(), aLineColor))); // Create the 'plus' or 'arrow' primitive B2DRectangle aSignArea(B2DPoint(aRect.Right() - BUTTON_WIDTH, 0.0), B2DVector(aRect.Right(), aRect.getOpenHeight())); B2DPolygon aSign; bool bIsEmptyHeaderFooter = IsEmptyHeaderFooter(); if (bIsEmptyHeaderFooter) { // 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 pProcessor( drawinglayer::processor2d::createProcessor2DFromOutputDevice(*m_xVirDev, 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(COL_WHITE.getBColor(), 1.0 - nFadeRate); aGhostedSeq[0] = drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::ModifiedColorPrimitive2D(std::move(aSeq), aBColorModifier)); pProcessor->process(aGhostedSeq); if (bIsEmptyHeaderFooter) m_xPushButton->set_custom_button(m_xVirDev.get()); else m_xMenuButton->set_custom_button(m_xVirDev.get()); } bool SwHeaderFooterWin::IsEmptyHeaderFooter( ) const { bool bResult = true; const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); if (!pPageFrame) { return bResult; } // Actually check it const SwPageDesc* pDesc = pPageFrame->GetPageDesc(); bool const bFirst(pPageFrame->OnFirstPage()); const SwFrameFormat *const pFormat = (pPageFrame->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(std::u16string_view rIdent) { SwView& rView = m_pEditWin->GetView(); SwWrtShell& rSh = rView.GetWrtShell(); const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); const OUString& rStyleName = pPageFrame->GetPageDesc()->GetName(); if (rIdent == u"edit") { OUString sPageId = m_bIsHeader ? OUString("header") : OUString("footer"); rView.GetDocShell()->FormatPage(rView.GetFrameWeld(), rStyleName, sPageId, rSh); } else if (rIdent == u"borderback") { const SwPageDesc* pDesc = pPageFrame->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 aBoxInfo(std::make_shared(SID_ATTR_BORDER_INNER)); if (const SvxBoxInfoItem *pBoxInfo = pHFFormat->GetAttrSet().GetItemIfSet(SID_ATTR_BORDER_INNER)) aBoxInfo.reset(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 == u"delete") { rSh.ChangeHeaderOrFooter( rStyleName, m_bIsHeader, false, true ); // warning: "this" may be disposed now rSh.GetWin()->GrabFocusToDocument(); } else if (rIdent == u"insert_pagenumber") { SfxViewFrame& rVFrame = rSh.GetView().GetViewFrame(); rVFrame.GetBindings().Execute(FN_INSERT_FLD_PGNUMBER); } else if (rIdent == u"insert_pagecount") { SfxViewFrame& rVFrame = rSh.GetView().GetViewFrame(); rVFrame.GetBindings().Execute(FN_INSERT_FLD_PGCOUNT); } } IMPL_LINK_NOARG(SwHeaderFooterWin, ClickHdl, weld::Button&, void) { SwView& rView = m_pEditWin->GetView(); SwWrtShell& rSh = rView.GetWrtShell(); const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); const OUString& rStyleName = pPageFrame->GetPageDesc()->GetName(); { VclPtr xThis(this); rSh.ChangeHeaderOrFooter( rStyleName, m_bIsHeader, true, false ); //tdf#153059 after ChangeHeaderOrFooter is it possible that "this" is disposed if (xThis->isDisposed()) return; } m_xPushButton->hide(); m_xMenuButton->show(); PaintButton(); } IMPL_LINK(SwHeaderFooterWin, SelectHdl, const OUString&, rIdent, void) { ExecuteCommand(rIdent); } 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(); } else if (m_nFadeRate == 100 && IsVisible()) { Show(false); } else PaintButton(); if (IsVisible() && m_nFadeRate > 0 && m_nFadeRate < 100) m_aFadeTimer.Start(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */