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