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 <basegfx/polygon/b2dpolygon.hxx>
11 #include <comphelper/dispatchcommand.hxx>
12 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
13 #include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
14 #include <drawinglayer/processor2d/baseprocessor2d.hxx>
15 #include <drawinglayer/processor2d/processor2dtools.hxx>
17 #include <officecfg/Office/UI/Infobar.hxx>
18 #include <officecfg/Office/Common.hxx>
19 #include <sfx2/bindings.hxx>
20 #include <sfx2/dispatch.hxx>
21 #include <sfx2/infobar.hxx>
22 #include <sfx2/objface.hxx>
23 #include <sfx2/sfxsids.hrc>
24 #include <sfx2/viewfrm.hxx>
26 #include <vcl/image.hxx>
27 #include <vcl/settings.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/virdev.hxx>
30 #include <vcl/weldutils.hxx>
31 #include <bitmaps.hlst>
33 using namespace drawinglayer::geometry
;
34 using namespace drawinglayer::processor2d
;
35 using namespace drawinglayer::primitive2d
;
36 using namespace drawinglayer::attribute
;
37 using namespace basegfx
;
38 using namespace css::frame
;
42 void GetInfoBarColors(InfobarType ibType
, BColor
& rBackgroundColor
, BColor
& rForegroundColor
,
43 BColor
& rMessageColor
)
45 const StyleSettings
& rSettings
= Application::GetSettings().GetStyleSettings();
49 case InfobarType::INFO
: // blue; #004785/0,71,133; #BDE5F8/189,229,248
50 rBackgroundColor
= basegfx::BColor(0.741, 0.898, 0.973);
51 rForegroundColor
= basegfx::BColor(0.0, 0.278, 0.522);
52 rMessageColor
= basegfx::BColor(0.0, 0.278, 0.522);
54 case InfobarType::SUCCESS
: // green; #32550C/50,85,12; #DFF2BF/223,242,191
55 rBackgroundColor
= basegfx::BColor(0.874, 0.949, 0.749);
56 rForegroundColor
= basegfx::BColor(0.196, 0.333, 0.047);
57 rMessageColor
= basegfx::BColor(0.196, 0.333, 0.047);
59 case InfobarType::WARNING
: // orange; #704300/112,67,0; #FEEFB3/254,239,179
60 rBackgroundColor
= rSettings
.GetWarningColor().getBColor();
61 rForegroundColor
= rSettings
.GetWarningTextColor().getBColor();
62 rMessageColor
= rSettings
.GetWarningTextColor().getBColor();
64 case InfobarType::DANGER
: // red; #7A0006/122,0,6; #FFBABA/255,186,186
65 rBackgroundColor
= rSettings
.GetErrorColor().getBColor();
66 rForegroundColor
= rSettings
.GetErrorTextColor().getBColor();
67 rMessageColor
= rSettings
.GetErrorTextColor().getBColor();
71 if (rSettings
.GetHighContrastMode())
73 rBackgroundColor
= rSettings
.GetLightColor().getBColor();
74 rForegroundColor
= rSettings
.GetDialogTextColor().getBColor();
77 OUString
GetInfoBarIconName(InfobarType ibType
)
83 case InfobarType::INFO
:
84 aRet
= "vcl/res/infobox.png";
86 case InfobarType::SUCCESS
:
87 aRet
= "vcl/res/successbox.png";
89 case InfobarType::WARNING
:
90 aRet
= "vcl/res/warningbox.png";
92 case InfobarType::DANGER
:
93 aRet
= "vcl/res/errorbox.png";
100 } // anonymous namespace
102 void SfxInfoBarWindow::SetCloseButtonImage()
104 Size aSize
= Image(StockImage::Yes
, CLOSEDOC
).GetSizePixel();
105 aSize
= Size(aSize
.Width() * 1.5, aSize
.Height() * 1.5);
107 ScopedVclPtr
<VirtualDevice
> xDevice(m_xCloseBtn
->create_virtual_device());
108 xDevice
->SetOutputSizePixel(Size(24, 24));
109 xDevice
->SetBackground(Color(m_aBackgroundColor
));
112 const int nPos
= (24 - aSize
.getWidth()) / 2;
113 Point
aBtnPos(nPos
, nPos
);
115 const ViewInformation2D aNewViewInfos
;
116 const std::unique_ptr
<BaseProcessor2D
> pProcessor(
117 createProcessor2DFromOutputDevice(*xDevice
, aNewViewInfos
));
119 const ::tools::Rectangle
aRect(aBtnPos
, xDevice
->PixelToLogic(aSize
));
121 drawinglayer::primitive2d::Primitive2DContainer
aSeq(2);
123 // Draw background. The right and bottom need to be extended by 1 or
124 // there will be a white line on both edges when Skia is enabled.
126 aPolygon
.append(B2DPoint(aRect
.Left(), aRect
.Top()));
127 aPolygon
.append(B2DPoint(aRect
.Right() + 1, aRect
.Top()));
128 aPolygon
.append(B2DPoint(aRect
.Right() + 1, aRect
.Bottom() + 1));
129 aPolygon
.append(B2DPoint(aRect
.Left(), aRect
.Bottom() + 1));
130 aPolygon
.setClosed(true);
132 aSeq
[0] = new PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon
), m_aBackgroundColor
);
134 LineAttribute
aLineAttribute(m_aForegroundColor
, 2.0);
137 B2DPolyPolygon aCross
;
140 aLine1
.append(B2DPoint(aRect
.Left(), aRect
.Top()));
141 aLine1
.append(B2DPoint(aRect
.Right(), aRect
.Bottom()));
142 aCross
.append(aLine1
);
145 aLine2
.append(B2DPoint(aRect
.Right(), aRect
.Top()));
146 aLine2
.append(B2DPoint(aRect
.Left(), aRect
.Bottom()));
147 aCross
.append(aLine2
);
150 = new PolyPolygonStrokePrimitive2D(std::move(aCross
), aLineAttribute
, StrokeAttribute());
152 pProcessor
->process(aSeq
);
154 m_xCloseBtn
->set_item_image(u
"close"_ustr
, xDevice
);
160 std::unique_ptr
<weld::Builder
> m_xBuilder
;
161 std::unique_ptr
<weld::Container
> m_xContainer
;
162 std::unique_ptr
<weld::Button
> m_xButton
;
163 /** StatusListener. Updates the button as the slot state changes */
164 rtl::Reference
<weld::WidgetStatusListener
> m_xStatusListener
;
167 DECL_LINK(CommandHdl
, weld::Button
&, void);
170 ExtraButton(weld::Container
* pContainer
, const OUString
* pCommand
)
171 : m_xBuilder(Application::CreateBuilder(pContainer
, u
"sfx/ui/extrabutton.ui"_ustr
))
172 , m_xContainer(m_xBuilder
->weld_container(u
"ExtraButton"_ustr
))
173 , m_xButton(m_xBuilder
->weld_button(u
"button"_ustr
))
177 m_aCommand
= *pCommand
;
178 m_xButton
->connect_clicked(LINK(this, ExtraButton
, CommandHdl
));
179 m_xStatusListener
.set(new weld::WidgetStatusListener(m_xButton
.get(), m_aCommand
));
180 m_xStatusListener
->startListening();
186 if (m_xStatusListener
.is())
187 m_xStatusListener
->dispose();
190 weld::Button
& get_widget() { return *m_xButton
; }
193 IMPL_LINK_NOARG(ExtraButton
, CommandHdl
, weld::Button
&, void)
195 comphelper::dispatchCommand(m_aCommand
, css::uno::Sequence
<css::beans::PropertyValue
>());
198 SfxInfoBarWindow::SfxInfoBarWindow(vcl::Window
* pParent
, OUString sId
,
199 const OUString
& sPrimaryMessage
,
200 const OUString
& sSecondaryMessage
, InfobarType ibType
,
201 bool bShowCloseButton
)
202 : InterimItemWindow(pParent
, u
"sfx/ui/infobar.ui"_ustr
, u
"InfoBar"_ustr
)
203 , m_sId(std::move(sId
))
205 , m_bLayingOut(false)
206 , m_xImage(m_xBuilder
->weld_image(u
"image"_ustr
))
207 , m_xPrimaryMessage(m_xBuilder
->weld_label(u
"primary"_ustr
))
208 , m_xSecondaryMessage(m_xBuilder
->weld_text_view(u
"secondary"_ustr
))
209 , m_xButtonBox(m_xBuilder
->weld_container(u
"buttonbox"_ustr
))
210 , m_xCloseBtn(m_xBuilder
->weld_toolbar(u
"closebar"_ustr
))
212 SetStyle(GetStyle() | WB_DIALOGCONTROL
);
214 InitControlBase(m_xCloseBtn
.get());
216 m_xImage
->set_from_icon_name(GetInfoBarIconName(ibType
));
217 m_xSecondaryMessage
->set_margin_top(m_xImage
->get_preferred_size().Height() / 4);
219 if (!sPrimaryMessage
.isEmpty())
221 m_xPrimaryMessage
->set_label(sPrimaryMessage
);
222 m_xPrimaryMessage
->show();
225 m_xSecondaryMessage
->set_text(sSecondaryMessage
);
226 m_aOrigMessageSize
= m_xSecondaryMessage
->get_preferred_size();
227 m_aMessageSize
= m_aOrigMessageSize
;
228 m_xSecondaryMessage
->connect_size_allocate(LINK(this, SfxInfoBarWindow
, SizeAllocHdl
));
230 if (bShowCloseButton
)
232 m_xCloseBtn
->connect_clicked(LINK(this, SfxInfoBarWindow
, CloseHandler
));
236 EnableChildTransparentMode();
238 SetForeAndBackgroundColors(m_eType
);
240 auto nWidth
= pParent
->GetSizePixel().getWidth();
241 auto nHeight
= get_preferred_size().Height();
242 SetSizePixel(Size(nWidth
, nHeight
+ 2));
247 IMPL_LINK(SfxInfoBarWindow
, SizeAllocHdl
, const Size
&, rSize
, void)
249 if (m_aMessageSize
!= rSize
)
251 m_aMessageSize
= rSize
;
252 static_cast<SfxInfoBarContainerWindow
*>(GetParent())->TriggerUpdateLayout();
256 Size
SfxInfoBarWindow::DoLayout()
258 Size
aGivenSize(GetSizePixel());
260 // disconnect SizeAllocHdl because we don't care about the size change
262 m_xSecondaryMessage
->connect_size_allocate(Link
<const Size
&, void>());
264 // blow away size cache in case m_aMessageSize.Width() is already the width request
265 // and we would get the cached preferred size instead of the recalc we want to force
266 m_xSecondaryMessage
->set_size_request(-1, -1);
267 // make the width we were detected as set to by SizeAllocHdl as our desired width
268 m_xSecondaryMessage
->set_size_request(m_aMessageSize
.Width(), -1);
269 // get our preferred size with that message width
270 Size
aSizeForWidth(aGivenSize
.Width(), m_xContainer
->get_preferred_size().Height());
271 // restore the message preferred size so we can freely resize, and get a new
272 // m_aMessageSize and repeat the process if we do
273 m_xSecondaryMessage
->set_size_request(m_aOrigMessageSize
.Width(), -1);
275 // connect SizeAllocHdl so changes outside of this layout will trigger a new layout
276 m_xSecondaryMessage
->connect_size_allocate(LINK(this, SfxInfoBarWindow
, SizeAllocHdl
));
278 return aSizeForWidth
;
281 void SfxInfoBarWindow::Layout()
287 InterimItemWindow::Layout();
289 m_bLayingOut
= false;
292 weld::Button
& SfxInfoBarWindow::addButton(const OUString
* pCommand
)
294 m_aActionBtns
.emplace_back(std::make_unique
<ExtraButton
>(m_xButtonBox
.get(), pCommand
));
296 return m_aActionBtns
.back()->get_widget();
299 SfxInfoBarWindow::~SfxInfoBarWindow() { disposeOnce(); }
301 void SfxInfoBarWindow::SetForeAndBackgroundColors(InfobarType eType
)
303 basegfx::BColor aMessageColor
;
304 GetInfoBarColors(eType
, m_aBackgroundColor
, m_aForegroundColor
, aMessageColor
);
306 m_xPrimaryMessage
->set_font_color(Color(aMessageColor
));
307 m_xSecondaryMessage
->set_font_color(Color(aMessageColor
));
309 Color
aBackgroundColor(m_aBackgroundColor
);
310 m_xPrimaryMessage
->set_background(aBackgroundColor
);
311 m_xSecondaryMessage
->set_background(aBackgroundColor
);
312 m_xContainer
->set_background(aBackgroundColor
);
313 if (m_xCloseBtn
->get_visible())
315 m_xCloseBtn
->set_background(aBackgroundColor
);
316 SetCloseButtonImage();
320 void SfxInfoBarWindow::dispose()
322 for (auto& rxBtn
: m_aActionBtns
)
326 m_xPrimaryMessage
.reset();
327 m_xSecondaryMessage
.reset();
328 m_xButtonBox
.reset();
330 m_aActionBtns
.clear();
331 InterimItemWindow::dispose();
334 void SfxInfoBarWindow::Update(const OUString
& sPrimaryMessage
, const OUString
& sSecondaryMessage
,
337 if (m_eType
!= eType
)
340 SetForeAndBackgroundColors(m_eType
);
341 m_xImage
->set_from_icon_name(GetInfoBarIconName(eType
));
344 m_xPrimaryMessage
->set_label(sPrimaryMessage
);
345 m_xSecondaryMessage
->set_text(sSecondaryMessage
);
350 IMPL_LINK_NOARG(SfxInfoBarWindow
, CloseHandler
, const OUString
&, void)
352 static_cast<SfxInfoBarContainerWindow
*>(GetParent())->removeInfoBar(this);
355 SfxInfoBarContainerWindow::SfxInfoBarContainerWindow(SfxInfoBarContainerChild
* pChildWin
)
356 : Window(pChildWin
->GetParent(), WB_DIALOGCONTROL
)
357 , m_pChildWin(pChildWin
)
358 , m_aLayoutIdle("SfxInfoBarContainerWindow m_aLayoutIdle")
361 m_aLayoutIdle
.SetPriority(TaskPriority::HIGHEST
);
362 m_aLayoutIdle
.SetInvokeHandler(LINK(this, SfxInfoBarContainerWindow
, DoUpdateLayout
));
365 IMPL_LINK_NOARG(SfxInfoBarContainerWindow
, DoUpdateLayout
, Timer
*, void) { m_pChildWin
->Update(); }
367 SfxInfoBarContainerWindow::~SfxInfoBarContainerWindow() { disposeOnce(); }
369 void SfxInfoBarContainerWindow::dispose()
371 for (auto& infoBar
: m_pInfoBars
)
372 infoBar
.disposeAndClear();
377 VclPtr
<SfxInfoBarWindow
> SfxInfoBarContainerWindow::appendInfoBar(const OUString
& sId
,
378 const OUString
& sPrimaryMessage
,
379 const OUString
& sSecondaryMessage
,
381 bool bShowCloseButton
)
383 if (!isInfobarEnabled(sId
))
386 auto pInfoBar
= VclPtr
<SfxInfoBarWindow
>::Create(this, sId
, sPrimaryMessage
, sSecondaryMessage
,
387 ibType
, bShowCloseButton
);
389 basegfx::BColor aBackgroundColor
;
390 basegfx::BColor aForegroundColor
;
391 basegfx::BColor aMessageColor
;
392 GetInfoBarColors(ibType
, aBackgroundColor
, aForegroundColor
, aMessageColor
);
393 pInfoBar
->m_aBackgroundColor
= aBackgroundColor
;
394 pInfoBar
->m_aForegroundColor
= aForegroundColor
;
395 m_pInfoBars
.push_back(pInfoBar
);
401 VclPtr
<SfxInfoBarWindow
> SfxInfoBarContainerWindow::getInfoBar(std::u16string_view sId
)
403 for (auto const& infoBar
: m_pInfoBars
)
405 if (infoBar
->getId() == sId
)
411 bool SfxInfoBarContainerWindow::hasInfoBarWithID(std::u16string_view sId
)
413 return (getInfoBar(sId
) != nullptr);
416 void SfxInfoBarContainerWindow::removeInfoBar(VclPtr
<SfxInfoBarWindow
> const& pInfoBar
)
419 auto it
= std::find(m_pInfoBars
.begin(), m_pInfoBars
.end(), pInfoBar
);
420 if (it
!= m_pInfoBars
.end())
422 it
->disposeAndClear();
423 m_pInfoBars
.erase(it
);
426 m_pChildWin
->Update();
429 bool SfxInfoBarContainerWindow::isInfobarEnabled(std::u16string_view sId
)
431 if (sId
== u
"readonly")
432 return officecfg::Office::UI::Infobar::Enabled::Readonly::get();
433 if (sId
== u
"signature")
434 return officecfg::Office::UI::Infobar::Enabled::Signature::get();
435 if (sId
== u
"donate")
436 return officecfg::Office::UI::Infobar::Enabled::Donate::get();
437 if (sId
== u
"getinvolved")
438 return officecfg::Office::UI::Infobar::Enabled::GetInvolved::get();
439 if (sId
== u
"hyphenationmissing")
440 return officecfg::Office::UI::Infobar::Enabled::HyphenationMissing::get();
441 if (sId
== u
"whatsnew")
442 return officecfg::Office::UI::Infobar::Enabled::WhatsNew::get();
443 if (sId
== u
"hiddentrackchanges")
444 return officecfg::Office::UI::Infobar::Enabled::HiddenTrackChanges::get();
446 return officecfg::Office::UI::Infobar::Enabled::MacrosDisabled::get();
447 if (sId
== u
"securitywarn")
449 return officecfg::Office::Common::Security::Scripting::WarnSaveOrSendDoc::get()
450 || officecfg::Office::Common::Security::Scripting::WarnSignDoc::get()
451 || officecfg::Office::Common::Security::Scripting::WarnPrintDoc::get()
452 || officecfg::Office::Common::Security::Scripting::WarnCreatePDF::get();
458 // This triggers the SfxFrame to re-layout its childwindows
459 void SfxInfoBarContainerWindow::TriggerUpdateLayout() { m_aLayoutIdle
.Start(); }
461 void SfxInfoBarContainerWindow::Resize()
466 const Size aWindowOrigSize
= GetSizePixel();
467 auto nOrigWidth
= aWindowOrigSize
.getWidth();
468 auto nOrigHeight
= aWindowOrigSize
.getHeight();
470 tools::Long nHeight
= 0;
472 for (auto& rxInfoBar
: m_pInfoBars
)
474 Size aOrigSize
= rxInfoBar
->GetSizePixel();
475 Size
aSize(nOrigWidth
, aOrigSize
.Height());
477 Point
aPos(0, nHeight
);
478 // stage 1: provisionally size the infobar,
479 rxInfoBar
->SetPosSizePixel(aPos
, aSize
);
481 // stage 2: perhaps allow height to stretch to fit
483 aSize
= rxInfoBar
->DoLayout();
484 rxInfoBar
->SetPosSizePixel(aPos
, aSize
);
487 // Stretch to fit the infobar(s)
488 nHeight
+= aSize
.getHeight();
491 if (nOrigHeight
!= nHeight
)
493 SetSizePixel(Size(nOrigWidth
, nHeight
));
494 TriggerUpdateLayout();
500 SFX_IMPL_POS_CHILDWINDOW_WITHID(SfxInfoBarContainerChild
, SID_INFOBAR
, SFX_OBJECTBAR_OBJECT
);
502 SfxInfoBarContainerChild::SfxInfoBarContainerChild(vcl::Window
* _pParent
, sal_uInt16 nId
,
503 SfxBindings
* pBindings
, SfxChildWinInfo
*)
504 : SfxChildWindow(_pParent
, nId
)
505 , m_pBindings(pBindings
)
507 SetWindow(VclPtr
<SfxInfoBarContainerWindow
>::Create(this));
508 GetWindow()->SetPosSizePixel(Point(0, 0), Size(_pParent
->GetSizePixel().getWidth(), 0));
511 SetAlignment(SfxChildAlignment::LOWESTTOP
);
514 SfxInfoBarContainerChild::~SfxInfoBarContainerChild() {}
516 SfxChildWinInfo
SfxInfoBarContainerChild::GetInfo() const
518 SfxChildWinInfo aInfo
= SfxChildWindow::GetInfo();
522 void SfxInfoBarContainerChild::Update()
524 // Layout to current width, this may change the height
525 if (vcl::Window
* pChild
= GetWindow())
527 Size
aSize(pChild
->GetSizePixel());
529 if (aSize
== pChild
->GetSizePixel())
533 // Refresh the frame to take the infobars container height change into account
534 const sal_uInt16 nId
= GetChildWindowId();
535 SfxViewFrame
* pVFrame
= m_pBindings
->GetDispatcher()->GetFrame();
536 pVFrame
->ShowChildWindow(nId
);
538 // Give the focus to the document view
539 pVFrame
->GetWindow().GrabFocusToDocument();
542 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */