diff options
Diffstat (limited to 'sw/source/uibase/docvw/PageBreakWin.cxx')
-rw-r--r-- | sw/source/uibase/docvw/PageBreakWin.cxx | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/sw/source/uibase/docvw/PageBreakWin.cxx b/sw/source/uibase/docvw/PageBreakWin.cxx new file mode 100644 index 000000000..600d23a44 --- /dev/null +++ b/sw/source/uibase/docvw/PageBreakWin.cxx @@ -0,0 +1,506 @@ +/* -*- 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 <bitmaps.hlst> + +#include <cmdid.h> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <ndtxt.hxx> +#include <DashedLine.hxx> +#include <doc.hxx> +#include <edtwin.hxx> +#include <fmtpdsc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <PageBreakWin.hxx> +#include <pagefrm.hxx> +#include <PostItMgr.hxx> +#include <FrameControlsManager.hxx> +#include <strings.hrc> +#include <tabfrm.hxx> +#include <uiitems.hxx> +#include <uiobject.hxx> +#include <view.hxx> +#include <viewopt.hxx> +#include <wrtsh.hxx> + +#include <basegfx/color/bcolortools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <editeng/formatbreakitem.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/stritem.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <memory> + +#define BUTTON_WIDTH 30 +#define BUTTON_HEIGHT 19 +#define ARROW_WIDTH 9 + +using namespace basegfx; +using namespace basegfx::utils; + +SwBreakDashedLine::SwBreakDashedLine(SwEditWin* pEditWin, const SwFrame *pFrame) + : SwDashedLine(pEditWin, &SwViewOption::GetPageBreakColor) + , m_pEditWin(pEditWin) + , m_pFrame(pFrame) +{ + set_id("PageBreak"); // for uitest +} + +SwPageBreakWin& SwBreakDashedLine::GetOrCreateWin() +{ + if (!m_pWin) + { + m_pWin = VclPtr<SwPageBreakWin>::Create(this, m_pEditWin, m_pFrame); + m_pWin->SetPosSizePixel(m_aBtnRect.TopLeft(), m_aBtnRect.GetSize()); + m_pWin->SetZOrder(this, ZOrderFlags::Before); + } + return *m_pWin; +} + +void SwBreakDashedLine::DestroyWin() +{ + m_pWin.disposeAndClear(); +} + +void SwBreakDashedLine::MouseMove( const MouseEvent& rMEvt ) +{ + if ( rMEvt.IsLeaveWindow() ) + { + // don't fade if we just move to the 'button' + Point aEventPos( GetPosPixel() + rMEvt.GetPosPixel() ); + if (m_pWin && (!Contains(aEventPos) || !m_pWin->IsVisible())) + m_pWin->Fade(false); + } + else if (!m_pWin || !m_pWin->IsVisible()) + { + GetOrCreateWin().Fade(true); + } + + if (!rMEvt.IsSynthetic() && (!m_pWin || !m_pWin->IsVisible())) + { + UpdatePosition(rMEvt.GetPosPixel()); + } +} + +void SwBreakDashedLine::ShowAll(bool bShow) +{ + Show(bShow); +} + +void SwBreakDashedLine::SetReadonly(bool bReadonly) +{ + ShowAll(!bReadonly); +} + +bool SwBreakDashedLine::Contains(const Point &rDocPt) const +{ + if (m_aBtnRect.Contains(rDocPt)) + return true; + + ::tools::Rectangle aLineRect(GetPosPixel(), GetSizePixel()); + return aLineRect.Contains(rDocPt); +} + +SwPageBreakWin::SwPageBreakWin(SwBreakDashedLine* pLine, SwEditWin* pEditWin, const SwFrame *pFrame) : + InterimItemWindow(pEditWin, "modules/swriter/ui/pbmenubutton.ui", "PBMenuButton"), + m_xMenuButton(m_xBuilder->weld_menu_button("menubutton")), + m_pLine(pLine), + m_pEditWin(pEditWin), + m_pFrame(pFrame), + m_bIsAppearing( false ), + m_nFadeRate( 100 ), + m_nDelayAppearing( 0 ), + m_aFadeTimer("SwPageBreakWin m_aFadeTimer"), + m_bDestroyed( false ) +{ + m_xMenuButton->connect_toggled(LINK(this, SwPageBreakWin, ToggleHdl)); + m_xMenuButton->connect_selected(LINK(this, SwPageBreakWin, SelectHdl)); + m_xMenuButton->set_accessible_name(SwResId(STR_PAGE_BREAK_BUTTON)); + + m_xVirDev = m_xMenuButton->create_virtual_device(); + SwFrameMenuButtonBase::SetVirDevFont(*m_xVirDev); + + // Use pixels for the rest of the drawing + m_xVirDev->SetMapMode( MapMode ( MapUnit::MapPixel ) ); + + m_aFadeTimer.SetTimeout( 50 ); + m_aFadeTimer.SetInvokeHandler( LINK( this, SwPageBreakWin, FadeHandler ) ); +} + +SwPageBreakWin::~SwPageBreakWin( ) +{ + disposeOnce(); +} + +void SwPageBreakWin::dispose() +{ + m_bDestroyed = true; + m_aFadeTimer.Stop(); + m_xVirDev.disposeAndClear(); + + m_pLine.clear(); + m_pEditWin.clear(); + + m_xMenuButton.reset(); + InterimItemWindow::dispose(); +} + +void SwPageBreakWin::PaintButton() +{ + if (!m_xVirDev) + return; + + const ::tools::Rectangle aRect(::tools::Rectangle(Point(0, 0), m_xVirDev->PixelToLogic(GetSizePixel()))); + + // Properly paint the control + BColor aColor = SwViewOption::GetPageBreakColor().getBColor(); + + BColor aHslLine = rgb2hsl(aColor); + double nLuminance = aHslLine.getZ(); + nLuminance += (1.0 - nLuminance) * 0.75; + if ( aHslLine.getZ() > 0.7 ) + nLuminance = aHslLine.getZ() * 0.7; + aHslLine.setZ(nLuminance); + BColor aOtherColor = hsl2rgb(aHslLine); + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + if (rSettings.GetHighContrastMode()) + { + aColor = rSettings.GetDialogTextColor().getBColor(); + aOtherColor = rSettings.GetDialogColor().getBColor(); + } + + bool bRtl = AllSettings::GetLayoutRTL(); + + drawinglayer::primitive2d::Primitive2DContainer aSeq(3); + B2DRectangle aBRect = vcl::unotools::b2DRectangleFromRectangle(aRect); + B2DPolygon aPolygon = createPolygonFromRect(aBRect, 3.0 / BUTTON_WIDTH, 3.0 / BUTTON_HEIGHT); + + // Create the polygon primitives + aSeq[0].set(new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + B2DPolyPolygon(aPolygon), aOtherColor)); + aSeq[1].set(new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aPolygon, aColor)); + + // Create the primitive for the image + BitmapEx aBmpEx(RID_BMP_PAGE_BREAK); + double nImgOfstX = 3.0; + if (bRtl) + nImgOfstX = aRect.Right() - aBmpEx.GetSizePixel().Width() - 3.0; + aSeq[2].set(new drawinglayer::primitive2d::DiscreteBitmapPrimitive2D( + aBmpEx, B2DPoint(nImgOfstX, 1.0))); + + double nTop = double(aRect.getHeight()) / 2.0; + double nBottom = nTop + 4.0; + double nLeft = aRect.getWidth() - ARROW_WIDTH - 6.0; + if (bRtl) + nLeft = ARROW_WIDTH - 2.0; + double nRight = nLeft + 8.0; + + B2DPolygon aTriangle; + aTriangle.append(B2DPoint(nLeft, nTop)); + aTriangle.append(B2DPoint(nRight, nTop)); + aTriangle.append(B2DPoint((nLeft + nRight) / 2.0, nBottom)); + aTriangle.setClosed(true); + + BColor aTriangleColor = COL_BLACK.getBColor(); + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + aTriangleColor = COL_WHITE.getBColor(); + + aSeq.emplace_back(); + aSeq.back().set( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + B2DPolyPolygon(aTriangle), aTriangleColor)); + + 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].set( new drawinglayer::primitive2d::ModifiedColorPrimitive2D( + std::move(aSeq), aBColorModifier)); + + // Create the processor and process the primitives + const drawinglayer::geometry::ViewInformation2D aNewViewInfos; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( + drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(*m_xVirDev, aNewViewInfos)); + + pProcessor->process(aGhostedSeq); + + m_xMenuButton->set_custom_button(m_xVirDev.get()); +} + +static SvxBreak lcl_GetBreakItem(const SwContentFrame* pCnt) +{ + SvxBreak eBreak = SvxBreak::NONE; + if ( pCnt ) + { + if ( pCnt->IsInTab() ) + eBreak = pCnt->FindTabFrame()->GetBreakItem().GetBreak(); + else + eBreak = pCnt->GetBreakItem().GetBreak(); + } + return eBreak; +} + +IMPL_LINK(SwPageBreakWin, SelectHdl, const OString&, rIdent, void) +{ + SwFrameControlPtr pFrameControl = m_pEditWin->GetFrameControlsManager().GetControl(FrameControlType::PageBreak, m_pFrame); + + m_pLine->execute(rIdent); + + // Only fade if there is more than this temporary shared pointer: + // The main reference has been deleted due to a page break removal + if (pFrameControl.use_count() > 1) + Fade( false ); +} + +void SwBreakDashedLine::execute(std::string_view rIdent) +{ + const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); + // Is there a PageBefore break on this page? + SwContentFrame *pCnt = const_cast<SwContentFrame*>(pPageFrame->FindFirstBodyContent()); + SvxBreak eBreak = lcl_GetBreakItem( pCnt ); + + // Also check the previous page - to see if there is a PageAfter break + SwContentFrame *pPrevCnt = nullptr; + SvxBreak ePrevBreak = SvxBreak::NONE; + const SwPageFrame* pPrevPage = static_cast<const SwPageFrame*>(pPageFrame->GetPrev()); + if ( pPrevPage ) + { + pPrevCnt = const_cast<SwContentFrame*>(pPrevPage->FindLastBodyContent()); + ePrevBreak = lcl_GetBreakItem( pPrevCnt ); + } + + if (pCnt && rIdent == "edit") + { + SwWrtShell& rSh = m_pEditWin->GetView().GetWrtShell(); + bool bOldLock = rSh.IsViewLocked(); + rSh.LockView( true ); + + // Order of edit detection: first RES_BREAK PageAfter, then RES_BREAK PageBefore/RES_PAGEDESC + if ( ePrevBreak == SvxBreak::PageAfter ) + pCnt = pPrevCnt; + + SwContentNode& rNd = pCnt->IsTextFrame() + ? *static_cast<SwTextFrame*>(pCnt)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame*>(pCnt)->GetNode(); + + if ( pCnt->IsInTab() ) + { + rSh.Push( ); + rSh.ClearMark(); + + rSh.SetSelection( rNd ); + + SfxStringItem aItem(m_pEditWin->GetView().GetPool().GetWhich(FN_FORMAT_TABLE_DLG), "textflow"); + m_pEditWin->GetView().GetViewFrame()->GetDispatcher()->ExecuteList( + FN_FORMAT_TABLE_DLG, + SfxCallMode::SYNCHRON | SfxCallMode::RECORD, + { &aItem }); + + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + else + { + SwPaM aPaM( rNd ); + SwPaMItem aPaMItem( m_pEditWin->GetView().GetPool( ).GetWhich( FN_PARAM_PAM ), &aPaM ); + SfxStringItem aItem( SID_PARA_DLG, "textflow" ); + m_pEditWin->GetView().GetViewFrame()->GetDispatcher()->ExecuteList( + SID_PARA_DLG, + SfxCallMode::SYNCHRON | SfxCallMode::RECORD, + { &aItem, &aPaMItem }); + } + rSh.LockView( bOldLock ); + m_pEditWin->GrabFocus( ); + } + else if (pCnt && rIdent == "delete") + { + SwContentNode& rNd = pCnt->IsTextFrame() + ? *static_cast<SwTextFrame*>(pCnt)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame*>(pCnt)->GetNode(); + + rNd.GetDoc().GetIDocumentUndoRedo( ).StartUndo( SwUndoId::UI_DELETE_PAGE_BREAK, nullptr ); + + SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> aSet( + m_pEditWin->GetView().GetWrtShell().GetAttrPool()); + + aSet.Put( SwFormatPageDesc( nullptr ) ); + // This break could be from the current paragraph, if it has a PageBefore break. + if ( eBreak == SvxBreak::PageBefore ) + aSet.Put( SvxFormatBreakItem( SvxBreak::NONE, RES_BREAK ) ); + + rNd.GetDoc().getIDocumentContentOperations().InsertItemSet( + SwPaM(rNd), aSet, SetAttrMode::DEFAULT, pPageFrame->getRootFrame()); + + // This break could be from the previous paragraph, if it has a PageAfter break. + if ( ePrevBreak == SvxBreak::PageAfter ) + { + SwContentNode& rPrevNd = pPrevCnt->IsTextFrame() + ? *static_cast<SwTextFrame*>(pPrevCnt)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame*>(pPrevCnt)->GetNode(); + aSet.ClearItem(); + aSet.Put( SvxFormatBreakItem( SvxBreak::NONE, RES_BREAK ) ); + rPrevNd.GetDoc().getIDocumentContentOperations().InsertItemSet( + SwPaM(rPrevNd), aSet, SetAttrMode::DEFAULT, pPrevCnt->getRootFrame()); + } + + rNd.GetDoc().GetIDocumentUndoRedo( ).EndUndo( SwUndoId::UI_DELETE_PAGE_BREAK, nullptr ); + } +} + +void SwBreakDashedLine::UpdatePosition(const std::optional<Point>& xEvtPt) +{ + if ( xEvtPt ) + { + if ( xEvtPt == m_xMousePt ) + return; + m_xMousePt = xEvtPt; + } + + const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); + const SwFrame* pPrevPage = pPageFrame; + do + { + pPrevPage = pPrevPage->GetPrev(); + } + while ( pPrevPage && ( ( pPrevPage->getFrameArea().Top( ) == pPageFrame->getFrameArea().Top( ) ) + || static_cast< const SwPageFrame* >( pPrevPage )->IsEmptyPage( ) ) ); + + ::tools::Rectangle aBoundRect = GetEditWin()->LogicToPixel( pPageFrame->GetBoundRect(GetEditWin()->GetOutDev()).SVRect() ); + ::tools::Rectangle aFrameRect = GetEditWin()->LogicToPixel( pPageFrame->getFrameArea().SVRect() ); + + tools::Long nYLineOffset = ( aBoundRect.Top() + aFrameRect.Top() ) / 2; + if ( pPrevPage ) + { + ::tools::Rectangle aPrevFrameRect = GetEditWin()->LogicToPixel( pPrevPage->getFrameArea().SVRect() ); + nYLineOffset = ( aPrevFrameRect.Bottom() + aFrameRect.Top() ) / 2; + } + + // Get the page + sidebar coords + tools::Long nPgLeft = aFrameRect.Left(); + tools::Long nPgRight = aFrameRect.Right(); + + tools::ULong nSidebarWidth = 0; + const SwPostItMgr* pPostItMngr = GetEditWin()->GetView().GetWrtShell().GetPostItMgr(); + if ( pPostItMngr && pPostItMngr->HasNotes() && pPostItMngr->ShowNotes() ) + nSidebarWidth = pPostItMngr->GetSidebarBorderWidth( true ) + pPostItMngr->GetSidebarWidth( true ); + + if ( pPageFrame->SidebarPosition( ) == sw::sidebarwindows::SidebarPosition::LEFT ) + nPgLeft -= nSidebarWidth; + else if ( pPageFrame->SidebarPosition( ) == sw::sidebarwindows::SidebarPosition::RIGHT ) + nPgRight += nSidebarWidth; + + Size aBtnSize( BUTTON_WIDTH + ARROW_WIDTH, BUTTON_HEIGHT ); + + // Place the button on the left or right? + ::tools::Rectangle aVisArea = GetEditWin()->LogicToPixel( GetEditWin()->GetView().GetVisArea() ); + + tools::Long nLineLeft = std::max( nPgLeft, aVisArea.Left() ); + tools::Long nLineRight = std::min( nPgRight, aVisArea.Right() ); + tools::Long nBtnLeft = nLineLeft; + + if ( m_xMousePt ) + { + nBtnLeft = nLineLeft + m_xMousePt->X() - aBtnSize.getWidth() / 2; + + if ( nBtnLeft < nLineLeft ) + nBtnLeft = nLineLeft; + else if ( ( nBtnLeft + aBtnSize.getWidth() ) > nLineRight ) + nBtnLeft = nLineRight - aBtnSize.getWidth(); + } + + // Set the button position + m_aBtnRect = ::tools::Rectangle(Point(nBtnLeft, nYLineOffset - BUTTON_HEIGHT / 2), aBtnSize); + if (m_pWin) + m_pWin->SetRectanglePixel(m_aBtnRect); + + // Set the line position + Point aLinePos( nLineLeft, nYLineOffset - 5 ); + Size aLineSize( nLineRight - nLineLeft, 10 ); + SetPosSizePixel(aLinePos, aLineSize); +} + +void SwPageBreakWin::SetRectanglePixel(const ::tools::Rectangle& rRect) +{ + SetPosSizePixel(rRect.TopLeft(), rRect.GetSize()); + m_xVirDev->SetOutputSizePixel(rRect.GetSize()); +} + +void SwPageBreakWin::Fade( bool bFadeIn ) +{ + m_bIsAppearing = bFadeIn; + if ( bFadeIn ) + m_nDelayAppearing = 0; + + if ( !m_bDestroyed && m_aFadeTimer.IsActive( ) ) + m_aFadeTimer.Stop(); + if ( !m_bDestroyed ) + m_aFadeTimer.Start( ); +} + +IMPL_LINK(SwPageBreakWin, ToggleHdl, weld::Toggleable&, rMenuButton, void) +{ + // hide on dropdown, draw fully unfaded if dropdown before fully faded in + Fade(rMenuButton.get_active()); +} + +IMPL_LINK_NOARG(SwPageBreakWin, FadeHandler, Timer *, void) +{ + const int TICKS_BEFORE_WE_APPEAR = 10; + if ( m_bIsAppearing && m_nDelayAppearing < TICKS_BEFORE_WE_APPEAR ) + { + ++m_nDelayAppearing; + m_aFadeTimer.Start(); + return; + } + + 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( ) ) + { + Hide(); + m_pLine->DestroyWin(); + return; + } + else + { + m_pLine->UpdatePosition(); + PaintButton(); + } + + if (IsVisible( ) && m_nFadeRate > 0 && m_nFadeRate < 100) + m_aFadeTimer.Start(); +} + +FactoryFunction SwBreakDashedLine::GetUITestFactory() const +{ + return PageBreakUIObject::create; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |