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(u
"PageBreak"_ustr
); // 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
, u
"modules/swriter/ui/pbmenubutton.ui"_ustr
, u
"PBMenuButton"_ustr
),
127 m_xMenuButton(m_xBuilder
->weld_menu_button(u
"menubutton"_ustr
)),
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
;
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
.push_back(new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
202 B2DPolyPolygon(aPolygon
), aOtherColor
));
203 aSeq
.push_back(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
.push_back(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();
231 aSeq
.push_back( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
232 B2DPolyPolygon(aTriangle
), aTriangleColor
));
234 drawinglayer::primitive2d::Primitive2DContainer aGhostedSeq
;
235 double nFadeRate
= double(m_nFadeRate
) / 100.0;
236 const basegfx::BColorModifierSharedPtr aBColorModifier
=
237 std::make_shared
<basegfx::BColorModifier_interpolate
>(COL_WHITE
.getBColor(),
239 aGhostedSeq
.push_back( new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
240 std::move(aSeq
), aBColorModifier
));
242 // Create the processor and process the primitives
243 const drawinglayer::geometry::ViewInformation2D aNewViewInfos
;
244 std::unique_ptr
<drawinglayer::processor2d::BaseProcessor2D
> pProcessor(
245 drawinglayer::processor2d::createProcessor2DFromOutputDevice(*m_xVirDev
, aNewViewInfos
));
247 pProcessor
->process(aGhostedSeq
);
249 m_xMenuButton
->set_custom_button(m_xVirDev
.get());
252 static SvxBreak
lcl_GetBreakItem(const SwContentFrame
* pCnt
)
254 SvxBreak eBreak
= SvxBreak::NONE
;
257 if ( pCnt
->IsInTab() )
258 eBreak
= pCnt
->FindTabFrame()->GetBreakItem().GetBreak();
260 eBreak
= pCnt
->GetBreakItem().GetBreak();
265 IMPL_LINK(SwPageBreakWin
, SelectHdl
, const OUString
&, rIdent
, void)
267 SwFrameControlPtr pFrameControl
= m_pEditWin
->GetFrameControlsManager().GetControl(FrameControlType::PageBreak
, m_pFrame
);
269 m_pLine
->execute(rIdent
);
271 // Only fade if there is more than this temporary shared pointer:
272 // The main reference has been deleted due to a page break removal
273 if (pFrameControl
.use_count() > 1)
277 void SwBreakDashedLine::execute(std::u16string_view rIdent
)
279 const SwPageFrame
* pPageFrame
= SwFrameMenuButtonBase::GetPageFrame(m_pFrame
);
280 // Is there a PageBefore break on this page?
281 SwContentFrame
*pCnt
= const_cast<SwContentFrame
*>(pPageFrame
->FindFirstBodyContent());
282 SvxBreak eBreak
= lcl_GetBreakItem( pCnt
);
284 // Also check the previous page - to see if there is a PageAfter break
285 SwContentFrame
*pPrevCnt
= nullptr;
286 SvxBreak ePrevBreak
= SvxBreak::NONE
;
287 const SwPageFrame
* pPrevPage
= static_cast<const SwPageFrame
*>(pPageFrame
->GetPrev());
290 pPrevCnt
= const_cast<SwContentFrame
*>(pPrevPage
->FindLastBodyContent());
291 ePrevBreak
= lcl_GetBreakItem( pPrevCnt
);
294 if (pCnt
&& rIdent
== u
"edit")
296 SwWrtShell
& rSh
= m_pEditWin
->GetView().GetWrtShell();
297 bool bOldLock
= rSh
.IsViewLocked();
298 rSh
.LockView( true );
300 // Order of edit detection: first RES_BREAK PageAfter, then RES_BREAK PageBefore/RES_PAGEDESC
301 if ( ePrevBreak
== SvxBreak::PageAfter
)
304 SwContentNode
& rNd
= pCnt
->IsTextFrame()
305 ? *static_cast<SwTextFrame
*>(pCnt
)->GetTextNodeFirst()
306 : *static_cast<SwNoTextFrame
*>(pCnt
)->GetNode();
308 if ( pCnt
->IsInTab() )
313 rSh
.SetSelection( SwPaM(rNd
) );
315 SfxStringItem
aItem(m_pEditWin
->GetView().GetPool().GetWhichIDFromSlotID(FN_FORMAT_TABLE_DLG
), u
"textflow"_ustr
);
316 m_pEditWin
->GetView().GetViewFrame().GetDispatcher()->ExecuteList(
318 SfxCallMode::SYNCHRON
| SfxCallMode::RECORD
,
321 rSh
.Pop(SwCursorShell::PopMode::DeleteCurrent
);
326 SwPaMItem
aPaMItem( m_pEditWin
->GetView().GetPool( ).GetWhichIDFromSlotID( FN_PARAM_PAM
), &aPaM
);
327 SfxStringItem
aItem( SID_PARA_DLG
, u
"textflow"_ustr
);
328 m_pEditWin
->GetView().GetViewFrame().GetDispatcher()->ExecuteList(
330 SfxCallMode::SYNCHRON
| SfxCallMode::RECORD
,
331 { &aItem
, &aPaMItem
});
333 rSh
.LockView( bOldLock
);
334 m_pEditWin
->GrabFocus( );
336 else if (pCnt
&& rIdent
== u
"delete")
338 SwContentNode
& rNd
= pCnt
->IsTextFrame()
339 ? *static_cast<SwTextFrame
*>(pCnt
)->GetTextNodeFirst()
340 : *static_cast<SwNoTextFrame
*>(pCnt
)->GetNode();
342 rNd
.GetDoc().GetIDocumentUndoRedo( ).StartUndo( SwUndoId::UI_DELETE_PAGE_BREAK
, nullptr );
344 SfxItemSetFixed
<RES_PAGEDESC
, RES_BREAK
> aSet(
345 m_pEditWin
->GetView().GetWrtShell().GetAttrPool());
347 aSet
.Put( SwFormatPageDesc( nullptr ) );
348 // This break could be from the current paragraph, if it has a PageBefore break.
349 if ( eBreak
== SvxBreak::PageBefore
)
350 aSet
.Put( SvxFormatBreakItem( SvxBreak::NONE
, RES_BREAK
) );
352 rNd
.GetDoc().getIDocumentContentOperations().InsertItemSet(
353 SwPaM(rNd
), aSet
, SetAttrMode::DEFAULT
, pPageFrame
->getRootFrame());
355 // This break could be from the previous paragraph, if it has a PageAfter break.
356 if ( ePrevBreak
== SvxBreak::PageAfter
)
358 SwContentNode
& rPrevNd
= pPrevCnt
->IsTextFrame()
359 ? *static_cast<SwTextFrame
*>(pPrevCnt
)->GetTextNodeFirst()
360 : *static_cast<SwNoTextFrame
*>(pPrevCnt
)->GetNode();
362 aSet
.Put( SvxFormatBreakItem( SvxBreak::NONE
, RES_BREAK
) );
363 rPrevNd
.GetDoc().getIDocumentContentOperations().InsertItemSet(
364 SwPaM(rPrevNd
), aSet
, SetAttrMode::DEFAULT
, pPrevCnt
->getRootFrame());
367 rNd
.GetDoc().GetIDocumentUndoRedo( ).EndUndo( SwUndoId::UI_DELETE_PAGE_BREAK
, nullptr );
371 void SwBreakDashedLine::UpdatePosition(const std::optional
<Point
>& xEvtPt
)
375 if ( xEvtPt
== m_xMousePt
)
380 const SwPageFrame
* pPageFrame
= SwFrameMenuButtonBase::GetPageFrame(m_pFrame
);
381 const SwFrame
* pPrevPage
= pPageFrame
;
384 pPrevPage
= pPrevPage
->GetPrev();
386 while ( pPrevPage
&& ( ( pPrevPage
->getFrameArea().Top( ) == pPageFrame
->getFrameArea().Top( ) )
387 || static_cast< const SwPageFrame
* >( pPrevPage
)->IsEmptyPage( ) ) );
389 ::tools::Rectangle aBoundRect
= GetEditWin()->LogicToPixel( pPageFrame
->GetBoundRect(GetEditWin()->GetOutDev()).SVRect() );
390 ::tools::Rectangle aFrameRect
= GetEditWin()->LogicToPixel( pPageFrame
->getFrameArea().SVRect() );
392 tools::Long nYLineOffset
= ( aBoundRect
.Top() + aFrameRect
.Top() ) / 2;
395 ::tools::Rectangle aPrevFrameRect
= GetEditWin()->LogicToPixel( pPrevPage
->getFrameArea().SVRect() );
396 nYLineOffset
= ( aPrevFrameRect
.Bottom() + aFrameRect
.Top() ) / 2;
399 // Get the page + sidebar coords
400 tools::Long nPgLeft
= aFrameRect
.Left();
401 tools::Long nPgRight
= aFrameRect
.Right();
403 tools::ULong nSidebarWidth
= 0;
404 const SwPostItMgr
* pPostItMngr
= GetEditWin()->GetView().GetWrtShell().GetPostItMgr();
405 if ( pPostItMngr
&& pPostItMngr
->HasNotes() && pPostItMngr
->ShowNotes() )
406 nSidebarWidth
= pPostItMngr
->GetSidebarBorderWidth( true ) + pPostItMngr
->GetSidebarWidth( true );
408 if ( pPageFrame
->SidebarPosition( ) == sw::sidebarwindows::SidebarPosition::LEFT
)
409 nPgLeft
-= nSidebarWidth
;
410 else if ( pPageFrame
->SidebarPosition( ) == sw::sidebarwindows::SidebarPosition::RIGHT
)
411 nPgRight
+= nSidebarWidth
;
413 Size
aBtnSize( BUTTON_WIDTH
+ ARROW_WIDTH
, BUTTON_HEIGHT
);
415 // Place the button on the left or right?
416 ::tools::Rectangle aVisArea
= GetEditWin()->LogicToPixel( GetEditWin()->GetView().GetVisArea() );
418 tools::Long nLineLeft
= std::max( nPgLeft
, aVisArea
.Left() );
419 tools::Long nLineRight
= std::min( nPgRight
, aVisArea
.Right() );
420 tools::Long nBtnLeft
= nLineLeft
;
424 nBtnLeft
= nLineLeft
+ m_xMousePt
->X() - aBtnSize
.getWidth() / 2;
426 if ( nBtnLeft
< nLineLeft
)
427 nBtnLeft
= nLineLeft
;
428 else if ( ( nBtnLeft
+ aBtnSize
.getWidth() ) > nLineRight
)
429 nBtnLeft
= nLineRight
- aBtnSize
.getWidth();
432 // Set the button position
433 m_aBtnRect
= ::tools::Rectangle(Point(nBtnLeft
, nYLineOffset
- BUTTON_HEIGHT
/ 2), aBtnSize
);
435 m_pWin
->SetRectanglePixel(m_aBtnRect
);
437 // Set the line position
438 Point
aLinePos( nLineLeft
, nYLineOffset
- 5 );
439 Size
aLineSize( nLineRight
- nLineLeft
, 10 );
440 SetPosSizePixel(aLinePos
, aLineSize
);
443 void SwPageBreakWin::SetRectanglePixel(const ::tools::Rectangle
& rRect
)
445 SetPosSizePixel(rRect
.TopLeft(), rRect
.GetSize());
446 m_xVirDev
->SetOutputSizePixel(rRect
.GetSize());
449 void SwPageBreakWin::Fade( bool bFadeIn
)
451 m_bIsAppearing
= bFadeIn
;
453 m_nDelayAppearing
= 0;
455 if ( !m_bDestroyed
&& m_aFadeTimer
.IsActive( ) )
458 m_aFadeTimer
.Start( );
461 IMPL_LINK(SwPageBreakWin
, ToggleHdl
, weld::Toggleable
&, rMenuButton
, void)
463 // hide on dropdown, draw fully unfaded if dropdown before fully faded in
464 Fade(rMenuButton
.get_active());
467 IMPL_LINK_NOARG(SwPageBreakWin
, FadeHandler
, Timer
*, void)
469 const int TICKS_BEFORE_WE_APPEAR
= 10;
470 if ( m_bIsAppearing
&& m_nDelayAppearing
< TICKS_BEFORE_WE_APPEAR
)
473 m_aFadeTimer
.Start();
477 if ( m_bIsAppearing
&& m_nFadeRate
> 0 )
479 else if ( !m_bIsAppearing
&& m_nFadeRate
< 100 )
482 if ( m_nFadeRate
!= 100 && !IsVisible() )
484 else if ( m_nFadeRate
== 100 && IsVisible( ) )
487 m_pLine
->DestroyWin();
492 m_pLine
->UpdatePosition();
496 if (IsVisible( ) && m_nFadeRate
> 0 && m_nFadeRate
< 100)
497 m_aFadeTimer
.Start();
500 FactoryFunction
SwBreakDashedLine::GetUITestFactory() const
502 return PageBreakUIObject::create
;
505 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */