diff options
Diffstat (limited to 'vcl/source/window/dialog.cxx')
-rw-r--r-- | vcl/source/window/dialog.cxx | 1736 |
1 files changed, 1736 insertions, 0 deletions
diff --git a/vcl/source/window/dialog.cxx b/vcl/source/window/dialog.cxx new file mode 100644 index 0000000000..83a8e8baba --- /dev/null +++ b/vcl/source/window/dialog.cxx @@ -0,0 +1,1736 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_feature_desktop.h> + +#ifdef IOS +#include <premac.h> +#include <UIKit/UIKit.h> +#include <postmac.h> +#endif + +#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp> +#include <comphelper/lok.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/processfactory.hxx> +#include <officecfg/Office/Common.hxx> +#include <osl/diagnose.h> + +#include <svdata.hxx> +#include <window.h> +#include <accel.hxx> +#include <brdwin.hxx> +#include <salinst.hxx> + +#include <rtl/bootstrap.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +#include <vcl/abstdlg.hxx> +#include <vcl/builder.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/layout.hxx> +#include <vcl/svapp.hxx> +#include <vcl/event.hxx> +#include <vcl/locktoplevels.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/toolkit/button.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/toolkit/dialog.hxx> +#include <vcl/dialoghelper.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/IDialogRenderable.hxx> +#include <messagedialog.hxx> +#include <salframe.hxx> +#include <tools/json_writer.hxx> + +#include <iostream> +#include <stack> +#include <utility> +#include <vector> + +static OString ImplGetDialogText( Dialog* pDialog ) +{ + OUString aErrorStr(pDialog->GetText()); + + OUString sMessage; + if (MessageDialog* pMessDialog = dynamic_cast<MessageDialog*>(pDialog)) + { + sMessage = pMessDialog->get_primary_text(); + } + + if (!sMessage.isEmpty()) + { + aErrorStr += ", " + sMessage; + } + return OUStringToOString(aErrorStr, RTL_TEXTENCODING_UTF8); +} + +static bool ImplIsMnemonicCtrl( vcl::Window* pWindow ) +{ + if( ! pWindow->GetSettings().GetStyleSettings().GetAutoMnemonic() ) + return false; + + if ( (pWindow->GetType() == WindowType::RADIOBUTTON) || + (pWindow->GetType() == WindowType::CHECKBOX) || + (pWindow->GetType() == WindowType::TRISTATEBOX) || + (pWindow->GetType() == WindowType::PUSHBUTTON) ) + return true; + + if ( pWindow->GetType() == WindowType::FIXEDTEXT ) + { + FixedText *pText = static_cast<FixedText*>(pWindow); + if (pText->get_mnemonic_widget()) + return true; + //This is the legacy pre-layout logic which we retain + //until we can be sure we can remove it + if (pWindow->GetStyle() & WB_NOLABEL) + return false; + vcl::Window* pNextWindow = pWindow->GetWindow( GetWindowType::Next ); + if ( !pNextWindow ) + return false; + pNextWindow = pNextWindow->GetWindow( GetWindowType::Client ); + return !(!(pNextWindow->GetStyle() & WB_TABSTOP) || + (pNextWindow->GetType() == WindowType::FIXEDTEXT) || + (pNextWindow->GetType() == WindowType::GROUPBOX) || + (pNextWindow->GetType() == WindowType::RADIOBUTTON) || + (pNextWindow->GetType() == WindowType::CHECKBOX) || + (pNextWindow->GetType() == WindowType::TRISTATEBOX) || + (pNextWindow->GetType() == WindowType::PUSHBUTTON)); + } + + return false; +} + +// Called by native error dialog popup implementations +void ImplHideSplash() +{ + ImplSVData* pSVData = ImplGetSVData(); + if( pSVData->mpIntroWindow ) + pSVData->mpIntroWindow->Hide(); +} + +vcl::Window * nextLogicalChildOfParent(const vcl::Window *pTopLevel, const vcl::Window *pChild) +{ + const vcl::Window *pLastChild = pChild; + + if (pChild->GetType() == WindowType::SCROLLWINDOW) + pChild = static_cast<const VclScrolledWindow*>(pChild)->get_child(); + else if (isContainerWindow(*pChild)) + pChild = pChild->GetWindow(GetWindowType::FirstChild); + else + pChild = pChild->GetWindow(GetWindowType::Next); + + while (!pChild) + { + vcl::Window *pParent = pLastChild->GetParent(); + if (!pParent) + return nullptr; + if (pParent == pTopLevel) + return nullptr; + pLastChild = pParent; + pChild = pParent->GetWindow(GetWindowType::Next); + } + + if (isContainerWindow(*pChild)) + pChild = nextLogicalChildOfParent(pTopLevel, pChild); + + return const_cast<vcl::Window *>(pChild); +} + +vcl::Window * prevLogicalChildOfParent(const vcl::Window *pTopLevel, const vcl::Window *pChild) +{ + const vcl::Window *pLastChild = pChild; + + if (pChild->GetType() == WindowType::SCROLLWINDOW) + pChild = static_cast<const VclScrolledWindow*>(pChild)->get_child(); + else if (isContainerWindow(*pChild)) + pChild = pChild->GetWindow(GetWindowType::LastChild); + else + pChild = pChild->GetWindow(GetWindowType::Prev); + + while (!pChild) + { + vcl::Window *pParent = pLastChild->GetParent(); + if (!pParent) + return nullptr; + if (pParent == pTopLevel) + return nullptr; + pLastChild = pParent; + pChild = pParent->GetWindow(GetWindowType::Prev); + } + + if (isContainerWindow(*pChild)) + pChild = prevLogicalChildOfParent(pTopLevel, pChild); + + return const_cast<vcl::Window *>(pChild); +} + +vcl::Window * firstLogicalChildOfParent(const vcl::Window *pTopLevel) +{ + const vcl::Window *pChild = pTopLevel->GetWindow(GetWindowType::FirstChild); + if (pChild && isContainerWindow(*pChild)) + pChild = nextLogicalChildOfParent(pTopLevel, pChild); + return const_cast<vcl::Window *>(pChild); +} + +vcl::Window * lastLogicalChildOfParent(const vcl::Window *pTopLevel) +{ + const vcl::Window *pChild = pTopLevel->GetWindow(GetWindowType::LastChild); + if (pChild && isContainerWindow(*pChild)) + pChild = prevLogicalChildOfParent(pTopLevel, pChild); + return const_cast<vcl::Window *>(pChild); +} + +void GenerateAutoMnemonicsOnHierarchy(const vcl::Window* pWindow) +{ + MnemonicGenerator aMnemonicGenerator; + vcl::Window* pGetChild; + vcl::Window* pChild; + + // register the assigned mnemonics + pGetChild = pWindow->GetWindow( GetWindowType::FirstChild ); + while ( pGetChild ) + { + pChild = pGetChild->ImplGetWindow(); + aMnemonicGenerator.RegisterMnemonic( pChild->GetText() ); + pGetChild = nextLogicalChildOfParent(pWindow, pGetChild); + } + + // take the Controls of the dialog into account for TabPages + if ( pWindow->GetType() == WindowType::TABPAGE ) + { + vcl::Window* pParent = pWindow->GetParent(); + if (pParent && pParent->GetType() == WindowType::TABCONTROL ) + pParent = pParent->GetParent(); + + if (pParent && (pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL ) + { + pGetChild = pParent->GetWindow( GetWindowType::FirstChild ); + while ( pGetChild ) + { + pChild = pGetChild->ImplGetWindow(); + aMnemonicGenerator.RegisterMnemonic( pChild->GetText() ); + pGetChild = nextLogicalChildOfParent(pWindow, pGetChild); + } + } + } + + // assign mnemonics to Controls which have none + pGetChild = pWindow->GetWindow( GetWindowType::FirstChild ); + while ( pGetChild ) + { + pChild = pGetChild->ImplGetWindow(); + if ( ImplIsMnemonicCtrl( pChild ) ) + { + OUString aText = pChild->GetText(); + OUString aNewText = aMnemonicGenerator.CreateMnemonic( aText ); + if ( aText != aNewText ) + pChild->SetText( aNewText ); + } + + pGetChild = nextLogicalChildOfParent(pWindow, pGetChild); + } +} + +static VclButtonBox* getActionArea(Dialog const *pDialog) +{ + VclButtonBox *pButtonBox = nullptr; + if (pDialog->isLayoutEnabled()) + { + vcl::Window *pBox = pDialog->GetWindow(GetWindowType::FirstChild); + vcl::Window *pChild = pBox->GetWindow(GetWindowType::LastChild); + while (pChild) + { + pButtonBox = dynamic_cast<VclButtonBox*>(pChild); + if (pButtonBox) + break; + pChild = pChild->GetWindow(GetWindowType::Prev); + } + } + return pButtonBox; +} + +static vcl::Window* getActionAreaButtonList(Dialog const *pDialog) +{ + VclButtonBox* pButtonBox = getActionArea(pDialog); + if (pButtonBox) + return pButtonBox->GetWindow(GetWindowType::FirstChild); + return pDialog->GetWindow(GetWindowType::FirstChild); +} + +static PushButton* ImplGetDefaultButton( Dialog const * pDialog ) +{ + vcl::Window* pChild = getActionAreaButtonList(pDialog); + while ( pChild ) + { + if ( pChild->ImplIsPushButton() ) + { + PushButton* pPushButton = static_cast<PushButton*>(pChild); + if ( pPushButton->ImplIsDefButton() ) + return pPushButton; + } + + pChild = pChild->GetWindow( GetWindowType::Next ); + } + + return nullptr; +} + +static PushButton* ImplGetOKButton( Dialog const * pDialog ) +{ + vcl::Window* pChild = getActionAreaButtonList(pDialog); + while ( pChild ) + { + if ( pChild->GetType() == WindowType::OKBUTTON ) + return static_cast<PushButton*>(pChild); + + pChild = pChild->GetWindow( GetWindowType::Next ); + } + + return nullptr; +} + +static PushButton* ImplGetCancelButton( Dialog const * pDialog ) +{ + vcl::Window* pChild = getActionAreaButtonList(pDialog); + + while ( pChild ) + { + if ( pChild->GetType() == WindowType::CANCELBUTTON ) + return static_cast<PushButton*>(pChild); + + pChild = pChild->GetWindow( GetWindowType::Next ); + } + + return nullptr; +} + +static void ImplMouseAutoPos( Dialog* pDialog ) +{ + MouseSettingsOptions nMouseOptions = pDialog->GetSettings().GetMouseSettings().GetOptions(); + if ( nMouseOptions & MouseSettingsOptions::AutoCenterPos ) + { + Size aSize = pDialog->GetOutputSizePixel(); + pDialog->SetPointerPosPixel( Point( aSize.Width()/2, aSize.Height()/2 ) ); + } + else if ( nMouseOptions & MouseSettingsOptions::AutoDefBtnPos ) + { + vcl::Window* pWindow = ImplGetDefaultButton( pDialog ); + if ( !pWindow ) + pWindow = ImplGetOKButton( pDialog ); + if ( !pWindow ) + pWindow = ImplGetCancelButton( pDialog ); + if ( !pWindow ) + pWindow = pDialog; + Size aSize = pWindow->GetOutputSizePixel(); + pWindow->SetPointerPosPixel( Point( aSize.Width()/2, aSize.Height()/2 ) ); + } +} + +struct DialogImpl +{ + std::vector<VclPtr<PushButton>> maOwnedButtons; + std::map<VclPtr<vcl::Window>, short> maResponses; + tools::Long mnResult; + bool mbStartedModal; + VclAbstractDialog::AsyncContext maEndCtx; + Link<const CommandEvent&, bool> m_aPopupMenuHdl; + Link<void*, vcl::ILibreOfficeKitNotifier*> m_aInstallLOKNotifierHdl; + bool m_bLOKTunneling; + + DialogImpl() : mnResult( -1 ), mbStartedModal( false ), m_bLOKTunneling( true ) {} + +#ifndef NDEBUG + short get_response(vcl::Window *pWindow) const + { + auto aFind = maResponses.find(pWindow); + if (aFind != maResponses.end()) + return aFind->second; + return RET_CANCEL; + } +#endif + + ~DialogImpl() + { + for (VclPtr<PushButton> & pOwnedButton : maOwnedButtons) + pOwnedButton.disposeAndClear(); + } +}; + +void Dialog::disposeOwnedButtons() +{ + for (VclPtr<PushButton> & pOwnedButton : mpDialogImpl->maOwnedButtons) + pOwnedButton.disposeAndClear(); +} + +void Dialog::ImplInitDialogData() +{ + mpWindowImpl->mbDialog = true; + mbInExecute = false; + mbInSyncExecute = false; + mbInClose = false; + mbModalMode = false; + mpContentArea.clear(); + mpActionArea.clear(); + mnMousePositioned = 0; + mpDialogImpl.reset(new DialogImpl); +} + +void Dialog::PixelInvalidate(const tools::Rectangle* pRectangle) +{ + if (!mpDialogImpl->m_bLOKTunneling) + return; + + Window::PixelInvalidate(pRectangle); +} + +vcl::Window* Dialog::GetDefaultParent(WinBits nStyle) +{ + vcl::Window* pParent = Dialog::GetDefDialogParent(); + if (!pParent && !(nStyle & WB_SYSTEMWINDOW)) + pParent = ImplGetSVData()->maFrameData.mpAppWin; + + // If Parent is disabled, then we search for a modal dialog + // in this frame + if (pParent && (!pParent->IsInputEnabled() || pParent->IsInModalMode())) + { + ImplSVData* pSVData = ImplGetSVData(); + auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs; + auto it = std::find_if(rExecuteDialogs.rbegin(), rExecuteDialogs.rend(), + [&pParent](VclPtr<Dialog>& rDialogPtr) { + return pParent->ImplGetFirstOverlapWindow() && + pParent->ImplGetFirstOverlapWindow()->IsWindowOrChild(rDialogPtr, true) && + rDialogPtr->IsReallyVisible() && rDialogPtr->IsEnabled() && + rDialogPtr->IsInputEnabled() && !rDialogPtr->IsInModalMode(); }); + if (it != rExecuteDialogs.rend()) + pParent = it->get(); + } + + return pParent; +} + +VclPtr<vcl::Window> Dialog::AddBorderWindow(vcl::Window* pParent, WinBits nStyle) +{ + VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, nStyle, BorderWindowStyle::Frame ); + ImplInit( pBorderWin, nStyle & ~WB_BORDER, nullptr ); + pBorderWin->mpWindowImpl->mpClientWindow = this; + pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder ); + mpWindowImpl->mpBorderWindow = pBorderWin; + mpWindowImpl->mpRealParent = pParent; + + return pBorderWin; +} + +void Dialog::ImplInitDialog( vcl::Window* pParent, WinBits nStyle, InitFlag eFlag ) +{ + SystemWindowFlags nSysWinMode = Application::GetSystemWindowMode(); + + if ( !(nStyle & WB_NODIALOGCONTROL) ) + nStyle |= WB_DIALOGCONTROL; + + // Now, all Dialogs are per default system windows !!! + nStyle |= WB_SYSTEMWINDOW; + + if (InitFlag::NoParent == eFlag) + { + pParent = nullptr; + } + else if (!pParent) // parent is NULL: get the default Dialog parent + { + pParent = Dialog::GetDefaultParent(nStyle); + } + + if ( !pParent || (nStyle & WB_SYSTEMWINDOW) || + (pParent->mpWindowImpl->mpFrameData->mbNeedSysWindow && !(nSysWinMode & SystemWindowFlags::NOAUTOMODE)) || + (nSysWinMode & SystemWindowFlags::DIALOG) ) + { + // create window with a small border ? + if ((nStyle & WB_ALLOWMENUBAR) || ((nStyle & (WB_BORDER | WB_NOBORDER | WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE)) == WB_BORDER)) + { + AddBorderWindow(pParent, nStyle); + } + else + { + mpWindowImpl->mbFrame = true; + mpWindowImpl->mbOverlapWin = true; + ImplInit( pParent, (nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_STANDALONE)) | WB_CLOSEABLE, nullptr ); + // Now set all style bits + mpWindowImpl->mnStyle = nStyle; + } + } + else + { + VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, nStyle, BorderWindowStyle::Overlap ); + ImplInit( pBorderWin, nStyle & ~WB_BORDER, nullptr ); + pBorderWin->mpWindowImpl->mpClientWindow = this; + pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder ); + mpWindowImpl->mpBorderWindow = pBorderWin; + mpWindowImpl->mpRealParent = pParent; + } + + SetActivateMode( ActivateModeFlags::GrabFocus ); + + ImplInitSettings(); +} + +void Dialog::ApplySettings(vcl::RenderContext& rRenderContext) +{ + if (IsControlBackground()) + { + // user override + SetBackground(GetControlBackground()); + } + else if (rRenderContext.IsNativeControlSupported(ControlType::WindowBackground, ControlPart::BackgroundDialog)) + { + // NWF background + mpWindowImpl->mnNativeBackground = ControlPart::BackgroundDialog; + EnableChildTransparentMode(); + } + else + { + // fallback to settings color + rRenderContext.SetBackground(GetSettings().GetStyleSettings().GetDialogColor()); + } +} + +void Dialog::ImplInitSettings() +{ + // user override + if (IsControlBackground()) + SetBackground(GetControlBackground()); + // NWF background + else if( IsNativeControlSupported(ControlType::WindowBackground, ControlPart::BackgroundDialog)) + { + mpWindowImpl->mnNativeBackground = ControlPart::BackgroundDialog; + EnableChildTransparentMode(); + } + // fallback to settings color + else + SetBackground(GetSettings().GetStyleSettings().GetDialogColor()); +} + +void Dialog::ImplLOKNotifier(vcl::Window* pParent) +{ + if (comphelper::LibreOfficeKit::isActive() && pParent) + { + if (VclPtr<vcl::Window> pWin = pParent->GetParentWithLOKNotifier()) + { + SetLOKNotifier(pWin->GetLOKNotifier()); + } + } +} + +Dialog::Dialog( WindowType nType ) + : SystemWindow( nType, "vcl::Dialog maLayoutIdle" ) + , mnInitFlag(InitFlag::Default) +{ + ImplInitDialogData(); +} + +void VclBuilderContainer::disposeBuilder() +{ + if (m_pUIBuilder) + m_pUIBuilder->disposeBuilder(); +} + +OUString AllSettings::GetUIRootDir() +{ + OUString sShareLayer("$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/config/soffice.cfg/"); + rtl::Bootstrap::expandMacros(sShareLayer); + return sShareLayer; +} + +//we can't change sizeable after the fact, so need to defer until we know and then +//do the init. Find the real parent stashed in mpDialogParent. +void Dialog::doDeferredInit(WinBits nBits) +{ + VclPtr<vcl::Window> pParent = mpDialogParent; + mpDialogParent = nullptr; + ImplInitDialog(pParent, nBits | WB_BORDER, mnInitFlag); + mbIsDeferredInit = false; +} + +Dialog::Dialog(vcl::Window* pParent, const OUString& rID, const OUString& rUIXMLDescription) + : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle") + , mnInitFlag(InitFlag::Default) +{ + ImplLOKNotifier(pParent); + ImplInitDialogData(); + loadUI(pParent, rID, rUIXMLDescription); +} + +Dialog::Dialog(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag) + : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle") + , mnInitFlag(eFlag) +{ + ImplLOKNotifier(pParent); + ImplInitDialogData(); + ImplInitDialog( pParent, nStyle, eFlag ); +} + +void Dialog::set_action_area(VclButtonBox* pBox) +{ + mpActionArea.set(pBox); + if (pBox) + { + const DialogStyle& rDialogStyle = + GetSettings().GetStyleSettings().GetDialogStyle(); + pBox->set_border_width(rDialogStyle.action_area_border); + } +} + +void Dialog::set_content_area(VclBox* pBox) +{ + mpContentArea.set(pBox); +} + +void Dialog::settingOptimalLayoutSize(Window *pBox) +{ + const DialogStyle& rDialogStyle = + GetSettings().GetStyleSettings().GetDialogStyle(); + VclBox * pBox2 = static_cast<VclBox*>(pBox); + pBox2->set_border_width(rDialogStyle.content_area_border); +} + +Dialog::~Dialog() +{ + disposeOnce(); +} + +void Dialog::dispose() +{ + bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling; + + mpDialogImpl.reset(); + RemoveFromDlgList(); + mpActionArea.clear(); + mpContentArea.clear(); + + css::uno::Reference< css::uno::XComponentContext > xContext( + comphelper::getProcessComponentContext() ); + css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster(css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW); + css::document::DocumentEvent aObject; + aObject.EventName = "DialogClosed"; + xEventBroadcaster->documentEventOccured(aObject); + UITestLogger::getInstance().log(u"Close Dialog"); + + if (comphelper::LibreOfficeKit::isActive()) + { + if(const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + if (bTunnelingEnabled) + pNotifier->notifyWindow(GetLOKWindowId(), "close"); + ReleaseLOKNotifier(); + } + } + + SystemWindow::dispose(); +} + +IMPL_LINK_NOARG(Dialog, ImplAsyncCloseHdl, void*, void) +{ + Close(); +} + +bool Dialog::EventNotify( NotifyEvent& rNEvt ) +{ + // first call the base class due to Tab control + bool bRet = SystemWindow::EventNotify( rNEvt ); + if ( !bRet ) + { + if ( rNEvt.GetType() == NotifyEventType::KEYINPUT ) + { + const KeyEvent* pKEvt = rNEvt.GetKeyEvent(); + vcl::KeyCode aKeyCode = pKEvt->GetKeyCode(); + sal_uInt16 nKeyCode = aKeyCode.GetCode(); + + if ( (nKeyCode == KEY_ESCAPE) && + ((GetStyle() & WB_CLOSEABLE) || ImplGetCancelButton( this ) || ImplGetOKButton( this )) ) + { + // #i89505# for the benefit of slightly mentally challenged implementations + // like e.g. SfxModelessDialog which destroy themselves inside Close() + // post this Close asynchronous so we can leave our key handler before + // we get destroyed + PostUserEvent( LINK( this, Dialog, ImplAsyncCloseHdl ), nullptr, true); + return true; + } + } + else if ( rNEvt.GetType() == NotifyEventType::GETFOCUS ) + { + // make sure the dialog is still modal + // changing focus between application frames may + // have re-enabled input for our parent + if( mbInExecute && mbModalMode ) + { + ImplSetModalInputMode( false ); + ImplSetModalInputMode( true ); + + // #93022# def-button might have changed after show + if( !mnMousePositioned ) + { + mnMousePositioned = 1; + ImplMouseAutoPos( this ); + } + + } + } + } + + return bRet; +} + +//What we really want here is something that gives the available width and +//height of a users screen, taking away the space taken up the OS +//taskbar, menus, etc. +Size bestmaxFrameSizeForScreenSize(const Size &rScreenSize) +{ +#ifndef IOS + tools::Long w = rScreenSize.Width(); + if (w <= 800) + w -= 15; + else if (w <= 1024) + w -= 65; + else + w -= 115; + + tools::Long h = rScreenSize.Height(); + if (h <= 768) + h -= 50; + else + h -= 100; + + return Size(std::max<tools::Long>(w, 640 - 15), + std::max<tools::Long>(h, 480 - 50)); +#else + // Don't bother with ancient magic numbers of unclear relevance on non-desktop apps anyway. It + // seems that at least currently in the iOS app, this function is called just once per dialog, + // with a rScreenSize parameter of 1x1 (!). This would lead to always returning 625x430 which is + // a bit random and needlessly small on an iPad at least. We want something that closely will + // just fit on the display in either orientation. + + // We ignore the rScreenSize as it will be the dummy 1x1 from iosinst.cxx (see "Totally wrong of course"). + (void) rScreenSize; + + const int n = std::min<CGFloat>([[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height); + return Size(n-10, n-10); +#endif +} + +void Dialog::SetPopupMenuHdl(const Link<const CommandEvent&, bool>& rLink) +{ + mpDialogImpl->m_aPopupMenuHdl = rLink; +} + +void Dialog::SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>& rLink) +{ + mpDialogImpl->m_aInstallLOKNotifierHdl = rLink; +} + +void Dialog::SetLOKTunnelingState(bool bEnabled) +{ + mpDialogImpl->m_bLOKTunneling = bEnabled; +} + +void Dialog::StateChanged( StateChangedType nType ) +{ + bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling; + + if (nType == StateChangedType::InitShow) + { + DoInitialLayout(); + + const bool bKitActive = comphelper::LibreOfficeKit::isActive(); + if (bKitActive && bTunnelingEnabled) + { + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("type", "dialog"); + aItems.emplace_back("size", GetSizePixel().toString()); + aItems.emplace_back("unique_id", this->get_id().toUtf8()); + if (!GetText().isEmpty()) + aItems.emplace_back("title", GetText().toUtf8()); + + if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + pNotifier->notifyWindow(GetLOKWindowId(), "created", aItems); + pNotifier->notifyWindow(GetLOKWindowId(), "created", aItems); + } + else + { + vcl::ILibreOfficeKitNotifier* pViewShell = mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr); + if (pViewShell) + { + SetLOKNotifier(pViewShell); + pViewShell->notifyWindow(GetLOKWindowId(), "created", aItems); + } + } + } + + if ( !HasChildPathFocus() || HasFocus() ) + GrabFocusToFirstControl(); + if ( !(GetStyle() & WB_CLOSEABLE) ) + { + if ( ImplGetCancelButton( this ) || ImplGetOKButton( this ) ) + { + if ( ImplGetBorderWindow() ) + static_cast<ImplBorderWindow*>(ImplGetBorderWindow())->SetCloseButton(); + } + } + + ImplMouseAutoPos( this ); + } + else if (nType == StateChangedType::Text) + { + const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier(); + if (pNotifier && bTunnelingEnabled) + { + std::vector<vcl::LOKPayloadItem> aPayload; + aPayload.emplace_back("title", GetText().toUtf8()); + pNotifier->notifyWindow(GetLOKWindowId(), "title_changed", aPayload); + } + } + + SystemWindow::StateChanged( nType ); + + if (nType == StateChangedType::ControlBackground) + { + ImplInitSettings(); + Invalidate(); + } + + if (!mbModalMode && nType == StateChangedType::Visible) + { + const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier(); + if (pNotifier && bTunnelingEnabled) + { + std::vector<vcl::LOKPayloadItem> aPayload; + aPayload.emplace_back("title", GetText().toUtf8()); + pNotifier->notifyWindow(GetLOKWindowId(), IsVisible()? OUString("show"): OUString("hide"), aPayload); + } + } +} + +void Dialog::DataChanged( const DataChangedEvent& rDCEvt ) +{ + SystemWindow::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + ImplInitSettings(); + Invalidate(); + } +} + +bool Dialog::Close() +{ + VclPtr<vcl::Window> xWindow = this; + CallEventListeners( VclEventId::WindowClose ); + if ( xWindow->isDisposed() ) + return false; + + if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() && !IsInExecute() ) + return false; + + // If there's a cancel button with a custom handler, then always give it a chance to + // handle Dialog::Close + PushButton* pCustomCancelButton; + PushButton* pCancelButton = dynamic_cast<PushButton*>(get_widget_for_response(RET_CANCEL)); + if (!mbInClose && pCancelButton && pCancelButton->GetClickHdl().IsSet()) + pCustomCancelButton = pCancelButton; + else + pCustomCancelButton = nullptr; + + mbInClose = true; + + if (pCustomCancelButton) + { + pCustomCancelButton->Click(); + if (xWindow->isDisposed()) + return true; + mbInClose = false; + return false; + } + + if ( !(GetStyle() & WB_CLOSEABLE) ) + { + bool bRet = true; + PushButton* pButton = ImplGetCancelButton( this ); + if ( pButton ) + pButton->Click(); + else + { + pButton = ImplGetOKButton( this ); + if ( pButton ) + pButton->Click(); + else + bRet = false; + } + if ( xWindow->isDisposed() ) + return true; + return bRet; + } + + if (IsInExecute() || mpDialogImpl->maEndCtx.isSet()) + { + EndDialog(); + mbInClose = false; + return true; + } + else + { + mbInClose = false; + return SystemWindow::Close(); + } +} + +bool Dialog::ImplStartExecute() +{ + setDeferredProperties(); + + if (IsInExecute() || mpDialogImpl->maEndCtx.isSet()) + { +#ifdef DBG_UTIL + SAL_WARN( "vcl", "Dialog::StartExecuteModal() is called in Dialog::StartExecuteModal(): " + << ImplGetDialogText(this) ); +#endif + return false; + } + + ImplSVData* pSVData = ImplGetSVData(); + + const bool bKitActive = comphelper::LibreOfficeKit::isActive(); + + const bool bModal = GetType() != WindowType::MODELESSDIALOG; + + if (bModal) + { + if (bKitActive && !GetLOKNotifier()) + { +#ifdef IOS + // gh#5908 handle pasting disallowed clipboard contents on iOS + // When another app owns the current clipboard contents, pasting + // will display a "allow or disallow" dialog. If the disallow + // option is selected, the data from the UIPasteboard will be + // garbage and we will find ourselves here. Since calling + // SetLOKNotifier() with a nullptr aborts in an assert(), fix + // the crash by failing gracefully. + return false; +#else + SetLOKNotifier(mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr)); +#endif + } + + switch ( Application::GetDialogCancelMode() ) + { + case DialogCancelMode::Off: + break; + case DialogCancelMode::Silent: + if (bModal && GetLOKNotifier()) + { + // check if there's already some dialog being ::Execute()d + const bool bDialogExecuting = std::any_of(pSVData->mpWinData->mpExecuteDialogs.begin(), + pSVData->mpWinData->mpExecuteDialogs.end(), + [](const Dialog* pDialog) { + return pDialog->IsInSyncExecute(); + }); + if (!(bDialogExecuting && IsInSyncExecute())) + break; + else + SAL_WARN("lok.dialog", "Dialog \"" << ImplGetDialogText(this) << "\" is being synchronously executed over an existing synchronously executing dialog."); + } + + if (SalInstance::IsRunningUnitTest()) + { // helps starbasic unit tests show their errors + std::cerr << "Dialog \"" << ImplGetDialogText(this) + << "\"cancelled in silent mode"; + } + + SAL_INFO( + "vcl", + "Dialog \"" << ImplGetDialogText(this) + << "\"cancelled in silent mode"); + return false; + + case DialogCancelMode::LOKSilent: + return false; + + default: // default cannot happen + case DialogCancelMode::Fatal: + std::abort(); + } + +#ifdef DBG_UTIL + vcl::Window* pParent = GetParent(); + if ( pParent ) + { + pParent = pParent->ImplGetFirstOverlapWindow(); + if (pParent) + { + SAL_WARN_IF( !pParent->IsReallyVisible(), "vcl", + "Dialog::StartExecuteModal() - Parent not visible" ); + SAL_WARN_IF( !pParent->IsInputEnabled(), "vcl", + "Dialog::StartExecuteModal() - Parent input disabled, use another parent to ensure modality!" ); + SAL_WARN_IF( pParent->IsInModalMode(), "vcl", + "Dialog::StartExecuteModal() - Parent already modally disabled, use another parent to ensure modality!" ); + } + } +#endif + + // link all dialogs which are being executed + pSVData->mpWinData->mpExecuteDialogs.push_back(this); + + // stop capturing, in order to have control over the dialog + if (pSVData->mpWinData->mpTrackWin) + pSVData->mpWinData->mpTrackWin->EndTracking(TrackingEventFlags::Cancel); + if (pSVData->mpWinData->mpCaptureWin) + pSVData->mpWinData->mpCaptureWin->ReleaseMouse(); + EnableInput(); + } + + mbInExecute = true; + // no real modality in LibreOfficeKit + if (!bKitActive && bModal) + SetModalInputMode(true); + + // FIXME: no layouting, workaround some clipping issues + ImplAdjustNWFSizes(); + + css::uno::Reference< css::uno::XComponentContext > xContext( + comphelper::getProcessComponentContext()); + bool bForceFocusAndToFront(officecfg::Office::Common::View::NewDocumentHandling::ForceFocusAndToFront::get()); + ShowFlags showFlags = bForceFocusAndToFront ? ShowFlags::ForegroundTask : ShowFlags::NONE; + Show(true, showFlags); + + if (bModal) + pSVData->maAppData.mnModalMode++; + + css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster( + css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW); + css::document::DocumentEvent aObject; + aObject.EventName = "DialogExecute"; + xEventBroadcaster->documentEventOccured(aObject); + if (bModal) + UITestLogger::getInstance().log(Concat2View("Open Modal " + get_id())); + else + UITestLogger::getInstance().log(Concat2View("Open Modeless " + get_id())); + + bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling; + if (comphelper::LibreOfficeKit::isActive() && bTunnelingEnabled) + { + if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + // Dialog boxes don't get the Resize call and they + // can have invalid size at 'created' message above. + // If there is no difference, the client should detect it and ignore us, + // otherwise, this should make sure that the window has the correct size. + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("size", GetSizePixel().toString()); + aItems.emplace_back("unique_id", this->get_id().toUtf8()); + pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems); + } + } + + return true; +} + +void Dialog::ImplEndExecuteModal() +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maAppData.mnModalMode--; +} + +short Dialog::Execute() +{ + VclPtr<vcl::Window> xWindow = this; + + mbInSyncExecute = true; + comphelper::ScopeGuard aGuard([&]() { + mbInSyncExecute = false; + }); + + if ( !ImplStartExecute() ) + return 0; + + // Yield util EndDialog is called or dialog gets destroyed + // (the latter should not happen, but better safe than sorry + while ( !xWindow->isDisposed() && mbInExecute && !Application::IsQuit() ) + Application::Yield(); + + ImplEndExecuteModal(); +#ifdef DBG_UTIL + assert (!mpDialogParent || !mpDialogParent->isDisposed()); +#endif + if ( !xWindow->isDisposed() ) + xWindow.clear(); + else + { + OSL_FAIL( "Dialog::Execute() - Dialog destroyed in Execute()" ); + } + + assert(mpDialogImpl); + + if (mpDialogImpl) + { + tools::Long nRet = mpDialogImpl->mnResult; + mpDialogImpl->mnResult = -1; + + return static_cast<short>(nRet); + } + else + { + SAL_WARN( "vcl", "Dialog::Execute() : missing mpDialogImpl " ); + return 0; + } +} + +// virtual +bool Dialog::StartExecuteAsync( VclAbstractDialog::AsyncContext &rCtx ) +{ + const bool bModal = GetType() != WindowType::MODELESSDIALOG; + if (!ImplStartExecute()) + { + rCtx.mxOwner.disposeAndClear(); + rCtx.mxOwnerDialogController.reset(); + rCtx.mxOwnerSelf.reset(); + return false; + } + + mpDialogImpl->maEndCtx = rCtx; + mpDialogImpl->mbStartedModal = bModal; + + return true; +} + +void Dialog::RemoveFromDlgList() +{ + ImplSVData* pSVData = ImplGetSVData(); + auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs; + + // remove dialog from the list of dialogs which are being executed + std::erase_if(rExecuteDialogs, [this](VclPtr<Dialog>& dialog){ return dialog.get() == this; }); +} + +void Dialog::EndDialog( tools::Long nResult ) +{ + if (!mbInExecute || isDisposed()) + return; + + const bool bModal = GetType() != WindowType::MODELESSDIALOG; + + Hide(); + + if (comphelper::LibreOfficeKit::isActive()) + { + if(const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + if (mpDialogImpl->m_bLOKTunneling) + pNotifier->notifyWindow(GetLOKWindowId(), "close"); + ReleaseLOKNotifier(); + } + } + + if (bModal) + { + SetModalInputMode(false); + + RemoveFromDlgList(); + + // set focus to previous modal dialog if it is modal for + // the same frame parent (or NULL) + ImplSVData* pSVData = ImplGetSVData(); + if (!pSVData->mpWinData->mpExecuteDialogs.empty()) + { + VclPtr<Dialog> pPrevious = pSVData->mpWinData->mpExecuteDialogs.back(); + + vcl::Window* pFrameParent = ImplGetFrameWindow()->ImplGetParent(); + vcl::Window* pPrevFrameParent = pPrevious->ImplGetFrameWindow()? pPrevious->ImplGetFrameWindow()->ImplGetParent(): nullptr; + if( ( !pFrameParent && !pPrevFrameParent ) || + ( pFrameParent && pPrevFrameParent && pFrameParent->ImplGetFrame() == pPrevFrameParent->ImplGetFrame() ) + ) + { + pPrevious->GrabFocus(); + } + } + } + + mpDialogImpl->mnResult = nResult; + + if ( mpDialogImpl->mbStartedModal ) + ImplEndExecuteModal(); + + // coverity[check_after_deref] - ImplEndExecuteModal might trigger destruction of mpDialogImpl + if ( mpDialogImpl && mpDialogImpl->maEndCtx.isSet() ) + { + auto fn = std::move(mpDialogImpl->maEndCtx.maEndDialogFn); + // std::move leaves maEndDialogFn in a valid state with unspecified + // value. For the SwSyncBtnDlg case gcc and msvc left maEndDialogFn + // unset, but clang left maEndDialogFn at its original value, keeping + // an extra reference to the DialogController in its lambda giving + // an inconsistent lifecycle for the dialog. Force it to be unset. + mpDialogImpl->maEndCtx.maEndDialogFn = nullptr; + fn(nResult); + } + + if ( mpDialogImpl && mpDialogImpl->mbStartedModal ) + { + mpDialogImpl->mbStartedModal = false; + mpDialogImpl->mnResult = -1; + } + mbInExecute = false; + + if ( mpDialogImpl ) + { + // Destroy ourselves (if we have a context with VclPtr owner) + std::shared_ptr<weld::DialogController> xOwnerDialogController = std::move(mpDialogImpl->maEndCtx.mxOwnerDialogController); + std::shared_ptr<weld::Dialog> xOwnerSelf = std::move(mpDialogImpl->maEndCtx.mxOwnerSelf); + mpDialogImpl->maEndCtx.mxOwner.disposeAndClear(); + xOwnerDialogController.reset(); + xOwnerSelf.reset(); + } +} + +namespace vcl +{ + void EndAllDialogs( vcl::Window const * pParent ) + { + ImplSVData* pSVData = ImplGetSVData(); + auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs; + + for (auto it = rExecuteDialogs.rbegin(); it != rExecuteDialogs.rend(); ++it) + { + if (!pParent || pParent->IsWindowOrChild(*it, true)) + { + (*it)->EndDialog(); + (*it)->PostUserEvent(Link<void*, void>()); + } + } + } + + void EnableDialogInput(vcl::Window* pWindow) + { + if (Dialog* pDialog = dynamic_cast<Dialog*>(pWindow)) + { + pDialog->EnableInput(); + } + } + + void CloseTopLevel(vcl::Window* pWindow) + { + if (Dialog* pDialog = dynamic_cast<Dialog*>(pWindow)) + pDialog->Close(); + else if (FloatingWindow* pFloatWin = dynamic_cast<FloatingWindow*>(pWindow)) + pFloatWin->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll); + } +} + +void Dialog::SetModalInputMode( bool bModal ) +{ + if ( bModal == mbModalMode ) + return; + + ImplGetFrame()->SetModal(bModal); + + if (GetParent()) + { + SalFrame* pFrame = GetParent()->ImplGetFrame(); + pFrame->NotifyModalHierarchy(bModal); + } + + ImplSetModalInputMode(bModal); +} + +void Dialog::ImplSetModalInputMode( bool bModal ) +{ + if ( bModal == mbModalMode ) + return; + + // previously Execute()'d dialog - the one below the top-most one + VclPtr<Dialog> pPrevious; + ImplSVData* pSVData = ImplGetSVData(); + auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs; + if (rExecuteDialogs.size() > 1) + pPrevious = rExecuteDialogs[rExecuteDialogs.size() - 2]; + + mbModalMode = bModal; + if ( bModal ) + { + // Disable the prev Modal Dialog, because our dialog must close at first, + // before the other dialog can be closed (because the other dialog + // is on stack since our dialog returns) + if (pPrevious && !pPrevious->IsWindowOrChild(this, true)) + pPrevious->EnableInput(false, this); + + // determine next overlap dialog parent + vcl::Window* pParent = GetParent(); + if ( pParent ) + { + // #103716# dialogs should always be modal to the whole frame window + // #115933# disable the whole frame hierarchy, useful if our parent + // is a modeless dialog + mpDialogParent = pParent->mpWindowImpl->mpFrameWindow; + mpDialogParent->IncModalCount(); + } + } + else + { + if ( mpDialogParent ) + { + // #115933# re-enable the whole frame hierarchy again (see above) + // note that code in getfocus assures that we do not accidentally enable + // windows that were disabled before + mpDialogParent->DecModalCount(); + } + + // Enable the prev Modal Dialog + if (pPrevious && !pPrevious->IsWindowOrChild(this, true)) + { + pPrevious->EnableInput(true, this); + + // ensure continued modality of prev dialog + // do not change modality counter + + // #i119994# need find the last modal dialog before reactive it + if (pPrevious->IsModalInputMode() || !pPrevious->IsWindowOrChild(this, true)) + { + pPrevious->ImplSetModalInputMode(false); + pPrevious->ImplSetModalInputMode(true); + } + } + } +} + +vcl::Window* Dialog::GetFirstControlForFocus() +{ + vcl::Window* pFocusControl = nullptr; + vcl::Window* pFirstOverlapWindow = ImplGetFirstOverlapWindow(); + + // find focus control, even if the dialog has focus + if (!HasFocus() && pFirstOverlapWindow && pFirstOverlapWindow->mpWindowImpl) + { + // prefer a child window which had focus before + pFocusControl = ImplGetFirstOverlapWindow()->mpWindowImpl->mpLastFocusWindow; + // find the control out of the dialog control + if ( pFocusControl ) + pFocusControl = ImplFindDlgCtrlWindow( pFocusControl ); + } + // no control had the focus before or the control is not + // part of the tab-control, now give focus to the + // first control in the tab-control + if ( !pFocusControl || + !(pFocusControl->GetStyle() & WB_TABSTOP) || + !isVisibleInLayout(pFocusControl) || + !isEnabledInLayout(pFocusControl) || !pFocusControl->IsInputEnabled() ) + { + pFocusControl = ImplGetDlgWindow( 0, GetDlgWindowType::First ); + } + + return pFocusControl; +} + +void Dialog::GrabFocusToFirstControl() +{ + vcl::Window* pFocusControl = GetFirstControlForFocus(); + if ( pFocusControl ) + pFocusControl->ImplControlFocus( GetFocusFlags::Init ); +} + +void Dialog::GetDrawWindowBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder, sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const +{ + ScopedVclPtrInstance<ImplBorderWindow> aImplWin( static_cast<vcl::Window*>(const_cast<Dialog *>(this)), WB_BORDER|WB_STDWORK, BorderWindowStyle::Overlap ); + aImplWin->GetBorder( rLeftBorder, rTopBorder, rRightBorder, rBottomBorder ); +} + +void Dialog::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags ) +{ + Point aPos = pDev->LogicToPixel( rPos ); + Size aSize = GetSizePixel(); + + Wallpaper aWallpaper = GetBackground(); + if ( !aWallpaper.IsBitmap() ) + ImplInitSettings(); + + pDev->Push(); + pDev->SetMapMode(); + pDev->SetLineColor(); + + if ( aWallpaper.IsBitmap() ) + pDev->DrawBitmapEx( aPos, aSize, aWallpaper.GetBitmap() ); + else + { + pDev->SetFillColor( aWallpaper.GetColor() ); + pDev->DrawRect( tools::Rectangle( aPos, aSize ) ); + } + + if (!( GetStyle() & WB_NOBORDER )) + { + ScopedVclPtrInstance< ImplBorderWindow > aImplWin( this, WB_BORDER|WB_STDWORK, BorderWindowStyle::Overlap ); + aImplWin->SetText( GetText() ); + aImplWin->setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() ); + aImplWin->SetDisplayActive( true ); + aImplWin->InitView(); + + aImplWin->Draw( pDev, aPos ); + } + + pDev->Pop(); +} + +void Dialog::queue_resize(StateChangedType eReason) +{ + if (IsInClose()) + return; + SystemWindow::queue_resize(eReason); +} + +void Dialog::Resize() +{ + SystemWindow::Resize(); + + if (comphelper::LibreOfficeKit::isDialogPainting()) + return; + + bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling; + const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier(); + if (pNotifier && bTunnelingEnabled) + { + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("size", GetSizePixel().toString()); + aItems.emplace_back("unique_id", this->get_id().toUtf8()); + pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems); + } +} + +bool Dialog::set_property(const OUString &rKey, const OUString &rValue) +{ + if (rKey == "border-width") + set_border_width(rValue.toInt32()); + else + return SystemWindow::set_property(rKey, rValue); + return true; +} + +FactoryFunction Dialog::GetUITestFactory() const +{ + return DialogUIObject::create; +} + +IMPL_LINK(Dialog, ResponseHdl, Button*, pButton, void) +{ + auto aFind = mpDialogImpl->maResponses.find(pButton); + if (aFind == mpDialogImpl->maResponses.end()) + return; + short nResponse = aFind->second; + if (nResponse == RET_HELP) + { + vcl::Window* pFocusWin = Application::GetFocusWindow(); + if (!pFocusWin || comphelper::LibreOfficeKit::isActive()) + pFocusWin = pButton; + HelpEvent aEvt(pFocusWin->GetPointerPosPixel(), HelpEventMode::CONTEXT); + pFocusWin->RequestHelp(aEvt); + return; + } + EndDialog(nResponse); +} + +void Dialog::add_button(PushButton* pButton, int response, bool bTransferOwnership) +{ + if (bTransferOwnership) + mpDialogImpl->maOwnedButtons.push_back(pButton); + mpDialogImpl->maResponses[pButton] = response; + switch (pButton->GetType()) + { + case WindowType::PUSHBUTTON: + { + if (!pButton->GetClickHdl().IsSet()) + pButton->SetClickHdl(LINK(this, Dialog, ResponseHdl)); + break; + } + //insist that the response ids match the default actions for those + //widgets, and leave their default handlers in place + case WindowType::OKBUTTON: + assert(mpDialogImpl->get_response(pButton) == RET_OK); + break; + case WindowType::CANCELBUTTON: + assert(mpDialogImpl->get_response(pButton) == RET_CANCEL || mpDialogImpl->get_response(pButton) == RET_CLOSE); + break; + case WindowType::HELPBUTTON: + assert(mpDialogImpl->get_response(pButton) == RET_HELP); + break; + default: + SAL_WARN("vcl.layout", "The type of widget " << + pButton->GetHelpId() << " is currently not handled"); + break; + } +} + +vcl::Window* Dialog::get_widget_for_response(int response) +{ + //copy explicit responses + std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses); + + if (mpActionArea) + { + //add implicit responses + for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (aResponses.find(pChild) != aResponses.end()) + continue; + switch (pChild->GetType()) + { + case WindowType::OKBUTTON: + aResponses[pChild] = RET_OK; + break; + case WindowType::CANCELBUTTON: + aResponses[pChild] = RET_CANCEL; + break; + case WindowType::HELPBUTTON: + aResponses[pChild] = RET_HELP; + break; + default: + break; + } + } + } + + for (const auto& a : aResponses) + { + if (a.second == response) + return a.first; + } + + return nullptr; +} + +int Dialog::get_default_response() const +{ + //copy explicit responses + std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses); + + if (mpActionArea) + { + //add implicit responses + for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (aResponses.find(pChild) != aResponses.end()) + continue; + switch (pChild->GetType()) + { + case WindowType::OKBUTTON: + aResponses[pChild] = RET_OK; + break; + case WindowType::CANCELBUTTON: + aResponses[pChild] = RET_CANCEL; + break; + case WindowType::HELPBUTTON: + aResponses[pChild] = RET_HELP; + break; + default: + break; + } + } + } + + for (const auto& a : aResponses) + { + if (a.first->GetStyle() & WB_DEFBUTTON) + { + return a.second; + } + } + return RET_CANCEL; +} + +void Dialog::set_default_response(int response) +{ + //copy explicit responses + std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses); + + if (mpActionArea) + { + //add implicit responses + for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (aResponses.find(pChild) != aResponses.end()) + continue; + switch (pChild->GetType()) + { + case WindowType::OKBUTTON: + aResponses[pChild] = RET_OK; + break; + case WindowType::CANCELBUTTON: + aResponses[pChild] = RET_CANCEL; + break; + case WindowType::HELPBUTTON: + aResponses[pChild] = RET_HELP; + break; + default: + break; + } + } + } + + for (auto& a : aResponses) + { + if (a.second == response) + { + a.first->SetStyle(a.first->GetStyle() | WB_DEFBUTTON); + a.first->GrabFocus(); + } + else + { + a.first->SetStyle(a.first->GetStyle() & ~WB_DEFBUTTON); + } + } +} + +VclBuilderContainer::VclBuilderContainer() +{ +} + +void VclBuilderContainer::setDeferredProperties() +{ + if (!m_pUIBuilder) + return; + m_pUIBuilder->setDeferredProperties(); +} + +VclBuilderContainer::~VclBuilderContainer() +{ +} + +void Dialog::Activate() +{ + if (GetType() == WindowType::MODELESSDIALOG) + { + css::uno::Reference< css::uno::XComponentContext > xContext( + comphelper::getProcessComponentContext() ); + css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster(css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW); + css::document::DocumentEvent aObject; + aObject.EventName = "ModelessDialogVisible"; + xEventBroadcaster->documentEventOccured(aObject); + } + SystemWindow::Activate(); +} + +void Dialog::Command(const CommandEvent& rCEvt) +{ + if (mpDialogImpl && mpDialogImpl->m_aPopupMenuHdl.Call(rCEvt)) + return; + SystemWindow::Command(rCEvt); +} + +struct TopLevelWindowLockerImpl +{ + std::stack<std::vector<VclPtr<vcl::Window>>> m_aBusyStack; +}; + +TopLevelWindowLocker::TopLevelWindowLocker() + : m_xImpl(std::make_unique<TopLevelWindowLockerImpl>()) +{ +} + +void TopLevelWindowLocker::incBusy(const weld::Widget* pIgnore) +{ + // lock any toplevel windows from being closed until busy is over + std::vector<VclPtr<vcl::Window>> aTopLevels; + vcl::Window *pTopWin = Application::GetFirstTopLevelWindow(); + while (pTopWin) + { + vcl::Window* pCandidate = pTopWin; + if (pCandidate->GetType() == WindowType::BORDERWINDOW) + pCandidate = pCandidate->GetWindow(GetWindowType::FirstChild); + // tdf#125266 ignore HelpTextWindows + if (pCandidate && + pCandidate->GetType() != WindowType::HELPTEXTWINDOW && + pCandidate->GetType() != WindowType::FLOATINGWINDOW && + pCandidate->GetFrameWeld() != pIgnore) + { + aTopLevels.push_back(pCandidate); + } + pTopWin = Application::GetNextTopLevelWindow(pTopWin); + } + for (auto& a : aTopLevels) + { + a->IncModalCount(); + a->ImplGetFrame()->NotifyModalHierarchy(true); + } + m_xImpl->m_aBusyStack.push(aTopLevels); +} + +void TopLevelWindowLocker::decBusy() +{ + // unlock locked toplevel windows from being closed now busy is over + for (auto& a : m_xImpl->m_aBusyStack.top()) + { + if (a->isDisposed()) + continue; + a->DecModalCount(); + a->ImplGetFrame()->NotifyModalHierarchy(false); + } + m_xImpl->m_aBusyStack.pop(); +} + +bool TopLevelWindowLocker::isBusy() const +{ + return !m_xImpl->m_aBusyStack.empty(); +} + +TopLevelWindowLocker::~TopLevelWindowLocker() +{ +} + +void Dialog::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + SystemWindow::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("title", GetText()); + if (vcl::Window* pActionArea = get_action_area()) + { + if (!pActionArea->IsVisible()) + rJsonWriter.put("collapsed", true); + } + + OUString sDialogId = GetHelpId(); + sal_Int32 nStartPos = sDialogId.lastIndexOf('/'); + nStartPos = nStartPos >= 0 ? nStartPos + 1 : 0; + rJsonWriter.put("dialogid", sDialogId.copy(nStartPos)); + + { + auto aResponses = rJsonWriter.startArray("responses"); + for (const auto& rResponse : mpDialogImpl->maResponses) + { + auto aResponse = rJsonWriter.startStruct(); + rJsonWriter.put("id", rResponse.first->get_id()); + rJsonWriter.put("response", rResponse.second); + } + } + + vcl::Window* pFocusControl = GetFirstControlForFocus(); + if (pFocusControl) + rJsonWriter.put("init_focus_id", pFocusControl->get_id()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |