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/processorfromoutputdevice.hxx>
17 #include <officecfg/Office/UI/Infobar.hxx>
18 #include <sfx2/bindings.hxx>
19 #include <sfx2/dispatch.hxx>
20 #include <sfx2/infobar.hxx>
21 #include <sfx2/objface.hxx>
22 #include <sfx2/sfxsids.hrc>
23 #include <sfx2/viewfrm.hxx>
24 #include <vcl/image.hxx>
25 #include <vcl/settings.hxx>
26 #include <vcl/svapp.hxx>
27 #include <vcl/virdev.hxx>
28 #include <vcl/weldutils.hxx>
29 #include <bitmaps.hlst>
31 using namespace drawinglayer::geometry
;
32 using namespace drawinglayer::processor2d
;
33 using namespace drawinglayer::primitive2d
;
34 using namespace drawinglayer::attribute
;
35 using namespace basegfx
;
36 using namespace css::frame
;
40 void GetInfoBarColors(InfobarType ibType
, BColor
& rBackgroundColor
, BColor
& rForegroundColor
,
41 BColor
& rMessageColor
)
43 rMessageColor
= basegfx::BColor(0.0, 0.0, 0.0);
47 case InfobarType::INFO
: // blue; #004785/0,71,133; #BDE5F8/189,229,248
48 rBackgroundColor
= basegfx::BColor(0.741, 0.898, 0.973);
49 rForegroundColor
= basegfx::BColor(0.0, 0.278, 0.522);
51 case InfobarType::SUCCESS
: // green; #32550C/50,85,12; #DFF2BF/223,242,191
52 rBackgroundColor
= basegfx::BColor(0.874, 0.949, 0.749);
53 rForegroundColor
= basegfx::BColor(0.196, 0.333, 0.047);
55 case InfobarType::WARNING
: // orange; #704300/112,67,0; #FEEFB3/254,239,179
56 rBackgroundColor
= basegfx::BColor(0.996, 0.937, 0.702);
57 rForegroundColor
= basegfx::BColor(0.439, 0.263, 0.0);
59 case InfobarType::DANGER
: // red; #7A0006/122,0,6; #FFBABA/255,186,186
60 rBackgroundColor
= basegfx::BColor(1.0, 0.729, 0.729);
61 rForegroundColor
= basegfx::BColor(0.478, 0.0, 0.024);
66 const StyleSettings
& rSettings
= Application::GetSettings().GetStyleSettings();
67 if (rSettings
.GetHighContrastMode())
69 rBackgroundColor
= rSettings
.GetLightColor().getBColor();
70 rForegroundColor
= rSettings
.GetDialogTextColor().getBColor();
73 OUString
GetInfoBarIconName(InfobarType ibType
)
79 case InfobarType::INFO
:
80 aRet
= "vcl/res/infobox.png";
82 case InfobarType::SUCCESS
:
83 aRet
= "vcl/res/successbox.png";
85 case InfobarType::WARNING
:
86 aRet
= "vcl/res/warningbox.png";
88 case InfobarType::DANGER
:
89 aRet
= "vcl/res/errorbox.png";
96 } // anonymous namespace
98 void SfxInfoBarWindow::SetCloseButtonImage()
100 Size aSize
= Image(StockImage::Yes
, CLOSEDOC
).GetSizePixel();
101 aSize
= Size(aSize
.Width() * 1.5, aSize
.Height() * 1.5);
103 ScopedVclPtr
<VirtualDevice
> xDevice(m_xCloseBtn
->create_virtual_device());
104 xDevice
->SetOutputSizePixel(aSize
);
108 const ViewInformation2D aNewViewInfos
;
109 const std::unique_ptr
<BaseProcessor2D
> pProcessor(
110 createBaseProcessor2DFromOutputDevice(*xDevice
, aNewViewInfos
));
112 const ::tools::Rectangle
aRect(aBtnPos
, xDevice
->PixelToLogic(aSize
));
114 drawinglayer::primitive2d::Primitive2DContainer
aSeq(2);
118 aPolygon
.append(B2DPoint(aRect
.Left(), aRect
.Top()));
119 aPolygon
.append(B2DPoint(aRect
.Right(), aRect
.Top()));
120 aPolygon
.append(B2DPoint(aRect
.Right(), aRect
.Bottom()));
121 aPolygon
.append(B2DPoint(aRect
.Left(), aRect
.Bottom()));
122 aPolygon
.setClosed(true);
124 aSeq
[0] = new PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon
), m_aBackgroundColor
);
126 LineAttribute
aLineAttribute(m_aForegroundColor
, 2.0);
129 B2DPolyPolygon aCross
;
132 aLine1
.append(B2DPoint(aRect
.Left(), aRect
.Top()));
133 aLine1
.append(B2DPoint(aRect
.Right(), aRect
.Bottom()));
134 aCross
.append(aLine1
);
137 aLine2
.append(B2DPoint(aRect
.Right(), aRect
.Top()));
138 aLine2
.append(B2DPoint(aRect
.Left(), aRect
.Bottom()));
139 aCross
.append(aLine2
);
141 aSeq
[1] = new PolyPolygonStrokePrimitive2D(aCross
, aLineAttribute
, StrokeAttribute());
143 pProcessor
->process(aSeq
);
145 m_xCloseBtn
->set_item_image("close", xDevice
);
151 std::unique_ptr
<weld::Builder
> m_xBuilder
;
152 std::unique_ptr
<weld::Container
> m_xContainer
;
153 std::unique_ptr
<weld::Button
> m_xButton
;
154 /** StatusListener. Updates the button as the slot state changes */
155 rtl::Reference
<weld::WidgetStatusListener
> m_xStatusListener
;
158 DECL_LINK(CommandHdl
, weld::Button
&, void);
161 ExtraButton(weld::Container
* pContainer
, const OUString
* pCommand
)
162 : m_xBuilder(Application::CreateBuilder(pContainer
, "sfx/ui/extrabutton.ui"))
163 , m_xContainer(m_xBuilder
->weld_container("ExtraButton"))
164 , m_xButton(m_xBuilder
->weld_button("button"))
168 m_aCommand
= *pCommand
;
169 m_xButton
->connect_clicked(LINK(this, ExtraButton
, CommandHdl
));
170 m_xStatusListener
.set(new weld::WidgetStatusListener(m_xButton
.get(), m_aCommand
));
171 m_xStatusListener
->startListening();
177 if (m_xStatusListener
.is())
178 m_xStatusListener
->dispose();
181 weld::Button
& get_widget() { return *m_xButton
; }
184 IMPL_LINK_NOARG(ExtraButton
, CommandHdl
, weld::Button
&, void)
186 comphelper::dispatchCommand(m_aCommand
, css::uno::Sequence
<css::beans::PropertyValue
>());
189 SfxInfoBarWindow::SfxInfoBarWindow(vcl::Window
* pParent
, const OUString
& sId
,
190 const OUString
& sPrimaryMessage
,
191 const OUString
& sSecondaryMessage
, InfobarType ibType
,
192 bool bShowCloseButton
)
193 : InterimItemWindow(pParent
, "sfx/ui/infobar.ui", "InfoBar")
196 , m_bLayingOut(false)
197 , m_xImage(m_xBuilder
->weld_image("image"))
198 , m_xPrimaryMessage(m_xBuilder
->weld_label("primary"))
199 , m_xSecondaryMessage(m_xBuilder
->weld_text_view("secondary"))
200 , m_xButtonBox(m_xBuilder
->weld_container("buttonbox"))
201 , m_xCloseBtn(m_xBuilder
->weld_toolbar("closebar"))
203 SetStyle(GetStyle() | WB_DIALOGCONTROL
);
205 InitControlBase(m_xCloseBtn
.get());
207 m_xImage
->set_from_icon_name(GetInfoBarIconName(ibType
));
208 m_xSecondaryMessage
->set_margin_top(m_xImage
->get_preferred_size().Height() / 4);
210 if (!sPrimaryMessage
.isEmpty())
212 m_xPrimaryMessage
->set_label(sPrimaryMessage
);
213 m_xPrimaryMessage
->show();
216 m_xSecondaryMessage
->set_text(sSecondaryMessage
);
217 m_aOrigMessageSize
= m_xSecondaryMessage
->get_preferred_size();
218 m_aMessageSize
= m_aOrigMessageSize
;
219 m_xSecondaryMessage
->connect_size_allocate(LINK(this, SfxInfoBarWindow
, SizeAllocHdl
));
221 if (bShowCloseButton
)
223 m_xCloseBtn
->connect_clicked(LINK(this, SfxInfoBarWindow
, CloseHandler
));
227 EnableChildTransparentMode();
229 SetForeAndBackgroundColors(m_eType
);
231 auto nWidth
= pParent
->GetSizePixel().getWidth();
232 auto nHeight
= get_preferred_size().Height();
233 SetSizePixel(Size(nWidth
, nHeight
+ 2));
238 IMPL_LINK(SfxInfoBarWindow
, SizeAllocHdl
, const Size
&, rSize
, void)
240 if (m_aMessageSize
!= rSize
)
242 m_aMessageSize
= rSize
;
243 static_cast<SfxInfoBarContainerWindow
*>(GetParent())->TriggerUpdateLayout();
247 Size
SfxInfoBarWindow::DoLayout()
249 Size
aGivenSize(GetSizePixel());
251 // disconnect SizeAllocHdl because we don't care about the size change
253 m_xSecondaryMessage
->connect_size_allocate(Link
<const Size
&, void>());
255 // blow away size cache in case m_aMessageSize.Width() is already the width request
256 // and we would get the cached preferred size instead of the recalc we want to force
257 m_xSecondaryMessage
->set_size_request(-1, -1);
258 // make the width we were detected as set to by SizeAllocHdl as our desired width
259 m_xSecondaryMessage
->set_size_request(m_aMessageSize
.Width(), -1);
260 // get our preferred size with that message width
261 Size
aSizeForWidth(aGivenSize
.Width(), m_xContainer
->get_preferred_size().Height());
262 // restore the message preferred size so we can freely resize, and get a new
263 // m_aMessageSize and repeat the process if we do
264 m_xSecondaryMessage
->set_size_request(m_aOrigMessageSize
.Width(), -1);
266 // connect SizeAllocHdl so changes outside of this layout will trigger a new layout
267 m_xSecondaryMessage
->connect_size_allocate(LINK(this, SfxInfoBarWindow
, SizeAllocHdl
));
269 return aSizeForWidth
;
272 void SfxInfoBarWindow::Layout()
278 InterimItemWindow::Layout();
280 m_bLayingOut
= false;
283 weld::Button
& SfxInfoBarWindow::addButton(const OUString
* pCommand
)
285 m_aActionBtns
.emplace_back(std::make_unique
<ExtraButton
>(m_xButtonBox
.get(), pCommand
));
287 return m_aActionBtns
.back()->get_widget();
290 SfxInfoBarWindow::~SfxInfoBarWindow() { disposeOnce(); }
292 void SfxInfoBarWindow::SetForeAndBackgroundColors(InfobarType eType
)
294 basegfx::BColor aMessageColor
;
295 GetInfoBarColors(eType
, m_aBackgroundColor
, m_aForegroundColor
, aMessageColor
);
297 m_xPrimaryMessage
->set_font_color(Color(aMessageColor
));
298 m_xSecondaryMessage
->set_font_color(Color(aMessageColor
));
300 Color
aBackgroundColor(m_aBackgroundColor
);
301 m_xPrimaryMessage
->set_background(aBackgroundColor
);
302 m_xSecondaryMessage
->set_background(aBackgroundColor
);
303 m_xContainer
->set_background(aBackgroundColor
);
304 if (m_xCloseBtn
->get_visible())
306 m_xCloseBtn
->set_background(aBackgroundColor
);
307 SetCloseButtonImage();
311 void SfxInfoBarWindow::dispose()
313 for (auto& rxBtn
: m_aActionBtns
)
317 m_xPrimaryMessage
.reset();
318 m_xSecondaryMessage
.reset();
319 m_xButtonBox
.reset();
321 m_aActionBtns
.clear();
322 InterimItemWindow::dispose();
325 void SfxInfoBarWindow::Update(const OUString
& sPrimaryMessage
, const OUString
& sSecondaryMessage
,
328 if (m_eType
!= eType
)
331 SetForeAndBackgroundColors(m_eType
);
332 m_xImage
->set_from_icon_name(GetInfoBarIconName(eType
));
335 m_xPrimaryMessage
->set_label(sPrimaryMessage
);
336 m_xSecondaryMessage
->set_text(sSecondaryMessage
);
341 IMPL_LINK_NOARG(SfxInfoBarWindow
, CloseHandler
, const OString
&, void)
343 static_cast<SfxInfoBarContainerWindow
*>(GetParent())->removeInfoBar(this);
346 SfxInfoBarContainerWindow::SfxInfoBarContainerWindow(SfxInfoBarContainerChild
* pChildWin
)
347 : Window(pChildWin
->GetParent(), WB_DIALOGCONTROL
)
348 , m_pChildWin(pChildWin
)
349 , m_aLayoutIdle("SfxInfoBarContainerWindow m_aLayoutIdle")
352 m_aLayoutIdle
.SetPriority(TaskPriority::HIGHEST
);
353 m_aLayoutIdle
.SetInvokeHandler(LINK(this, SfxInfoBarContainerWindow
, DoUpdateLayout
));
356 IMPL_LINK_NOARG(SfxInfoBarContainerWindow
, DoUpdateLayout
, Timer
*, void) { m_pChildWin
->Update(); }
358 SfxInfoBarContainerWindow::~SfxInfoBarContainerWindow() { disposeOnce(); }
360 void SfxInfoBarContainerWindow::dispose()
362 for (auto& infoBar
: m_pInfoBars
)
363 infoBar
.disposeAndClear();
368 VclPtr
<SfxInfoBarWindow
> SfxInfoBarContainerWindow::appendInfoBar(const OUString
& sId
,
369 const OUString
& sPrimaryMessage
,
370 const OUString
& sSecondaryMessage
,
372 bool bShowCloseButton
)
374 if (!isInfobarEnabled(sId
))
377 auto pInfoBar
= VclPtr
<SfxInfoBarWindow
>::Create(this, sId
, sPrimaryMessage
, sSecondaryMessage
,
378 ibType
, bShowCloseButton
);
380 basegfx::BColor aBackgroundColor
;
381 basegfx::BColor aForegroundColor
;
382 basegfx::BColor aMessageColor
;
383 GetInfoBarColors(ibType
, aBackgroundColor
, aForegroundColor
, aMessageColor
);
384 pInfoBar
->m_aBackgroundColor
= aBackgroundColor
;
385 pInfoBar
->m_aForegroundColor
= aForegroundColor
;
386 m_pInfoBars
.push_back(pInfoBar
);
392 VclPtr
<SfxInfoBarWindow
> SfxInfoBarContainerWindow::getInfoBar(std::u16string_view sId
)
394 for (auto const& infoBar
: m_pInfoBars
)
396 if (infoBar
->getId() == sId
)
402 bool SfxInfoBarContainerWindow::hasInfoBarWithID(std::u16string_view sId
)
404 return (getInfoBar(sId
) != nullptr);
407 void SfxInfoBarContainerWindow::removeInfoBar(VclPtr
<SfxInfoBarWindow
> const& pInfoBar
)
410 auto it
= std::find(m_pInfoBars
.begin(), m_pInfoBars
.end(), pInfoBar
);
411 if (it
!= m_pInfoBars
.end())
413 it
->disposeAndClear();
414 m_pInfoBars
.erase(it
);
417 m_pChildWin
->Update();
420 bool SfxInfoBarContainerWindow::isInfobarEnabled(std::u16string_view sId
)
422 if (sId
== u
"readonly")
423 return officecfg::Office::UI::Infobar::Enabled::Readonly::get();
424 if (sId
== u
"signature")
425 return officecfg::Office::UI::Infobar::Enabled::Signature::get();
426 if (sId
== u
"donate")
427 return officecfg::Office::UI::Infobar::Enabled::Donate::get();
428 if (sId
== u
"getinvolved")
429 return officecfg::Office::UI::Infobar::Enabled::GetInvolved::get();
430 if (sId
== u
"hyphenationmissing")
431 return officecfg::Office::UI::Infobar::Enabled::HyphenationMissing::get();
432 if (sId
== u
"whatsnew")
433 return officecfg::Office::UI::Infobar::Enabled::WhatsNew::get();
434 if (sId
== u
"hiddentrackchanges")
435 return officecfg::Office::UI::Infobar::Enabled::HiddenTrackChanges::get();
440 // This triggers the SfxFrame to re-layout its childwindows
441 void SfxInfoBarContainerWindow::TriggerUpdateLayout() { m_aLayoutIdle
.Start(); }
443 void SfxInfoBarContainerWindow::Resize()
448 const Size
& rOrigSize
= GetSizePixel();
449 auto nOrigWidth
= rOrigSize
.getWidth();
450 auto nOrigHeight
= rOrigSize
.getHeight();
452 tools::Long nHeight
= 0;
454 for (auto& rxInfoBar
: m_pInfoBars
)
456 Size aOrigSize
= rxInfoBar
->GetSizePixel();
457 Size
aSize(nOrigWidth
, aOrigSize
.Height());
459 Point
aPos(0, nHeight
);
460 // stage 1: provisionally size the infobar,
461 rxInfoBar
->SetPosSizePixel(aPos
, aSize
);
463 // stage 2: perhaps allow height to stretch to fit
465 aSize
= rxInfoBar
->DoLayout();
466 rxInfoBar
->SetPosSizePixel(aPos
, aSize
);
469 // Stretch to fit the infobar(s)
470 nHeight
+= aSize
.getHeight();
473 if (nOrigHeight
!= nHeight
)
475 SetSizePixel(Size(nOrigWidth
, nHeight
));
476 TriggerUpdateLayout();
482 SFX_IMPL_POS_CHILDWINDOW_WITHID(SfxInfoBarContainerChild
, SID_INFOBAR
, SFX_OBJECTBAR_OBJECT
);
484 SfxInfoBarContainerChild::SfxInfoBarContainerChild(vcl::Window
* _pParent
, sal_uInt16 nId
,
485 SfxBindings
* pBindings
, SfxChildWinInfo
*)
486 : SfxChildWindow(_pParent
, nId
)
487 , m_pBindings(pBindings
)
489 SetWindow(VclPtr
<SfxInfoBarContainerWindow
>::Create(this));
490 GetWindow()->SetPosSizePixel(Point(0, 0), Size(_pParent
->GetSizePixel().getWidth(), 0));
493 SetAlignment(SfxChildAlignment::LOWESTTOP
);
496 SfxInfoBarContainerChild::~SfxInfoBarContainerChild() {}
498 SfxChildWinInfo
SfxInfoBarContainerChild::GetInfo() const
500 SfxChildWinInfo aInfo
= SfxChildWindow::GetInfo();
504 void SfxInfoBarContainerChild::Update()
506 // Layout to current width, this may change the height
507 if (vcl::Window
* pChild
= GetWindow())
509 Size
aSize(pChild
->GetSizePixel());
511 if (aSize
== pChild
->GetSizePixel())
515 // Refresh the frame to take the infobars container height change into account
516 const sal_uInt16 nId
= GetChildWindowId();
517 SfxViewFrame
* pVFrame
= m_pBindings
->GetDispatcher()->GetFrame();
518 pVFrame
->ShowChildWindow(nId
);
520 // Give the focus to the document view
521 pVFrame
->GetWindow().GrabFocusToDocument();
524 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */