diff options
Diffstat (limited to '')
-rw-r--r-- | sfx2/source/dialog/infobar.cxx | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/sfx2/source/dialog/infobar.cxx b/sfx2/source/dialog/infobar.cxx new file mode 100644 index 000000000..eade717ea --- /dev/null +++ b/sfx2/source/dialog/infobar.cxx @@ -0,0 +1,524 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <comphelper/dispatchcommand.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <memory> +#include <officecfg/Office/UI/Infobar.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/infobar.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/viewfrm.hxx> +#include <vcl/image.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weldutils.hxx> +#include <bitmaps.hlst> + +using namespace drawinglayer::geometry; +using namespace drawinglayer::processor2d; +using namespace drawinglayer::primitive2d; +using namespace drawinglayer::attribute; +using namespace basegfx; +using namespace css::frame; + +namespace +{ +void GetInfoBarColors(InfobarType ibType, BColor& rBackgroundColor, BColor& rForegroundColor, + BColor& rMessageColor) +{ + rMessageColor = basegfx::BColor(0.0, 0.0, 0.0); + + switch (ibType) + { + case InfobarType::INFO: // blue; #004785/0,71,133; #BDE5F8/189,229,248 + rBackgroundColor = basegfx::BColor(0.741, 0.898, 0.973); + rForegroundColor = basegfx::BColor(0.0, 0.278, 0.522); + break; + case InfobarType::SUCCESS: // green; #32550C/50,85,12; #DFF2BF/223,242,191 + rBackgroundColor = basegfx::BColor(0.874, 0.949, 0.749); + rForegroundColor = basegfx::BColor(0.196, 0.333, 0.047); + break; + case InfobarType::WARNING: // orange; #704300/112,67,0; #FEEFB3/254,239,179 + rBackgroundColor = basegfx::BColor(0.996, 0.937, 0.702); + rForegroundColor = basegfx::BColor(0.439, 0.263, 0.0); + break; + case InfobarType::DANGER: // red; #7A0006/122,0,6; #FFBABA/255,186,186 + rBackgroundColor = basegfx::BColor(1.0, 0.729, 0.729); + rForegroundColor = basegfx::BColor(0.478, 0.0, 0.024); + break; + } + + //remove this? + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + if (rSettings.GetHighContrastMode()) + { + rBackgroundColor = rSettings.GetLightColor().getBColor(); + rForegroundColor = rSettings.GetDialogTextColor().getBColor(); + } +} +OUString GetInfoBarIconName(InfobarType ibType) +{ + OUString aRet; + + switch (ibType) + { + case InfobarType::INFO: + aRet = "vcl/res/infobox.png"; + break; + case InfobarType::SUCCESS: + aRet = "vcl/res/successbox.png"; + break; + case InfobarType::WARNING: + aRet = "vcl/res/warningbox.png"; + break; + case InfobarType::DANGER: + aRet = "vcl/res/errorbox.png"; + break; + } + + return aRet; +} + +} // anonymous namespace + +void SfxInfoBarWindow::SetCloseButtonImage() +{ + Size aSize = Image(StockImage::Yes, CLOSEDOC).GetSizePixel(); + aSize = Size(aSize.Width() * 1.5, aSize.Height() * 1.5); + + ScopedVclPtr<VirtualDevice> xDevice(m_xCloseBtn->create_virtual_device()); + xDevice->SetOutputSizePixel(aSize); + + Point aBtnPos(0, 0); + + const ViewInformation2D aNewViewInfos; + const std::unique_ptr<BaseProcessor2D> pProcessor( + createBaseProcessor2DFromOutputDevice(*xDevice, aNewViewInfos)); + + const ::tools::Rectangle aRect(aBtnPos, xDevice->PixelToLogic(aSize)); + + drawinglayer::primitive2d::Primitive2DContainer aSeq(2); + + // background + B2DPolygon aPolygon; + aPolygon.append(B2DPoint(aRect.Left(), aRect.Top())); + aPolygon.append(B2DPoint(aRect.Right(), aRect.Top())); + aPolygon.append(B2DPoint(aRect.Right(), aRect.Bottom())); + aPolygon.append(B2DPoint(aRect.Left(), aRect.Bottom())); + aPolygon.setClosed(true); + + aSeq[0] = new PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), m_aBackgroundColor); + + LineAttribute aLineAttribute(m_aForegroundColor, 2.0); + + // Cross + B2DPolyPolygon aCross; + + B2DPolygon aLine1; + aLine1.append(B2DPoint(aRect.Left(), aRect.Top())); + aLine1.append(B2DPoint(aRect.Right(), aRect.Bottom())); + aCross.append(aLine1); + + B2DPolygon aLine2; + aLine2.append(B2DPoint(aRect.Right(), aRect.Top())); + aLine2.append(B2DPoint(aRect.Left(), aRect.Bottom())); + aCross.append(aLine2); + + aSeq[1] = new PolyPolygonStrokePrimitive2D(aCross, aLineAttribute, StrokeAttribute()); + + pProcessor->process(aSeq); + + m_xCloseBtn->set_item_image("close", xDevice); +} + +class ExtraButton +{ +private: + std::unique_ptr<weld::Builder> m_xBuilder; + std::unique_ptr<weld::Container> m_xContainer; + std::unique_ptr<weld::Button> m_xButton; + /** StatusListener. Updates the button as the slot state changes */ + rtl::Reference<weld::WidgetStatusListener> m_xStatusListener; + OUString m_aCommand; + + DECL_LINK(CommandHdl, weld::Button&, void); + +public: + ExtraButton(weld::Container* pContainer, const OUString* pCommand) + : m_xBuilder(Application::CreateBuilder(pContainer, "sfx/ui/extrabutton.ui")) + , m_xContainer(m_xBuilder->weld_container("ExtraButton")) + , m_xButton(m_xBuilder->weld_button("button")) + { + if (pCommand) + { + m_aCommand = *pCommand; + m_xButton->connect_clicked(LINK(this, ExtraButton, CommandHdl)); + m_xStatusListener.set(new weld::WidgetStatusListener(m_xButton.get(), m_aCommand)); + m_xStatusListener->startListening(); + } + } + + ~ExtraButton() + { + if (m_xStatusListener.is()) + m_xStatusListener->dispose(); + } + + weld::Button& get_widget() { return *m_xButton; } +}; + +IMPL_LINK_NOARG(ExtraButton, CommandHdl, weld::Button&, void) +{ + comphelper::dispatchCommand(m_aCommand, css::uno::Sequence<css::beans::PropertyValue>()); +} + +SfxInfoBarWindow::SfxInfoBarWindow(vcl::Window* pParent, const OUString& sId, + const OUString& sPrimaryMessage, + const OUString& sSecondaryMessage, InfobarType ibType, + bool bShowCloseButton) + : InterimItemWindow(pParent, "sfx/ui/infobar.ui", "InfoBar") + , m_sId(sId) + , m_eType(ibType) + , m_bLayingOut(false) + , m_xImage(m_xBuilder->weld_image("image")) + , m_xPrimaryMessage(m_xBuilder->weld_label("primary")) + , m_xSecondaryMessage(m_xBuilder->weld_text_view("secondary")) + , m_xButtonBox(m_xBuilder->weld_container("buttonbox")) + , m_xCloseBtn(m_xBuilder->weld_toolbar("closebar")) +{ + SetStyle(GetStyle() | WB_DIALOGCONTROL); + + InitControlBase(m_xCloseBtn.get()); + + m_xImage->set_from_icon_name(GetInfoBarIconName(ibType)); + m_xSecondaryMessage->set_margin_top(m_xImage->get_preferred_size().Height() / 4); + + if (!sPrimaryMessage.isEmpty()) + { + m_xPrimaryMessage->set_label(sPrimaryMessage); + m_xPrimaryMessage->show(); + } + + m_xSecondaryMessage->set_text(sSecondaryMessage); + m_aOrigMessageSize = m_xSecondaryMessage->get_preferred_size(); + m_aMessageSize = m_aOrigMessageSize; + m_xSecondaryMessage->connect_size_allocate(LINK(this, SfxInfoBarWindow, SizeAllocHdl)); + + if (bShowCloseButton) + { + m_xCloseBtn->connect_clicked(LINK(this, SfxInfoBarWindow, CloseHandler)); + m_xCloseBtn->show(); + } + + EnableChildTransparentMode(); + + SetForeAndBackgroundColors(m_eType); + + auto nWidth = pParent->GetSizePixel().getWidth(); + auto nHeight = get_preferred_size().Height(); + SetSizePixel(Size(nWidth, nHeight + 2)); + + Resize(); +} + +IMPL_LINK(SfxInfoBarWindow, SizeAllocHdl, const Size&, rSize, void) +{ + if (m_aMessageSize != rSize) + { + m_aMessageSize = rSize; + static_cast<SfxInfoBarContainerWindow*>(GetParent())->TriggerUpdateLayout(); + } +} + +Size SfxInfoBarWindow::DoLayout() +{ + Size aGivenSize(GetSizePixel()); + + // disconnect SizeAllocHdl because we don't care about the size change + // during layout + m_xSecondaryMessage->connect_size_allocate(Link<const Size&, void>()); + + // blow away size cache in case m_aMessageSize.Width() is already the width request + // and we would get the cached preferred size instead of the recalc we want to force + m_xSecondaryMessage->set_size_request(-1, -1); + // make the width we were detected as set to by SizeAllocHdl as our desired width + m_xSecondaryMessage->set_size_request(m_aMessageSize.Width(), -1); + // get our preferred size with that message width + Size aSizeForWidth(aGivenSize.Width(), m_xContainer->get_preferred_size().Height()); + // restore the message preferred size so we can freely resize, and get a new + // m_aMessageSize and repeat the process if we do + m_xSecondaryMessage->set_size_request(m_aOrigMessageSize.Width(), -1); + + // connect SizeAllocHdl so changes outside of this layout will trigger a new layout + m_xSecondaryMessage->connect_size_allocate(LINK(this, SfxInfoBarWindow, SizeAllocHdl)); + + return aSizeForWidth; +} + +void SfxInfoBarWindow::Layout() +{ + if (m_bLayingOut) + return; + m_bLayingOut = true; + + InterimItemWindow::Layout(); + + m_bLayingOut = false; +} + +weld::Button& SfxInfoBarWindow::addButton(const OUString* pCommand) +{ + m_aActionBtns.emplace_back(std::make_unique<ExtraButton>(m_xButtonBox.get(), pCommand)); + + return m_aActionBtns.back()->get_widget(); +} + +SfxInfoBarWindow::~SfxInfoBarWindow() { disposeOnce(); } + +void SfxInfoBarWindow::SetForeAndBackgroundColors(InfobarType eType) +{ + basegfx::BColor aMessageColor; + GetInfoBarColors(eType, m_aBackgroundColor, m_aForegroundColor, aMessageColor); + + m_xPrimaryMessage->set_font_color(Color(aMessageColor)); + m_xSecondaryMessage->set_font_color(Color(aMessageColor)); + + Color aBackgroundColor(m_aBackgroundColor); + m_xPrimaryMessage->set_background(aBackgroundColor); + m_xSecondaryMessage->set_background(aBackgroundColor); + m_xContainer->set_background(aBackgroundColor); + if (m_xCloseBtn->get_visible()) + { + m_xCloseBtn->set_background(aBackgroundColor); + SetCloseButtonImage(); + } +} + +void SfxInfoBarWindow::dispose() +{ + for (auto& rxBtn : m_aActionBtns) + rxBtn.reset(); + + m_xImage.reset(); + m_xPrimaryMessage.reset(); + m_xSecondaryMessage.reset(); + m_xButtonBox.reset(); + m_xCloseBtn.reset(); + m_aActionBtns.clear(); + InterimItemWindow::dispose(); +} + +void SfxInfoBarWindow::Update(const OUString& sPrimaryMessage, const OUString& sSecondaryMessage, + InfobarType eType) +{ + if (m_eType != eType) + { + m_eType = eType; + SetForeAndBackgroundColors(m_eType); + m_xImage->set_from_icon_name(GetInfoBarIconName(eType)); + } + + m_xPrimaryMessage->set_label(sPrimaryMessage); + m_xSecondaryMessage->set_text(sSecondaryMessage); + Resize(); + Invalidate(); +} + +IMPL_LINK_NOARG(SfxInfoBarWindow, CloseHandler, const OString&, void) +{ + static_cast<SfxInfoBarContainerWindow*>(GetParent())->removeInfoBar(this); +} + +SfxInfoBarContainerWindow::SfxInfoBarContainerWindow(SfxInfoBarContainerChild* pChildWin) + : Window(pChildWin->GetParent(), WB_DIALOGCONTROL) + , m_pChildWin(pChildWin) + , m_aLayoutIdle("SfxInfoBarContainerWindow m_aLayoutIdle") + , m_bResizing(false) +{ + m_aLayoutIdle.SetPriority(TaskPriority::HIGHEST); + m_aLayoutIdle.SetInvokeHandler(LINK(this, SfxInfoBarContainerWindow, DoUpdateLayout)); +} + +IMPL_LINK_NOARG(SfxInfoBarContainerWindow, DoUpdateLayout, Timer*, void) { m_pChildWin->Update(); } + +SfxInfoBarContainerWindow::~SfxInfoBarContainerWindow() { disposeOnce(); } + +void SfxInfoBarContainerWindow::dispose() +{ + for (auto& infoBar : m_pInfoBars) + infoBar.disposeAndClear(); + m_pInfoBars.clear(); + Window::dispose(); +} + +VclPtr<SfxInfoBarWindow> SfxInfoBarContainerWindow::appendInfoBar(const OUString& sId, + const OUString& sPrimaryMessage, + const OUString& sSecondaryMessage, + InfobarType ibType, + bool bShowCloseButton) +{ + if (!isInfobarEnabled(sId)) + return nullptr; + + auto pInfoBar = VclPtr<SfxInfoBarWindow>::Create(this, sId, sPrimaryMessage, sSecondaryMessage, + ibType, bShowCloseButton); + + basegfx::BColor aBackgroundColor; + basegfx::BColor aForegroundColor; + basegfx::BColor aMessageColor; + GetInfoBarColors(ibType, aBackgroundColor, aForegroundColor, aMessageColor); + pInfoBar->m_aBackgroundColor = aBackgroundColor; + pInfoBar->m_aForegroundColor = aForegroundColor; + m_pInfoBars.push_back(pInfoBar); + + Resize(); + return pInfoBar; +} + +VclPtr<SfxInfoBarWindow> SfxInfoBarContainerWindow::getInfoBar(std::u16string_view sId) +{ + for (auto const& infoBar : m_pInfoBars) + { + if (infoBar->getId() == sId) + return infoBar; + } + return nullptr; +} + +bool SfxInfoBarContainerWindow::hasInfoBarWithID(std::u16string_view sId) +{ + return (getInfoBar(sId) != nullptr); +} + +void SfxInfoBarContainerWindow::removeInfoBar(VclPtr<SfxInfoBarWindow> const& pInfoBar) +{ + // Remove + auto it = std::find(m_pInfoBars.begin(), m_pInfoBars.end(), pInfoBar); + if (it != m_pInfoBars.end()) + { + it->disposeAndClear(); + m_pInfoBars.erase(it); + } + + m_pChildWin->Update(); +} + +bool SfxInfoBarContainerWindow::isInfobarEnabled(std::u16string_view sId) +{ + if (sId == u"readonly") + return officecfg::Office::UI::Infobar::Enabled::Readonly::get(); + if (sId == u"signature") + return officecfg::Office::UI::Infobar::Enabled::Signature::get(); + if (sId == u"donate") + return officecfg::Office::UI::Infobar::Enabled::Donate::get(); + if (sId == u"getinvolved") + return officecfg::Office::UI::Infobar::Enabled::GetInvolved::get(); + if (sId == u"hyphenationmissing") + return officecfg::Office::UI::Infobar::Enabled::HyphenationMissing::get(); + if (sId == u"whatsnew") + return officecfg::Office::UI::Infobar::Enabled::WhatsNew::get(); + if (sId == u"hiddentrackchanges") + return officecfg::Office::UI::Infobar::Enabled::HiddenTrackChanges::get(); + + return true; +} + +// This triggers the SfxFrame to re-layout its childwindows +void SfxInfoBarContainerWindow::TriggerUpdateLayout() { m_aLayoutIdle.Start(); } + +void SfxInfoBarContainerWindow::Resize() +{ + if (m_bResizing) + return; + m_bResizing = true; + const Size& rOrigSize = GetSizePixel(); + auto nOrigWidth = rOrigSize.getWidth(); + auto nOrigHeight = rOrigSize.getHeight(); + + tools::Long nHeight = 0; + + for (auto& rxInfoBar : m_pInfoBars) + { + Size aOrigSize = rxInfoBar->GetSizePixel(); + Size aSize(nOrigWidth, aOrigSize.Height()); + + Point aPos(0, nHeight); + // stage 1: provisionally size the infobar, + rxInfoBar->SetPosSizePixel(aPos, aSize); + + // stage 2: perhaps allow height to stretch to fit + // the stage 1 width + aSize = rxInfoBar->DoLayout(); + rxInfoBar->SetPosSizePixel(aPos, aSize); + rxInfoBar->Show(); + + // Stretch to fit the infobar(s) + nHeight += aSize.getHeight(); + } + + if (nOrigHeight != nHeight) + { + SetSizePixel(Size(nOrigWidth, nHeight)); + TriggerUpdateLayout(); + } + + m_bResizing = false; +} + +SFX_IMPL_POS_CHILDWINDOW_WITHID(SfxInfoBarContainerChild, SID_INFOBAR, SFX_OBJECTBAR_OBJECT); + +SfxInfoBarContainerChild::SfxInfoBarContainerChild(vcl::Window* _pParent, sal_uInt16 nId, + SfxBindings* pBindings, SfxChildWinInfo*) + : SfxChildWindow(_pParent, nId) + , m_pBindings(pBindings) +{ + SetWindow(VclPtr<SfxInfoBarContainerWindow>::Create(this)); + GetWindow()->SetPosSizePixel(Point(0, 0), Size(_pParent->GetSizePixel().getWidth(), 0)); + GetWindow()->Show(); + + SetAlignment(SfxChildAlignment::LOWESTTOP); +} + +SfxInfoBarContainerChild::~SfxInfoBarContainerChild() {} + +SfxChildWinInfo SfxInfoBarContainerChild::GetInfo() const +{ + SfxChildWinInfo aInfo = SfxChildWindow::GetInfo(); + return aInfo; +} + +void SfxInfoBarContainerChild::Update() +{ + // Layout to current width, this may change the height + if (vcl::Window* pChild = GetWindow()) + { + Size aSize(pChild->GetSizePixel()); + pChild->Resize(); + if (aSize == pChild->GetSizePixel()) + return; + } + + // Refresh the frame to take the infobars container height change into account + const sal_uInt16 nId = GetChildWindowId(); + SfxViewFrame* pVFrame = m_pBindings->GetDispatcher()->GetFrame(); + pVFrame->ShowChildWindow(nId); + + // Give the focus to the document view + pVFrame->GetWindow().GrabFocusToDocument(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |