1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <bitmaps.hlst>
15 #include <notxtfrm.hxx>
17 #include <DashedLine.hxx>
20 #include <fmtpdsc.hxx>
21 #include <IDocumentUndoRedo.hxx>
22 #include <IDocumentContentOperations.hxx>
23 #include <PageBreakWin.hxx>
24 #include <pagefrm.hxx>
25 #include <PostItMgr.hxx>
26 #include <FrameControlsManager.hxx>
27 #include <strings.hrc>
29 #include <uiitems.hxx>
30 #include <uiobject.hxx>
32 #include <viewopt.hxx>
35 #include <basegfx/color/bcolortools.hxx>
36 #include <basegfx/polygon/b2dpolygon.hxx>
37 #include <basegfx/polygon/b2dpolygontools.hxx>
38 #include <basegfx/range/b2drectangle.hxx>
39 #include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx>
40 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
41 #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
42 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
43 #include <drawinglayer/processor2d/baseprocessor2d.hxx>
44 #include <drawinglayer/processor2d/processor2dtools.hxx>
45 #include <editeng/formatbreakitem.hxx>
46 #include <sfx2/dispatch.hxx>
47 #include <sfx2/viewfrm.hxx>
48 #include <svl/stritem.hxx>
49 #include <vcl/canvastools.hxx>
50 #include <vcl/event.hxx>
51 #include <vcl/svapp.hxx>
52 #include <vcl/settings.hxx>
55 #define BUTTON_WIDTH 30
56 #define BUTTON_HEIGHT 19
59 using namespace basegfx
;
60 using namespace basegfx::utils
;
62 SwBreakDashedLine::SwBreakDashedLine(SwEditWin
* pEditWin
, const SwFrame
*pFrame
)
63 : SwDashedLine(pEditWin
, &SwViewOption::GetPageBreakColor
)
64 , m_pEditWin(pEditWin
)
67 set_id("PageBreak"); // for uitest
70 SwPageBreakWin
& SwBreakDashedLine::GetOrCreateWin()
74 m_pWin
= VclPtr
<SwPageBreakWin
>::Create(this, m_pEditWin
, m_pFrame
);
75 m_pWin
->SetPosSizePixel(m_aBtnRect
.TopLeft(), m_aBtnRect
.GetSize());
76 m_pWin
->SetZOrder(this, ZOrderFlags::Before
);
81 void SwBreakDashedLine::DestroyWin()
83 m_pWin
.disposeAndClear();
86 void SwBreakDashedLine::MouseMove( const MouseEvent
& rMEvt
)
88 if ( rMEvt
.IsLeaveWindow() )
90 // don't fade if we just move to the 'button'
91 Point
aEventPos( GetPosPixel() + rMEvt
.GetPosPixel() );
92 if (m_pWin
&& (!Contains(aEventPos
) || !m_pWin
->IsVisible()))
95 else if (!m_pWin
|| !m_pWin
->IsVisible())
97 GetOrCreateWin().Fade(true);
100 if (!rMEvt
.IsSynthetic() && (!m_pWin
|| !m_pWin
->IsVisible()))
102 UpdatePosition(rMEvt
.GetPosPixel());
106 void SwBreakDashedLine::ShowAll(bool bShow
)
111 void SwBreakDashedLine::SetReadonly(bool bReadonly
)
116 bool SwBreakDashedLine::Contains(const Point
&rDocPt
) const
118 if (m_aBtnRect
.Contains(rDocPt
))
121 ::tools::Rectangle
aLineRect(GetPosPixel(), GetSizePixel());
122 return aLineRect
.Contains(rDocPt
);
125 SwPageBreakWin::SwPageBreakWin(SwBreakDashedLine
* pLine
, SwEditWin
* pEditWin
, const SwFrame
*pFrame
) :
126 InterimItemWindow(pEditWin
, "modules/swriter/ui/pbmenubutton.ui", "PBMenuButton"),
127 m_xMenuButton(m_xBuilder
->weld_menu_button("menubutton")),
129 m_pEditWin(pEditWin
),
131 m_bIsAppearing( false ),
133 m_nDelayAppearing( 0 ),
134 m_aFadeTimer("SwPageBreakWin m_aFadeTimer"),
135 m_bDestroyed( false )
137 m_xMenuButton
->connect_toggled(LINK(this, SwPageBreakWin
, ToggleHdl
));
138 m_xMenuButton
->connect_selected(LINK(this, SwPageBreakWin
, SelectHdl
));
139 m_xMenuButton
->set_accessible_name(SwResId(STR_PAGE_BREAK_BUTTON
));
141 m_xVirDev
= m_xMenuButton
->create_virtual_device();
142 SwFrameMenuButtonBase::SetVirDevFont(*m_xVirDev
);
144 // Use pixels for the rest of the drawing
145 m_xVirDev
->SetMapMode( MapMode ( MapUnit::MapPixel
) );
147 m_aFadeTimer
.SetTimeout( 50 );
148 m_aFadeTimer
.SetInvokeHandler( LINK( this, SwPageBreakWin
, FadeHandler
) );
151 SwPageBreakWin::~SwPageBreakWin( )
156 void SwPageBreakWin::dispose()
160 m_xVirDev
.disposeAndClear();
165 m_xMenuButton
.reset();
166 InterimItemWindow::dispose();
169 void SwPageBreakWin::PaintButton()
174 const ::tools::Rectangle
aRect(::tools::Rectangle(Point(0, 0), m_xVirDev
->PixelToLogic(GetSizePixel())));
176 // Properly paint the control
177 BColor aColor
= SwViewOption::GetCurrentViewOptions().GetPageBreakColor().getBColor();
179 BColor aHslLine
= rgb2hsl(aColor
);
180 double nLuminance
= aHslLine
.getZ();
181 nLuminance
+= (1.0 - nLuminance
) * 0.75;
182 if ( aHslLine
.getZ() > 0.7 )
183 nLuminance
= aHslLine
.getZ() * 0.7;
184 aHslLine
.setZ(nLuminance
);
185 BColor aOtherColor
= hsl2rgb(aHslLine
);
187 const StyleSettings
& rSettings
= Application::GetSettings().GetStyleSettings();
188 if (rSettings
.GetHighContrastMode())
190 aColor
= rSettings
.GetDialogTextColor().getBColor();
191 aOtherColor
= rSettings
.GetDialogColor().getBColor();
194 bool bRtl
= AllSettings::GetLayoutRTL();
196 drawinglayer::primitive2d::Primitive2DContainer
aSeq(3);
197 B2DRectangle aBRect
= vcl::unotools::b2DRectangleFromRectangle(aRect
);
198 B2DPolygon aPolygon
= createPolygonFromRect(aBRect
, 3.0 / BUTTON_WIDTH
, 3.0 / BUTTON_HEIGHT
);
200 // Create the polygon primitives
201 aSeq
[0].set(new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
202 B2DPolyPolygon(aPolygon
), aOtherColor
));
203 aSeq
[1].set(new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
204 std::move(aPolygon
), aColor
));
206 // Create the primitive for the image
207 BitmapEx
aBmpEx(RID_BMP_PAGE_BREAK
);
208 double nImgOfstX
= 3.0;
210 nImgOfstX
= aRect
.Right() - aBmpEx
.GetSizePixel().Width() - 3.0;
211 aSeq
[2].set(new drawinglayer::primitive2d::DiscreteBitmapPrimitive2D(
212 aBmpEx
, B2DPoint(nImgOfstX
, 1.0)));
214 double nTop
= double(aRect
.getOpenHeight()) / 2.0;
215 double nBottom
= nTop
+ 4.0;
216 double nLeft
= aRect
.getOpenWidth() - ARROW_WIDTH
- 6.0;
218 nLeft
= ARROW_WIDTH
- 2.0;
219 double nRight
= nLeft
+ 8.0;
221 B2DPolygon aTriangle
;
222 aTriangle
.append(B2DPoint(nLeft
, nTop
));
223 aTriangle
.append(B2DPoint(nRight
, nTop
));
224 aTriangle
.append(B2DPoint((nLeft
+ nRight
) / 2.0, nBottom
));
225 aTriangle
.setClosed(true);
227 BColor aTriangleColor
= COL_BLACK
.getBColor();
228 if (Application::GetSettings().GetStyleSettings().GetHighContrastMode())
229 aTriangleColor
= COL_WHITE
.getBColor();
232 aSeq
.back().set( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
233 B2DPolyPolygon(aTriangle
), aTriangleColor
));
235 drawinglayer::primitive2d::Primitive2DContainer
aGhostedSeq(1);
236 double nFadeRate
= double(m_nFadeRate
) / 100.0;
237 const basegfx::BColorModifierSharedPtr aBColorModifier
=
238 std::make_shared
<basegfx::BColorModifier_interpolate
>(COL_WHITE
.getBColor(),
240 aGhostedSeq
[0].set( new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
241 std::move(aSeq
), aBColorModifier
));
243 // Create the processor and process the primitives
244 const drawinglayer::geometry::ViewInformation2D aNewViewInfos
;
245 std::unique_ptr
<drawinglayer::processor2d::BaseProcessor2D
> pProcessor(
246 drawinglayer::processor2d::createProcessor2DFromOutputDevice(*m_xVirDev
, aNewViewInfos
));
248 pProcessor
->process(aGhostedSeq
);
250 m_xMenuButton
->set_custom_button(m_xVirDev
.get());
253 static SvxBreak
lcl_GetBreakItem(const SwContentFrame
* pCnt
)
255 SvxBreak eBreak
= SvxBreak::NONE
;
258 if ( pCnt
->IsInTab() )
259 eBreak
= pCnt
->FindTabFrame()->GetBreakItem().GetBreak();
261 eBreak
= pCnt
->GetBreakItem().GetBreak();
266 IMPL_LINK(SwPageBreakWin
, SelectHdl
, const OUString
&, rIdent
, void)
268 SwFrameControlPtr pFrameControl
= m_pEditWin
->GetFrameControlsManager().GetControl(FrameControlType::PageBreak
, m_pFrame
);
270 m_pLine
->execute(rIdent
);
272 // Only fade if there is more than this temporary shared pointer:
273 // The main reference has been deleted due to a page break removal
274 if (pFrameControl
.use_count() > 1)
278 void SwBreakDashedLine::execute(std::u16string_view rIdent
)
280 const SwPageFrame
* pPageFrame
= SwFrameMenuButtonBase::GetPageFrame(m_pFrame
);
281 // Is there a PageBefore break on this page?
282 SwContentFrame
*pCnt
= const_cast<SwContentFrame
*>(pPageFrame
->FindFirstBodyContent());
283 SvxBreak eBreak
= lcl_GetBreakItem( pCnt
);
285 // Also check the previous page - to see if there is a PageAfter break
286 SwContentFrame
*pPrevCnt
= nullptr;
287 SvxBreak ePrevBreak
= SvxBreak::NONE
;
288 const SwPageFrame
* pPrevPage
= static_cast<const SwPageFrame
*>(pPageFrame
->GetPrev());
291 pPrevCnt
= const_cast<SwContentFrame
*>(pPrevPage
->FindLastBodyContent());
292 ePrevBreak
= lcl_GetBreakItem( pPrevCnt
);
295 if (pCnt
&& rIdent
== u
"edit")
297 SwWrtShell
& rSh
= m_pEditWin
->GetView().GetWrtShell();
298 bool bOldLock
= rSh
.IsViewLocked();
299 rSh
.LockView( true );
301 // Order of edit detection: first RES_BREAK PageAfter, then RES_BREAK PageBefore/RES_PAGEDESC
302 if ( ePrevBreak
== SvxBreak::PageAfter
)
305 SwContentNode
& rNd
= pCnt
->IsTextFrame()
306 ? *static_cast<SwTextFrame
*>(pCnt
)->GetTextNodeFirst()
307 : *static_cast<SwNoTextFrame
*>(pCnt
)->GetNode();
309 if ( pCnt
->IsInTab() )
314 rSh
.SetSelection( SwPaM(rNd
) );
316 SfxStringItem
aItem(m_pEditWin
->GetView().GetPool().GetWhich(FN_FORMAT_TABLE_DLG
), "textflow");
317 m_pEditWin
->GetView().GetViewFrame().GetDispatcher()->ExecuteList(
319 SfxCallMode::SYNCHRON
| SfxCallMode::RECORD
,
322 rSh
.Pop(SwCursorShell::PopMode::DeleteCurrent
);
327 SwPaMItem
aPaMItem( m_pEditWin
->GetView().GetPool( ).GetWhich( FN_PARAM_PAM
), &aPaM
);
328 SfxStringItem
aItem( SID_PARA_DLG
, "textflow" );
329 m_pEditWin
->GetView().GetViewFrame().GetDispatcher()->ExecuteList(
331 SfxCallMode::SYNCHRON
| SfxCallMode::RECORD
,
332 { &aItem
, &aPaMItem
});
334 rSh
.LockView( bOldLock
);
335 m_pEditWin
->GrabFocus( );
337 else if (pCnt
&& rIdent
== u
"delete")
339 SwContentNode
& rNd
= pCnt
->IsTextFrame()
340 ? *static_cast<SwTextFrame
*>(pCnt
)->GetTextNodeFirst()
341 : *static_cast<SwNoTextFrame
*>(pCnt
)->GetNode();
343 rNd
.GetDoc().GetIDocumentUndoRedo( ).StartUndo( SwUndoId::UI_DELETE_PAGE_BREAK
, nullptr );
345 SfxItemSetFixed
<RES_PAGEDESC
, RES_BREAK
> aSet(
346 m_pEditWin
->GetView().GetWrtShell().GetAttrPool());
348 aSet
.Put( SwFormatPageDesc( nullptr ) );
349 // This break could be from the current paragraph, if it has a PageBefore break.
350 if ( eBreak
== SvxBreak::PageBefore
)
351 aSet
.Put( SvxFormatBreakItem( SvxBreak::NONE
, RES_BREAK
) );
353 rNd
.GetDoc().getIDocumentContentOperations().InsertItemSet(
354 SwPaM(rNd
), aSet
, SetAttrMode::DEFAULT
, pPageFrame
->getRootFrame());
356 // This break could be from the previous paragraph, if it has a PageAfter break.
357 if ( ePrevBreak
== SvxBreak::PageAfter
)
359 SwContentNode
& rPrevNd
= pPrevCnt
->IsTextFrame()
360 ? *static_cast<SwTextFrame
*>(pPrevCnt
)->GetTextNodeFirst()
361 : *static_cast<SwNoTextFrame
*>(pPrevCnt
)->GetNode();
363 aSet
.Put( SvxFormatBreakItem( SvxBreak::NONE
, RES_BREAK
) );
364 rPrevNd
.GetDoc().getIDocumentContentOperations().InsertItemSet(
365 SwPaM(rPrevNd
), aSet
, SetAttrMode::DEFAULT
, pPrevCnt
->getRootFrame());
368 rNd
.GetDoc().GetIDocumentUndoRedo( ).EndUndo( SwUndoId::UI_DELETE_PAGE_BREAK
, nullptr );
372 void SwBreakDashedLine::UpdatePosition(const std::optional
<Point
>& xEvtPt
)
376 if ( xEvtPt
== m_xMousePt
)
381 const SwPageFrame
* pPageFrame
= SwFrameMenuButtonBase::GetPageFrame(m_pFrame
);
382 const SwFrame
* pPrevPage
= pPageFrame
;
385 pPrevPage
= pPrevPage
->GetPrev();
387 while ( pPrevPage
&& ( ( pPrevPage
->getFrameArea().Top( ) == pPageFrame
->getFrameArea().Top( ) )
388 || static_cast< const SwPageFrame
* >( pPrevPage
)->IsEmptyPage( ) ) );
390 ::tools::Rectangle aBoundRect
= GetEditWin()->LogicToPixel( pPageFrame
->GetBoundRect(GetEditWin()->GetOutDev()).SVRect() );
391 ::tools::Rectangle aFrameRect
= GetEditWin()->LogicToPixel( pPageFrame
->getFrameArea().SVRect() );
393 tools::Long nYLineOffset
= ( aBoundRect
.Top() + aFrameRect
.Top() ) / 2;
396 ::tools::Rectangle aPrevFrameRect
= GetEditWin()->LogicToPixel( pPrevPage
->getFrameArea().SVRect() );
397 nYLineOffset
= ( aPrevFrameRect
.Bottom() + aFrameRect
.Top() ) / 2;
400 // Get the page + sidebar coords
401 tools::Long nPgLeft
= aFrameRect
.Left();
402 tools::Long nPgRight
= aFrameRect
.Right();
404 tools::ULong nSidebarWidth
= 0;
405 const SwPostItMgr
* pPostItMngr
= GetEditWin()->GetView().GetWrtShell().GetPostItMgr();
406 if ( pPostItMngr
&& pPostItMngr
->HasNotes() && pPostItMngr
->ShowNotes() )
407 nSidebarWidth
= pPostItMngr
->GetSidebarBorderWidth( true ) + pPostItMngr
->GetSidebarWidth( true );
409 if ( pPageFrame
->SidebarPosition( ) == sw::sidebarwindows::SidebarPosition::LEFT
)
410 nPgLeft
-= nSidebarWidth
;
411 else if ( pPageFrame
->SidebarPosition( ) == sw::sidebarwindows::SidebarPosition::RIGHT
)
412 nPgRight
+= nSidebarWidth
;
414 Size
aBtnSize( BUTTON_WIDTH
+ ARROW_WIDTH
, BUTTON_HEIGHT
);
416 // Place the button on the left or right?
417 ::tools::Rectangle aVisArea
= GetEditWin()->LogicToPixel( GetEditWin()->GetView().GetVisArea() );
419 tools::Long nLineLeft
= std::max( nPgLeft
, aVisArea
.Left() );
420 tools::Long nLineRight
= std::min( nPgRight
, aVisArea
.Right() );
421 tools::Long nBtnLeft
= nLineLeft
;
425 nBtnLeft
= nLineLeft
+ m_xMousePt
->X() - aBtnSize
.getWidth() / 2;
427 if ( nBtnLeft
< nLineLeft
)
428 nBtnLeft
= nLineLeft
;
429 else if ( ( nBtnLeft
+ aBtnSize
.getWidth() ) > nLineRight
)
430 nBtnLeft
= nLineRight
- aBtnSize
.getWidth();
433 // Set the button position
434 m_aBtnRect
= ::tools::Rectangle(Point(nBtnLeft
, nYLineOffset
- BUTTON_HEIGHT
/ 2), aBtnSize
);
436 m_pWin
->SetRectanglePixel(m_aBtnRect
);
438 // Set the line position
439 Point
aLinePos( nLineLeft
, nYLineOffset
- 5 );
440 Size
aLineSize( nLineRight
- nLineLeft
, 10 );
441 SetPosSizePixel(aLinePos
, aLineSize
);
444 void SwPageBreakWin::SetRectanglePixel(const ::tools::Rectangle
& rRect
)
446 SetPosSizePixel(rRect
.TopLeft(), rRect
.GetSize());
447 m_xVirDev
->SetOutputSizePixel(rRect
.GetSize());
450 void SwPageBreakWin::Fade( bool bFadeIn
)
452 m_bIsAppearing
= bFadeIn
;
454 m_nDelayAppearing
= 0;
456 if ( !m_bDestroyed
&& m_aFadeTimer
.IsActive( ) )
459 m_aFadeTimer
.Start( );
462 IMPL_LINK(SwPageBreakWin
, ToggleHdl
, weld::Toggleable
&, rMenuButton
, void)
464 // hide on dropdown, draw fully unfaded if dropdown before fully faded in
465 Fade(rMenuButton
.get_active());
468 IMPL_LINK_NOARG(SwPageBreakWin
, FadeHandler
, Timer
*, void)
470 const int TICKS_BEFORE_WE_APPEAR
= 10;
471 if ( m_bIsAppearing
&& m_nDelayAppearing
< TICKS_BEFORE_WE_APPEAR
)
474 m_aFadeTimer
.Start();
478 if ( m_bIsAppearing
&& m_nFadeRate
> 0 )
480 else if ( !m_bIsAppearing
&& m_nFadeRate
< 100 )
483 if ( m_nFadeRate
!= 100 && !IsVisible() )
485 else if ( m_nFadeRate
== 100 && IsVisible( ) )
488 m_pLine
->DestroyWin();
493 m_pLine
->UpdatePosition();
497 if (IsVisible( ) && m_nFadeRate
> 0 && m_nFadeRate
< 100)
498 m_aFadeTimer
.Start();
501 FactoryFunction
SwBreakDashedLine::GetUITestFactory() const
503 return PageBreakUIObject::create
;
506 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */