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 <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>
16 #include <memory>
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>
25 #include <utility>
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>
32
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;
39
40 namespace
41 {
GetInfoBarColors(InfobarType ibType,BColor & rBackgroundColor,BColor & rForegroundColor,BColor & rMessageColor)42 void GetInfoBarColors(InfobarType ibType, BColor& rBackgroundColor, BColor& rForegroundColor,
43 BColor& rMessageColor)
44 {
45 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
46
47 switch (ibType)
48 {
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);
53 break;
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);
58 break;
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();
63 break;
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();
68 break;
69 }
70
71 if (rSettings.GetHighContrastMode())
72 {
73 rBackgroundColor = rSettings.GetLightColor().getBColor();
74 rForegroundColor = rSettings.GetDialogTextColor().getBColor();
75 }
76 }
GetInfoBarIconName(InfobarType ibType)77 OUString GetInfoBarIconName(InfobarType ibType)
78 {
79 OUString aRet;
80
81 switch (ibType)
82 {
83 case InfobarType::INFO:
84 aRet = "vcl/res/infobox.png";
85 break;
86 case InfobarType::SUCCESS:
87 aRet = "vcl/res/successbox.png";
88 break;
89 case InfobarType::WARNING:
90 aRet = "vcl/res/warningbox.png";
91 break;
92 case InfobarType::DANGER:
93 aRet = "vcl/res/errorbox.png";
94 break;
95 }
96
97 return aRet;
98 }
99
100 } // anonymous namespace
101
SetCloseButtonImage()102 void SfxInfoBarWindow::SetCloseButtonImage()
103 {
104 Size aSize = Image(StockImage::Yes, CLOSEDOC).GetSizePixel();
105 aSize = Size(aSize.Width() * 1.5, aSize.Height() * 1.5);
106
107 ScopedVclPtr<VirtualDevice> xDevice(m_xCloseBtn->create_virtual_device());
108 xDevice->SetOutputSizePixel(Size(24, 24));
109 xDevice->SetBackground(Color(m_aBackgroundColor));
110 xDevice->Erase();
111
112 const int nPos = (24 - aSize.getWidth()) / 2;
113 Point aBtnPos(nPos, nPos);
114
115 const ViewInformation2D aNewViewInfos;
116 const std::unique_ptr<BaseProcessor2D> pProcessor(
117 createProcessor2DFromOutputDevice(*xDevice, aNewViewInfos));
118
119 const ::tools::Rectangle aRect(aBtnPos, xDevice->PixelToLogic(aSize));
120
121 drawinglayer::primitive2d::Primitive2DContainer aSeq(2);
122
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.
125 B2DPolygon aPolygon;
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);
131
132 aSeq[0] = new PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), m_aBackgroundColor);
133
134 LineAttribute aLineAttribute(m_aForegroundColor, 2.0);
135
136 // Cross
137 B2DPolyPolygon aCross;
138
139 B2DPolygon aLine1;
140 aLine1.append(B2DPoint(aRect.Left(), aRect.Top()));
141 aLine1.append(B2DPoint(aRect.Right(), aRect.Bottom()));
142 aCross.append(aLine1);
143
144 B2DPolygon aLine2;
145 aLine2.append(B2DPoint(aRect.Right(), aRect.Top()));
146 aLine2.append(B2DPoint(aRect.Left(), aRect.Bottom()));
147 aCross.append(aLine2);
148
149 aSeq[1]
150 = new PolyPolygonStrokePrimitive2D(std::move(aCross), aLineAttribute, StrokeAttribute());
151
152 pProcessor->process(aSeq);
153
154 m_xCloseBtn->set_item_image(u"close"_ustr, xDevice);
155 }
156
157 class ExtraButton
158 {
159 private:
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;
165 OUString m_aCommand;
166
167 DECL_LINK(CommandHdl, weld::Button&, void);
168
169 public:
ExtraButton(weld::Container * pContainer,const OUString * pCommand)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))
174 {
175 if (pCommand)
176 {
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();
181 }
182 }
183
~ExtraButton()184 ~ExtraButton()
185 {
186 if (m_xStatusListener.is())
187 m_xStatusListener->dispose();
188 }
189
get_widget()190 weld::Button& get_widget() { return *m_xButton; }
191 };
192
IMPL_LINK_NOARG(ExtraButton,CommandHdl,weld::Button &,void)193 IMPL_LINK_NOARG(ExtraButton, CommandHdl, weld::Button&, void)
194 {
195 comphelper::dispatchCommand(m_aCommand, css::uno::Sequence<css::beans::PropertyValue>());
196 }
197
SfxInfoBarWindow(vcl::Window * pParent,OUString sId,const OUString & sPrimaryMessage,const OUString & sSecondaryMessage,InfobarType ibType,bool bShowCloseButton)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))
204 , m_eType(ibType)
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))
211 {
212 SetStyle(GetStyle() | WB_DIALOGCONTROL);
213
214 InitControlBase(m_xCloseBtn.get());
215
216 m_xImage->set_from_icon_name(GetInfoBarIconName(ibType));
217 m_xSecondaryMessage->set_margin_top(m_xImage->get_preferred_size().Height() / 4);
218
219 if (!sPrimaryMessage.isEmpty())
220 {
221 m_xPrimaryMessage->set_label(sPrimaryMessage);
222 m_xPrimaryMessage->show();
223 }
224
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));
229
230 if (bShowCloseButton)
231 {
232 m_xCloseBtn->connect_clicked(LINK(this, SfxInfoBarWindow, CloseHandler));
233 m_xCloseBtn->show();
234 }
235
236 EnableChildTransparentMode();
237
238 SetForeAndBackgroundColors(m_eType);
239
240 auto nWidth = pParent->GetSizePixel().getWidth();
241 auto nHeight = get_preferred_size().Height();
242 SetSizePixel(Size(nWidth, nHeight + 2));
243
244 Resize();
245 }
246
IMPL_LINK(SfxInfoBarWindow,SizeAllocHdl,const Size &,rSize,void)247 IMPL_LINK(SfxInfoBarWindow, SizeAllocHdl, const Size&, rSize, void)
248 {
249 if (m_aMessageSize != rSize)
250 {
251 m_aMessageSize = rSize;
252 static_cast<SfxInfoBarContainerWindow*>(GetParent())->TriggerUpdateLayout();
253 }
254 }
255
DoLayout()256 Size SfxInfoBarWindow::DoLayout()
257 {
258 Size aGivenSize(GetSizePixel());
259
260 // disconnect SizeAllocHdl because we don't care about the size change
261 // during layout
262 m_xSecondaryMessage->connect_size_allocate(Link<const Size&, void>());
263
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);
274
275 // connect SizeAllocHdl so changes outside of this layout will trigger a new layout
276 m_xSecondaryMessage->connect_size_allocate(LINK(this, SfxInfoBarWindow, SizeAllocHdl));
277
278 return aSizeForWidth;
279 }
280
Layout()281 void SfxInfoBarWindow::Layout()
282 {
283 if (m_bLayingOut)
284 return;
285 m_bLayingOut = true;
286
287 InterimItemWindow::Layout();
288
289 m_bLayingOut = false;
290 }
291
addButton(const OUString * pCommand)292 weld::Button& SfxInfoBarWindow::addButton(const OUString* pCommand)
293 {
294 m_aActionBtns.emplace_back(std::make_unique<ExtraButton>(m_xButtonBox.get(), pCommand));
295
296 return m_aActionBtns.back()->get_widget();
297 }
298
~SfxInfoBarWindow()299 SfxInfoBarWindow::~SfxInfoBarWindow() { disposeOnce(); }
300
SetForeAndBackgroundColors(InfobarType eType)301 void SfxInfoBarWindow::SetForeAndBackgroundColors(InfobarType eType)
302 {
303 basegfx::BColor aMessageColor;
304 GetInfoBarColors(eType, m_aBackgroundColor, m_aForegroundColor, aMessageColor);
305
306 m_xPrimaryMessage->set_font_color(Color(aMessageColor));
307 m_xSecondaryMessage->set_font_color(Color(aMessageColor));
308
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())
314 {
315 m_xCloseBtn->set_background(aBackgroundColor);
316 SetCloseButtonImage();
317 }
318 }
319
dispose()320 void SfxInfoBarWindow::dispose()
321 {
322 for (auto& rxBtn : m_aActionBtns)
323 rxBtn.reset();
324
325 m_xImage.reset();
326 m_xPrimaryMessage.reset();
327 m_xSecondaryMessage.reset();
328 m_xButtonBox.reset();
329 m_xCloseBtn.reset();
330 m_aActionBtns.clear();
331 InterimItemWindow::dispose();
332 }
333
Update(const OUString & sPrimaryMessage,const OUString & sSecondaryMessage,InfobarType eType)334 void SfxInfoBarWindow::Update(const OUString& sPrimaryMessage, const OUString& sSecondaryMessage,
335 InfobarType eType)
336 {
337 if (m_eType != eType)
338 {
339 m_eType = eType;
340 SetForeAndBackgroundColors(m_eType);
341 m_xImage->set_from_icon_name(GetInfoBarIconName(eType));
342 }
343
344 m_xPrimaryMessage->set_label(sPrimaryMessage);
345 m_xSecondaryMessage->set_text(sSecondaryMessage);
346 Resize();
347 Invalidate();
348 }
349
IMPL_LINK_NOARG(SfxInfoBarWindow,CloseHandler,const OUString &,void)350 IMPL_LINK_NOARG(SfxInfoBarWindow, CloseHandler, const OUString&, void)
351 {
352 static_cast<SfxInfoBarContainerWindow*>(GetParent())->removeInfoBar(this);
353 }
354
SfxInfoBarContainerWindow(SfxInfoBarContainerChild * pChildWin)355 SfxInfoBarContainerWindow::SfxInfoBarContainerWindow(SfxInfoBarContainerChild* pChildWin)
356 : Window(pChildWin->GetParent(), WB_DIALOGCONTROL)
357 , m_pChildWin(pChildWin)
358 , m_aLayoutIdle("SfxInfoBarContainerWindow m_aLayoutIdle")
359 , m_bResizing(false)
360 {
361 m_aLayoutIdle.SetPriority(TaskPriority::HIGHEST);
362 m_aLayoutIdle.SetInvokeHandler(LINK(this, SfxInfoBarContainerWindow, DoUpdateLayout));
363 }
364
IMPL_LINK_NOARG(SfxInfoBarContainerWindow,DoUpdateLayout,Timer *,void)365 IMPL_LINK_NOARG(SfxInfoBarContainerWindow, DoUpdateLayout, Timer*, void) { m_pChildWin->Update(); }
366
~SfxInfoBarContainerWindow()367 SfxInfoBarContainerWindow::~SfxInfoBarContainerWindow() { disposeOnce(); }
368
dispose()369 void SfxInfoBarContainerWindow::dispose()
370 {
371 for (auto& infoBar : m_pInfoBars)
372 infoBar.disposeAndClear();
373 m_pInfoBars.clear();
374 Window::dispose();
375 }
376
appendInfoBar(const OUString & sId,const OUString & sPrimaryMessage,const OUString & sSecondaryMessage,InfobarType ibType,bool bShowCloseButton)377 VclPtr<SfxInfoBarWindow> SfxInfoBarContainerWindow::appendInfoBar(const OUString& sId,
378 const OUString& sPrimaryMessage,
379 const OUString& sSecondaryMessage,
380 InfobarType ibType,
381 bool bShowCloseButton)
382 {
383 if (!isInfobarEnabled(sId))
384 return nullptr;
385
386 auto pInfoBar = VclPtr<SfxInfoBarWindow>::Create(this, sId, sPrimaryMessage, sSecondaryMessage,
387 ibType, bShowCloseButton);
388
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);
396
397 Resize();
398 return pInfoBar;
399 }
400
getInfoBar(std::u16string_view sId)401 VclPtr<SfxInfoBarWindow> SfxInfoBarContainerWindow::getInfoBar(std::u16string_view sId)
402 {
403 for (auto const& infoBar : m_pInfoBars)
404 {
405 if (infoBar->getId() == sId)
406 return infoBar;
407 }
408 return nullptr;
409 }
410
hasInfoBarWithID(std::u16string_view sId)411 bool SfxInfoBarContainerWindow::hasInfoBarWithID(std::u16string_view sId)
412 {
413 return (getInfoBar(sId) != nullptr);
414 }
415
removeInfoBar(VclPtr<SfxInfoBarWindow> const & pInfoBar)416 void SfxInfoBarContainerWindow::removeInfoBar(VclPtr<SfxInfoBarWindow> const& pInfoBar)
417 {
418 // Remove
419 auto it = std::find(m_pInfoBars.begin(), m_pInfoBars.end(), pInfoBar);
420 if (it != m_pInfoBars.end())
421 {
422 it->disposeAndClear();
423 m_pInfoBars.erase(it);
424 }
425
426 m_pChildWin->Update();
427 }
428
isInfobarEnabled(std::u16string_view sId)429 bool SfxInfoBarContainerWindow::isInfobarEnabled(std::u16string_view sId)
430 {
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();
445 if (sId == u"macro")
446 return officecfg::Office::UI::Infobar::Enabled::MacrosDisabled::get();
447 if (sId == u"securitywarn")
448 {
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();
453 }
454
455 return true;
456 }
457
458 // This triggers the SfxFrame to re-layout its childwindows
TriggerUpdateLayout()459 void SfxInfoBarContainerWindow::TriggerUpdateLayout() { m_aLayoutIdle.Start(); }
460
Resize()461 void SfxInfoBarContainerWindow::Resize()
462 {
463 if (m_bResizing)
464 return;
465 m_bResizing = true;
466 const Size& rOrigSize = GetSizePixel();
467 auto nOrigWidth = rOrigSize.getWidth();
468 auto nOrigHeight = rOrigSize.getHeight();
469
470 tools::Long nHeight = 0;
471
472 for (auto& rxInfoBar : m_pInfoBars)
473 {
474 Size aOrigSize = rxInfoBar->GetSizePixel();
475 Size aSize(nOrigWidth, aOrigSize.Height());
476
477 Point aPos(0, nHeight);
478 // stage 1: provisionally size the infobar,
479 rxInfoBar->SetPosSizePixel(aPos, aSize);
480
481 // stage 2: perhaps allow height to stretch to fit
482 // the stage 1 width
483 aSize = rxInfoBar->DoLayout();
484 rxInfoBar->SetPosSizePixel(aPos, aSize);
485 rxInfoBar->Show();
486
487 // Stretch to fit the infobar(s)
488 nHeight += aSize.getHeight();
489 }
490
491 if (nOrigHeight != nHeight)
492 {
493 SetSizePixel(Size(nOrigWidth, nHeight));
494 TriggerUpdateLayout();
495 }
496
497 m_bResizing = false;
498 }
499
500 SFX_IMPL_POS_CHILDWINDOW_WITHID(SfxInfoBarContainerChild, SID_INFOBAR, SFX_OBJECTBAR_OBJECT);
501
SfxInfoBarContainerChild(vcl::Window * _pParent,sal_uInt16 nId,SfxBindings * pBindings,SfxChildWinInfo *)502 SfxInfoBarContainerChild::SfxInfoBarContainerChild(vcl::Window* _pParent, sal_uInt16 nId,
503 SfxBindings* pBindings, SfxChildWinInfo*)
504 : SfxChildWindow(_pParent, nId)
505 , m_pBindings(pBindings)
506 {
507 SetWindow(VclPtr<SfxInfoBarContainerWindow>::Create(this));
508 GetWindow()->SetPosSizePixel(Point(0, 0), Size(_pParent->GetSizePixel().getWidth(), 0));
509 GetWindow()->Show();
510
511 SetAlignment(SfxChildAlignment::LOWESTTOP);
512 }
513
~SfxInfoBarContainerChild()514 SfxInfoBarContainerChild::~SfxInfoBarContainerChild() {}
515
GetInfo() const516 SfxChildWinInfo SfxInfoBarContainerChild::GetInfo() const
517 {
518 SfxChildWinInfo aInfo = SfxChildWindow::GetInfo();
519 return aInfo;
520 }
521
Update()522 void SfxInfoBarContainerChild::Update()
523 {
524 // Layout to current width, this may change the height
525 if (vcl::Window* pChild = GetWindow())
526 {
527 Size aSize(pChild->GetSizePixel());
528 pChild->Resize();
529 if (aSize == pChild->GetSizePixel())
530 return;
531 }
532
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);
537
538 // Give the focus to the document view
539 pVFrame->GetWindow().GrabFocusToDocument();
540 }
541
542 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
543