1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  */
9 
10 #include <memory>
11 #include <basegfx/polygon/b2dpolygon.hxx>
12 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
13 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
14 #include <drawinglayer/processor2d/baseprocessor2d.hxx>
15 #include <drawinglayer/processor2d/processorfromoutputdevice.hxx>
16 #include <sfx2/bindings.hxx>
17 #include <sfx2/dispatch.hxx>
18 #include <sfx2/infobar.hxx>
19 #include <sfx2/objface.hxx>
20 #include <sfx2/objsh.hxx>
21 #include <sfx2/sfxsids.hrc>
22 #include <sfx2/viewfrm.hxx>
23 #include <sfx2/viewsh.hxx>
24 #include <vcl/svapp.hxx>
25 #include <vcl/settings.hxx>
26 #include <vcl/decoview.hxx>
27 
28 using namespace std;
29 using namespace drawinglayer::geometry;
30 using namespace drawinglayer::processor2d;
31 using namespace drawinglayer::primitive2d;
32 using namespace drawinglayer::attribute;
33 using namespace basegfx;
34 using namespace css::frame;
35 
36 namespace
37 {
38 
39 const long INFO_BAR_BASE_HEIGHT = 40;
40 
GetInfoBarColors(InfobarType ibType,BColor & rBackgroundColor,BColor & rForegroundColor,BColor & rMessageColor)41 void GetInfoBarColors(InfobarType ibType, BColor&  rBackgroundColor, BColor& rForegroundColor, BColor& rMessageColor)
42 {
43     rMessageColor = basegfx::BColor(0.0, 0.0, 0.0);
44 
45     switch (ibType)
46     {
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);
50         break;
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);
54         break;
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);
58         break;
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);
62         break;
63     }
64 
65     //remove this?
66     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
67     if (rSettings.GetHighContrastMode())
68     {
69         rBackgroundColor = rSettings.GetLightColor().getBColor();
70         rForegroundColor = rSettings.GetDialogTextColor().getBColor();
71     }
72 
73 }
GetInfoBarIconName(InfobarType ibType)74 OUString GetInfoBarIconName(InfobarType ibType)
75 {
76 
77     OUString aRet;
78 
79     switch (ibType)
80     {
81     case InfobarType::INFO:
82        aRet = "vcl/res/infobox.svg";
83        break;
84     case InfobarType::SUCCESS:
85         aRet = "vcl/res/successbox.svg";
86         break;
87     case InfobarType::WARNING:
88         aRet = "vcl/res/warningbox.svg";
89         break;
90     case InfobarType::DANGER:
91         aRet = "vcl/res/errorbox.svg";
92         break;
93     }
94 
95     return aRet;
96 }
97 
98 class SfxCloseButton : public PushButton
99 {
100     basegfx::BColor m_aBackgroundColor;
101     basegfx::BColor m_aForegroundColor;
102 
103 public:
SfxCloseButton(vcl::Window * pParent)104     explicit SfxCloseButton(vcl::Window* pParent) : PushButton(pParent, 0)
105     {
106         basegfx::BColor aMessageColor;
107         GetInfoBarColors(InfobarType::WARNING, m_aBackgroundColor, m_aForegroundColor, aMessageColor);
108     }
109 
110     virtual void Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect) override;
111 
112     void setBackgroundColor(const basegfx::BColor& rColor);
113     void setForegroundColor(const basegfx::BColor& rColor);
114 };
115 
Paint(vcl::RenderContext & rRenderContext,const::tools::Rectangle &)116 void SfxCloseButton::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle&)
117 {
118     Point aBtnPos(0, 0);
119     if (GetButtonState() & DrawButtonFlags::Pressed)
120         aBtnPos.Move(Size(1, 1));
121 
122     const ViewInformation2D aNewViewInfos;
123     const unique_ptr<BaseProcessor2D> pProcessor(
124                 createBaseProcessor2DFromOutputDevice(rRenderContext, aNewViewInfos));
125 
126     const ::tools::Rectangle aRect(aBtnPos, PixelToLogic(GetSizePixel()));
127 
128     drawinglayer::primitive2d::Primitive2DContainer aSeq(2);
129 
130     //  background
131     B2DPolygon aPolygon;
132     aPolygon.append(B2DPoint(aRect.Left(), aRect.Top()));
133     aPolygon.append(B2DPoint(aRect.Right(), aRect.Top()));
134     aPolygon.append(B2DPoint(aRect.Right(), aRect.Bottom()));
135     aPolygon.append(B2DPoint(aRect.Left(), aRect.Bottom()));
136     aPolygon.setClosed(true);
137 
138     Color aBackgroundColor(m_aBackgroundColor);
139     if (IsMouseOver() || HasFocus())
140         aBackgroundColor.ApplyTintOrShade(-2000);
141 
142     PolyPolygonColorPrimitive2D* pBack =
143         new PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), aBackgroundColor.getBColor());
144     aSeq[0] = pBack;
145 
146     LineAttribute aLineAttribute(m_aForegroundColor, 2.0);
147 
148     // Cross
149     B2DPolyPolygon aCross;
150 
151     B2DPolygon aLine1;
152     aLine1.append(B2DPoint(aRect.Left(), aRect.Top()));
153     aLine1.append(B2DPoint(aRect.Right(), aRect.Bottom()));
154     aCross.append(aLine1);
155 
156     B2DPolygon aLine2;
157     aLine2.append(B2DPoint(aRect.Right(), aRect.Top()));
158     aLine2.append(B2DPoint(aRect.Left(), aRect.Bottom()));
159     aCross.append(aLine2);
160 
161     PolyPolygonStrokePrimitive2D* pCross =
162         new PolyPolygonStrokePrimitive2D(aCross, aLineAttribute, StrokeAttribute());
163 
164     aSeq[1] = pCross;
165 
166     pProcessor->process(aSeq);
167 }
168 
setBackgroundColor(const basegfx::BColor & rColor)169 void SfxCloseButton::setBackgroundColor(const basegfx::BColor& rColor)
170 {
171     m_aBackgroundColor = rColor;
172 }
173 
setForegroundColor(const basegfx::BColor & rColor)174 void SfxCloseButton::setForegroundColor(const basegfx::BColor& rColor)
175 {
176     m_aForegroundColor = rColor;
177 }
178 
179 } // anonymous namespace
180 
SfxInfoBarWindow(vcl::Window * pParent,const OUString & sId,const OUString & sPrimaryMessage,const OUString & sSecondaryMessage,InfobarType ibType,bool bShowCloseButton,WinBits nMessageStyle=WB_LEFT|WB_VCENTER)181 SfxInfoBarWindow::SfxInfoBarWindow(vcl::Window* pParent, const OUString& sId,
182        const OUString& sPrimaryMessage,
183        const OUString& sSecondaryMessage,
184        InfobarType ibType,
185        bool bShowCloseButton,
186        WinBits nMessageStyle = WB_LEFT|WB_VCENTER) :
187     Window(pParent, WB_DIALOGCONTROL),
188     m_sId(sId),
189     m_eType(ibType),
190     m_pImage(VclPtr<FixedImage>::Create(this, nMessageStyle)),
191     m_pPrimaryMessage(VclPtr<FixedText>::Create(this, nMessageStyle | WB_WORDBREAK)),
192     m_pSecondaryMessage(VclPtr<FixedText>::Create(this, nMessageStyle | WB_WORDBREAK)),
193     m_pCloseBtn(VclPtr<SfxCloseButton>::Create(this)),
194     m_aActionBtns()
195 {
196     m_pCloseBtn->SetStyle(WB_DEFBUTTON | WB_TABSTOP);
197     SetForeAndBackgroundColors(m_eType);
198     float fScaleFactor = GetDPIScaleFactor();
199     long nWidth = pParent->GetSizePixel().getWidth();
200     SetPosSizePixel(Point(0, 0), Size(nWidth, INFO_BAR_BASE_HEIGHT * fScaleFactor));
201 
202     m_pImage->SetImage(Image(StockImage::Yes, GetInfoBarIconName(ibType)));
203     m_pImage->SetPaintTransparent(true);
204     m_pImage->Show();
205 
206     vcl::Font aFont(m_pPrimaryMessage->GetControlFont());
207     aFont.SetWeight(WEIGHT_BOLD);
208     m_pPrimaryMessage->SetControlFont(aFont);
209     if (!sPrimaryMessage.isEmpty())
210     {
211         m_pPrimaryMessage->SetText(sPrimaryMessage);
212         m_pPrimaryMessage->Show();
213     }
214 
215     m_pSecondaryMessage->SetText(sSecondaryMessage);
216     m_pSecondaryMessage->Show();
217 
218     if (bShowCloseButton)
219     {
220         m_pCloseBtn->SetClickHdl(LINK(this, SfxInfoBarWindow, CloseHandler));
221         m_pCloseBtn->Show();
222     }
223 
224     EnableChildTransparentMode();
225 
226     Resize();
227 }
228 
addButton(PushButton * pButton)229 void SfxInfoBarWindow::addButton(PushButton* pButton) {
230     pButton->SetParent(this);
231     pButton->Show();
232     m_aActionBtns.emplace_back(pButton);
233     Resize();
234 }
235 
~SfxInfoBarWindow()236 SfxInfoBarWindow::~SfxInfoBarWindow()
237 {
238     disposeOnce();
239 }
240 
SetForeAndBackgroundColors(InfobarType eType)241 void SfxInfoBarWindow::SetForeAndBackgroundColors(InfobarType eType)
242 {
243     basegfx::BColor aMessageColor;
244     GetInfoBarColors(eType,m_aBackgroundColor,m_aForegroundColor,aMessageColor);
245 
246     static_cast<SfxCloseButton*>(m_pCloseBtn.get())->setBackgroundColor(m_aBackgroundColor);
247     static_cast<SfxCloseButton*>(m_pCloseBtn.get())->setForegroundColor(m_aForegroundColor);
248     m_pPrimaryMessage->SetControlForeground(Color(aMessageColor));
249     m_pSecondaryMessage->SetControlForeground(Color(aMessageColor));
250 }
251 
dispose()252 void SfxInfoBarWindow::dispose()
253 {
254     for ( auto& rxBtn : m_aActionBtns )
255         rxBtn.disposeAndClear();
256 
257     m_pImage.disposeAndClear();
258     m_pPrimaryMessage.disposeAndClear();
259     m_pSecondaryMessage.disposeAndClear();
260     m_pCloseBtn.disposeAndClear();
261     m_aActionBtns.clear( );
262     vcl::Window::dispose();
263 }
264 
Paint(vcl::RenderContext & rRenderContext,const::tools::Rectangle & rPaintRect)265 void SfxInfoBarWindow::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rPaintRect)
266 {
267     const ViewInformation2D aNewViewInfos;
268     const unique_ptr<BaseProcessor2D> pProcessor(
269                 createBaseProcessor2DFromOutputDevice(rRenderContext, aNewViewInfos));
270 
271     const ::tools::Rectangle aRect(Point(0, 0), PixelToLogic(GetSizePixel()));
272 
273     drawinglayer::primitive2d::Primitive2DContainer aSeq(2);
274 
275     // Light background
276     B2DPolygon aPolygon;
277     aPolygon.append(B2DPoint(aRect.Left(), aRect.Top()));
278     aPolygon.append(B2DPoint(aRect.Right(), aRect.Top()));
279     aPolygon.append(B2DPoint(aRect.Right(), aRect.Bottom()));
280     aPolygon.append(B2DPoint(aRect.Left(), aRect.Bottom()));
281     aPolygon.setClosed(true);
282 
283     PolyPolygonColorPrimitive2D* pBack =
284         new PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), m_aBackgroundColor);
285     aSeq[0] = pBack;
286 
287     LineAttribute aLineAttribute(m_aForegroundColor, 1.0);
288 
289     // Bottom dark line
290     B2DPolygon aPolygonBottom;
291     aPolygonBottom.append(B2DPoint(aRect.Left(), aRect.Bottom()));
292     aPolygonBottom.append(B2DPoint(aRect.Right(), aRect.Bottom()));
293 
294     PolygonStrokePrimitive2D* pLineBottom =
295             new PolygonStrokePrimitive2D (aPolygonBottom, aLineAttribute);
296 
297     aSeq[1] = pLineBottom;
298 
299     pProcessor->process(aSeq);
300 
301     Window::Paint(rRenderContext, rPaintRect);
302 }
303 
Resize()304 void SfxInfoBarWindow::Resize()
305 {
306     float fScaleFactor = GetDPIScaleFactor();
307 
308     long nWidth = GetSizePixel().getWidth();
309     m_pCloseBtn->SetPosSizePixel(Point(nWidth - 25 * fScaleFactor, 15 * fScaleFactor), Size(10 * fScaleFactor, 10 * fScaleFactor));
310 
311     // Reparent the buttons and place them on the right of the bar
312     long nX = m_pCloseBtn->GetPosPixel().getX() - 15 * fScaleFactor;
313     long nButtonGap = 5 * fScaleFactor;
314 
315     for (auto const& actionBtn : m_aActionBtns)
316     {
317         long nButtonWidth = actionBtn->GetSizePixel().getWidth();
318         nX -= nButtonWidth;
319         actionBtn->SetPosSizePixel(Point(nX, 5 * fScaleFactor), Size(nButtonWidth, 30 * fScaleFactor));
320         nX -= nButtonGap;
321     }
322 
323     Point aPrimaryMessagePosition(32 * fScaleFactor + 10 * fScaleFactor, 10 * fScaleFactor);
324     Point aSecondaryMessagePosition(aPrimaryMessagePosition);
325     Size aMessageSize(nX - 35 * fScaleFactor, 20 * fScaleFactor);
326     Size aPrimaryTextSize = m_pPrimaryMessage->CalcMinimumSize(aMessageSize.getWidth());
327     Size aSecondaryTextSize = m_pSecondaryMessage->CalcMinimumSize(aMessageSize.getWidth()
328                                                                    - aPrimaryTextSize.getWidth());
329     if (!m_pPrimaryMessage->GetText().isEmpty())
330         aSecondaryMessagePosition.AdjustX(aPrimaryTextSize.getWidth() + 6 * fScaleFactor);
331 
332     long aMinimumHeight = std::max(m_pPrimaryMessage->CalcMinimumSize().getHeight(),
333                                    m_pSecondaryMessage->CalcMinimumSize().getHeight());
334 
335     long aExtraHeight = aSecondaryTextSize.getHeight() - aMinimumHeight;
336 
337     // The message won't be legible and the window will get too high
338     if (aMessageSize.getWidth() < 30)
339     {
340         aExtraHeight = 0;
341     }
342 
343     m_pPrimaryMessage->SetPosSizePixel(aPrimaryMessagePosition, aPrimaryTextSize);
344     m_pSecondaryMessage->SetPosSizePixel(aSecondaryMessagePosition, aSecondaryTextSize);
345     m_pImage->SetPosSizePixel(Point(4, 4), Size(32 * fScaleFactor, 32 * fScaleFactor));
346 
347     SetPosSizePixel(GetPosPixel(), Size(nWidth, INFO_BAR_BASE_HEIGHT * fScaleFactor + aExtraHeight * fScaleFactor));
348 }
349 
Update(const OUString & sPrimaryMessage,const OUString & sSecondaryMessage,InfobarType eType)350 void SfxInfoBarWindow::Update( const OUString& sPrimaryMessage, const OUString& sSecondaryMessage, InfobarType eType )
351 {
352     if (m_eType != eType)
353     {
354         m_eType = eType;
355         SetForeAndBackgroundColors(m_eType);
356         m_pImage->SetImage(Image(StockImage::Yes, GetInfoBarIconName(eType)));
357     }
358 
359     m_pPrimaryMessage->SetText( sPrimaryMessage );
360     m_pSecondaryMessage->SetText( sSecondaryMessage );
361     Resize();
362     Invalidate();
363 }
364 
IMPL_LINK_NOARG(SfxInfoBarWindow,CloseHandler,Button *,void)365 IMPL_LINK_NOARG(SfxInfoBarWindow, CloseHandler, Button*, void)
366 {
367     static_cast<SfxInfoBarContainerWindow*>(GetParent())->removeInfoBar(this);
368 }
369 
SfxInfoBarContainerWindow(SfxInfoBarContainerChild * pChildWin)370 SfxInfoBarContainerWindow::SfxInfoBarContainerWindow(SfxInfoBarContainerChild* pChildWin ) :
371     Window(pChildWin->GetParent(), WB_DIALOGCONTROL),
372     m_pChildWin(pChildWin),
373     m_pInfoBars()
374 {
375 }
376 
~SfxInfoBarContainerWindow()377 SfxInfoBarContainerWindow::~SfxInfoBarContainerWindow()
378 {
379     disposeOnce();
380 }
381 
dispose()382 void SfxInfoBarContainerWindow::dispose()
383 {
384     for (auto & infoBar : m_pInfoBars)
385         infoBar.disposeAndClear();
386     m_pInfoBars.clear( );
387     Window::dispose();
388 }
389 
390 VclPtr<SfxInfoBarWindow>
appendInfoBar(const OUString & sId,const OUString & sPrimaryMessage,const OUString & sSecondaryMessage,InfobarType ibType,WinBits nMessageStyle,bool bShowCloseButton)391 SfxInfoBarContainerWindow::appendInfoBar(const OUString& sId, const OUString& sPrimaryMessage,
392                                          const OUString& sSecondaryMessage, InfobarType ibType,
393                                          WinBits nMessageStyle, bool bShowCloseButton)
394 {
395     auto pInfoBar = VclPtr<SfxInfoBarWindow>::Create(this, sId, sPrimaryMessage, sSecondaryMessage,
396                                                      ibType, nMessageStyle, bShowCloseButton);
397 
398     basegfx::BColor aBackgroundColor;
399     basegfx::BColor aForegroundColor;
400     basegfx::BColor aMessageColor;
401     GetInfoBarColors(ibType,aBackgroundColor,aForegroundColor,aMessageColor);
402     pInfoBar->m_aBackgroundColor = aBackgroundColor;
403     pInfoBar->m_aForegroundColor = aForegroundColor;
404     m_pInfoBars.push_back(pInfoBar);
405 
406     Resize();
407     return pInfoBar;
408 }
409 
getInfoBar(const OUString & sId)410 VclPtr<SfxInfoBarWindow> SfxInfoBarContainerWindow::getInfoBar(const OUString& sId)
411 {
412     for (auto const& infoBar : m_pInfoBars)
413     {
414         if (infoBar->getId() == sId)
415             return infoBar;
416     }
417     return nullptr;
418 }
419 
hasInfoBarWithID(const OUString & sId)420 bool SfxInfoBarContainerWindow::hasInfoBarWithID( const OUString &sId )
421 {
422     return ( getInfoBar( sId ) != nullptr );
423 }
424 
removeInfoBar(VclPtr<SfxInfoBarWindow> const & pInfoBar)425 void SfxInfoBarContainerWindow::removeInfoBar(VclPtr<SfxInfoBarWindow> const & pInfoBar)
426 {
427     // Remove
428     auto it = std::find(m_pInfoBars.begin(), m_pInfoBars.end(), pInfoBar);
429     if (it != m_pInfoBars.end())
430     {
431         it->disposeAndClear();
432         m_pInfoBars.erase(it);
433     }
434 
435     Resize();
436 
437     m_pChildWin->Update();
438 }
439 
Resize()440 void SfxInfoBarContainerWindow::Resize()
441 {
442     long nWidth = GetSizePixel().getWidth();
443     long nHeight = 0;
444 
445     for (auto& rxInfoBar : m_pInfoBars)
446     {
447         Size aSize = rxInfoBar->GetSizePixel();
448         aSize.setWidth(nWidth);
449         Point aPos(0, nHeight);
450         rxInfoBar->SetPosSizePixel(aPos, aSize);
451         rxInfoBar->Resize();
452         rxInfoBar->Show();
453 
454         // Stretch to fit the infobar(s)
455         nHeight += aSize.getHeight();
456     }
457 
458     SetSizePixel(Size(nWidth, nHeight));
459 }
460 
461 SFX_IMPL_POS_CHILDWINDOW_WITHID(SfxInfoBarContainerChild, SID_INFOBAR, SFX_OBJECTBAR_OBJECT);
462 
SfxInfoBarContainerChild(vcl::Window * _pParent,sal_uInt16 nId,SfxBindings * pBindings,SfxChildWinInfo *)463 SfxInfoBarContainerChild::SfxInfoBarContainerChild( vcl::Window* _pParent, sal_uInt16 nId, SfxBindings* pBindings, SfxChildWinInfo* ) :
464     SfxChildWindow(_pParent, nId),
465     m_pBindings(pBindings)
466 {
467     SetWindow( VclPtr<SfxInfoBarContainerWindow>::Create(this) );
468     GetWindow()->SetPosSizePixel(Point(0, 0), Size(_pParent->GetSizePixel().getWidth(), 0));
469     GetWindow()->Show();
470 
471     SetAlignment(SfxChildAlignment::LOWESTTOP);
472 }
473 
~SfxInfoBarContainerChild()474 SfxInfoBarContainerChild::~SfxInfoBarContainerChild()
475 {
476 }
477 
GetInfo() const478 SfxChildWinInfo SfxInfoBarContainerChild::GetInfo() const
479 {
480     SfxChildWinInfo aInfo = SfxChildWindow::GetInfo();
481     return aInfo;
482 }
483 
Update()484 void SfxInfoBarContainerChild::Update()
485 {
486     // Refresh the frame to take the infobars container height change into account
487     const sal_uInt16 nId = GetChildWindowId();
488     SfxViewFrame* pVFrame = m_pBindings->GetDispatcher()->GetFrame();
489     pVFrame->ShowChildWindow(nId);
490 
491     // Give the focus to the document view
492     pVFrame->GetWindow().GrabFocusToDocument();
493 }
494 
495 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
496