diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/win | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/win')
138 files changed, 29200 insertions, 0 deletions
diff --git a/vcl/win/app/fileregistration.cxx b/vcl/win/app/fileregistration.cxx new file mode 100644 index 0000000000..ec7ccbcee2 --- /dev/null +++ b/vcl/win/app/fileregistration.cxx @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#if !defined(NTDDI_VERSION) || NTDDI_VERSION < NTDDI_WIN8 +#define NTDDI_VERSION NTDDI_WIN8 // needed for IApplicationActivationManager +#endif + +#include <sal/config.h> + +#include <comphelper/scopeguard.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <officecfg/Office/Common.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/abstdlg.hxx> +#include <vcl/fileregistration.hxx> + +#include <strings.hrc> +#include <svdata.hxx> + +#include <utility> + +#include <prewin.h> +#include <Shobjidl.h> +#include <systools/win32/comtools.hxx> +#include <versionhelpers.h> +#include <postwin.h> + +#define MAX_LONG_PATH 32767 + +namespace vcl::fileregistration +{ +static void LaunchModernSettingsDialogDefaultApps() +{ + sal::systools::COMReference<IApplicationActivationManager> pIf( + CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER); + + DWORD pid; + HRESULT hr = pIf->ActivateApplication(L"windows.immersivecontrolpanel_cw5n1h2txyewy" + L"!microsoft.windows.immersivecontrolpanel", + L"page=SettingsPageAppsDefaults", AO_NONE, &pid); + if (SUCCEEDED(hr)) + { + // Do not check error because we could at least open + // the "Default apps" setting. + pIf->ActivateApplication(L"windows.immersivecontrolpanel_cw5n1h2txyewy" + L"!microsoft.windows.immersivecontrolpanel", + L"page=SettingsPageAppsDefaults" + L"&target=SettingsPageAppsDefaultsDefaultAppsListView", + AO_NONE, &pid); + } +} + +static HRESULT +IsPathDefaultForClass(sal::systools::COMReference<IApplicationAssociationRegistration>& pAAR, + LPCWSTR aClassName, LPCWSTR progID) +{ + // Make sure the Prog ID matches what we have + sal::systools::CoTaskMemAllocated<wchar_t> registeredApp; + HRESULT hr + = pAAR->QueryCurrentDefault(aClassName, AT_FILEEXTENSION, AL_EFFECTIVE, ®isteredApp); + if (SUCCEEDED(hr)) + { + if (wcsnicmp(registeredApp, progID, wcslen(progID)) == 0) + hr = S_OK; + else + hr = S_FALSE; + } + + return hr; +} + +static bool IsDefaultAppInstalledInReg() +{ + const wchar_t* keyPath = L"SOFTWARE\\LibreOffice\\UNO\\InstallPath"; + + WCHAR szRegPath[MAX_LONG_PATH]; + DWORD cbData = static_cast<DWORD>(MAX_LONG_PATH * sizeof(WCHAR)); + auto rc = RegGetValueW(HKEY_LOCAL_MACHINE, keyPath, nullptr, RRF_RT_REG_SZ, nullptr, + static_cast<PVOID>(szRegPath), &cbData); + if (rc != ERROR_SUCCESS) + return false; + + WCHAR szProcPath[MAX_LONG_PATH]; + if (!GetModuleFileNameW(nullptr, szProcPath, MAX_LONG_PATH)) + return false; + + WCHAR szFullProcPath[MAX_LONG_PATH]; + if (!GetFullPathNameW(szProcPath, MAX_LONG_PATH, szFullProcPath, nullptr)) + return false; + + if (!GetLongPathNameW(szFullProcPath, szFullProcPath, MAX_LONG_PATH)) + return false; + + if (!GetLongPathNameW(szRegPath, szRegPath, MAX_LONG_PATH)) + return false; + + if (wcslen(szRegPath) > 0 && wcsstr(szFullProcPath, szRegPath) != nullptr) + return true; + + return false; +} + +void LaunchRegistrationUI() +{ + try + { + sal::systools::CoInitializeGuard aGuard(COINIT_APARTMENTTHREADED); + if (IsWindows10OrGreater()) + { + LaunchModernSettingsDialogDefaultApps(); + } + else + { + sal::systools::COMReference<IApplicationAssociationRegistrationUI> pIf( + CLSID_ApplicationAssociationRegistrationUI, nullptr, CLSCTX_INPROC_SERVER); + + // LaunchAdvancedAssociationUI only works for applications registered under + // Software\RegisteredApplications. See scp2/source/ooo/registryitem_ooo.scp + const OUString expanded = Translate::ExpandVariables("%PRODUCTNAME %PRODUCTVERSION"); + pIf->LaunchAdvancedAssociationUI(o3tl::toW(expanded.getStr())); + } + } + catch (...) + { + // Just ignore any error here: this is not something we need to make sure to succeed + } +} + +void CheckFileExtRegistration(weld::Window* pDialogParent) +{ + if (!officecfg::Office::Common::Misc::PerformFileExtCheck::get()) + return; + + if (!IsDefaultAppInstalledInReg()) + return; + + sal::systools::CoInitializeGuard aGuard(COINIT_APARTMENTTHREADED, false, + sal::systools::CoInitializeGuard::WhenFailed::NoThrow); + sal::systools::COMReference<IApplicationAssociationRegistration> pAAR; + try + { + pAAR.CoCreateInstance(CLSID_ApplicationAssociationRegistration, nullptr, + CLSCTX_INPROC_SERVER); + } + catch (...) + { + // Just return on any error here: this is not something we need to make sure to succeed + return; + } + + static const std::pair<LPCWSTR, LPCWSTR> formats[] = { + { L".odp", L"LibreOffice.ImpressDocument.1" }, + { L".odt", L"LibreOffice.WriterDocument.1" }, + { L".ods", L"LibreOffice.CalcDocument.1" }, + }; + OUString aNonDefaults; + + for (const auto & [ szExt, szProgId ] : formats) + { + if (IsPathDefaultForClass(pAAR, szExt, szProgId) == S_FALSE) + aNonDefaults += OUString::Concat(o3tl::toU(szExt)) + "\n"; + } + + if (!aNonDefaults.isEmpty()) + { + OUString aMsg(VclResId(STR_FILEEXT_NONDEFAULT_ASK_MSG).replaceFirst("$1", aNonDefaults)); + + VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create(); + ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateFileExtCheckDialog( + pDialogParent, VclResId(STR_FILEEXT_NONDEFAULT_ASK_TITLE), aMsg)); + pDlg->Execute(); + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/win/app/saldata.cxx b/vcl/win/app/saldata.cxx new file mode 100644 index 0000000000..31fa661636 --- /dev/null +++ b/vcl/win/app/saldata.cxx @@ -0,0 +1,78 @@ +/* -*- 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 <svsys.h> +#include <rtl/tencinfo.h> +#include <vcl/svapp.hxx> + +#include <win/saldata.hxx> + +rtl_TextEncoding ImplSalGetSystemEncoding() +{ + static const UINT nOldAnsiCodePage = 0; + static rtl_TextEncoding eEncoding = RTL_TEXTENCODING_MS_1252; + + UINT nAnsiCodePage = GetACP(); + if ( nAnsiCodePage != nOldAnsiCodePage ) + { + rtl_TextEncoding nEnc + = rtl_getTextEncodingFromWindowsCodePage(nAnsiCodePage); + if (nEnc != RTL_TEXTENCODING_DONTKNOW) + eEncoding = nEnc; + } + + return eEncoding; +} + +OUString ImplSalGetUniString(const char* pStr, sal_Int32 const nLen) +{ + return OUString( pStr, (-1 == nLen) ? strlen(pStr) : nLen, + ImplSalGetSystemEncoding(), + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_DEFAULT | + RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_DEFAULT | + RTL_TEXTTOUNICODE_FLAGS_INVALID_DEFAULT ); +} + +int ImplSalWICompareAscii( const wchar_t* pStr1, const char* pStr2 ) +{ + int nRet; + char c2; + do + { + // change to LowerCase if the char is between 'A' and 'Z' + wchar_t c1 = *pStr1; + c2 = *pStr2; + if ( (c1 >= 65) && (c1 <= 90) ) + c1 += 32; + if ( (c2 >= 65) && (c2 <= 90) ) + c2 += 32; + nRet = static_cast<sal_Int32>(c1)- static_cast<sal_Int32>(static_cast<unsigned char>(c2)); + if ( nRet != 0 ) + break; + + pStr1++; + pStr2++; + } + while ( c2 ); + + return nRet; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/app/salinfo.cxx b/vcl/win/app/salinfo.cxx new file mode 100644 index 0000000000..23d542d23b --- /dev/null +++ b/vcl/win/app/salinfo.cxx @@ -0,0 +1,177 @@ +/* -*- 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 <svsys.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <sal/log.hxx> +#include <vcl/window.hxx> + +#include <win/salsys.h> +#include <win/salframe.h> +#include <win/salinst.h> +#include <win/saldata.hxx> + +#include <svdata.hxx> + +#include <unordered_map> + +SalSystem* WinSalInstance::CreateSalSystem() +{ + return new WinSalSystem(); +} + +WinSalSystem::~WinSalSystem() +{ +} + +static BOOL CALLBACK ImplEnumMonitorProc( HMONITOR hMonitor, + HDC hDC, + LPRECT lpRect, + LPARAM dwData ) +{ + WinSalSystem* pSys = reinterpret_cast<WinSalSystem*>(dwData); + return pSys->handleMonitorCallback( reinterpret_cast<sal_IntPtr>(hMonitor), + reinterpret_cast<sal_IntPtr>(hDC), + reinterpret_cast<sal_IntPtr>(lpRect) ); +} + +bool WinSalSystem::handleMonitorCallback( sal_IntPtr hMonitor, sal_IntPtr, sal_IntPtr ) +{ + MONITORINFOEXW aInfo; + aInfo.cbSize = sizeof( aInfo ); + if( GetMonitorInfoW( reinterpret_cast<HMONITOR>(hMonitor), &aInfo ) ) + { + aInfo.szDevice[CCHDEVICENAME-1] = 0; + OUString aDeviceName( o3tl::toU(aInfo.szDevice) ); + std::map< OUString, unsigned int >::const_iterator it = + m_aDeviceNameToMonitor.find( aDeviceName ); + if( it != m_aDeviceNameToMonitor.end() ) + { + DisplayMonitor& rMon( m_aMonitors[ it->second ] ); + rMon.m_aArea = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( aInfo.rcMonitor.left, + aInfo.rcMonitor.top ), + AbsoluteScreenPixelSize( aInfo.rcMonitor.right - aInfo.rcMonitor.left, + aInfo.rcMonitor.bottom - aInfo.rcMonitor.top ) ); + if( (aInfo.dwFlags & MONITORINFOF_PRIMARY) != 0 ) + m_nPrimary = it->second; + } + } + return true; +} + +void WinSalSystem::clearMonitors() +{ + m_aMonitors.clear(); + m_nPrimary = 0; +} + +bool WinSalSystem::initMonitors() +{ + if( m_aMonitors.size() > 0 ) + return true; + + int nMonitors = GetSystemMetrics( SM_CMONITORS ); + if( nMonitors == 1 ) + { + int w = GetSystemMetrics( SM_CXSCREEN ); + int h = GetSystemMetrics( SM_CYSCREEN ); + AbsoluteScreenPixelRectangle aRect(AbsoluteScreenPixelPoint(), AbsoluteScreenPixelSize( w, h )); + m_aMonitors.push_back( DisplayMonitor( OUString(), aRect ) ); + m_aDeviceNameToMonitor[ OUString() ] = 0; + m_nPrimary = 0; + } + else + { + DISPLAY_DEVICEW aDev; + aDev.cb = sizeof( aDev ); + DWORD nDevice = 0; + std::unordered_map< OUString, int > aDeviceStringCount; + while( EnumDisplayDevicesW( nullptr, nDevice++, &aDev, 0 ) ) + { + if( (aDev.StateFlags & DISPLAY_DEVICE_ACTIVE) + && !(aDev.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) ) // sort out non/disabled monitors + { + aDev.DeviceName[31] = 0; + aDev.DeviceString[127] = 0; + OUString aDeviceName( o3tl::toU(aDev.DeviceName) ); + OUString aDeviceString( o3tl::toU(aDev.DeviceString) ); + aDeviceStringCount[ aDeviceString ]++; + m_aDeviceNameToMonitor[ aDeviceName ] = m_aMonitors.size(); + m_aMonitors.push_back( DisplayMonitor( aDeviceString, + AbsoluteScreenPixelRectangle() ) ); + } + } + HDC aDesktopRC = GetDC( nullptr ); + EnumDisplayMonitors( aDesktopRC, nullptr, ImplEnumMonitorProc, reinterpret_cast<LPARAM>(this) ); + + // append monitor numbers to name strings + std::unordered_map< OUString, int > aDevCount( aDeviceStringCount ); + unsigned int nMonitorCount = m_aMonitors.size(); + for( unsigned int i = 0; i < nMonitorCount; i++ ) + { + const OUString& rDev( m_aMonitors[i].m_aName ); + if( aDeviceStringCount[ rDev ] > 1 ) + { + int nInstance = aDeviceStringCount[ rDev ] - (-- aDevCount[ rDev ] ); + m_aMonitors[ i ].m_aName = rDev + " (" + OUString::number( nInstance ) + ")"; + } + } + ReleaseDC(nullptr, aDesktopRC); + } + + return m_aMonitors.size() > 0; +} + +unsigned int WinSalSystem::GetDisplayScreenCount() +{ + initMonitors(); + return m_aMonitors.size(); +} + +unsigned int WinSalSystem::GetDisplayBuiltInScreen() +{ + initMonitors(); + return m_nPrimary; +} + +AbsoluteScreenPixelRectangle WinSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen ) +{ + initMonitors(); + if (nScreen >= m_aMonitors.size()) + { + SAL_WARN("vcl", "Requested screen size/pos for screen #" + << nScreen << ", but only " << m_aMonitors.size() << " screens found."); + assert(false); + return AbsoluteScreenPixelRectangle(); + } + return m_aMonitors[nScreen].m_aArea; +} + +int WinSalSystem::ShowNativeMessageBox(const OUString& rTitle, const OUString& rMessage) +{ + ImplHideSplash(); + return MessageBoxW( + nullptr, + o3tl::toW(rMessage.getStr()), + o3tl::toW(rTitle.getStr()), + MB_TASKMODAL | MB_SETFOREGROUND | MB_ICONWARNING | MB_DEFBUTTON1); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/app/salinst.cxx b/vcl/win/app/salinst.cxx new file mode 100644 index 0000000000..eb4adb853e --- /dev/null +++ b/vcl/win/app/salinst.cxx @@ -0,0 +1,1008 @@ +/* -*- 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 <string.h> +#include <svsys.h> +#include <process.h> + +#include <osl/conditn.hxx> +#include <osl/file.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/time.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/solarmutex.hxx> +#include <comphelper/windowserrorstring.hxx> +#include <com/sun/star/uno/Reference.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <dndhelper.hxx> +#include <vcl/inputtypes.hxx> +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/timer.hxx> +#include <vclpluginapi.h> + +#include <win/dnd_source.hxx> +#include <win/dnd_target.hxx> +#include <win/wincomp.hxx> +#include <win/salids.hrc> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salframe.h> +#include <win/salobj.h> +#include <win/saltimer.h> +#include <win/salbmp.h> +#include <win/winlayout.hxx> + +#include <config_features.h> +#include <vcl/skia/SkiaHelper.hxx> +#if HAVE_FEATURE_SKIA +#include <config_skia.h> +#include <skia/salbmp.hxx> +#include <skia/win/gdiimpl.hxx> +#endif + +#include <salsys.hxx> + +#include <desktop/crashreport.hxx> + +#include <prewin.h> + +#include <gdiplus.h> +#include <shlobj.h> + +#include <postwin.h> + +static LRESULT CALLBACK SalComWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam ); + +class SalYieldMutex : public comphelper::SolarMutex +{ +public: // for ImplSalYield() and ImplSalYieldMutexAcquireWithWait() + osl::Condition m_condition; /// for MsgWaitForMultipleObjects() + +protected: + virtual void doAcquire( sal_uInt32 nLockCount ) override; + virtual sal_uInt32 doRelease( bool bUnlockAll ) override; + + static void BeforeReleaseHandler(); + +public: + explicit SalYieldMutex(); + + virtual bool IsCurrentThread() const override; + virtual bool tryToAcquire() override; +}; + +SalYieldMutex::SalYieldMutex() +{ + SetBeforeReleaseHandler( &SalYieldMutex::BeforeReleaseHandler ); +} + +void SalYieldMutex::BeforeReleaseHandler() +{ + OpenGLContext::prepareForYield(); + + if ( GetSalData()->mnAppThreadId != GetCurrentThreadId() ) + { + // If we don't call these message, the Output from the + // Java clients doesn't come in the right order + GdiFlush(); + } +} + +/// note: while VCL is fully up and running (other threads started and +/// before shutdown), the main thread must acquire SolarMutex only via +/// this function to avoid deadlock +void SalYieldMutex::doAcquire( sal_uInt32 nLockCount ) +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst && pInst->IsMainThread() ) + { + if ( pInst->m_nNoYieldLock ) + return; + // tdf#96887 If this is the main thread, then we must wait for two things: + // - the yield mutex being unlocked + // - SendMessage() being triggered + // This can nicely be done using MsgWaitForMultipleObjects, which is called in + // m_condition.wait(). The 2nd one is + // needed because if we don't reschedule, then we create deadlocks if a + // Window's create/destroy is called via SendMessage() from another thread. + // Have a look at the osl_waitCondition implementation for more info. + do { + // Calling Condition::reset frequently turns out to be a little expensive, + // and the vast majority of the time there is no contention, so first + // try just acquiring the mutex. + if (m_aMutex.tryToAcquire()) + break; + // reset condition *before* acquiring! + m_condition.reset(); + if (m_aMutex.tryToAcquire()) + break; + // wait for SalYieldMutex::release() to set the condition + osl::Condition::Result res = m_condition.wait(); + assert(osl::Condition::Result::result_ok == res); + (void) res; + } + while ( true ); + } + else + m_aMutex.acquire(); + ++m_nCount; + --nLockCount; + + comphelper::SolarMutex::doAcquire( nLockCount ); +} + +sal_uInt32 SalYieldMutex::doRelease( const bool bUnlockAll ) +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst && pInst->m_nNoYieldLock && pInst->IsMainThread() ) + return 1; + + sal_uInt32 nCount = comphelper::SolarMutex::doRelease( bUnlockAll ); + // wake up ImplSalYieldMutexAcquireWithWait() after release + if ( 0 == m_nCount ) + m_condition.set(); + return nCount; +} + +bool SalYieldMutex::tryToAcquire() +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst ) + { + if ( pInst->m_nNoYieldLock && pInst->IsMainThread() ) + return true; + else + return comphelper::SolarMutex::tryToAcquire(); + } + else + return false; +} + +void ImplSalYieldMutexAcquireWithWait( sal_uInt32 nCount ) +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst ) + pInst->GetYieldMutex()->acquire( nCount ); +} + +bool ImplSalYieldMutexTryToAcquire() +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + return pInst && pInst->GetYieldMutex()->tryToAcquire(); +} + +void ImplSalYieldMutexRelease() +{ + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst ) + { + GdiFlush(); + pInst->GetYieldMutex()->release(); + } +} + +bool SalYieldMutex::IsCurrentThread() const +{ + if ( !GetSalData()->mpInstance->m_nNoYieldLock ) + return SolarMutex::IsCurrentThread(); + else + return GetSalData()->mpInstance->IsMainThread(); +} + +void SalData::initKeyCodeMap() +{ + UINT nKey; + #define initKey( a, b )\ + nKey = LOWORD( VkKeyScanW( a ) );\ + if( nKey < 0xffff )\ + maVKMap[ nKey ] = b; + + maVKMap.clear(); + + initKey( L'+', KEY_ADD ); + initKey( L'-', KEY_SUBTRACT ); + initKey( L'*', KEY_MULTIPLY ); + initKey( L'/', KEY_DIVIDE ); + initKey( L'.', KEY_POINT ); + initKey( L',', KEY_COMMA ); + initKey( L'<', KEY_LESS ); + initKey( L'>', KEY_GREATER ); + initKey( L'=', KEY_EQUAL ); + initKey( L'~', KEY_TILDE ); + initKey( L'`', KEY_QUOTELEFT ); + initKey( L'[', KEY_BRACKETLEFT ); + initKey( L']', KEY_BRACKETRIGHT ); + initKey( L';', KEY_SEMICOLON ); + initKey( L'\'', KEY_QUOTERIGHT ); + initKey( L'}', KEY_RIGHTCURLYBRACKET ); + initKey( L'#', KEY_NUMBERSIGN); + initKey( L':', KEY_COLON ); +} + +// SalData + +SalData::SalData() + : sal::systools::CoInitializeGuard(COINIT_APARTMENTTHREADED, false, + sal::systools::CoInitializeGuard::WhenFailed::NoThrow) + // put main thread in Single Threaded Apartment (STA) +{ + mhInst = nullptr; // default instance handle + mnCmdShow = 0; // default frame show style + mhDitherPal = nullptr; // dither palette + mhDitherDIB = nullptr; // dither memory handle + mpDitherDIB = nullptr; // dither memory + mpDitherDIBData = nullptr; // beginning of DIB data + mpDitherDiff = nullptr; // Dither mapping table + mpDitherLow = nullptr; // Dither mapping table + mpDitherHigh = nullptr; // Dither mapping table + mhSalObjMsgHook = nullptr; // hook to get interesting msg for SalObject + mhWantLeaveMsg = nullptr; // window handle, that want a MOUSELEAVE message + mpInstance = nullptr; // pointer of first instance + mpFirstFrame = nullptr; // pointer of first frame + mpFirstObject = nullptr; // pointer of first object window + mpFirstVD = nullptr; // first VirDev + mpFirstPrinter = nullptr; // first printing printer + mh50Bmp = nullptr; // 50% Bitmap + mh50Brush = nullptr; // 50% Brush + int i; + for(i=0; i<MAX_STOCKPEN; i++) + { + maStockPenColorAry[i] = 0; + mhStockPenAry[i] = nullptr; + } + for(i=0; i<MAX_STOCKBRUSH; i++) + { + maStockBrushColorAry[i] = 0; + mhStockBrushAry[i] = nullptr; + } + mnStockPenCount = 0; // count of static pens + mnStockBrushCount = 0; // count of static brushes + mnSalObjWantKeyEvt = 0; // KeyEvent for the SalObj hook + mnCacheDCInUse = 0; // count of CacheDC in use + mbObjClassInit = false; // is SALOBJECTCLASS initialised + mbInPalChange = false; // is in WM_QUERYNEWPALETTE + mnAppThreadId = 0; // Id from Application-Thread + mpFirstIcon = nullptr; // icon cache, points to first icon, NULL if none + mpSharedTempFontItem = nullptr; + mpOtherTempFontItem = nullptr; + mbThemeChanged = false; // true if visual theme was changed: throw away theme handles + mbThemeMenuSupport = false; + + // init with NULL + gdiplusToken = 0; + + initKeyCodeMap(); + + SetSalData( this ); + initNWF(); + + static Gdiplus::GdiplusStartupInput gdiplusStartupInput; + Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); +} + +SalData::~SalData() +{ + deInitNWF(); + SetSalData( nullptr ); + + if (gdiplusToken) + Gdiplus::GdiplusShutdown(gdiplusToken); +} + +bool OSSupportsDarkMode() +{ + bool bRet = false; + if (HMODULE h_ntdll = GetModuleHandleW(L"ntdll.dll")) + { + typedef LONG(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW); + if (auto RtlGetVersion + = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(h_ntdll, "RtlGetVersion"))) + { + RTL_OSVERSIONINFOW vi2{}; + vi2.dwOSVersionInfoSize = sizeof(vi2); + if (RtlGetVersion(&vi2) == 0) + { + if (vi2.dwMajorVersion > 10) + bRet = true; + else if (vi2.dwMajorVersion == 10) + { + if (vi2.dwMinorVersion > 0) + bRet = true; + else if (vi2.dwBuildNumber >= 18362) + bRet = true; + } + } + } + } + return bRet; +} + +namespace { + +enum PreferredAppMode +{ + Default, + AllowDark, + ForceDark, + ForceLight, + Max +}; + +} + +extern "C" { +VCLPLUG_WIN_PUBLIC SalInstance* create_SalInstance() +{ + SalData* pSalData = new SalData(); + + STARTUPINFOW aSI; + aSI.cb = sizeof( aSI ); + GetStartupInfoW( &aSI ); + pSalData->mhInst = GetModuleHandleW( nullptr ); + pSalData->mnCmdShow = aSI.wShowWindow; + + pSalData->mnAppThreadId = GetCurrentThreadId(); + + static bool bSetAllowDarkMode = OSSupportsDarkMode(); // too early to additionally check LibreOffice's config + if (bSetAllowDarkMode) + { + typedef PreferredAppMode(WINAPI* SetPreferredAppMode_t)(PreferredAppMode); + if (HINSTANCE hUxthemeLib = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)) + { + if (auto SetPreferredAppMode = reinterpret_cast<SetPreferredAppMode_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(135)))) + SetPreferredAppMode(AllowDark); + FreeLibrary(hUxthemeLib); + } + } + + // register frame class + WNDCLASSEXW aWndClassEx; + aWndClassEx.cbSize = sizeof( aWndClassEx ); + aWndClassEx.style = CS_OWNDC; + aWndClassEx.lpfnWndProc = SalFrameWndProcW; + aWndClassEx.cbClsExtra = 0; + aWndClassEx.cbWndExtra = SAL_FRAME_WNDEXTRA; + aWndClassEx.hInstance = pSalData->mhInst; + aWndClassEx.hCursor = nullptr; + aWndClassEx.hbrBackground = nullptr; + aWndClassEx.lpszMenuName = nullptr; + aWndClassEx.lpszClassName = SAL_FRAME_CLASSNAMEW; + ImplLoadSalIcon( SAL_RESID_ICON_DEFAULT, aWndClassEx.hIcon, aWndClassEx.hIconSm ); + if ( !RegisterClassExW( &aWndClassEx ) ) + return nullptr; + + aWndClassEx.hIcon = nullptr; + aWndClassEx.hIconSm = nullptr; + aWndClassEx.style |= CS_SAVEBITS; + aWndClassEx.lpszClassName = SAL_SUBFRAME_CLASSNAMEW; + if ( !RegisterClassExW( &aWndClassEx ) ) + return nullptr; + + // shadow effect for popups on XP + aWndClassEx.style |= CS_DROPSHADOW; + aWndClassEx.lpszClassName = SAL_TMPSUBFRAME_CLASSNAMEW; + if ( !RegisterClassExW( &aWndClassEx ) ) + return nullptr; + + aWndClassEx.style = 0; + aWndClassEx.lpfnWndProc = SalComWndProcW; + aWndClassEx.cbWndExtra = 0; + aWndClassEx.lpszClassName = SAL_COM_CLASSNAMEW; + if ( !RegisterClassExW( &aWndClassEx ) ) + return nullptr; + + HWND hComWnd = CreateWindowExW( WS_EX_TOOLWINDOW, SAL_COM_CLASSNAMEW, + L"", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, + pSalData->mhInst, nullptr ); + if ( !hComWnd ) + return nullptr; + + WinSalInstance* pInst = new WinSalInstance; + + // init instance (only one instance in this version !!!) + pSalData->mpInstance = pInst; + pInst->mhInst = pSalData->mhInst; + pInst->mhComWnd = hComWnd; + + // init static GDI Data + ImplInitSalGDI(); + + return pInst; +} +} + +WinSalInstance::WinSalInstance() + : SalInstance(std::make_unique<SalYieldMutex>()) + , mhInst( nullptr ) + , mhComWnd( nullptr ) + , m_nNoYieldLock( 0 ) +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maAppData.mxToolkitName = OUString("win"); + m_bSupportsOpenGL = true; +#if HAVE_FEATURE_SKIA + WinSkiaSalGraphicsImpl::prepareSkia(); +#if SKIA_USE_BITMAP32 + if (SkiaHelper::isVCLSkiaEnabled()) + m_bSupportsBitmap32 = true; +#endif +#endif +} + +WinSalInstance::~WinSalInstance() +{ + ImplFreeSalGDI(); + DestroyWindow( mhComWnd ); +#if HAVE_FEATURE_SKIA + SkiaHelper::cleanup(); +#endif +} + +void WinSalInstance::AfterAppInit() +{ +// (1) Ideally this would be done at the place that creates the thread, but since this thread is normally +// just the default/main thread, that is not possible. +// (2) Don't do this on unix, where it causes tools like pstree on Linux to +// confusingly report soffice.bin as VCL Main instead. + osl_setThreadName("VCL Main"); +} + +static LRESULT ImplSalDispatchMessage( const MSG* pMsg ) +{ + SalData* pSalData = GetSalData(); + if ( pSalData->mpFirstObject && ImplSalPreDispatchMsg( pMsg ) ) + return 0; + LRESULT lResult = DispatchMessageW( pMsg ); + if ( pSalData->mpFirstObject ) + ImplSalPostDispatchMsg( pMsg ); + return lResult; +} + +// probably can't be static, because of SalTimer friend? (static gives C4211) +bool ImplSalYield(const bool bWait, const bool bHandleAllCurrentEvents) +{ + // used to abort further message processing on tick count wraps + static sal_uInt32 nLastTicks = 0; + + // we should never yield in m_nNoYieldLock mode! + const bool bNoYieldLock = (GetSalData()->mpInstance->m_nNoYieldLock > 0); + assert(!bNoYieldLock); + if (bNoYieldLock) + return false; + + MSG aMsg; + bool bWasMsg = false, bWasTimeoutMsg = false; + WinSalTimer* pTimer = static_cast<WinSalTimer*>(ImplGetSVData()->maSchedCtx.mpSalTimer); + + sal_uInt32 nCurTicks = GetTickCount(); + + do + { + if (!PeekMessageW(&aMsg, nullptr, 0, 0, PM_REMOVE)) + break; + + bWasMsg = true; + TranslateMessage(&aMsg); + LRESULT nRet = ImplSalDispatchMessage(&aMsg); + + bWasTimeoutMsg |= (SAL_MSG_TIMER_CALLBACK == aMsg.message) && static_cast<bool>(nRet); + + if (!bHandleAllCurrentEvents) + break; + + if ((aMsg.time > nCurTicks) && (nLastTicks <= nCurTicks || aMsg.time < nLastTicks)) + break; + } + while( true ); + + // 0ms timeouts are handled out-of-bounds to prevent busy-locking the + // event loop with timeout messages. + // We ensure we never handle more than one timeout per call. + // This way we'll always process a normal system message. + if ( !bWasTimeoutMsg && pTimer && pTimer->IsDirectTimeout() ) + { + pTimer->ImplHandleElapsedTimer(); + bWasMsg = true; + } + + nLastTicks = nCurTicks; + + if ( bWait && !bWasMsg ) + { + switch (GetMessageW(&aMsg, nullptr, 0, 0)) + { + case -1: + SAL_WARN("vcl.schedule", "GetMessageW failed: " << WindowsErrorString(GetLastError())); + // should we std::abort() / SalAbort here? + break; + case 0: + SAL_INFO("vcl.schedule", "GetMessageW received WM_QUIT while waiting"); + break; + default: + bWasMsg = true; + TranslateMessage(&aMsg); + ImplSalDispatchMessage(&aMsg); + break; + } + } + + // If we enabled ForceRealTimer mode skipping our direct timeout processing, + // mainly because some Windows API call spawns its own nested message loop, + // switch back to our own processing (like after window resize or move) + if ( pTimer ) + pTimer->SetForceRealTimer( false ); + + return bWasMsg; +} + +bool WinSalInstance::IsMainThread() const +{ + const SalData* pSalData = GetSalData(); + return pSalData->mnAppThreadId == GetCurrentThreadId(); +} + +bool WinSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents) +{ + bool bDidWork = false; + SolarMutexReleaser aReleaser; + if ( !IsMainThread() ) + { + bDidWork = SendMessageW( mhComWnd, SAL_MSG_THREADYIELD, + WPARAM(false), static_cast<LPARAM>(bHandleAllCurrentEvents) ); + if ( !bDidWork && bWait ) + { + maWaitingYieldCond.reset(); + maWaitingYieldCond.wait(); + bDidWork = true; + } + } + else + { + bDidWork = ImplSalYield( bWait, bHandleAllCurrentEvents ); + if ( bDidWork ) + maWaitingYieldCond.set(); + } + + return bDidWork; +} + +#define CASE_NOYIELDLOCK( salmsg, function ) \ + case salmsg: \ + if (bIsOtherThreadMessage) \ + { \ + ++pInst->m_nNoYieldLock; \ + function; \ + --pInst->m_nNoYieldLock; \ + } \ + else \ + { \ + DBG_TESTSOLARMUTEX(); \ + function; \ + } \ + break; + +#define CASE_NOYIELDLOCK_RESULT( salmsg, function ) \ + case salmsg: \ + if (bIsOtherThreadMessage) \ + { \ + ++pInst->m_nNoYieldLock; \ + nRet = reinterpret_cast<LRESULT>( function ); \ + --pInst->m_nNoYieldLock; \ + } \ + else \ + { \ + DBG_TESTSOLARMUTEX(); \ + nRet = reinterpret_cast<LRESULT>( function ); \ + } \ + break; + +LRESULT CALLBACK SalComWndProc( HWND, UINT nMsg, WPARAM wParam, LPARAM lParam, bool& rDef ) +{ + const bool bIsOtherThreadMessage = InSendMessage(); + LRESULT nRet = 0; + WinSalInstance *pInst = GetSalData()->mpInstance; + WinSalTimer *const pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer ); + + SAL_INFO("vcl.gdi.wndproc", "SalComWndProc(nMsg=" << nMsg << ", wParam=" << wParam + << ", lParam=" << lParam << "); inSendMsg: " << bIsOtherThreadMessage); + + if (ImplGetSVData()->mbDeInit) + { + SAL_WARN("vcl.gdi.wndproc", "ignoring timer event because we are shutting down"); + return 0; + } + + switch ( nMsg ) + { + case SAL_MSG_THREADYIELD: + assert( !static_cast<bool>(wParam) ); + nRet = static_cast<LRESULT>(ImplSalYield( + false, static_cast<bool>( lParam ) )); + break; + + case SAL_MSG_STARTTIMER: + { + auto const nParam = static_cast<sal_uInt64>( lParam ); + sal_uInt64 nTime = tools::Time::GetSystemTicks(); + if ( nTime < nParam ) + nTime = nParam - nTime; + else + nTime = 0; + assert( pTimer != nullptr ); + pTimer->ImplStart( nTime ); + break; + } + + case SAL_MSG_STOPTIMER: + assert( pTimer != nullptr ); + pTimer->ImplStop(); + break; + + CASE_NOYIELDLOCK_RESULT( SAL_MSG_CREATEFRAME, ImplSalCreateFrame( GetSalData()->mpInstance, + reinterpret_cast<HWND>(lParam), static_cast<SalFrameStyleFlags>(wParam)) ) + CASE_NOYIELDLOCK_RESULT( SAL_MSG_RECREATEHWND, ImplSalReCreateHWND( + reinterpret_cast<HWND>(wParam), reinterpret_cast<HWND>(lParam), false) ) + CASE_NOYIELDLOCK_RESULT( SAL_MSG_RECREATECHILDHWND, ImplSalReCreateHWND( + reinterpret_cast<HWND>(wParam), reinterpret_cast<HWND>(lParam), true) ) + CASE_NOYIELDLOCK( SAL_MSG_DESTROYFRAME, delete reinterpret_cast<SalFrame*>(lParam) ) + + case SAL_MSG_DESTROYHWND: + // We only destroy the native window here. We do NOT destroy the SalFrame contained + // in the structure (GetWindowPtr()). + if (DestroyWindow(reinterpret_cast<HWND>(lParam)) == 0) + { + OSL_FAIL("DestroyWindow failed!"); + // Failure: We remove the SalFrame from the window structure. So we avoid that + // the window structure may contain an invalid pointer, once the SalFrame is deleted. + SetWindowPtr(reinterpret_cast<HWND>(lParam), nullptr); + } + break; + + CASE_NOYIELDLOCK_RESULT( SAL_MSG_CREATEOBJECT, ImplSalCreateObject( + GetSalData()->mpInstance, reinterpret_cast<WinSalFrame*>(lParam)) ) + CASE_NOYIELDLOCK( SAL_MSG_DESTROYOBJECT, delete reinterpret_cast<SalObject*>(lParam) ) + CASE_NOYIELDLOCK_RESULT( SAL_MSG_GETCACHEDDC, GetDCEx( + reinterpret_cast<HWND>(wParam), nullptr, DCX_CACHE) ) + CASE_NOYIELDLOCK( SAL_MSG_RELEASEDC, ReleaseDC( + reinterpret_cast<HWND>(wParam), reinterpret_cast<HDC>(lParam)) ) + + case SAL_MSG_TIMER_CALLBACK: + assert( pTimer != nullptr ); + pTimer->ImplHandleTimerEvent( wParam ); + break; + + case WM_TIMER: + assert( pTimer != nullptr ); + pTimer->ImplHandle_WM_TIMER( wParam ); + break; + + case SAL_MSG_FORCE_REAL_TIMER: + assert(pTimer != nullptr); + pTimer->SetForceRealTimer(true); + break; + + case SAL_MSG_DUMMY: + break; + + default: + rDef = true; + break; + } + + return nRet; +} + +#undef CASE_NOYIELDLOCK +#undef CASE_NOYIELDLOCK_RESULT + +LRESULT CALLBACK SalComWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam ) +{ + bool bDef = false; + LRESULT nRet = 0; + __try + { + nRet = SalComWndProc( hWnd, nMsg, wParam, lParam, bDef ); + } + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation())) + { + } + if ( bDef ) + { + if ( !ImplHandleGlobalMsg( hWnd, nMsg, wParam, lParam, nRet ) ) + nRet = DefWindowProcW( hWnd, nMsg, wParam, lParam ); + } + return nRet; +} + +bool WinSalInstance::AnyInput( VclInputFlags nType ) +{ + if ( nType & VclInputFlags::TIMER ) + { + const WinSalTimer* pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer ); + if ( pTimer && pTimer->HasTimerElapsed() ) + return true; + } + + // Note: Do not use PeekMessage(), despite the name it may dispatch events, + // even with PM_NOREMOVE specified, which may lead to unwanted recursion. + + if ( (nType & VCL_INPUT_ANY) == VCL_INPUT_ANY ) + { + // revert bugfix for #108919# which never reported timeouts when called from the timer handler + // which made the application completely unresponsive during background formatting + if ( GetQueueStatus( QS_ALLEVENTS )) + return true; + } + else + { + UINT flags = 0; + + // This code previously considered modifier keys as OTHER, + // but that makes this hard to do without PeekMessage, + // is inconsistent with the X11 backend, and I see no good reason. + if ( nType & VclInputFlags::KEYBOARD ) + flags |= QS_KEY; + + if ( nType & VclInputFlags::MOUSE ) + flags |= QS_MOUSE; + + if ( nType & VclInputFlags::PAINT ) + flags |= QS_PAINT; + + if ( nType & VclInputFlags::TIMER ) + flags |= QS_TIMER; + + if( nType & VclInputFlags::OTHER ) + flags |= QS_ALLEVENTS & ~QS_KEY & ~QS_MOUSE & ~QS_PAINT & ~QS_TIMER; + + if( GetQueueStatus( flags )) + return true; + } + + return false; +} + +SalFrame* WinSalInstance::CreateChildFrame( SystemParentData* pSystemParentData, SalFrameStyleFlags nSalFrameStyle ) +{ + // to switch to Main-Thread + return reinterpret_cast<SalFrame*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEFRAME, static_cast<WPARAM>(nSalFrameStyle), reinterpret_cast<LPARAM>(pSystemParentData->hWnd) ))); +} + +SalFrame* WinSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle ) +{ + // to switch to Main-Thread + HWND hWndParent; + if ( pParent ) + hWndParent = static_cast<WinSalFrame*>(pParent)->mhWnd; + else + hWndParent = nullptr; + return reinterpret_cast<SalFrame*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEFRAME, static_cast<WPARAM>(nSalFrameStyle), reinterpret_cast<LPARAM>(hWndParent) ))); +} + +void WinSalInstance::DestroyFrame( SalFrame* pFrame ) +{ + OpenGLContext::prepareForYield(); + SendMessageW( mhComWnd, SAL_MSG_DESTROYFRAME, 0, reinterpret_cast<LPARAM>(pFrame) ); +} + +SalObject* WinSalInstance::CreateObject( SalFrame* pParent, + SystemWindowData* /*pWindowData*/, // SystemWindowData meaningless on Windows + bool /*bShow*/ ) +{ + // to switch to Main-Thread + return reinterpret_cast<SalObject*>(static_cast<sal_IntPtr>(SendMessageW( mhComWnd, SAL_MSG_CREATEOBJECT, 0, reinterpret_cast<LPARAM>(static_cast<WinSalFrame*>(pParent)) ))); +} + +void WinSalInstance::DestroyObject( SalObject* pObject ) +{ + SendMessageW( mhComWnd, SAL_MSG_DESTROYOBJECT, 0, reinterpret_cast<LPARAM>(pObject) ); +} + +OUString WinSalInstance::GetConnectionIdentifier() +{ + return OUString(); +} + +/** Add a file to the system shells recent document list if there is any. + This function may have no effect under Unix because there is no + standard API among the different desktop managers. + + @param aFileUrl + The file url of the document. +*/ +void WinSalInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& /*rMimeType*/, const OUString& rDocumentService) +{ + if (Application::IsHeadlessModeEnabled()) + return; + + OUString system_path; + osl::FileBase::RC rc = osl::FileBase::getSystemPathFromFileURL(rFileUrl, system_path); + + OSL_ENSURE(osl::FileBase::E_None == rc, "Invalid file url"); + + if (osl::FileBase::E_None == rc) + { + IShellItem* pShellItem = nullptr; + + HRESULT hr = SHCreateItemFromParsingName(o3tl::toW(system_path.getStr()), nullptr, IID_PPV_ARGS(&pShellItem)); + + if ( SUCCEEDED(hr) && pShellItem ) + { + OUString sApplicationName; + + if ( rDocumentService == "com.sun.star.text.TextDocument" || + rDocumentService == "com.sun.star.text.GlobalDocument" || + rDocumentService == "com.sun.star.text.WebDocument" || + rDocumentService == "com.sun.star.xforms.XMLFormDocument" ) + sApplicationName = "Writer"; + else if ( rDocumentService == "com.sun.star.sheet.SpreadsheetDocument" || + rDocumentService == "com.sun.star.chart2.ChartDocument" ) + sApplicationName = "Calc"; + else if ( rDocumentService == "com.sun.star.presentation.PresentationDocument" ) + sApplicationName = "Impress"; + else if ( rDocumentService == "com.sun.star.drawing.DrawingDocument" ) + sApplicationName = "Draw"; + else if ( rDocumentService == "com.sun.star.formula.FormulaProperties" ) + sApplicationName = "Math"; + else if ( rDocumentService == "com.sun.star.sdb.DatabaseDocument" || + rDocumentService == "com.sun.star.sdb.OfficeDatabaseDocument" || + rDocumentService == "com.sun.star.sdb.RelationDesign" || + rDocumentService == "com.sun.star.sdb.QueryDesign" || + rDocumentService == "com.sun.star.sdb.TableDesign" || + rDocumentService == "com.sun.star.sdb.DataSourceBrowser" ) + sApplicationName = "Base"; + + if ( !sApplicationName.isEmpty() ) + { + OUString sApplicationID("TheDocumentFoundation.LibreOffice." + sApplicationName); + + SHARDAPPIDINFO info; + info.psi = pShellItem; + info.pszAppID = o3tl::toW(sApplicationID.getStr()); + + SHAddToRecentDocs ( SHARD_APPIDINFO, &info ); + return; + } + } + // For whatever reason, we could not use the SHARD_APPIDINFO semantics + SHAddToRecentDocs(SHARD_PATHW, system_path.getStr()); + } +} + +SalTimer* WinSalInstance::CreateSalTimer() +{ + return new WinSalTimer(); +} + +std::shared_ptr<SalBitmap> WinSalInstance::CreateSalBitmap() +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + return std::make_shared<SkiaSalBitmap>(); + else +#endif + return std::make_shared<WinSalBitmap>(); +} + +int WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(int, LPEXCEPTION_POINTERS pExceptionInfo) +{ + // Decide if an exception is a c++ (mostly UNO) exception or a process violation. + // Depending on this information we pass process violations directly to our signal handler ... + // and c++ (UNO) exceptions are sended to the following code on the current stack. + // Problem behind: user32.dll sometime consumes exceptions/process violations .-) + // see also #112221# + + static const DWORD EXCEPTION_MSC_CPP_EXCEPTION = 0xE06D7363; + + if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_MSC_CPP_EXCEPTION) + return EXCEPTION_CONTINUE_SEARCH; + + return UnhandledExceptionFilter( pExceptionInfo ); +} + +typedef LONG NTSTATUS; +typedef NTSTATUS(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW); +constexpr NTSTATUS STATUS_SUCCESS = 0x00000000; + +OUString WinSalInstance::getOSVersion() +{ + OUStringBuffer aVer(50); // capacity for string like "Windows 6.1 Service Pack 1 build 7601" + aVer.append("Windows "); + // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are + // subject to manifest-based behavior since Windows 8.1, so give wrong results. + // Another approach would be to use NetWkstaGetInfo, but that has some small + // reported delays (some milliseconds), and might get slower in domains with + // poor network connections. + // So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429 + bool bHaveVerFromKernel32 = false; + if (HMODULE h_kernel32 = GetModuleHandleW(L"kernel32.dll")) + { + wchar_t szPath[MAX_PATH]; + DWORD dwCount = GetModuleFileNameW(h_kernel32, szPath, SAL_N_ELEMENTS(szPath)); + if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath)) + { + dwCount = GetFileVersionInfoSizeW(szPath, nullptr); + if (dwCount != 0) + { + std::unique_ptr<char[]> ver(new char[dwCount]); + if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE) + { + void* pBlock = nullptr; + UINT dwBlockSz = 0; + if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE && dwBlockSz >= sizeof(VS_FIXEDFILEINFO)) + { + VS_FIXEDFILEINFO* vi1 = static_cast<VS_FIXEDFILEINFO*>(pBlock); + aVer.append(OUString::number(HIWORD(vi1->dwProductVersionMS)) + "." + + OUString::number(LOWORD(vi1->dwProductVersionMS))); + bHaveVerFromKernel32 = true; + } + } + } + } + } + // Now use RtlGetVersion (which is not subject to deprecation for GetVersion(Ex) API) + // to get build number and SP info + bool bHaveVerFromRtlGetVersion = false; + if (HMODULE h_ntdll = GetModuleHandleW(L"ntdll.dll")) + { + if (auto RtlGetVersion + = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(h_ntdll, "RtlGetVersion"))) + { + RTL_OSVERSIONINFOW vi2{}; // initialize with zeroes - a better alternative to memset + vi2.dwOSVersionInfoSize = sizeof(vi2); + if (STATUS_SUCCESS == RtlGetVersion(&vi2)) + { + if (!bHaveVerFromKernel32) // we failed above; let's hope this would be useful + aVer.append(OUString::number(vi2.dwMajorVersion) + "." + + OUString::number(vi2.dwMinorVersion)); + aVer.append(" "); + if (vi2.szCSDVersion[0]) + aVer.append(OUString::Concat(o3tl::toU(vi2.szCSDVersion)) + " "); + aVer.append("Build " + OUString::number(vi2.dwBuildNumber)); + bHaveVerFromRtlGetVersion = true; + } + } + } + if (!bHaveVerFromKernel32 && !bHaveVerFromRtlGetVersion) + aVer.append("unknown"); + return aVer.makeStringAndClear(); +} + +void WinSalInstance::BeforeAbort(const OUString&, bool) +{ + ImplFreeSalGDI(); +} + +css::uno::Reference<css::uno::XInterface> WinSalInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv) +{ + return vcl::OleDnDHelper(new DragSource(comphelper::getProcessComponentContext()), + reinterpret_cast<sal_IntPtr>(pSysEnv->hWnd), vcl::DragOrDrop::Drag); +} + +css::uno::Reference<css::uno::XInterface> WinSalInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv) +{ + return vcl::OleDnDHelper(new DropTarget(comphelper::getProcessComponentContext()), + reinterpret_cast<sal_IntPtr>(pSysEnv->hWnd), vcl::DragOrDrop::Drop); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/app/salshl.cxx b/vcl/win/app/salshl.cxx new file mode 100644 index 0000000000..5619ece1f0 --- /dev/null +++ b/vcl/win/app/salshl.cxx @@ -0,0 +1,112 @@ +/* -*- 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 <svsys.h> +#include <sal/log.hxx> +#include <win/saldata.hxx> + +SalShlData aSalShlData; + +extern "C" BOOL WINAPI DllMain(HINSTANCE hInst, DWORD nReason, LPVOID) +{ + if ( nReason == DLL_PROCESS_ATTACH ) + aSalShlData.mhInst = hInst; + return 1; +} + +HCURSOR ImplLoadSalCursor( int nId ) +{ + SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" ); + + HCURSOR hCursor = LoadCursorW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ) ); + + SAL_WARN_IF( !hCursor, "vcl", "cursor not found in sal resource" ); + + return hCursor; +} + +HBITMAP ImplLoadSalBitmap( int nId ) +{ + SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" ); + + HBITMAP hBitmap = LoadBitmapW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ) ); + + SAL_WARN_IF( !hBitmap, "vcl", "bitmap not found in sal resource" ); + + return hBitmap; +} + +bool ImplLoadSalIcon( int nId, HICON& rIcon, HICON& rSmallIcon ) +{ + SAL_WARN_IF( !aSalShlData.mhInst, "vcl", "no DLL instance handle" ); + + SalData* pSalData = GetSalData(); + + // check the cache first + SalIcon *pSalIcon = pSalData->mpFirstIcon; + while( pSalIcon ) + { + if( pSalIcon->nId != nId ) + pSalIcon = pSalIcon->pNext; + else + { + rIcon = pSalIcon->hIcon; + rSmallIcon = pSalIcon->hSmallIcon; + return (rSmallIcon != nullptr); + } + } + + // Try at first to load the icons from the application exe file + rIcon = static_cast<HICON>(LoadImageW( pSalData->mhInst, MAKEINTRESOURCEW( nId ), + IMAGE_ICON, GetSystemMetrics( SM_CXICON ), GetSystemMetrics( SM_CYICON ), + LR_DEFAULTCOLOR )); + if ( !rIcon ) + { + // If the application don't provide these icons, then we try + // to load the icon from the VCL resource + rIcon = static_cast<HICON>(LoadImageW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ), + IMAGE_ICON, GetSystemMetrics( SM_CXICON ), GetSystemMetrics( SM_CYICON ), + LR_DEFAULTCOLOR )); + if ( rIcon ) + { + rSmallIcon = static_cast<HICON>(LoadImageW( aSalShlData.mhInst, MAKEINTRESOURCEW( nId ), + IMAGE_ICON, GetSystemMetrics( SM_CXSMICON ), GetSystemMetrics( SM_CYSMICON ), + LR_DEFAULTCOLOR )); + } + else + rSmallIcon = nullptr; + } + else + { + rSmallIcon = static_cast<HICON>(LoadImageW( pSalData->mhInst, MAKEINTRESOURCEW( nId ), + IMAGE_ICON, GetSystemMetrics( SM_CXSMICON ), GetSystemMetrics( SM_CYSMICON ), + LR_DEFAULTCOLOR )); + } + + if( rIcon ) + { + // add to icon cache + pSalData->mpFirstIcon = new SalIcon{ + nId, rIcon, rSmallIcon, pSalData->mpFirstIcon}; + } + + return (rSmallIcon != nullptr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/app/saltimer.cxx b/vcl/win/app/saltimer.cxx new file mode 100644 index 0000000000..1ccab54e96 --- /dev/null +++ b/vcl/win/app/saltimer.cxx @@ -0,0 +1,201 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <tools/time.hxx> + +#include <svsys.h> +#include <win/saldata.hxx> +#include <win/saltimer.h> +#include <win/salinst.h> + +void CALLBACK SalTimerProc(PVOID pParameter, BOOLEAN bTimerOrWaitFired); + +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms687003%28v=vs.85%29.aspx +// (and related pages) for details about the Timer Queues. + +// in order to prevent concurrent execution of ImplSalStartTimer and double +// deletion of timer (which is extremely likely, given that +// INVALID_HANDLE_VALUE waits for the callback to run on the main thread), +// this must run on the main thread too +void WinSalTimer::ImplStop() +{ + SalData *const pSalData = GetSalData(); + const WinSalInstance *pInst = pSalData->mpInstance; + assert( !pInst || pSalData->mnAppThreadId == GetCurrentThreadId() ); + + if ( m_bSetTimerRunning ) + { + m_bSetTimerRunning = false; + KillTimer( pInst->mhComWnd, m_aWmTimerId ); + } + m_bDirectTimeout = false; + + const HANDLE hTimer = m_nTimerId; + if ( nullptr == hTimer ) + return; + + m_nTimerId = nullptr; + DeleteTimerQueueTimer( nullptr, hTimer, INVALID_HANDLE_VALUE ); + // Keep InvalidateEvent after DeleteTimerQueueTimer, because the event id + // is set in SalTimerProc, which DeleteTimerQueueTimer will finish or cancel. + InvalidateEvent(); +} + +void WinSalTimer::ImplStart( sal_uInt64 nMS ) +{ +#if !defined NDEBUG + SalData* pSalData = GetSalData(); + assert( !pSalData->mpInstance || pSalData->mnAppThreadId == GetCurrentThreadId() ); +#endif + + // DueTime parameter is a DWORD, which is always an unsigned 32bit + if (nMS > SAL_MAX_UINT32) + nMS = SAL_MAX_UINT32; + + // cannot change a one-shot timer, so delete it and create a new one + ImplStop(); + + // directly indicate an elapsed timer + m_bDirectTimeout = ( 0 == nMS ); + // probably WT_EXECUTEONLYONCE is not needed, but it enforces Period + // to be 0 and should not hurt; also see + // https://www.microsoft.com/msj/0499/pooling/pooling.aspx + if ( !m_bDirectTimeout ) + CreateTimerQueueTimer(&m_nTimerId, nullptr, SalTimerProc, this, + nMS, 0, WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE); + else if ( m_bForceRealTimer ) + { + // so we don't block the nested message queue in move and resize + // with posted 0ms SAL_MSG_TIMER_CALLBACK messages + SetTimer( GetSalData()->mpInstance->mhComWnd, m_aWmTimerId, + USER_TIMER_MINIMUM, nullptr ); + m_bSetTimerRunning = true; + } + // we don't need any wakeup message, as this code can just run in the + // main thread! +} + +WinSalTimer::WinSalTimer() + : m_nTimerId( nullptr ) + , m_bDirectTimeout( false ) + , m_bForceRealTimer( false ) + , m_bSetTimerRunning( false ) +{ +} + +WinSalTimer::~WinSalTimer() +{ + Stop(); +} + +void WinSalTimer::Start( sal_uInt64 nMS ) +{ + WinSalInstance *pInst = GetSalData()->mpInstance; + if ( pInst && !pInst->IsMainThread() ) + { + bool const ret = PostMessageW(pInst->mhComWnd, + SAL_MSG_STARTTIMER, 0, static_cast<LPARAM>(tools::Time::GetSystemTicks()) + nMS); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + else + ImplStart( nMS ); +} + +void WinSalTimer::Stop() +{ + WinSalInstance *pInst = GetSalData()->mpInstance; + if ( pInst && !pInst->IsMainThread() ) + { + bool const ret = PostMessageW(pInst->mhComWnd, + SAL_MSG_STOPTIMER, 0, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + else + ImplStop(); +} + +/** + * This gets invoked from a Timer Queue thread. + * Don't acquire the SolarMutex to avoid deadlocks. + */ +void CALLBACK SalTimerProc(PVOID data, BOOLEAN) +{ + __try + { + WinSalTimer *pTimer = static_cast<WinSalTimer*>( data ); + bool const ret = PostMessageW( + GetSalData()->mpInstance->mhComWnd, SAL_MSG_TIMER_CALLBACK, + static_cast<WPARAM>(pTimer->GetNextEventVersion()), 0 ); +#if OSL_DEBUG_LEVEL > 0 + if (!ret) // SEH prevents using SAL_WARN here? + fputs("ERROR: PostMessage() failed!\n", stderr); +#endif + } + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation())) + { + } +} + +void WinSalTimer::ImplHandleElapsedTimer() +{ + // Test for MouseLeave + SalTestMouseLeave(); + + m_bDirectTimeout = false; + ImplSalYieldMutexAcquireWithWait(); + CallCallback(); + ImplSalYieldMutexRelease(); +} + +void WinSalTimer::ImplHandleTimerEvent( const WPARAM aWPARAM ) +{ + assert( aWPARAM <= SAL_MAX_INT32 ); + if ( !IsValidEventVersion( static_cast<sal_Int32>( aWPARAM ) ) ) + return; + + ImplHandleElapsedTimer(); +} + +void WinSalTimer::SetForceRealTimer( const bool bVal ) +{ + if ( m_bForceRealTimer == bVal ) + return; + + m_bForceRealTimer = bVal; + + // we need a real timer, as m_bDirectTimeout won't be processed + if ( bVal && m_bDirectTimeout ) + Start( 0 ); +} + +void WinSalTimer::ImplHandle_WM_TIMER( const WPARAM aWPARAM ) +{ + assert( m_aWmTimerId == aWPARAM ); + if ( !(m_aWmTimerId == aWPARAM && m_bSetTimerRunning) ) + return; + + m_bSetTimerRunning = false; + KillTimer( GetSalData()->mpInstance->mhComWnd, m_aWmTimerId ); + ImplHandleElapsedTimer(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/APNDataObject.cxx b/vcl/win/dtrans/APNDataObject.cxx new file mode 100644 index 0000000000..2492647f26 --- /dev/null +++ b/vcl/win/dtrans/APNDataObject.cxx @@ -0,0 +1,320 @@ +/* -*- 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 "APNDataObject.hxx" +#include <osl/diagnose.h> + +#include <systools/win32/comtools.hxx> + +#define FREE_HGLOB_ON_RELEASE TRUE +#define KEEP_HGLOB_ON_RELEASE FALSE + +// ctor + +CAPNDataObject::CAPNDataObject( IDataObjectPtr rIDataObject ) : + m_rIDataObjectOrg( rIDataObject ), + m_hGlobal( nullptr ), + m_nRefCnt( 0 ) +{ + + OSL_ENSURE( m_rIDataObjectOrg.get( ), "constructing CAPNDataObject with empty data object" ); + + // we marshal the IDataObject interface pointer here so + // that it can be unmarshalled multiple times when this + // class will be used from another apartment + sal::systools::COMReference<IStream> pStm; + HRESULT hr = CreateStreamOnHGlobal( nullptr, KEEP_HGLOB_ON_RELEASE, &pStm ); + + OSL_ENSURE( E_INVALIDARG != hr, "invalid args passed to CreateStreamOnHGlobal" ); + + if ( SUCCEEDED( hr ) ) + { + HRESULT hr_marshal = CoMarshalInterface( + pStm, + __uuidof(IDataObject), + m_rIDataObjectOrg, + MSHCTX_LOCAL, + nullptr, + MSHLFLAGS_TABLEWEAK ); + + OSL_ENSURE( CO_E_NOTINITIALIZED != hr_marshal, "COM is not initialized" ); + + // marshalling may fail if COM is not initialized + // for the calling thread which is a program time + // error or because of stream errors which are runtime + // errors for instance E_OUTOFMEMORY etc. + + hr = GetHGlobalFromStream(pStm, &m_hGlobal ); + + OSL_ENSURE( E_INVALIDARG != hr, "invalid stream passed to GetHGlobalFromStream" ); + + // if the marshalling failed we free the + // global memory again and set m_hGlobal + // to a defined value + if (FAILED(hr_marshal)) + { + OSL_FAIL("marshalling failed"); + + HGLOBAL hGlobal = + GlobalFree(m_hGlobal); + OSL_ENSURE(nullptr == hGlobal, "GlobalFree failed"); + m_hGlobal = nullptr; + } + } +} + +CAPNDataObject::~CAPNDataObject( ) +{ + if (m_hGlobal) + { + sal::systools::COMReference<IStream> pStm; + HRESULT hr = CreateStreamOnHGlobal(m_hGlobal, FREE_HGLOB_ON_RELEASE, &pStm); + + OSL_ENSURE( E_INVALIDARG != hr, "invalid args passed to CreateStreamOnHGlobal" ); + + if (SUCCEEDED(hr)) + { + hr = CoReleaseMarshalData(pStm); + OSL_ENSURE(SUCCEEDED(hr), "CoReleaseMarshalData failed"); + } + } +} + +// IUnknown->QueryInterface + +STDMETHODIMP CAPNDataObject::QueryInterface( REFIID iid, void** ppvObject ) +{ + OSL_ASSERT( nullptr != ppvObject ); + + if ( nullptr == ppvObject ) + return E_INVALIDARG; + + HRESULT hr = E_NOINTERFACE; + *ppvObject = nullptr; + + if ( ( __uuidof( IUnknown ) == iid ) || ( __uuidof( IDataObject ) == iid ) ) + { + *ppvObject = static_cast< IUnknown* >( this ); + AddRef( ); + hr = S_OK; + } + + return hr; +} + +// IUnknown->AddRef + +STDMETHODIMP_(ULONG) CAPNDataObject::AddRef( ) +{ + return static_cast< ULONG >( InterlockedIncrement( &m_nRefCnt ) ); +} + +// IUnknown->Release + +STDMETHODIMP_(ULONG) CAPNDataObject::Release( ) +{ + // we need a helper variable because it's not allowed to access + // a member variable after an object is destroyed + ULONG nRefCnt = static_cast< ULONG >( InterlockedDecrement( &m_nRefCnt ) ); + + if ( 0 == nRefCnt ) + delete this; + + return nRefCnt; +} + +// IDataObject->GetData + +STDMETHODIMP CAPNDataObject::GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) +{ + HRESULT hr = m_rIDataObjectOrg->GetData( pFormatetc, pmedium ); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->GetData(pFormatetc, pmedium); + } + return hr; +} + +// IDataObject->EnumFormatEtc + +STDMETHODIMP CAPNDataObject::EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) +{ + HRESULT hr = m_rIDataObjectOrg->EnumFormatEtc(dwDirection, ppenumFormatetc); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->EnumFormatEtc(dwDirection, ppenumFormatetc); + } + return hr; +} + +// IDataObject->QueryGetData + +STDMETHODIMP CAPNDataObject::QueryGetData( FORMATETC * pFormatetc ) +{ + HRESULT hr = m_rIDataObjectOrg->QueryGetData( pFormatetc ); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment( &pIDOTmp ); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->QueryGetData(pFormatetc); + } + return hr; +} + +// IDataObject->GetDataHere + +STDMETHODIMP CAPNDataObject::GetDataHere( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) +{ + HRESULT hr = m_rIDataObjectOrg->GetDataHere(pFormatetc, pmedium); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->GetDataHere(pFormatetc, pmedium); + } + return hr; +} + +// IDataObject->GetCanonicalFormatEtc + +STDMETHODIMP CAPNDataObject::GetCanonicalFormatEtc(FORMATETC * pFormatectIn, FORMATETC * pFormatetcOut) +{ + HRESULT hr = m_rIDataObjectOrg->GetCanonicalFormatEtc( pFormatectIn, pFormatetcOut ); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->GetCanonicalFormatEtc(pFormatectIn, pFormatetcOut); + } + return hr; +} + +// IDataObject->SetData + +STDMETHODIMP CAPNDataObject::SetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease ) +{ + HRESULT hr = m_rIDataObjectOrg->SetData( pFormatetc, pmedium, fRelease ); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->SetData(pFormatetc, pmedium, fRelease); + } + return hr; +} + +// IDataObject->DAdvise + +STDMETHODIMP CAPNDataObject::DAdvise( FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD * pdwConnection ) +{ + HRESULT hr = m_rIDataObjectOrg->DAdvise(pFormatetc, advf, pAdvSink, pdwConnection); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->DAdvise(pFormatetc, advf, pAdvSink, pdwConnection); + } + return hr; +} + +// IDataObject->DUnadvise + +STDMETHODIMP CAPNDataObject::DUnadvise( DWORD dwConnection ) +{ + HRESULT hr = m_rIDataObjectOrg->DUnadvise( dwConnection ); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->DUnadvise(dwConnection); + } + return hr; +} + +// IDataObject->EnumDAdvise + +STDMETHODIMP CAPNDataObject::EnumDAdvise( IEnumSTATDATA ** ppenumAdvise ) +{ + HRESULT hr = m_rIDataObjectOrg->EnumDAdvise(ppenumAdvise); + + if (RPC_E_WRONG_THREAD == hr) + { + IDataObjectPtr pIDOTmp; + hr = MarshalIDataObjectIntoCurrentApartment(&pIDOTmp); + + if (SUCCEEDED(hr)) + hr = pIDOTmp->EnumDAdvise(ppenumAdvise); + } + return hr; +} + +// helper function + +HRESULT CAPNDataObject::MarshalIDataObjectIntoCurrentApartment( IDataObject** ppIDataObj ) +{ + OSL_ASSERT(nullptr != ppIDataObj); + + *ppIDataObj = nullptr; + HRESULT hr = E_FAIL; + + if (m_hGlobal) + { + sal::systools::COMReference<IStream> pStm; + hr = CreateStreamOnHGlobal(m_hGlobal, KEEP_HGLOB_ON_RELEASE, &pStm); + + OSL_ENSURE(E_INVALIDARG != hr, "CreateStreamOnHGlobal with invalid args called"); + + if (SUCCEEDED(hr)) + { + hr = CoUnmarshalInterface(pStm, IID_PPV_ARGS(ppIDataObj)); + OSL_ENSURE(CO_E_NOTINITIALIZED != hr, "COM is not initialized"); + } + } + return hr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/APNDataObject.hxx b/vcl/win/dtrans/APNDataObject.hxx new file mode 100644 index 0000000000..5de8ccfcc8 --- /dev/null +++ b/vcl/win/dtrans/APNDataObject.hxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + +#pragma once + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <objidl.h> + +#include <systools/win32/comtools.hxx> + +/* + an APartment Neutral dataobject wrapper; this wrapper of an IDataObject + pointer can be used from any apartment without RPC_E_WRONG_THREAD + which normally occurs if an apartment tries to use an interface + pointer of another apartment; we use containment to hold the original + DataObject +*/ +class CAPNDataObject : public IDataObject +{ +public: + explicit CAPNDataObject(IDataObjectPtr rIDataObject); + virtual ~CAPNDataObject(); + + //IUnknown interface methods + + STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override; + STDMETHODIMP_( ULONG ) AddRef( ) override; + STDMETHODIMP_( ULONG ) Release( ) override; + + // IDataObject interface methods + + STDMETHODIMP GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override; + STDMETHODIMP GetDataHere( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override; + STDMETHODIMP QueryGetData( FORMATETC * pFormatetc ) override; + STDMETHODIMP GetCanonicalFormatEtc( FORMATETC * pFormatectIn, FORMATETC * pFormatetcOut ) override; + STDMETHODIMP SetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease ) override; + STDMETHODIMP EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) override; + STDMETHODIMP DAdvise( FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD* pdwConnection ) override; + STDMETHODIMP DUnadvise( DWORD dwConnection ) override; + STDMETHODIMP EnumDAdvise( IEnumSTATDATA** ppenumAdvise ) override; + +private: + HRESULT MarshalIDataObjectIntoCurrentApartment( IDataObject** ppIDataObj ); + +private: + IDataObjectPtr m_rIDataObjectOrg; + HGLOBAL m_hGlobal; + LONG m_nRefCnt; + +// prevent copy and assignment +private: + CAPNDataObject( const CAPNDataObject& theOther ) = delete; + CAPNDataObject& operator=( const CAPNDataObject& theOther ) = delete; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/DOTransferable.cxx b/vcl/win/dtrans/DOTransferable.cxx new file mode 100644 index 0000000000..c2b54bf83a --- /dev/null +++ b/vcl/win/dtrans/DOTransferable.cxx @@ -0,0 +1,577 @@ +/* -*- 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 <sal/types.h> +#include <rtl/process.h> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include "DOTransferable.hxx" +#include "ImplHelper.hxx" +#include "WinClip.hxx" +#include "WinClipboard.hxx" +#include "DTransHelper.hxx" +#include "TxtCnvtHlp.hxx" +#include "MimeAttrib.hxx" +#include "FmtFilter.hxx" +#include "Fetc.hxx" +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +using namespace osl; +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::io; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; + +namespace +{ + const Type CPPUTYPE_SEQINT8 = cppu::UnoType<Sequence< sal_Int8 >>::get(); + const Type CPPUTYPE_OUSTRING = cppu::UnoType<OUString>::get(); + + bool isValidFlavor( const DataFlavor& aFlavor ) + { + return ( aFlavor.MimeType.getLength( ) && + ( ( aFlavor.DataType == CPPUTYPE_SEQINT8 ) || + ( aFlavor.DataType == CPPUTYPE_OUSTRING ) ) ); + } + +void clipDataToByteStream( CLIPFORMAT cf, STGMEDIUM stgmedium, CDOTransferable::ByteSequence_t& aByteSequence ) +{ + CStgTransferHelper memTransferHelper; + LPSTREAM pStream = nullptr; + + switch( stgmedium.tymed ) + { + case TYMED_HGLOBAL: + memTransferHelper.init( stgmedium.hGlobal ); + break; + + case TYMED_MFPICT: + memTransferHelper.init( stgmedium.hMetaFilePict ); + break; + + case TYMED_ENHMF: + memTransferHelper.init( stgmedium.hEnhMetaFile ); + break; + + case TYMED_ISTREAM: + pStream = stgmedium.pstm; + break; + + default: + throw UnsupportedFlavorException( ); + break; + } + + if (pStream) + { + // We have a stream, read from it. + STATSTG aStat; + HRESULT hr = pStream->Stat(&aStat, STATFLAG_NONAME); + if (FAILED(hr)) + { + SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Stat() failed"); + return; + } + + size_t nMemSize = aStat.cbSize.QuadPart; + aByteSequence.realloc(nMemSize); + LARGE_INTEGER li; + li.QuadPart = 0; + hr = pStream->Seek(li, STREAM_SEEK_SET, nullptr); + if (FAILED(hr)) + { + SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Seek() failed"); + } + + ULONG nRead = 0; + hr = pStream->Read(aByteSequence.getArray(), nMemSize, &nRead); + if (FAILED(hr)) + { + SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Read() failed"); + } + if (nRead < nMemSize) + { + SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Read() was partial"); + } + + return; + } + + int nMemSize = memTransferHelper.memSize( cf ); + aByteSequence.realloc( nMemSize ); + memTransferHelper.read( aByteSequence.getArray( ), nMemSize ); +} + +OUString byteStreamToOUString( CDOTransferable::ByteSequence_t& aByteStream ) +{ + sal_Int32 nWChars; + sal_Int32 nMemSize = aByteStream.getLength( ); + + // if there is a trailing L"\0" subtract 1 from length + // for unknown reason, the sequence may sometimes arrive empty + if ( aByteStream.getLength( ) > 1 && + 0 == aByteStream[ aByteStream.getLength( ) - 2 ] && + 0 == aByteStream[ aByteStream.getLength( ) - 1 ] ) + nWChars = static_cast< sal_Int32 >( nMemSize / sizeof( sal_Unicode ) ) - 1; + else + nWChars = static_cast< sal_Int32 >( nMemSize / sizeof( sal_Unicode ) ); + + return OUString( reinterpret_cast< sal_Unicode* >( aByteStream.getArray( ) ), nWChars ); +} + +Any byteStreamToAny( CDOTransferable::ByteSequence_t& aByteStream, const Type& aRequestedDataType ) +{ + Any aAny; + + if ( aRequestedDataType == CPPUTYPE_OUSTRING ) + { + OUString str = byteStreamToOUString( aByteStream ); + if (str.isEmpty()) + throw RuntimeException(); + aAny <<= str; + } + else + aAny <<= aByteStream; + + return aAny; +} + +bool cmpFullMediaType( + const Reference< XMimeContentType >& xLhs, const Reference< XMimeContentType >& xRhs ) +{ + return xLhs->getFullMediaType().equalsIgnoreAsciiCase( xRhs->getFullMediaType( ) ); +} + +bool cmpAllContentTypeParameter( + const Reference< XMimeContentType >& xLhs, const Reference< XMimeContentType >& xRhs ) +{ + Sequence< OUString > xLhsFlavors = xLhs->getParameters( ); + Sequence< OUString > xRhsFlavors = xRhs->getParameters( ); + bool bRet = true; + + try + { + if ( xLhsFlavors.getLength( ) == xRhsFlavors.getLength( ) ) + { + OUString pLhs; + OUString pRhs; + + for ( sal_Int32 i = 0; i < xLhsFlavors.getLength( ); i++ ) + { + pLhs = xLhs->getParameterValue( xLhsFlavors[i] ); + pRhs = xRhs->getParameterValue( xLhsFlavors[i] ); + + if ( !pLhs.equalsIgnoreAsciiCase( pRhs ) ) + { + bRet = false; + break; + } + } + } + else + bRet = false; + } + catch( NoSuchElementException& ) + { + bRet = false; + } + catch( IllegalArgumentException& ) + { + bRet = false; + } + + return bRet; +} + +} // end namespace + +CDOTransferable::CDOTransferable( + const Reference< XComponentContext >& rxContext, IDataObjectPtr rDataObject ) : + m_rDataObject( rDataObject ), + m_xContext( rxContext ), + m_DataFormatTranslator( rxContext ), + m_bUnicodeRegistered( false ), + m_TxtFormatOnClipboard( CF_INVALID ) +{ + initFlavorList(); +} + +CDOTransferable::CDOTransferable( + const Reference<XComponentContext>& rxContext, + const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& xClipboard, + const std::vector<sal_uInt32>& rFormats) + : m_xClipboard(xClipboard) + , m_xContext(rxContext) + , m_DataFormatTranslator(rxContext) + , m_bUnicodeRegistered(false) + , m_TxtFormatOnClipboard(CF_INVALID) +{ + initFlavorListFromFormatList(rFormats); +} + +Any SAL_CALL CDOTransferable::getTransferData( const DataFlavor& aFlavor ) +{ + OSL_ASSERT( isValidFlavor( aFlavor ) ); + + std::unique_lock aGuard( m_aMutex ); + + // convert dataflavor to formatetc + + CFormatEtc fetc = m_DataFormatTranslator.getFormatEtcFromDataFlavor( aFlavor ); + OSL_ASSERT( CF_INVALID != fetc.getClipformat() ); + + // get the data from clipboard in a byte stream + + ByteSequence_t clipDataStream; + + try + { + clipDataStream = getClipboardData( fetc ); + } + catch( UnsupportedFlavorException& ) + { + if ( CDataFormatTranslator::isUnicodeTextFormat( fetc.getClipformat( ) ) && + m_bUnicodeRegistered ) + { + OUString aUnicodeText = synthesizeUnicodeText( ); + Any aAny( aUnicodeText ); + return aAny; + } + // #i124085# CF_DIBV5 should not be possible, but keep for reading from the + // clipboard for being on the safe side + else if(CF_DIBV5 == fetc.getClipformat()) + { + // #i123407# CF_DIBV5 has priority; if the try to fetch this failed, + // check CF_DIB availability as an alternative + fetc.setClipformat(CF_DIB); + + clipDataStream = getClipboardData( fetc ); + // pass UnsupportedFlavorException out, tried all possibilities + } + else + throw; // pass through exception + } + + // return the data as any + + return byteStreamToAny( clipDataStream, aFlavor.DataType ); +} + +// getTransferDataFlavors + +Sequence< DataFlavor > SAL_CALL CDOTransferable::getTransferDataFlavors( ) +{ + return m_FlavorList; +} + +// isDataFlavorSupported +// returns true if we find a DataFlavor with the same MimeType and +// DataType + +sal_Bool SAL_CALL CDOTransferable::isDataFlavorSupported( const DataFlavor& aFlavor ) +{ + OSL_ASSERT( isValidFlavor( aFlavor ) ); + + for ( DataFlavor const & df : std::as_const(m_FlavorList) ) + if ( compareDataFlavors( aFlavor, df ) ) + return true; + + return false; +} + +// the list of dataflavors currently on the clipboard will be initialized +// only once; if the client of this Transferable will hold a reference +// to it and the underlying clipboard content changes, the client does +// possible operate on an invalid list +// if there is only text on the clipboard we will also offer unicode text +// an synthesize this format on the fly if requested, to accomplish this +// we save the first offered text format which we will later use for the +// conversion + +void CDOTransferable::initFlavorList( ) +{ + std::vector<sal_uInt32> aFormats; + sal::systools::COMReference<IEnumFORMATETC> pEnumFormatEtc; + HRESULT hr = m_rDataObject->EnumFormatEtc( DATADIR_GET, &pEnumFormatEtc ); + if ( SUCCEEDED( hr ) ) + { + pEnumFormatEtc->Reset( ); + + FORMATETC fetc; + while ( S_OK == pEnumFormatEtc->Next( 1, &fetc, nullptr ) ) + { + aFormats.push_back(fetc.cfFormat); + // see MSDN IEnumFORMATETC + CoTaskMemFree( fetc.ptd ); + } + initFlavorListFromFormatList(aFormats); + } +} + +void CDOTransferable::initFlavorListFromFormatList(const std::vector<sal_uInt32>& rFormats) +{ + for (sal_uInt32 cfFormat : rFormats) + { + // we use locales only to determine the + // charset if there is text on the clipboard + // we don't offer this format + if (CF_LOCALE == cfFormat) + continue; + + // if text or oemtext is offered we pretend to have unicode text + if (CDataFormatTranslator::isTextFormat(cfFormat)) + { + if (!m_bUnicodeRegistered) + { + m_TxtFormatOnClipboard = cfFormat; + m_bUnicodeRegistered = true; + + // register unicode text as format + addSupportedFlavor(formatEtcToDataFlavor(CF_UNICODETEXT)); + } + } + else + addSupportedFlavor(formatEtcToDataFlavor(cfFormat)); + } +} + +inline +void CDOTransferable::addSupportedFlavor( const DataFlavor& aFlavor ) +{ + // we ignore all formats that couldn't be translated + if ( aFlavor.MimeType.getLength( ) ) + { + OSL_ASSERT( isValidFlavor( aFlavor ) ); + + m_FlavorList.realloc( m_FlavorList.getLength( ) + 1 ); + m_FlavorList.getArray()[m_FlavorList.getLength( ) - 1] = aFlavor; + } +} + +DataFlavor CDOTransferable::formatEtcToDataFlavor(sal_uInt32 cfFormat) +{ + return m_DataFormatTranslator.getDataFlavorFromFormatEtc(cfFormat); +} + +// returns the current locale on clipboard; if there is no locale on +// clipboard the function returns the current thread locale + +LCID CDOTransferable::getLocaleFromClipboard( ) +{ + LCID lcid = GetThreadLocale( ); + + try + { + CFormatEtc fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_LOCALE ); + ByteSequence_t aLCIDSeq = getClipboardData( fetc ); + lcid = *reinterpret_cast<LCID*>( aLCIDSeq.getArray( ) ); + + // because of a Win95/98 Bug; there the high word + // of a locale has the same value as the + // low word e.g. 0x07040704 that's not right + // correct is 0x00000704 + lcid &= 0x0000FFFF; + } + catch(...) + { + // we take the default locale + } + + return lcid; +} + +void CDOTransferable::tryToGetIDataObjectIfAbsent() +{ + if (!m_rDataObject.is()) + { + auto xClipboard = m_xClipboard.get(); // holding the reference while we get the object + if (CWinClipboard* pWinClipboard = dynamic_cast<CWinClipboard*>(xClipboard.get())) + { + m_rDataObject = pWinClipboard->getIDataObject(); + } + } +} + +// I think it's not necessary to call ReleaseStgMedium +// in case of failures because nothing should have been +// allocated etc. + +CDOTransferable::ByteSequence_t CDOTransferable::getClipboardData( CFormatEtc& aFormatEtc ) +{ + STGMEDIUM stgmedium; + tryToGetIDataObjectIfAbsent(); + if (!m_rDataObject.is()) // Maybe we are shutting down, and clipboard is already destroyed? + throw RuntimeException(); + HRESULT hr = m_rDataObject->GetData( aFormatEtc, &stgmedium ); + + // in case of failure to get a WMF metafile handle, try to get a memory block + if( FAILED( hr ) && + ( CF_METAFILEPICT == aFormatEtc.getClipformat() ) && + ( TYMED_MFPICT == aFormatEtc.getTymed() ) ) + { + CFormatEtc aTempFormat( aFormatEtc ); + aTempFormat.setTymed( TYMED_HGLOBAL ); + hr = m_rDataObject->GetData( aTempFormat, &stgmedium ); + } + + if (FAILED(hr) && aFormatEtc.getTymed() == TYMED_HGLOBAL) + { + // Handle type is not memory, try stream. + CFormatEtc aTempFormat(aFormatEtc); + aTempFormat.setTymed(TYMED_ISTREAM); + hr = m_rDataObject->GetData(aTempFormat, &stgmedium); + } + + if ( FAILED( hr ) ) + { + OSL_ASSERT( (hr != E_INVALIDARG) && + (hr != DV_E_DVASPECT) && + (hr != DV_E_LINDEX) && + (hr != DV_E_TYMED) ); + + if ( DV_E_FORMATETC == hr ) + throw UnsupportedFlavorException( ); + else if ( STG_E_MEDIUMFULL == hr ) + throw IOException( ); + else + throw RuntimeException( ); + } + + ByteSequence_t byteStream; + + try + { + if ( CF_ENHMETAFILE == aFormatEtc.getClipformat() ) + byteStream = WinENHMFPictToOOMFPict( stgmedium.hEnhMetaFile ); + else if (CF_HDROP == aFormatEtc.getClipformat()) + byteStream = CF_HDROPToFileList(stgmedium.hGlobal); + else if ( CF_BITMAP == aFormatEtc.getClipformat() ) + { + byteStream = WinBITMAPToOOBMP(stgmedium.hBitmap); + if( aFormatEtc.getTymed() == TYMED_GDI && + ! stgmedium.pUnkForRelease ) + { + DeleteObject(stgmedium.hBitmap); + } + } + else + { + clipDataToByteStream( aFormatEtc.getClipformat( ), stgmedium, byteStream ); + + // format conversion if necessary + // #i124085# DIBV5 should not happen currently, but keep as a hint here + if(CF_DIBV5 == aFormatEtc.getClipformat() || CF_DIB == aFormatEtc.getClipformat()) + { + byteStream = WinDIBToOOBMP(byteStream); + } + else if(CF_METAFILEPICT == aFormatEtc.getClipformat()) + { + byteStream = WinMFPictToOOMFPict(byteStream); + } + } + + ReleaseStgMedium( &stgmedium ); + } + catch( CStgTransferHelper::CStgTransferException& ) + { + ReleaseStgMedium( &stgmedium ); + throw IOException( ); + } + + return byteStream; +} + +OUString CDOTransferable::synthesizeUnicodeText( ) +{ + ByteSequence_t aTextSequence; + CFormatEtc fetc; + LCID lcid = getLocaleFromClipboard( ); + sal_uInt32 cpForTxtCnvt = 0; + + if ( CF_TEXT == m_TxtFormatOnClipboard ) + { + fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_TEXT ); + aTextSequence = getClipboardData( fetc ); + + // determine the codepage used for text conversion + cpForTxtCnvt = getWinCPFromLocaleId( lcid, LOCALE_IDEFAULTANSICODEPAGE ).toInt32( ); + } + else if ( CF_OEMTEXT == m_TxtFormatOnClipboard ) + { + fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_OEMTEXT ); + aTextSequence = getClipboardData( fetc ); + + // determine the codepage used for text conversion + cpForTxtCnvt = getWinCPFromLocaleId( lcid, LOCALE_IDEFAULTCODEPAGE ).toInt32( ); + } + else + OSL_ASSERT( false ); + + CStgTransferHelper stgTransferHelper; + + // convert the text + MultiByteToWideCharEx( cpForTxtCnvt, + reinterpret_cast<char*>( aTextSequence.getArray( ) ), + -1, + stgTransferHelper, + false); + + CRawHGlobalPtr ptrHGlob(stgTransferHelper); + sal_Unicode* pWChar = static_cast<sal_Unicode*>(ptrHGlob.GetMemPtr()); + + return OUString(pWChar); +} + +bool CDOTransferable::compareDataFlavors( + const DataFlavor& lhs, const DataFlavor& rhs ) +{ + if ( !m_rXMimeCntFactory.is( ) ) + { + m_rXMimeCntFactory = MimeContentTypeFactory::create( m_xContext ); + } + + bool bRet = false; + + try + { + Reference< XMimeContentType > xLhs( m_rXMimeCntFactory->createMimeContentType( lhs.MimeType ) ); + Reference< XMimeContentType > xRhs( m_rXMimeCntFactory->createMimeContentType( rhs.MimeType ) ); + + if ( cmpFullMediaType( xLhs, xRhs ) ) + { + bRet = cmpAllContentTypeParameter( xLhs, xRhs ); + } + } + catch( IllegalArgumentException& ) + { + OSL_FAIL( "Invalid content type detected" ); + bRet = false; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/DOTransferable.hxx b/vcl/win/dtrans/DOTransferable.hxx new file mode 100644 index 0000000000..1824c7b448 --- /dev/null +++ b/vcl/win/dtrans/DOTransferable.hxx @@ -0,0 +1,98 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/datatransfer/XTransferable.hpp> + +#include <cppuhelper/implbase.hxx> +#include "DataFmtTransl.hxx" +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/XMimeContentType.hpp> +#include <com/sun/star/datatransfer/XSystemTransferable.hpp> +#include <cppuhelper/weakref.hxx> + +#include <systools/win32/comtools.hxx> + +#include <mutex> +#include <vector> + +// forward +class CFormatEtc; + +class CDOTransferable : public ::cppu::WeakImplHelper< + css::datatransfer::XTransferable> +{ +public: + typedef css::uno::Sequence< sal_Int8 > ByteSequence_t; + + // XTransferable + + virtual css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override; + + virtual css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override; + + virtual sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override; + + explicit CDOTransferable( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& xClipboard, + const std::vector<sal_uInt32>& rFormats); + + explicit CDOTransferable( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + IDataObjectPtr rDataObject ); + +private: + // some helper functions + + void initFlavorList( ); + void initFlavorListFromFormatList(const std::vector<sal_uInt32>& rFormats); + + void addSupportedFlavor( const css::datatransfer::DataFlavor& aFlavor ); + css::datatransfer::DataFlavor formatEtcToDataFlavor(sal_uInt32 cfFormat); + + void tryToGetIDataObjectIfAbsent(); + ByteSequence_t getClipboardData( CFormatEtc& aFormatEtc ); + OUString synthesizeUnicodeText( ); + + LCID getLocaleFromClipboard( ); + + bool compareDataFlavors( const css::datatransfer::DataFlavor& lhs, + const css::datatransfer::DataFlavor& rhs ); + +private: + css::uno::WeakReference<css::datatransfer::clipboard::XClipboard> m_xClipboard; + IDataObjectPtr m_rDataObject; + css::uno::Sequence< css::datatransfer::DataFlavor > m_FlavorList; + const css::uno::Reference< css::uno::XComponentContext > m_xContext; + CDataFormatTranslator m_DataFormatTranslator; + css::uno::Reference< css::datatransfer::XMimeContentTypeFactory > m_rXMimeCntFactory; + std::mutex m_aMutex; + bool m_bUnicodeRegistered; + CLIPFORMAT m_TxtFormatOnClipboard; + +// non supported operations +private: + CDOTransferable( const CDOTransferable& ); + CDOTransferable& operator=( const CDOTransferable& ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/DTransHelper.cxx b/vcl/win/dtrans/DTransHelper.cxx new file mode 100644 index 0000000000..66d18f9fb5 --- /dev/null +++ b/vcl/win/dtrans/DTransHelper.cxx @@ -0,0 +1,205 @@ +/* -*- 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 <rtl/ustring.h> +#include <osl/diagnose.h> +#include "DTransHelper.hxx" + +// implementation + +CStgTransferHelper::CStgTransferHelper( bool bAutoInit, + HGLOBAL hGlob, + bool bDelStgOnRelease ) : + m_lpStream( nullptr ), + m_bDelStgOnRelease( bDelStgOnRelease ) +{ + if ( bAutoInit ) + init( hGlob, m_bDelStgOnRelease ); +} + +// dtor + +CStgTransferHelper::~CStgTransferHelper( ) +{ + if ( m_lpStream ) + m_lpStream->Release( ); +} + +// TransferData into the + +void CStgTransferHelper::write( const void* lpData, ULONG cb, ULONG* cbWritten ) +{ + HRESULT hr = E_FAIL; + + if ( m_lpStream ) + hr = m_lpStream->Write( lpData, cb, cbWritten ); + + if ( FAILED( hr ) ) + throw CStgTransferException( hr ); + +#if OSL_DEBUG_LEVEL > 0 + HGLOBAL hGlob; + hr = GetHGlobalFromStream( m_lpStream, &hGlob ); + OSL_ASSERT( SUCCEEDED( hr ) ); + + /*DWORD dwSize =*/ GlobalSize( hGlob ); + /*LPVOID lpdbgData =*/ GlobalLock( hGlob ); + GlobalUnlock( hGlob ); +#endif +} + +// read + +void CStgTransferHelper::read( LPVOID pv, ULONG cb, ULONG* pcbRead ) +{ + HRESULT hr = E_FAIL; + + if ( m_lpStream ) + hr = m_lpStream->Read( pv, cb , pcbRead ); + + if ( FAILED( hr ) ) + throw CStgTransferException( hr ); +} + +// GetHGlobal + +HGLOBAL CStgTransferHelper::getHGlobal( ) const +{ + OSL_ASSERT( m_lpStream ); + + HGLOBAL hGlob = nullptr; + + if ( m_lpStream ) + { + HRESULT hr = GetHGlobalFromStream( m_lpStream, &hGlob ); + if ( FAILED( hr ) ) + hGlob = nullptr; + } + + return hGlob; +} + +// getIStream + +void CStgTransferHelper::getIStream( LPSTREAM* ppStream ) +{ + OSL_ASSERT( ppStream ); + *ppStream = m_lpStream; + if ( *ppStream ) + static_cast< LPUNKNOWN >( *ppStream )->AddRef( ); +} + +// Init + +void CStgTransferHelper::init( SIZE_T newSize, + sal_uInt32 uiFlags, + bool bDelStgOnRelease ) +{ + cleanup( ); + + m_bDelStgOnRelease = bDelStgOnRelease; + + HGLOBAL hGlob = GlobalAlloc( uiFlags, newSize ); + if ( nullptr == hGlob ) + throw CStgTransferException( STG_E_MEDIUMFULL ); + + HRESULT hr = CreateStreamOnHGlobal( hGlob, m_bDelStgOnRelease, &m_lpStream ); + if ( FAILED( hr ) ) + { + GlobalFree( hGlob ); + m_lpStream = nullptr; + throw CStgTransferException( hr ); + } + +#if OSL_DEBUG_LEVEL > 0 + STATSTG statstg; + hr = m_lpStream->Stat( &statstg, STATFLAG_DEFAULT ); + OSL_ASSERT( SUCCEEDED( hr ) ); +#endif +} + +// Init + +void CStgTransferHelper::init( HGLOBAL hGlob, + bool bDelStgOnRelease ) +{ + cleanup( ); + + m_bDelStgOnRelease = bDelStgOnRelease; + + HRESULT hr = CreateStreamOnHGlobal( hGlob, m_bDelStgOnRelease, &m_lpStream ); + if ( FAILED( hr ) ) + throw CStgTransferException( hr ); +} + +// free the global memory and invalidate the stream pointer + +void CStgTransferHelper::cleanup( ) +{ + if ( m_lpStream && !m_bDelStgOnRelease ) + { + HGLOBAL hGlob; + GetHGlobalFromStream( m_lpStream, &hGlob ); + GlobalFree( hGlob ); + } + + if ( m_lpStream ) + { + m_lpStream->Release( ); + m_lpStream = nullptr; + } +} + +// return the size of memory we point to + +sal_uInt32 CStgTransferHelper::memSize( CLIPFORMAT cf ) const +{ + DWORD dwSize = 0; + + if ( nullptr != m_lpStream ) + { + HGLOBAL hGlob; + GetHGlobalFromStream( m_lpStream, &hGlob ); + + if ( CF_TEXT == cf || RegisterClipboardFormatW( L"HTML Format" ) == cf ) + { + char* pText = static_cast< char* >( GlobalLock( hGlob ) ); + if ( pText ) + { + dwSize = strlen(pText) + 1; // strlen + trailing '\0' + GlobalUnlock( hGlob ); + } + } + else if ( CF_UNICODETEXT == cf ) + { + sal_Unicode* pText = static_cast< sal_Unicode* >( GlobalLock( hGlob ) ); + if ( pText ) + { + dwSize = rtl_ustr_getLength( pText ) * sizeof( sal_Unicode ); + GlobalUnlock( hGlob ); + } + } + else + dwSize = GlobalSize( hGlob ); + } + + return dwSize; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/DTransHelper.hxx b/vcl/win/dtrans/DTransHelper.hxx new file mode 100644 index 0000000000..d8ab3349cb --- /dev/null +++ b/vcl/win/dtrans/DTransHelper.hxx @@ -0,0 +1,167 @@ +/* -*- 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 . + */ + +#pragma once + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <objidl.h> +#include "WinClip.hxx" + +#define AUTO_INIT true + +// a helper class to manage a global memory area, the clients can write +// into the global memory area and extract the handle to the global mem +// note: not thread-safe + +class CStgTransferHelper +{ +public: + // will be thrown in case of failures + class CStgTransferException + { + public: + HRESULT m_hr; + explicit CStgTransferException( HRESULT hr ) : m_hr( hr ) {}; + }; + +public: + CStgTransferHelper( + bool bAutoInit = false, + HGLOBAL hGlob = nullptr, + bool bDelStgOnRelease = false ); + + ~CStgTransferHelper( ); + + void write( const void* lpData, ULONG cb, ULONG* cbWritten = nullptr ); + void read( LPVOID pv, ULONG cb, ULONG* pcbRead = nullptr ); + + HGLOBAL getHGlobal( ) const; + void getIStream( LPSTREAM* ppStream ); + + void init( + SIZE_T newSize, + sal_uInt32 uiFlags = GHND, + bool bDelStgOnRelease = false ); + + void init( + HGLOBAL hGlob, + bool bDelStgOnRelease = false ); + + // returns the size of the managed memory + sal_uInt32 memSize( CLIPFORMAT cf = CF_INVALID ) const; + + // free the global memory and necessary + // release the internal stream pointer + void cleanup( ); + +private: + LPSTREAM m_lpStream; + bool m_bDelStgOnRelease; + +private: + CStgTransferHelper( const CStgTransferHelper& ); + CStgTransferHelper& operator=( const CStgTransferHelper& ); +}; + +// something like an auto-pointer - allows access to the memory belonging +// to a HGLOBAL and automatically unlocks a global memory at destruction +// time + +class CRawHGlobalPtr +{ +public: + + // ctor + + explicit CRawHGlobalPtr( HGLOBAL hGlob ) : + m_hGlob( hGlob ), + m_bIsLocked( false ), + m_pGlobMem( nullptr ) + { + } + + // ctor + + explicit CRawHGlobalPtr( const CStgTransferHelper& theHGlobalHelper ) : + m_hGlob( theHGlobalHelper.getHGlobal( ) ), + m_bIsLocked( false ), + m_pGlobMem( nullptr ) + { + } + + // dtor + + ~CRawHGlobalPtr( ) + { + if ( m_bIsLocked ) + GlobalUnlock( m_hGlob ); + } + + // lock the global memory (initializes a + // pointer to this memory) + + BOOL Lock( ) + { + if ( !m_bIsLocked && ( nullptr != m_hGlob ) ) + { + m_pGlobMem = GlobalLock( m_hGlob ); + m_bIsLocked = ( nullptr != m_pGlobMem ); + } + + return m_bIsLocked; + } + + // unlock the global memory (invalidates the + // pointer to this memory) + + BOOL Unlock( ) + { + GlobalUnlock( m_hGlob ); + m_bIsLocked = false; + m_pGlobMem = nullptr; + + return ( NO_ERROR == GetLastError( ) ); + } + + // locks the global memory and returns a + // pointer to this memory + + LPVOID GetMemPtr( ) + { + Lock( ); + return m_pGlobMem; + } + + // size of mem we point to + + int MemSize( ) const + { + return GlobalSize( m_hGlob ); + } + +private: + HGLOBAL m_hGlob; + bool m_bIsLocked; + LPVOID m_pGlobMem; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/DataFmtTransl.cxx b/vcl/win/dtrans/DataFmtTransl.cxx new file mode 100644 index 0000000000..5daf0b453f --- /dev/null +++ b/vcl/win/dtrans/DataFmtTransl.cxx @@ -0,0 +1,252 @@ +/* -*- 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 "DataFmtTransl.hxx" +#include <rtl/string.hxx> +#include <osl/diagnose.h> +#include <rtl/tencinfo.h> +#include "ImplHelper.hxx" +#include "WinClip.hxx" +#include "MimeAttrib.hxx" +#include "DTransHelper.hxx" +#include <rtl/string.h> +#include <o3tl/char16_t2wchar_t.hxx> +#include "Fetc.hxx" +#include <com/sun/star/datatransfer/DataFormatTranslator.hpp> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <shlobj.h> + +using namespace com::sun::star::uno; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::lang; + +const Type CPPUTYPE_SALINT32 = cppu::UnoType<sal_Int32>::get(); +const Type CPPUTYPE_SALINT8 = cppu::UnoType<sal_Int8>::get(); +const Type CPPUTYPE_OUSTRING = cppu::UnoType<OUString>::get(); +const Type CPPUTYPE_SEQSALINT8 = cppu::UnoType<Sequence< sal_Int8>>::get(); +const sal_Int32 MAX_CLIPFORMAT_NAME = 256; + +CDataFormatTranslator::CDataFormatTranslator( const Reference< XComponentContext >& rxContext ) +{ + m_XDataFormatTranslator = DataFormatTranslator::create( rxContext ); +} + +CFormatEtc CDataFormatTranslator::getFormatEtcFromDataFlavor( const DataFlavor& aDataFlavor ) const +{ + sal_Int32 cf = CF_INVALID; + + try + { + if( m_XDataFormatTranslator.is( ) ) + { + Any aFormat = m_XDataFormatTranslator->getSystemDataTypeFromDataFlavor( aDataFlavor ); + + if ( aFormat.hasValue( ) ) + { + if ( aFormat.getValueType( ) == CPPUTYPE_SALINT32 ) + { + aFormat >>= cf; + OSL_ENSURE( CF_INVALID != cf, "Invalid Clipboard format delivered" ); + } + else if ( aFormat.getValueType( ) == CPPUTYPE_OUSTRING ) + { + OUString aClipFmtName; + aFormat >>= aClipFmtName; + + OSL_ASSERT( aClipFmtName.getLength( ) ); + cf = RegisterClipboardFormatW( o3tl::toW(aClipFmtName.getStr( )) ); + + OSL_ENSURE( CF_INVALID != cf, "RegisterClipboardFormat failed" ); + } + else + OSL_FAIL( "Wrong Any-Type detected" ); + } + } + } + catch( ... ) + { + OSL_FAIL( "Unexpected error" ); + } + + return sal::static_int_cast<CFormatEtc>(getFormatEtcForClipformat( sal::static_int_cast<CLIPFORMAT>(cf) )); +} + +DataFlavor CDataFormatTranslator::getDataFlavorFromFormatEtc(sal_uInt32 cfFormat, LCID lcid) const +{ + DataFlavor aFlavor; + + try + { + CLIPFORMAT aClipformat = cfFormat; + + Any aAny; + aAny <<= static_cast< sal_Int32 >( aClipformat ); + + if ( isOemOrAnsiTextFormat( aClipformat ) ) + { + aFlavor.MimeType = "text/plain;charset="; + aFlavor.MimeType += getTextCharsetFromLCID( lcid, aClipformat ); + + aFlavor.HumanPresentableName = "OEM/ANSI Text"; + aFlavor.DataType = CPPUTYPE_SEQSALINT8; + } + else if ( CF_INVALID != aClipformat ) + { + if ( m_XDataFormatTranslator.is( ) ) + { + aFlavor = m_XDataFormatTranslator->getDataFlavorFromSystemDataType( aAny ); + + if ( !aFlavor.MimeType.getLength( ) ) + { + // lookup of DataFlavor from clipboard format id + // failed, so we try to resolve via clipboard + // format name + OUString clipFormatName = getClipboardFormatName( aClipformat ); + + // if we could not get a clipboard format name an + // error must have occurred or it is a standard + // clipboard format that we don't translate, e.g. + // CF_BITMAP (the office only uses CF_DIB) + if ( clipFormatName.getLength( ) ) + { + aAny <<= clipFormatName; + aFlavor = m_XDataFormatTranslator->getDataFlavorFromSystemDataType( aAny ); + } + } + } + } + } + catch( ... ) + { + OSL_FAIL( "Unexpected error" ); + } + + return aFlavor; +} + +CFormatEtc CDataFormatTranslator::getFormatEtcForClipformatName( const OUString& aClipFmtName ) +{ + // check parameter + if ( !aClipFmtName.getLength( ) ) + return CFormatEtc( CF_INVALID ); + + CLIPFORMAT cf = sal::static_int_cast<CLIPFORMAT>(RegisterClipboardFormatW( o3tl::toW(aClipFmtName.getStr( )) )); + return getFormatEtcForClipformat( cf ); +} + +OUString CDataFormatTranslator::getClipboardFormatName( CLIPFORMAT aClipformat ) +{ + OSL_PRECOND( CF_INVALID != aClipformat, "Invalid clipboard format" ); + + sal_Unicode wBuff[ MAX_CLIPFORMAT_NAME + 1 ]; // Null terminator isn't counted, apparently. + sal_Int32 nLen = GetClipboardFormatNameW( aClipformat, o3tl::toW(wBuff), MAX_CLIPFORMAT_NAME ); + + return OUString( wBuff, nLen ); +} + +CFormatEtc CDataFormatTranslator::getFormatEtcForClipformat( CLIPFORMAT cf ) +{ + CFormatEtc fetc( cf, TYMED_NULL, nullptr, DVASPECT_CONTENT ); + + switch( cf ) + { + case CF_METAFILEPICT: + fetc.setTymed( TYMED_MFPICT ); + break; + + case CF_ENHMETAFILE: + fetc.setTymed( TYMED_ENHMF ); + break; + + default: + fetc.setTymed( TYMED_HGLOBAL /*| TYMED_ISTREAM*/ ); + } + + /* + hack: in order to paste urls copied by Internet Explorer + with "copy link" we set the lindex member to 0 + but if we really want to support CFSTR_FILECONTENT and + the accompany format CFSTR_FILEDESCRIPTOR (FileGroupDescriptor) + the client of the clipboard service has to provide a id + of which FileContents it wants to paste + see MSDN: "Handling Shell Data Transfer Scenarios" + */ + if ( cf == RegisterClipboardFormat( CFSTR_FILECONTENTS ) ) + fetc.setLindex( 0 ); + + return fetc; +} + +bool CDataFormatTranslator::isOemOrAnsiTextFormat( CLIPFORMAT cf ) +{ + return ( (cf == CF_TEXT) || (cf == CF_OEMTEXT) ); +} + +bool CDataFormatTranslator::isUnicodeTextFormat( CLIPFORMAT cf ) +{ + return ( cf == CF_UNICODETEXT ); +} + +bool CDataFormatTranslator::isTextFormat( CLIPFORMAT cf ) +{ + return ( isOemOrAnsiTextFormat( cf ) || isUnicodeTextFormat( cf ) ); +} + +bool CDataFormatTranslator::isHTMLFormat( CLIPFORMAT cf ) +{ + OUString clipFormatName = getClipboardFormatName( cf ); + return ( clipFormatName == "HTML Format" ); +} + +bool CDataFormatTranslator::isTextHtmlFormat( CLIPFORMAT cf ) +{ + OUString clipFormatName = getClipboardFormatName( cf ); + return clipFormatName.equalsIgnoreAsciiCase( "HTML (HyperText Markup Language)" ); +} + +OUString CDataFormatTranslator::getTextCharsetFromLCID( LCID lcid, CLIPFORMAT aClipformat ) +{ + OSL_ASSERT( isOemOrAnsiTextFormat( aClipformat ) ); + + OUString charset; + if ( CF_TEXT == aClipformat ) + { + charset = getMimeCharsetFromLocaleId( + lcid, + LOCALE_IDEFAULTANSICODEPAGE, + PRE_WINDOWS_CODEPAGE ); + } + else if ( CF_OEMTEXT == aClipformat ) + { + charset = getMimeCharsetFromLocaleId( + lcid, + LOCALE_IDEFAULTCODEPAGE, + PRE_OEM_CODEPAGE ); + } + else // CF_UNICODE + OSL_ASSERT( false ); + + return charset; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/DataFmtTransl.hxx b/vcl/win/dtrans/DataFmtTransl.hxx new file mode 100644 index 0000000000..e003f2538b --- /dev/null +++ b/vcl/win/dtrans/DataFmtTransl.hxx @@ -0,0 +1,64 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/datatransfer/XDataFormatTranslator.hpp> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <sal/types.h> +#include <rtl/ustring.hxx> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <objidl.h> + +// declaration + +class CFormatEtc; + +class CDataFormatTranslator +{ +public: + explicit CDataFormatTranslator( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + CFormatEtc getFormatEtcFromDataFlavor( const css::datatransfer::DataFlavor& aDataFlavor ) const; + css::datatransfer::DataFlavor getDataFlavorFromFormatEtc( + sal_uInt32 cfFormat, LCID lcid = GetThreadLocale()) const; + + static CFormatEtc getFormatEtcForClipformat( CLIPFORMAT cf ); + static CFormatEtc getFormatEtcForClipformatName( const OUString& aClipFmtName ); + static OUString getClipboardFormatName( CLIPFORMAT aClipformat ); + + static bool isHTMLFormat( CLIPFORMAT cf ); + static bool isTextHtmlFormat( CLIPFORMAT cf ); + static bool isOemOrAnsiTextFormat( CLIPFORMAT cf ); + static bool isUnicodeTextFormat( CLIPFORMAT cf ); + static bool isTextFormat( CLIPFORMAT cf ); + +private: + static OUString getTextCharsetFromLCID( LCID lcid, CLIPFORMAT aClipformat ); + +private: + css::uno::Reference< css::datatransfer::XDataFormatTranslator > m_XDataFormatTranslator; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/DtObjFactory.cxx b/vcl/win/dtrans/DtObjFactory.cxx new file mode 100644 index 0000000000..8fe03789b1 --- /dev/null +++ b/vcl/win/dtrans/DtObjFactory.cxx @@ -0,0 +1,34 @@ +/* -*- 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 "DtObjFactory.hxx" +#include "XTDataObject.hxx" + +using namespace com::sun::star::uno; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::lang; + +IDataObjectPtr +CDTransObjFactory::createDataObjFromTransferable(const Reference<XComponentContext>& rxContext, + const Reference<XTransferable>& refXTransferable) +{ + return (IDataObjectPtr(new CXTDataObject(rxContext, refXTransferable))); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/DtObjFactory.hxx b/vcl/win/dtrans/DtObjFactory.hxx new file mode 100644 index 0000000000..b5d5f50075 --- /dev/null +++ b/vcl/win/dtrans/DtObjFactory.hxx @@ -0,0 +1,33 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <systools/win32/comtools.hxx> + +namespace CDTransObjFactory +{ + IDataObjectPtr createDataObjFromTransferable( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::datatransfer::XTransferable >& refXTransferable ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/Fetc.cxx b/vcl/win/dtrans/Fetc.cxx new file mode 100644 index 0000000000..9bcb2f609f --- /dev/null +++ b/vcl/win/dtrans/Fetc.cxx @@ -0,0 +1,166 @@ +/* -*- 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 <osl/diagnose.h> +#include "Fetc.hxx" +#include "ImplHelper.hxx" + +CFormatEtc::CFormatEtc( ) +{ + m_FormatEtc.cfFormat = 0; + m_FormatEtc.ptd = nullptr; + m_FormatEtc.dwAspect = 0; + m_FormatEtc.lindex = -1; + m_FormatEtc.tymed = TYMED_NULL; +} + +// transfer of ownership + +CFormatEtc::CFormatEtc( const FORMATETC& aFormatEtc ) +{ + CopyFormatEtc( &m_FormatEtc, &const_cast< FORMATETC& >( aFormatEtc ) ); +} + +CFormatEtc::~CFormatEtc( ) +{ + DeleteTargetDevice( m_FormatEtc.ptd ); +} + +CFormatEtc::CFormatEtc( CLIPFORMAT cf, DWORD tymed, DVTARGETDEVICE* ptd, DWORD dwAspect, LONG lindex ) +{ + m_FormatEtc.cfFormat = cf; + m_FormatEtc.ptd = CopyTargetDevice( ptd ); + m_FormatEtc.dwAspect = dwAspect; + m_FormatEtc.lindex = lindex; + m_FormatEtc.tymed = tymed; +} + +CFormatEtc::CFormatEtc( const CFormatEtc& theOther ) +{ + m_FormatEtc.cfFormat = theOther.m_FormatEtc.cfFormat; + m_FormatEtc.ptd = CopyTargetDevice( theOther.m_FormatEtc.ptd ); + m_FormatEtc.dwAspect = theOther.m_FormatEtc.dwAspect; + m_FormatEtc.lindex = theOther.m_FormatEtc.lindex; + m_FormatEtc.tymed = theOther.m_FormatEtc.tymed; +} + +CFormatEtc& CFormatEtc::operator=( const CFormatEtc& theOther ) +{ + if ( this != &theOther ) + { + DeleteTargetDevice( m_FormatEtc.ptd ); + + m_FormatEtc.cfFormat = theOther.m_FormatEtc.cfFormat; + m_FormatEtc.ptd = CopyTargetDevice( theOther.m_FormatEtc.ptd ); + m_FormatEtc.dwAspect = theOther.m_FormatEtc.dwAspect; + m_FormatEtc.lindex = theOther.m_FormatEtc.lindex; + m_FormatEtc.tymed = theOther.m_FormatEtc.tymed; + } + + return *this; +} + +CFormatEtc::operator FORMATETC*( ) +{ + return &m_FormatEtc; +} + +CFormatEtc::operator FORMATETC( ) +{ + return m_FormatEtc; +} + +void CFormatEtc::getFORMATETC( LPFORMATETC lpFormatEtc ) +{ + OSL_ASSERT( lpFormatEtc ); + OSL_ASSERT( !IsBadWritePtr( lpFormatEtc, sizeof( FORMATETC ) ) ); + + CopyFormatEtc( lpFormatEtc, &m_FormatEtc ); +} + +CLIPFORMAT CFormatEtc::getClipformat( ) const +{ + return m_FormatEtc.cfFormat; +} + +DWORD CFormatEtc::getTymed( ) const +{ + return m_FormatEtc.tymed; +} + +void CFormatEtc::getTargetDevice( DVTARGETDEVICE** lpDvTargetDevice ) const +{ + OSL_ASSERT( lpDvTargetDevice ); + OSL_ASSERT( !IsBadWritePtr( lpDvTargetDevice, sizeof( DVTARGETDEVICE ) ) ); + + *lpDvTargetDevice = nullptr; + + if ( m_FormatEtc.ptd ) + *lpDvTargetDevice = CopyTargetDevice( m_FormatEtc.ptd ); +} + +DWORD CFormatEtc::getDvAspect( ) const +{ + return m_FormatEtc.dwAspect; +} + +LONG CFormatEtc::getLindex( ) const +{ + return m_FormatEtc.lindex; +} + +void CFormatEtc::setClipformat( CLIPFORMAT cf ) +{ + m_FormatEtc.cfFormat = cf; +} + +void CFormatEtc::setTymed( DWORD tymed ) +{ + m_FormatEtc.tymed = tymed; +} + +// transfer of ownership! + +void CFormatEtc::setTargetDevice( DVTARGETDEVICE* ptd ) +{ + DeleteTargetDevice( m_FormatEtc.ptd ); + m_FormatEtc.ptd = ptd; +} + +void CFormatEtc::setDvAspect( DWORD dwAspect ) +{ + m_FormatEtc.dwAspect = dwAspect; +} + +void CFormatEtc::setLindex( LONG lindex ) +{ + m_FormatEtc.lindex = lindex; +} + +bool operator==( const CFormatEtc& lhs, const CFormatEtc& rhs ) +{ + return CompareFormatEtc( &lhs.m_FormatEtc, &rhs.m_FormatEtc ); +} + +bool operator!=( const CFormatEtc& lhs, const CFormatEtc& rhs ) +{ + return !( lhs == rhs ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/Fetc.hxx b/vcl/win/dtrans/Fetc.hxx new file mode 100644 index 0000000000..e0880acd64 --- /dev/null +++ b/vcl/win/dtrans/Fetc.hxx @@ -0,0 +1,76 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <objidl.h> + +/********************************************************************** + stl container elements must fulfill the following requirements: + 1. they need a copy ctor and assignment operator(?) + 2. they must be comparable + because the FORMATETC structure has a pointer to a TARGETDEVICE + structure we need a simple wrapper class to fulfill these needs +***********************************************************************/ + +class CFormatEtc +{ +public: + CFormatEtc( ); + explicit CFormatEtc( const FORMATETC& aFormatEtc ); + CFormatEtc( CLIPFORMAT cf, DWORD tymed = TYMED_HGLOBAL, DVTARGETDEVICE* ptd = nullptr, DWORD dwAspect = DVASPECT_CONTENT, LONG lindex = -1 ); + CFormatEtc( const CFormatEtc& theOther ); + + ~CFormatEtc( ); + + CFormatEtc& operator=( const CFormatEtc& theOther ); + operator FORMATETC*( ); + operator FORMATETC( ); + + void getFORMATETC( LPFORMATETC lpFormatEtc ); + + CLIPFORMAT getClipformat( ) const; + DWORD getTymed( ) const; + void getTargetDevice( DVTARGETDEVICE** ptd ) const; + DWORD getDvAspect( ) const; + LONG getLindex( ) const; + + void setClipformat( CLIPFORMAT cf ); + void setTymed( DWORD tymed ); + void setTargetDevice( DVTARGETDEVICE* ptd ); + void setDvAspect( DWORD dwAspect ); + void setLindex( LONG lindex ); + +private: + FORMATETC m_FormatEtc; + + friend bool operator==( const CFormatEtc& lhs, const CFormatEtc& rhs ); + friend bool operator!=( const CFormatEtc& lhs, const CFormatEtc& rhs ); +}; + +bool operator==( const CFormatEtc& lhs, const CFormatEtc& rhs ); +bool operator!=( const CFormatEtc& lhs, const CFormatEtc& rhs ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/FetcList.cxx b/vcl/win/dtrans/FetcList.cxx new file mode 100644 index 0000000000..5ace3cdca6 --- /dev/null +++ b/vcl/win/dtrans/FetcList.cxx @@ -0,0 +1,340 @@ +/* -*- 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 <osl/diagnose.h> +#include "FetcList.hxx" +#include "Fetc.hxx" +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/XMimeContentType.hpp> + +#include "DataFmtTransl.hxx" +#include "ImplHelper.hxx" +#include "WinClip.hxx" + +#include <algorithm> + +#include "MimeAttrib.hxx" + +using namespace com::sun::star::uno; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; + +LCID CFormatRegistrar::m_TxtLocale = 0; +sal_uInt32 CFormatRegistrar::m_TxtCodePage = GetACP( ); + +CFormatEtcContainer::CFormatEtcContainer( ) +{ + m_EnumIterator = m_FormatMap.begin( ); +} + +void CFormatEtcContainer::addFormatEtc( const CFormatEtc& fetc ) +{ + m_FormatMap.push_back( fetc ); +} + +void CFormatEtcContainer::removeFormatEtc( const CFormatEtc& fetc ) +{ + FormatEtcMap_t::iterator iter = + find( m_FormatMap.begin(), m_FormatMap.end(), fetc ); + + if ( iter != m_FormatMap.end( ) ) + m_FormatMap.erase( iter ); +} + +void CFormatEtcContainer::removeAllFormatEtc( ) +{ + m_FormatMap.clear( ); +} + +bool CFormatEtcContainer::hasFormatEtc( const CFormatEtc& fetc ) const +{ + FormatEtcMap_t::const_iterator iter = + find( m_FormatMap.begin(), m_FormatMap.end(), fetc ); + + return ( iter != m_FormatMap.end( ) ); +} + +bool CFormatEtcContainer::hasElements( ) const +{ + return !m_FormatMap.empty(); +} + +void CFormatEtcContainer::beginEnumFormatEtc( ) +{ + m_EnumIterator = m_FormatMap.begin( ); +} + +sal_uInt32 CFormatEtcContainer::nextFormatEtc( LPFORMATETC lpFetc, + sal_uInt32 aNum ) +{ + OSL_ASSERT( lpFetc ); + OSL_ASSERT( !IsBadWritePtr( lpFetc, sizeof( FORMATETC ) * aNum ) ); + + sal_uInt32 nFetched = 0; + + for ( sal_uInt32 i = 0; i < aNum; i++, nFetched++, lpFetc++, ++m_EnumIterator ) + { + if ( m_EnumIterator == m_FormatMap.end() ) + break; + CopyFormatEtc( lpFetc, *m_EnumIterator ); + } + + return nFetched; +} + +bool CFormatEtcContainer::skipFormatEtc( sal_uInt32 aNum ) +{ + FormatEtcMap_t::const_iterator iter_end = m_FormatMap.end( ); + for ( sal_uInt32 i = 0; + (i < aNum) && (m_EnumIterator != iter_end); + i++, ++m_EnumIterator ) + ;/* intentionally left empty */ + + return ( m_EnumIterator != m_FormatMap.end( ) ); +} + +CFormatRegistrar::CFormatRegistrar( const Reference< XComponentContext >& rxContext, + const CDataFormatTranslator& aDataFormatTranslator ) : + m_DataFormatTranslator( aDataFormatTranslator ), + m_bHasSynthesizedLocale( false ), + m_xContext( rxContext ) +{ +} + +// this function converts all DataFlavors of the given FlavorList into +// an appropriate FORMATETC structure, for some formats like unicodetext, +// text and text/html we will offer an accompany format e.g.: +// +// DataFlavor | Registered Clipformat | Registered accompany clipformat +// -------------------------|---------------------------|----------------------------------- +// text/plain;charset=ansi | CF_TEXT | CF_UNICODETEXT +// | | CF_LOCALE (if charset != GetACP() +// | | +// text/plain;charset=oem | CF_OEMTEXT | CF_UNICODETEXT +// | | CF_LOCALE (if charset != GetOEMCP() +// | | +// text/plain;charset=utf-16| CF_UNICODETEXT | CF_TEXT +// | | +// text/html | HTML (Hypertext ...) | HTML Format +// | | +// +// if some tries to register different text formats with different charsets the last +// registered wins and the others are ignored + +void CFormatRegistrar::RegisterFormats( + const Reference< XTransferable >& aXTransferable, CFormatEtcContainer& aFormatEtcContainer ) +{ + Sequence< DataFlavor > aFlavorList = aXTransferable->getTransferDataFlavors( ); + sal_Int32 nFlavors = aFlavorList.getLength( ); + bool bUnicodeRegistered = false; + DataFlavor aFlavor; + + for( sal_Int32 i = 0; i < nFlavors; i++ ) + { + aFlavor = aFlavorList[i]; + CFormatEtc fetc = m_DataFormatTranslator.getFormatEtcFromDataFlavor( aFlavor ); + + // maybe an internal format so we ignore it + if ( CF_INVALID == fetc.getClipformat( ) ) + continue; + + if ( !needsToSynthesizeAccompanyFormats( fetc ) ) + aFormatEtcContainer.addFormatEtc( fetc ); + else + { + // if we haven't registered any text format up to now + if ( CDataFormatTranslator::isTextFormat( fetc.getClipformat() ) && !bUnicodeRegistered ) + { + // if the transferable supports unicode text we ignore + // any further text format the transferable offers + // because we can create it from Unicode text in addition + // we register CF_TEXT for non unicode clients + if ( CDataFormatTranslator::isUnicodeTextFormat( fetc.getClipformat() ) ) + { + aFormatEtcContainer.addFormatEtc( fetc ); // add CF_UNICODE + aFormatEtcContainer.addFormatEtc( + CDataFormatTranslator::getFormatEtcForClipformat( CF_TEXT ) ); // add CF_TEXT + bUnicodeRegistered = true; + } + else if ( !hasUnicodeFlavor( aXTransferable ) ) + { + // we try to investigate the charset and make a valid + // windows codepage from this charset the default + // return value is the result of GetACP( ) + OUString charset = getCharsetFromDataFlavor( aFlavor ); + sal_uInt32 txtCP = getWinCPFromMimeCharset( charset ); + + // we try to get a Locale appropriate for this codepage + if ( findLocaleForTextCodePage( ) ) + { + m_TxtCodePage = txtCP; + + aFormatEtcContainer.addFormatEtc( + CDataFormatTranslator::getFormatEtcForClipformat( CF_UNICODETEXT ) ); + + if ( !IsOEMCP( m_TxtCodePage ) ) + aFormatEtcContainer.addFormatEtc( + CDataFormatTranslator::getFormatEtcForClipformat( CF_TEXT ) ); + else + aFormatEtcContainer.addFormatEtc( + CDataFormatTranslator::getFormatEtcForClipformat( CF_OEMTEXT ) ); + + aFormatEtcContainer.addFormatEtc( + CDataFormatTranslator::getFormatEtcForClipformat( CF_LOCALE ) ); + + // we save the flavor so it's easier when + // queried for it in XTDataObject::GetData(...) + m_RegisteredTextFlavor = aFlavor; + m_bHasSynthesizedLocale = true; + } + } + } + else if ( CDataFormatTranslator::isTextHtmlFormat( fetc.getClipformat( ) ) ) // Html (Hyper Text...) + { + // we add text/html ( HTML (HyperText Markup Language) ) + aFormatEtcContainer.addFormatEtc( fetc ); + + // and HTML Format + aFormatEtcContainer.addFormatEtc( + CDataFormatTranslator::getFormatEtcForClipformatName( "HTML Format" ) ); + } + } + } +} + +bool CFormatRegistrar::hasSynthesizedLocale( ) const +{ + return m_bHasSynthesizedLocale; +} + +LCID CFormatRegistrar::getSynthesizedLocale( ) +{ + return m_TxtLocale; +} + +sal_uInt32 CFormatRegistrar::getRegisteredTextCodePage( ) +{ + return m_TxtCodePage; +} + +DataFlavor CFormatRegistrar::getRegisteredTextFlavor( ) const +{ + return m_RegisteredTextFlavor; +} + +bool CFormatRegistrar::isSynthesizeableFormat( const CFormatEtc& aFormatEtc ) +{ + return ( CDataFormatTranslator::isOemOrAnsiTextFormat( aFormatEtc.getClipformat() ) || + CDataFormatTranslator::isUnicodeTextFormat( aFormatEtc.getClipformat() ) || + CDataFormatTranslator::isHTMLFormat( aFormatEtc.getClipformat() ) ); +} + +inline +bool CFormatRegistrar::needsToSynthesizeAccompanyFormats( const CFormatEtc& aFormatEtc ) +{ + return ( CDataFormatTranslator::isOemOrAnsiTextFormat( aFormatEtc.getClipformat() ) || + CDataFormatTranslator::isUnicodeTextFormat( aFormatEtc.getClipformat() ) || + CDataFormatTranslator::isTextHtmlFormat( aFormatEtc.getClipformat( ) ) ); +} + +OUString CFormatRegistrar::getCharsetFromDataFlavor( const DataFlavor& aFlavor ) +{ + OUString charset; + + try + { + Reference< XMimeContentTypeFactory > xMimeFac = + MimeContentTypeFactory::create(m_xContext); + + Reference< XMimeContentType > xMimeType( xMimeFac->createMimeContentType( aFlavor.MimeType ) ); + if ( xMimeType->hasParameter( TEXTPLAIN_PARAM_CHARSET ) ) + charset = xMimeType->getParameterValue( TEXTPLAIN_PARAM_CHARSET ); + else + charset = getMimeCharsetFromWinCP( GetACP( ), PRE_WINDOWS_CODEPAGE ); + } + catch(NoSuchElementException&) + { + OSL_FAIL( "Unexpected" ); + } + catch(...) + { + OSL_FAIL( "Invalid data flavor" ); + } + + return charset; +} + +bool CFormatRegistrar::hasUnicodeFlavor( const Reference< XTransferable >& aXTransferable ) const +{ + DataFlavor aFlavor = m_DataFormatTranslator.getDataFlavorFromFormatEtc(CF_UNICODETEXT); + + return aXTransferable->isDataFlavorSupported( aFlavor ); +} + +bool CFormatRegistrar::findLocaleForTextCodePage( ) +{ + m_TxtLocale = 0; + EnumSystemLocalesA( CFormatRegistrar::EnumLocalesProc, LCID_INSTALLED ); + return IsValidLocale( m_TxtLocale, LCID_INSTALLED ); +} + +bool CFormatRegistrar::isLocaleCodePage( LCID lcid, LCTYPE lctype, sal_uInt32 codepage ) +{ + char buff[6]; + sal_uInt32 localeCodePage; + + OSL_ASSERT( IsValidLocale( lcid, LCID_INSTALLED ) ); + + // get the ansi codepage of the current locale + GetLocaleInfoA( lcid, lctype, buff, sizeof( buff ) ); + localeCodePage = atol( buff ); + + return ( localeCodePage == codepage ); +} + +inline +bool CFormatRegistrar::isLocaleOemCodePage( LCID lcid, sal_uInt32 codepage ) +{ + return isLocaleCodePage( lcid, LOCALE_IDEFAULTCODEPAGE, codepage ); +} + +inline +bool CFormatRegistrar::isLocaleAnsiCodePage( LCID lcid, sal_uInt32 codepage ) +{ + return isLocaleCodePage( lcid, LOCALE_IDEFAULTANSICODEPAGE, codepage ); +} + +BOOL CALLBACK CFormatRegistrar::EnumLocalesProc( LPSTR lpLocaleStr ) +{ + // the lpLocaleStr parameter is hexadecimal + LCID lcid = strtol( lpLocaleStr, nullptr, 16 ); + + if ( isLocaleAnsiCodePage( lcid, CFormatRegistrar::m_TxtCodePage ) || + isLocaleOemCodePage( lcid, CFormatRegistrar::m_TxtCodePage ) ) + { + CFormatRegistrar::m_TxtLocale = lcid; + return false; // stop enumerating + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/FetcList.hxx b/vcl/win/dtrans/FetcList.hxx new file mode 100644 index 0000000000..2df28ff575 --- /dev/null +++ b/vcl/win/dtrans/FetcList.hxx @@ -0,0 +1,139 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include "Fetc.hxx" + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +#include <vector> + +/***************************************************************** + a simple container for FORMATECT structures + instances of this class are not thread-safe +*****************************************************************/ + +class CFormatEtcContainer +{ +public: + CFormatEtcContainer( ); + + // duplicates not allowed + void addFormatEtc( const CFormatEtc& fetc ); + + // removes the specified formatetc + void removeFormatEtc( const CFormatEtc& fetc ); + + // removes the formatetc at pos + void removeAllFormatEtc( ); + + bool hasFormatEtc( const CFormatEtc& fetc ) const; + + bool hasElements( ) const; + + // begin enumeration + void beginEnumFormatEtc( ); + + // copies the specified number of formatetc structures starting + // at the current enum position + // the return value is the number of copied elements; if the + // current enum position is at the end the return value is 0 + sal_uInt32 nextFormatEtc( LPFORMATETC lpFetc, sal_uInt32 aNum = 1 ); + + // skips the specified number of elements in the container + bool skipFormatEtc( sal_uInt32 aNum ); + +protected: + typedef std::vector< CFormatEtc > FormatEtcMap_t; + +private: + FormatEtcMap_t m_FormatMap; + FormatEtcMap_t::iterator m_EnumIterator; +}; + +/***************************************************************** + a helper class which converts data flavors to clipformats, + creates an appropriate formatetc structures and if possible + synthesizes clipboard formats if necessary, e.g. if text + is provided a locale will also be provided; + the class registers the formatetc within a CFormatEtcContainer + + instances of this class are not thread-safe and multiple + instances of this class would use the same static variables + that's why this class should not be used by multiple threads, + only one thread of a process should use it +*****************************************************************/ + +// forward +class CDataFormatTranslator; + +class CFormatRegistrar +{ +public: + CFormatRegistrar( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const CDataFormatTranslator& aDataFormatTranslator ); + + void RegisterFormats( const css::uno::Reference< css::datatransfer::XTransferable >& aXTransferable, + CFormatEtcContainer& aFormatEtcContainer ); + + bool hasSynthesizedLocale( ) const; + static LCID getSynthesizedLocale( ); + static sal_uInt32 getRegisteredTextCodePage( ); + css::datatransfer::DataFlavor getRegisteredTextFlavor( ) const; + + static bool isSynthesizeableFormat( const CFormatEtc& aFormatEtc ); + static bool needsToSynthesizeAccompanyFormats( const CFormatEtc& aFormatEtc ); + +private: + OUString getCharsetFromDataFlavor( const css::datatransfer::DataFlavor& aFlavor ); + + bool hasUnicodeFlavor( + const css::uno::Reference< css::datatransfer::XTransferable >& aXTransferable ) const; + + static bool findLocaleForTextCodePage( ); + + static bool isLocaleOemCodePage( LCID lcid, sal_uInt32 codepage ); + static bool isLocaleAnsiCodePage( LCID lcid, sal_uInt32 codepage ); + static bool isLocaleCodePage( LCID lcid, LCTYPE lctype, sal_uInt32 codepage ); + + static BOOL CALLBACK EnumLocalesProc( LPSTR lpLocaleStr ); + +private: + const CDataFormatTranslator& m_DataFormatTranslator; + bool m_bHasSynthesizedLocale; + css::datatransfer::DataFlavor m_RegisteredTextFlavor; + + const css::uno::Reference< css::uno::XComponentContext > m_xContext; + + static LCID m_TxtLocale; + static sal_uInt32 m_TxtCodePage; + +private: + CFormatRegistrar( const CFormatRegistrar& ); + CFormatRegistrar& operator=( const CFormatRegistrar& ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/FmtFilter.cxx b/vcl/win/dtrans/FmtFilter.cxx new file mode 100644 index 0000000000..27d051f7e8 --- /dev/null +++ b/vcl/win/dtrans/FmtFilter.cxx @@ -0,0 +1,435 @@ +/* -*- 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 <string.h> + +#include "FmtFilter.hxx" + +#include <o3tl/safeint.hxx> +#include <o3tl/unit_conversion.hxx> +#include <osl/diagnose.h> + +#include <shobjidl.h> +#include <shlguid.h> +#include <objidl.h> +#include <shellapi.h> + +#include <string> +#include <sstream> +#include <vector> +#include <iomanip> + +#include <systools/win32/comtools.hxx> + +using namespace com::sun::star::uno; + +namespace { + +#pragma pack(2) +struct METAFILEHEADER +{ + DWORD key; + short hmf; + SMALL_RECT bbox; + WORD inch; + DWORD reserved; + WORD checksum; +}; +#pragma pack() + +} + +// convert a windows metafile picture to a LibreOffice metafile picture + +Sequence< sal_Int8 > WinMFPictToOOMFPict( Sequence< sal_Int8 >& aMetaFilePict ) +{ + OSL_ASSERT( aMetaFilePict.getLength( ) == sizeof( METAFILEPICT ) ); + + Sequence< sal_Int8 > mfpictStream; + METAFILEPICT* pMFPict = reinterpret_cast< METAFILEPICT* >( aMetaFilePict.getArray( ) ); + HMETAFILE hMf = pMFPict->hMF; + sal_uInt32 nCount = GetMetaFileBitsEx( hMf, 0, nullptr ); + + if ( nCount > 0 ) + { + mfpictStream.realloc( nCount + sizeof( METAFILEHEADER ) ); + + METAFILEHEADER* pMFHeader = reinterpret_cast< METAFILEHEADER* >( mfpictStream.getArray( ) ); + SMALL_RECT aRect = { 0, + 0, + static_cast< short >( pMFPict->xExt ), + static_cast< short >( pMFPict->yExt ) }; + USHORT nInch; + + switch( pMFPict->mm ) + { + case MM_TEXT: + nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::pt); + break; + + case MM_LOMETRIC: + nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm10); + break; + + case MM_HIMETRIC: + nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100); + break; + + case MM_LOENGLISH: + nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::in100); + break; + + case MM_HIENGLISH: + case MM_ISOTROPIC: + case MM_ANISOTROPIC: + nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::in1000); + break; + + case MM_TWIPS: + nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::twip); + break; + + default: + nInch = o3tl::convert(1, o3tl::Length::in, o3tl::Length::master); + } + + pMFHeader->key = 0x9AC6CDD7L; + pMFHeader->hmf = 0; + pMFHeader->bbox = aRect; + pMFHeader->inch = nInch; + pMFHeader->reserved = 0; + pMFHeader->checksum = 0; + + char* pMFBuff = reinterpret_cast< char* >( mfpictStream.getArray( ) ); + + nCount = GetMetaFileBitsEx( pMFPict->hMF, nCount, pMFBuff + sizeof( METAFILEHEADER ) ); + OSL_ASSERT( nCount > 0 ); + } + + return mfpictStream; +} + +// convert a windows enhanced metafile to a LibreOffice metafile + +Sequence< sal_Int8 > WinENHMFPictToOOMFPict( HENHMETAFILE hEnhMetaFile ) +{ + Sequence< sal_Int8 > aRet; + UINT nSize = 0; + + if( hEnhMetaFile && + ( ( nSize = GetEnhMetaFileBits( hEnhMetaFile, 0, nullptr ) ) != 0 ) ) + { + aRet.realloc( nSize ); + + if( GetEnhMetaFileBits( hEnhMetaFile, nSize, reinterpret_cast<unsigned char*>(aRet.getArray()) ) != nSize ) + aRet.realloc( 0 ); + } + + return aRet; +} + +// convert a LibreOffice metafile picture to a windows metafile picture + +HMETAFILEPICT OOMFPictToWinMFPict( Sequence< sal_Int8 > const & aOOMetaFilePict ) +{ + HMETAFILEPICT hPict = nullptr; + HMETAFILE hMtf = SetMetaFileBitsEx( aOOMetaFilePict.getLength(), reinterpret_cast<unsigned char const *>(aOOMetaFilePict.getConstArray()) ); + + if( hMtf ) + { + METAFILEPICT* pPict = static_cast<METAFILEPICT*>(GlobalLock( hPict = GlobalAlloc( GHND, sizeof( METAFILEPICT ) ) )); + + pPict->mm = 8; + pPict->xExt = 0; + pPict->yExt = 0; + pPict->hMF = hMtf; + + GlobalUnlock( hPict ); + } + + return hPict; +} + +// convert a LibreOffice metafile picture to a windows enhanced metafile picture + +HENHMETAFILE OOMFPictToWinENHMFPict( Sequence< sal_Int8 > const & aOOMetaFilePict ) +{ + HENHMETAFILE hEnhMtf = SetEnhMetaFileBits( aOOMetaFilePict.getLength(), reinterpret_cast<unsigned char const *>(aOOMetaFilePict.getConstArray()) ); + + return hEnhMtf; +} + +// convert a windows device independent bitmap into a LibreOffice bitmap + +Sequence< sal_Int8 > WinDIBToOOBMP( const Sequence< sal_Int8 >& aWinDIB ) +{ + OSL_ENSURE(o3tl::make_unsigned(aWinDIB.getLength()) > sizeof(BITMAPINFOHEADER), "CF_DIBV5/CF_DIB too small (!)"); + Sequence< sal_Int8 > ooBmpStream; + + ooBmpStream.realloc(aWinDIB.getLength( ) + sizeof(BITMAPFILEHEADER)); + const BITMAPINFOHEADER* pBmpInfoHdr = reinterpret_cast< const BITMAPINFOHEADER* >(aWinDIB.getConstArray()); + BITMAPFILEHEADER* pBmpFileHdr = reinterpret_cast< BITMAPFILEHEADER* >(ooBmpStream.getArray()); + const DWORD nSizeInfoOrV5(pBmpInfoHdr->biSize > sizeof(BITMAPINFOHEADER) ? sizeof(BITMAPV5HEADER) : sizeof(BITMAPINFOHEADER)); + DWORD nOffset(sizeof(BITMAPFILEHEADER) + nSizeInfoOrV5); + + memcpy(pBmpFileHdr + 1, pBmpInfoHdr, aWinDIB.getLength()); + + if(pBmpInfoHdr->biBitCount <= 8) + { + nOffset += (pBmpInfoHdr->biClrUsed ? pBmpInfoHdr->biClrUsed : (1 << pBmpInfoHdr->biBitCount)) << 2; + } + else if((BI_BITFIELDS == pBmpInfoHdr->biCompression ) && ((16 == pBmpInfoHdr->biBitCount ) || (32 == pBmpInfoHdr->biBitCount ))) + { + nOffset += 12; + } + + pBmpFileHdr->bfType = ('M' << 8) | 'B'; + pBmpFileHdr->bfSize = 0; // maybe: nMemSize + sizeof(BITMAPFILEHEADER) + pBmpFileHdr->bfReserved1 = 0; + pBmpFileHdr->bfReserved2 = 0; + pBmpFileHdr->bfOffBits = nOffset; + + return ooBmpStream; +} + +// convert a LibreOffice bitmap into a windows device independent bitmap + +Sequence< sal_Int8 > OOBmpToWinDIB( Sequence< sal_Int8 >& aOOBmp ) +{ + Sequence< sal_Int8 > winDIBStream( aOOBmp.getLength( ) - sizeof( BITMAPFILEHEADER ) ); + + memcpy( winDIBStream.getArray( ), + aOOBmp.getArray( ) + sizeof( BITMAPFILEHEADER ), + aOOBmp.getLength( ) - sizeof( BITMAPFILEHEADER ) ); + + return winDIBStream; +} + +static std::string GetHtmlFormatHeader(size_t startHtml, size_t endHtml, size_t startFragment, size_t endFragment) +{ + std::ostringstream htmlHeader; + htmlHeader << "Version:1.0" << '\r' << '\n'; + htmlHeader << "StartHTML:" << std::setw(10) << std::setfill('0') << std::dec << startHtml << '\r' << '\n'; + htmlHeader << "EndHTML:" << std::setw(10) << std::setfill('0') << std::dec << endHtml << '\r' << '\n'; + htmlHeader << "StartFragment:" << std::setw(10) << std::setfill('0') << std::dec << startFragment << '\r' << '\n'; + htmlHeader << "EndFragment:" << std::setw(10) << std::setfill('0') << std::dec << endFragment << '\r' << '\n'; + return htmlHeader.str(); +} + +// the case of these tags has to match what we output in our filters +// both tags don't allow parameters +const std::string TAG_HTML("<html>"); +const std::string TAG_END_HTML("</html>"); + +// The body tag may have parameters so we need to search for the +// closing '>' manually e.g. <body param> #92840# +const std::string TAG_BODY("<body"); +const std::string TAG_END_BODY("</body"); + +Sequence<sal_Int8> TextHtmlToHTMLFormat(Sequence<sal_Int8> const & aTextHtml) +{ + OSL_ASSERT(aTextHtml.getLength() > 0); + + if (aTextHtml.getLength() <= 0) + return Sequence<sal_Int8>(); + + // fill the buffer with dummy values to calc the exact length + std::string dummyHtmlHeader = GetHtmlFormatHeader(0, 0, 0, 0); + size_t lHtmlFormatHeader = dummyHtmlHeader.length(); + + std::string textHtml( + reinterpret_cast<const char*>(aTextHtml.getConstArray()), + reinterpret_cast<const char*>(aTextHtml.getConstArray()) + aTextHtml.getLength()); + + std::string::size_type nStartHtml = textHtml.find(TAG_HTML) + lHtmlFormatHeader - 1; // we start one before '<HTML>' Word 2000 does also so + std::string::size_type nEndHtml = textHtml.find(TAG_END_HTML) + lHtmlFormatHeader + TAG_END_HTML.length() + 1; // our SOffice 5.2 wants 2 behind </HTML>? + + // The body tag may have parameters so we need to search for the + // closing '>' manually e.g. <BODY param> #92840# + std::string::size_type nStartFragment = textHtml.find(">", textHtml.find(TAG_BODY)) + lHtmlFormatHeader + 1; + std::string::size_type nEndFragment = textHtml.find(TAG_END_BODY) + lHtmlFormatHeader; + + std::string htmlFormat = GetHtmlFormatHeader(nStartHtml, nEndHtml, nStartFragment, nEndFragment); + htmlFormat += textHtml; + + Sequence<sal_Int8> byteSequence(htmlFormat.length() + 1); // space the trailing '\0' + memset(byteSequence.getArray(), 0, byteSequence.getLength()); + + memcpy( + static_cast<void*>(byteSequence.getArray()), + static_cast<const void*>(htmlFormat.c_str()), + htmlFormat.length()); + + return byteSequence; +} + +static std::wstring getFileExtension(const std::wstring& aFilename) +{ + std::wstring::size_type idx = aFilename.rfind(L"."); + if (idx != std::wstring::npos) + { + return std::wstring(aFilename, idx); + } + return std::wstring(); +} + +const std::wstring SHELL_LINK_FILE_EXTENSION = L".lnk"; + +static bool isShellLink(const std::wstring& aFilename) +{ + std::wstring ext = getFileExtension(aFilename); + return (_wcsicmp(ext.c_str(), SHELL_LINK_FILE_EXTENSION.c_str()) == 0); +} + +/** Resolve a Windows Shell Link (lnk) file. If a resolution + is not possible simply return the provided name of the + lnk file. */ +static std::wstring getShellLinkTarget(const std::wstring& aLnkFile) +{ + OSL_ASSERT(isShellLink(aLnkFile)); + + std::wstring target = aLnkFile; + + try + { + sal::systools::COMReference<IShellLinkW> pIShellLink; + pIShellLink.CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER); + + sal::systools::COMReference<IPersistFile> pIPersistFile(pIShellLink, + sal::systools::COM_QUERY_THROW); + + HRESULT hr = pIPersistFile->Load(aLnkFile.c_str(), STGM_READ); + if (FAILED(hr)) + return target; + + hr = pIShellLink->Resolve(nullptr, SLR_UPDATE | SLR_NO_UI); + if (FAILED(hr)) + return target; + + wchar_t pathW[MAX_PATH]; + WIN32_FIND_DATAW wfd; + hr = pIShellLink->GetPath(pathW, MAX_PATH, &wfd, SLGP_RAWPATH); + if (FAILED(hr)) + return target; + + target = pathW; + } + catch(sal::systools::ComError& ex) + { + OSL_FAIL(ex.what()); + } + return target; +} + +typedef Sequence<sal_Int8> ByteSequence_t; + +/* Calculate the size required for turning a string list into + a double '\0' terminated string buffer */ +static size_t CalcSizeForStringListBuffer(const std::vector<std::wstring>& fileList) +{ + if ( fileList.empty() ) + return 0; + + size_t size = 1; // one for the very final '\0' + for (auto const& elem : fileList) + { + size += elem.length() + 1; // length including terminating '\0' + } + return (size * sizeof(std::vector<std::wstring>::value_type::value_type)); +} + +static ByteSequence_t FileListToByteSequence(const std::vector<std::wstring>& fileList) +{ + ByteSequence_t bseq; + size_t size = CalcSizeForStringListBuffer(fileList); + + if (size > 0) + { + bseq.realloc(size); + wchar_t* p = reinterpret_cast<wchar_t*>(bseq.getArray()); + ZeroMemory(p, size); + + for (auto const& elem : fileList) + { + wcsncpy(p, elem.c_str(), elem.length()); + p += (elem.length() + 1); + } + } + return bseq; +} + +css::uno::Sequence<sal_Int8> CF_HDROPToFileList(HGLOBAL hGlobal) +{ + UINT nFiles = DragQueryFileW(static_cast<HDROP>(hGlobal), 0xFFFFFFFF, nullptr, 0); + std::vector<std::wstring> files; + + for (UINT i = 0; i < nFiles; i++) + { + wchar_t buff[MAX_PATH]; + /*UINT size =*/ DragQueryFileW(static_cast<HDROP>(hGlobal), i, buff, MAX_PATH); + std::wstring filename = buff; + if (isShellLink(filename)) + filename = getShellLinkTarget(filename); + files.push_back(filename); + } + return FileListToByteSequence(files); +} + +// convert a windows bitmap handle into a LibreOffice bitmap + +Sequence< sal_Int8 > WinBITMAPToOOBMP( HBITMAP aHBMP ) +{ + Sequence< sal_Int8 > ooBmpStream; + + SIZE aBmpSize; + if( GetBitmapDimensionEx( aHBMP, &aBmpSize ) ) + { + // fill bitmap info header + size_t nDataBytes = 4 * aBmpSize.cy * aBmpSize.cy; + Sequence< sal_Int8 > aBitmapStream( + sizeof(BITMAPINFO) + + nDataBytes + ); + PBITMAPINFOHEADER pBmp = reinterpret_cast<PBITMAPINFOHEADER>(aBitmapStream.getArray()); + pBmp->biSize = sizeof( BITMAPINFOHEADER ); + pBmp->biWidth = aBmpSize.cx; + pBmp->biHeight = aBmpSize.cy; + pBmp->biPlanes = 1; + pBmp->biBitCount = 32; + pBmp->biCompression = BI_RGB; + pBmp->biSizeImage = static_cast<DWORD>(nDataBytes); + pBmp->biXPelsPerMeter = 1000; + pBmp->biYPelsPerMeter = 1000; + pBmp->biClrUsed = 0; + pBmp->biClrImportant = 0; + if( GetDIBits( nullptr, // DC, 0 is a default GC, basically that of the desktop + aHBMP, + 0, aBmpSize.cy, + aBitmapStream.getArray() + sizeof(BITMAPINFO), + reinterpret_cast<LPBITMAPINFO>(pBmp), + DIB_RGB_COLORS ) ) + { + ooBmpStream = WinDIBToOOBMP( aBitmapStream ); + } + } + + return ooBmpStream; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/FmtFilter.hxx b/vcl/win/dtrans/FmtFilter.hxx new file mode 100644 index 0000000000..b4fb1e1fc3 --- /dev/null +++ b/vcl/win/dtrans/FmtFilter.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> + +#include <com/sun/star/uno/Sequence.hxx> + +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <objidl.h> + +/*------------------------------------------------------------------------ + input: + aMetaFilePict - a sequence of bytes containing a METAFILEPICT struct +------------------------------------------------------------------------*/ +css::uno::Sequence<sal_Int8> WinMFPictToOOMFPict(css::uno::Sequence<sal_Int8>& aMetaFilePict); +css::uno::Sequence<sal_Int8> WinENHMFPictToOOMFPict(HENHMETAFILE hEnhMetaFile); + +/*------------------------------------------------------------------------ + input: + aByteStream - a sequence of bytes containing a LibreOffice metafile + picture with a leading METAFILEHEADER +------------------------------------------------------------------------*/ +HMETAFILEPICT OOMFPictToWinMFPict(css::uno::Sequence<sal_Int8> const& aOOMetaFilePict); +HENHMETAFILE OOMFPictToWinENHMFPict(css::uno::Sequence<sal_Int8> const& aOOMetaFilePict); + +/*------------------------------------------------------------------------ + input: + aWinDIB - sequence of bytes containing a windows device independent + bitmap +------------------------------------------------------------------------*/ +css::uno::Sequence<sal_Int8> WinDIBToOOBMP(const css::uno::Sequence<sal_Int8>& aWinDIB); + +/*------------------------------------------------------------------------ + input: + aWinDIB - sequence of bytes containing a windows bitmap handle +------------------------------------------------------------------------*/ +css::uno::Sequence<sal_Int8> WinBITMAPToOOBMP(HBITMAP); + +/*------------------------------------------------------------------------ + input: + aOOBmp - sequence of bytes containing a LibreOffice bitmap + May contain CF_DIBV5 or CF_DIB, but removing the BITMAPFILEHEADER + is always the same size +------------------------------------------------------------------------*/ +css::uno::Sequence<sal_Int8> OOBmpToWinDIB(css::uno::Sequence<sal_Int8>& aOOBmp); + +/*------------------------------------------------------------------------ + input: + aTextHtml - a sequence of text/html which will be converted to the + HTML Format; the HTML Format has header before the real html data + the Format is described in the MSDN Library under HTML Clipboard + Format +------------------------------------------------------------------------*/ +css::uno::Sequence<sal_Int8> TextHtmlToHTMLFormat(css::uno::Sequence<sal_Int8> const& aTextHtml); + +/** + Return a FileList in which Windows Shell Links (lnk) are resolved. + If for whatever reason a resolution is not possible leave the + original lnk file. +*/ +css::uno::Sequence<sal_Int8> CF_HDROPToFileList(HGLOBAL hGlobal); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/ImplHelper.cxx b/vcl/win/dtrans/ImplHelper.cxx new file mode 100644 index 0000000000..84c7383b2c --- /dev/null +++ b/vcl/win/dtrans/ImplHelper.cxx @@ -0,0 +1,352 @@ +/* -*- 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 <osl/diagnose.h> +#include "ImplHelper.hxx" +#include <rtl/tencinfo.h> +#include <o3tl/char16_t2wchar_t.hxx> +#include <string.h> +#include <memory> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +#include <vector> + +#define FORMATETC_EXACT_MATCH 1 +#define FORMATETC_PARTIAL_MATCH -1 +#define FORMATETC_NO_MATCH 0 + +// returns a windows codepage appropriate to the +// given mime charset parameter value + +sal_uInt32 getWinCPFromMimeCharset( const OUString& charset ) +{ + sal_uInt32 winCP = GetACP( ); + + if ( charset.getLength( ) ) + { + OString osCharset( + charset.getStr( ), charset.getLength( ), RTL_TEXTENCODING_ASCII_US ); + + rtl_TextEncoding txtEnc = + rtl_getTextEncodingFromMimeCharset( osCharset.getStr( ) ); + + sal_uIntPtr winChrs = rtl_getBestWindowsCharsetFromTextEncoding( txtEnc ); + + CHARSETINFO chrsInf; + bool bRet = TranslateCharsetInfo( reinterpret_cast<DWORD*>(winChrs), &chrsInf, TCI_SRCCHARSET ); + + // if one of the above functions fails + // we will return the current ANSI codepage + // of this thread + if ( bRet ) + winCP = chrsInf.ciACP; + } + + return winCP; +} + +// returns a windows codepage appropriate to the +// given locale and locale type + +OUString getWinCPFromLocaleId( LCID lcid, LCTYPE lctype ) +{ + OSL_ASSERT( IsValidLocale( lcid, LCID_SUPPORTED ) ); + + // we set a default value + OUString winCP; + + // set a default value + if ( LOCALE_IDEFAULTCODEPAGE == lctype ) + { + winCP = OUString::number( static_cast<sal_Int32>(GetOEMCP( )) ); + } + else if ( LOCALE_IDEFAULTANSICODEPAGE == lctype ) + { + winCP = OUString::number( static_cast<sal_Int32>(GetACP( )) ); + } + else + OSL_ASSERT( false ); + + // First, get required buffer size, in characters + int nResult = GetLocaleInfoW( + lcid, lctype, nullptr, 0 ); + + OSL_ASSERT( nResult ); + + if ( nResult ) + { + std::unique_ptr<wchar_t[]> buff( new wchar_t[nResult] ); + // Now get the actual data + nResult = GetLocaleInfoW( lcid, lctype, buff.get(), nResult ); + + OSL_ASSERT(nResult); + + if (nResult) + winCP = o3tl::toU( buff.get() ); + + } + + return winCP; +} + +// returns a mime charset parameter value appropriate +// to the given codepage, optional a prefix can be +// given, e.g. "windows-" or "cp" + +OUString getMimeCharsetFromWinCP( sal_uInt32 cp, std::u16string_view aPrefix ) +{ + return aPrefix + cptostr( cp ); +} + +// returns a mime charset parameter value appropriate +// to the given locale id and locale type, optional a +// prefix can be given, e.g. "windows-" or "cp" + +OUString getMimeCharsetFromLocaleId( LCID lcid, LCTYPE lctype, std::u16string_view aPrefix ) +{ + OUString charset = getWinCPFromLocaleId( lcid, lctype ); + return aPrefix + charset; +} + +// IsOEMCP + +bool IsOEMCP( sal_uInt32 codepage ) +{ + OSL_ASSERT( IsValidCodePage( codepage ) ); + + sal_uInt32 arrOEMCP[] = { 437, 708, 709, 710, 720, 737, + 775, 850, 852, 855, 857, 860, + 861, 862, 863, 864, 865, 866, + 869, 874, 932, 936, 949, 950, 1361 }; + + for ( size_t i = 0; i < SAL_N_ELEMENTS( arrOEMCP ); ++i ) + if ( arrOEMCP[i] == codepage ) + return true; + + return false; +} + +// converts a codepage into its string representation + +OUString cptostr( sal_uInt32 codepage ) +{ + OSL_ASSERT( IsValidCodePage( codepage ) ); + + return OUString::number( static_cast<sal_Int64>( codepage ) ); +} + +// OleStdDeleteTargetDevice() +// +// Purpose: +// +// Parameters: +// +// Return Value: +// SCODE - S_OK if successful +void DeleteTargetDevice( DVTARGETDEVICE* ptd ) +{ + __try + { + CoTaskMemFree( ptd ); + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + OSL_FAIL( "Error DeleteTargetDevice" ); + } +} + +// OleStdCopyTargetDevice() +// +// Purpose: +// duplicate a TARGETDEVICE struct. this function allocates memory for +// the copy. the caller MUST free the allocated copy when done with it +// using the standard allocator returned from CoGetMalloc. +// (OleStdFree can be used to free the copy). +// +// Parameters: +// ptdSrc pointer to source TARGETDEVICE +// +// Return Value: +// pointer to allocated copy of ptdSrc +// if ptdSrc==NULL then returns NULL is returned. +// if ptdSrc!=NULL and memory allocation fails, then NULL is returned +DVTARGETDEVICE* CopyTargetDevice( DVTARGETDEVICE* ptdSrc ) +{ + DVTARGETDEVICE* ptdDest = nullptr; + + __try + { + if ( nullptr != ptdSrc ) + { + ptdDest = static_cast< DVTARGETDEVICE* >( CoTaskMemAlloc( ptdSrc->tdSize ) ); + memcpy( ptdDest, ptdSrc, static_cast< size_t >( ptdSrc->tdSize ) ); + } + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + } + + return ptdDest; +} + +// OleStdCopyFormatEtc() +// +// Purpose: +// Copies the contents of a FORMATETC structure. this function takes +// special care to copy correctly copying the pointer to the TARGETDEVICE +// contained within the source FORMATETC structure. +// if the source FORMATETC has a non-NULL TARGETDEVICE, then a copy +// of the TARGETDEVICE will be allocated for the destination of the +// FORMATETC (petcDest). +// +// NOTE: the caller MUST free the allocated copy of the TARGETDEVICE +// within the destination FORMATETC when done with it +// using the standard allocator returned from CoGetMalloc. +// (OleStdFree can be used to free the copy). +// +// Parameters: +// petcDest pointer to destination FORMATETC +// petcSrc pointer to source FORMATETC +// +// Return Value: +// returns TRUE if copy was successful; +// returns FALSE if not successful, e.g. one or both of the pointers +// were invalid or the pointers were equal +bool CopyFormatEtc( LPFORMATETC petcDest, LPFORMATETC petcSrc ) +{ + bool bRet = false; + + __try + { + if ( petcDest != petcSrc ) + { + + petcDest->cfFormat = petcSrc->cfFormat; + + petcDest->ptd = nullptr; + if ( nullptr != petcSrc->ptd ) + petcDest->ptd = CopyTargetDevice(petcSrc->ptd); + + petcDest->dwAspect = petcSrc->dwAspect; + petcDest->lindex = petcSrc->lindex; + petcDest->tymed = petcSrc->tymed; + + bRet = true; + } + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + OSL_FAIL( "Error CopyFormatEtc" ); + } + + return bRet; +} + +// returns: +// 1 for exact match, +// 0 for no match, +// -1 for partial match (which is defined to mean the left is a subset +// of the right: fewer aspects, null target device, fewer medium). + +sal_Int32 CompareFormatEtc( const FORMATETC* pFetcLhs, const FORMATETC* pFetcRhs ) +{ + sal_Int32 nMatch = FORMATETC_EXACT_MATCH; + + __try + { + if ( pFetcLhs != pFetcRhs ) + { + if ( ( pFetcLhs->cfFormat != pFetcRhs->cfFormat ) || + ( pFetcLhs->lindex != pFetcRhs->lindex ) || + !CompareTargetDevice( pFetcLhs->ptd, pFetcRhs->ptd ) ) + { + nMatch = FORMATETC_NO_MATCH; + } + + else if ( pFetcLhs->dwAspect == pFetcRhs->dwAspect ) + // same aspects; equal + ; + else if ( ( pFetcLhs->dwAspect & ~pFetcRhs->dwAspect ) != 0 ) + { + // left not subset of aspects of right; not equal + nMatch = FORMATETC_NO_MATCH; + } + else + // left subset of right + nMatch = FORMATETC_PARTIAL_MATCH; + + if ( nMatch == FORMATETC_EXACT_MATCH || nMatch == FORMATETC_PARTIAL_MATCH ) + { + if ( pFetcLhs->tymed == pFetcRhs->tymed ) + // same medium flags; equal + ; + else if ( ( pFetcLhs->tymed & ~pFetcRhs->tymed ) != 0 ) + { + // left not subset of medium flags of right; not equal + nMatch = FORMATETC_NO_MATCH; + } + else + // left subset of right + nMatch = FORMATETC_PARTIAL_MATCH; + } + } + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + OSL_FAIL( "Error CompareFormatEtc" ); + nMatch = FORMATETC_NO_MATCH; + } + + return nMatch; +} + +bool CompareTargetDevice( DVTARGETDEVICE* ptdLeft, DVTARGETDEVICE const * ptdRight ) +{ + bool bRet = false; + + __try + { + if ( ptdLeft == ptdRight ) + { + // same address of td; must be same (handles NULL case) + bRet = true; + } + + // one of the two is NULL + else if ( ( nullptr != ptdRight ) && ( nullptr != ptdLeft ) ) + + if ( ptdLeft->tdSize == ptdRight->tdSize ) + + if ( memcmp( ptdLeft, ptdRight, ptdLeft->tdSize ) == 0 ) + bRet = true; + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + OSL_FAIL( "Error CompareTargetDevice" ); + bRet = false; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/ImplHelper.hxx b/vcl/win/dtrans/ImplHelper.hxx new file mode 100644 index 0000000000..df6731ec7a --- /dev/null +++ b/vcl/win/dtrans/ImplHelper.hxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <sal/types.h> +#include <rtl/ustring.hxx> + +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <objidl.h> + +// target device and formatetc helper +void DeleteTargetDevice(DVTARGETDEVICE* ptd); +bool CopyFormatEtc(LPFORMATETC petcDest, LPFORMATETC petcSrc); +sal_Int32 CompareFormatEtc(const FORMATETC* pFetcLeft, const FORMATETC* pFetcRight); +bool CompareTargetDevice(DVTARGETDEVICE* ptdLeft, DVTARGETDEVICE const* ptdRight); +DVTARGETDEVICE* CopyTargetDevice(DVTARGETDEVICE* ptdSrc); + +// some codepage helper functions + +// returns a windows codepage appropriate to the +// given mime charset parameter value + +sal_uInt32 getWinCPFromMimeCharset(const OUString& charset); + +// returns a windows codepage appropriate to the +// given locale and locale type + +OUString getWinCPFromLocaleId(LCID lcid, LCTYPE lctype); + +// returns a mime charset parameter value appropriate +// to the given codepage, optional a prefix can be +// given, e.g. "windows-" or "cp" + +OUString getMimeCharsetFromWinCP(sal_uInt32 cp, std::u16string_view aPrefix); + +// returns a mime charset parameter value appropriate +// to the given locale id and locale type, optional a +// prefix can be given, e.g. "windows-" or "cp" + +OUString getMimeCharsetFromLocaleId(LCID lcid, LCTYPE lctype, std::u16string_view aPrefix); + +// returns true, if a given codepage is an oem codepage + +bool IsOEMCP(sal_uInt32 codepage); + +// converts a codepage into a string representation + +OUString cptostr(sal_uInt32 codepage); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/MimeAttrib.hxx b/vcl/win/dtrans/MimeAttrib.hxx new file mode 100644 index 0000000000..48dd5fc11e --- /dev/null +++ b/vcl/win/dtrans/MimeAttrib.hxx @@ -0,0 +1,31 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> + +constexpr OUString TEXTPLAIN_PARAM_CHARSET(u"charset"_ustr); + +constexpr OUString PRE_WINDOWS_CODEPAGE(u"windows"_ustr); +constexpr OUString PRE_OEM_CODEPAGE(u"cp"_ustr); +constexpr OUString CHARSET_UTF16(u"utf-16"_ustr); +constexpr OUString CHARSET_UNICODE(u"unicode"_ustr); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/MtaOleClipb.cxx b/vcl/win/dtrans/MtaOleClipb.cxx new file mode 100644 index 0000000000..fcf16df1ad --- /dev/null +++ b/vcl/win/dtrans/MtaOleClipb.cxx @@ -0,0 +1,748 @@ +/* -*- 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 . + */ + +/* + MtaOleClipb.cxx - documentation + + This class setup a single threaded apartment (sta) thread to deal with + the ole clipboard, which runs only in an sta thread. + The consequence is that callback from the ole clipboard are in the + context of this sta thread. In the soffice applications this may lead + to problems because they all use the one and only mutex called + SolarMutex. + In order to transfer clipboard requests to our sta thread we use a + hidden window and forward these requests via window messages. +*/ + +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include "MtaOleClipb.hxx" + +#include <svsys.h> +#include <win/saldata.hxx> + +#include <osl/thread.h> + +#include <wchar.h> +#include <process.h> + +#include <systools/win32/comtools.hxx> +#include <systools/win32/retry_if_failed.hxx> + +#include <comphelper/windowserrorstring.hxx> + +namespace /* private */ +{ + const wchar_t g_szWndClsName[] = L"MtaOleReqWnd###"; + + // messages constants + + const sal_uInt32 MSG_SETCLIPBOARD = WM_USER + 0x0001; + const sal_uInt32 MSG_GETCLIPBOARD = WM_USER + 0x0002; + const sal_uInt32 MSG_REGCLIPVIEWER = WM_USER + 0x0003; + const sal_uInt32 MSG_FLUSHCLIPBOARD = WM_USER + 0x0004; + const sal_uInt32 MSG_SHUTDOWN = WM_USER + 0x0005; + + const sal_uInt32 MAX_WAITTIME = 10000; // msec + const sal_uInt32 MAX_WAIT_SHUTDOWN = 10000; // msec + + const bool MANUAL_RESET = true; + const bool INIT_NONSIGNALED = false; + + /* Cannot use osl conditions because they are blocking + without waking up on messages sent by another thread + this leads to deadlocks because we are blocking the + communication between inter-thread marshalled COM + pointers. + COM Proxy-Stub communication uses SendMessages for + synchronization purposes. + */ + class Win32Condition + { + public: + Win32Condition() = default; + + ~Win32Condition() { CloseHandle(m_hEvent); } + + // wait infinite for own event (or abort event) be signaled + // leave messages sent through + bool wait(HANDLE hEvtAbort) + { + const HANDLE hWaitArray[2] = { m_hEvent, hEvtAbort }; + while (true) + { + DWORD dwResult + = MsgWaitForMultipleObjects(2, hWaitArray, FALSE, INFINITE, QS_SENDMESSAGE); + + switch (dwResult) + { + case WAIT_OBJECT_0: // wait successful + return true; + + case WAIT_OBJECT_0 + 1: // wait aborted + return false; + + case WAIT_OBJECT_0 + 2: + { + /* PeekMessage processes all messages in the SendMessage + queue that's what we want, messages from the PostMessage + queue stay untouched */ + MSG msg; + PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE); + + break; + } + + default: // WAIT_FAILED? + return false; + } + } + } + + // set the event + void set() { SetEvent(m_hEvent); } + + private: + HANDLE m_hEvent = CreateEventW(nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr); + + // prevent copy/assignment + Win32Condition(const Win32Condition&) = delete; + Win32Condition& operator=(const Win32Condition&) = delete; + }; + + // we use one condition for every request + + struct MsgCtx + { + Win32Condition aCondition; + HRESULT hr; + }; + +} /* namespace private */ + +// static member initialization + +CMtaOleClipboard* CMtaOleClipboard::s_theMtaOleClipboardInst = nullptr; + +// marshal an IDataObject + +//inline +static HRESULT MarshalIDataObjectInStream( IDataObject* pIDataObject, LPSTREAM* ppStream ) +{ + OSL_ASSERT( nullptr != pIDataObject ); + OSL_ASSERT( nullptr != ppStream ); + + *ppStream = nullptr; + return CoMarshalInterThreadInterfaceInStream( + __uuidof(IDataObject), //The IID of interface to be marshalled + pIDataObject, //The interface pointer + ppStream //IStream pointer + ); +} + +// unmarshal an IDataObject + +//inline +static HRESULT UnmarshalIDataObjectAndReleaseStream( LPSTREAM lpStream, IDataObject** ppIDataObject ) +{ + OSL_ASSERT( nullptr != lpStream ); + OSL_ASSERT( nullptr != ppIDataObject ); + + *ppIDataObject = nullptr; + return CoGetInterfaceAndReleaseStream( + lpStream, + __uuidof(IDataObject), + reinterpret_cast<LPVOID*>(ppIDataObject)); +} + +// helper class to ensure that the calling thread has com initialized + +namespace { + +class CAutoComInit +{ +public: + /* + to be safe we call CoInitializeEx + although it is not necessary if + the calling thread was created + using osl_CreateThread because + this function calls CoInitializeEx + for every thread it creates + */ + CAutoComInit( ) : m_hResult( CoInitializeEx( nullptr, COINIT_APARTMENTTHREADED ) ) + { + if ( S_OK == m_hResult ) + OSL_FAIL( + "com was not yet initialized, the thread was not created using osl_createThread" ); + else if ( FAILED( m_hResult ) && !( RPC_E_CHANGED_MODE == m_hResult ) ) + OSL_FAIL( + "com could not be initialized, maybe the thread was not created using osl_createThread" ); + } + + ~CAutoComInit( ) + { + /* + we only call CoUninitialize when + CoInitializeEx returned S_FALSE, what + means that com was already initialize + for that thread so we keep the balance + if CoInitializeEx returned S_OK what means + com was not yet initialized we better + let com initialized or we may run into + the realm of undefined behaviour + */ + if ( m_hResult == S_FALSE ) + CoUninitialize( ); + } + +private: + HRESULT m_hResult; +}; + +} + +// ctor + +CMtaOleClipboard::CMtaOleClipboard( ) : + m_hOleThread( nullptr ), + m_uOleThreadId( 0 ), + // signals that the thread was successfully setup + m_hEvtThrdReady(CreateEventW( nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr )), + m_hwndMtaOleReqWnd( nullptr ), + // signals that the window is destroyed - to stop waiting any winproc result + m_hEvtWndDisposed(CreateEventW(nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr)), + m_MtaOleReqWndClassAtom( 0 ), + m_pfncClipViewerCallback( nullptr ), + m_bRunClipboardNotifierThread( true ), + m_hClipboardChangedEvent( m_hClipboardChangedNotifierEvents[0] ), + m_hTerminateClipboardChangedNotifierEvent( m_hClipboardChangedNotifierEvents[1] ), + m_ClipboardChangedEventCount( 0 ) +{ + OSL_ASSERT( nullptr != m_hEvtThrdReady ); + SAL_WARN_IF(!m_hEvtWndDisposed, "vcl.win.dtrans", "CreateEventW failed: m_hEvtWndDisposed is nullptr"); + + s_theMtaOleClipboardInst = this; + + m_hOleThread = reinterpret_cast<HANDLE>(_beginthreadex( + nullptr, 0, CMtaOleClipboard::oleThreadProc, this, 0, &m_uOleThreadId )); + OSL_ASSERT( nullptr != m_hOleThread ); + + // setup the clipboard changed notifier thread + + m_hClipboardChangedNotifierEvents[0] = CreateEventW( nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr ); + OSL_ASSERT( nullptr != m_hClipboardChangedNotifierEvents[0] ); + + m_hClipboardChangedNotifierEvents[1] = CreateEventW( nullptr, MANUAL_RESET, INIT_NONSIGNALED, nullptr ); + OSL_ASSERT( nullptr != m_hClipboardChangedNotifierEvents[1] ); + + m_hClipboardChangedNotifierThread = reinterpret_cast<HANDLE>(_beginthreadex( + nullptr, 0, CMtaOleClipboard::clipboardChangedNotifierThreadProc, this, 0, nullptr )); + + OSL_ASSERT( nullptr != m_hClipboardChangedNotifierThread ); +} + +// dtor + +CMtaOleClipboard::~CMtaOleClipboard( ) +{ + // block calling threads out + if ( nullptr != m_hEvtThrdReady ) + ResetEvent( m_hEvtThrdReady ); + + // terminate the clipboard changed notifier thread + m_bRunClipboardNotifierThread = false; + SetEvent( m_hTerminateClipboardChangedNotifierEvent ); + + // unblock whoever could still wait for event processing + if (m_hEvtWndDisposed) + SetEvent(m_hEvtWndDisposed); + + sal_uInt32 dwResult = WaitForSingleObject( + m_hClipboardChangedNotifierThread, MAX_WAIT_SHUTDOWN ); + + OSL_ENSURE( dwResult == WAIT_OBJECT_0, "clipboard notifier thread could not terminate" ); + + if ( nullptr != m_hClipboardChangedNotifierThread ) + CloseHandle( m_hClipboardChangedNotifierThread ); + + if ( nullptr != m_hClipboardChangedNotifierEvents[0] ) + CloseHandle( m_hClipboardChangedNotifierEvents[0] ); + + if ( nullptr != m_hClipboardChangedNotifierEvents[1] ) + CloseHandle( m_hClipboardChangedNotifierEvents[1] ); + + // end the thread + // because DestroyWindow can only be called + // from within the thread that created the window + sendMessage( MSG_SHUTDOWN ); + + // wait for thread shutdown + dwResult = WaitForSingleObject( m_hOleThread, MAX_WAIT_SHUTDOWN ); + OSL_ENSURE( dwResult == WAIT_OBJECT_0, "OleThread could not terminate" ); + + if ( nullptr != m_hOleThread ) + CloseHandle( m_hOleThread ); + + if ( nullptr != m_hEvtThrdReady ) + CloseHandle( m_hEvtThrdReady ); + + if (m_hEvtWndDisposed) + CloseHandle(m_hEvtWndDisposed); + + if ( m_MtaOleReqWndClassAtom ) + UnregisterClassW( g_szWndClsName, nullptr ); + + OSL_ENSURE( ( nullptr == m_pfncClipViewerCallback ), + "Clipboard viewer not properly unregistered" ); +} + +HRESULT CMtaOleClipboard::flushClipboard( ) +{ + if ( !WaitForThreadReady( ) ) + { + OSL_FAIL( "clipboard sta thread not ready" ); + return E_FAIL; + } + + OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId, + "flushClipboard from within clipboard sta thread called" ); + + MsgCtx aMsgCtx; + + const bool bWaitSuccess = postMessage(MSG_FLUSHCLIPBOARD, 0, reinterpret_cast<LPARAM>(&aMsgCtx)) + && aMsgCtx.aCondition.wait(m_hEvtWndDisposed); + + return bWaitSuccess ? aMsgCtx.hr : E_ABORT; +} + +HRESULT CMtaOleClipboard::getClipboard( IDataObject** ppIDataObject ) +{ + OSL_PRECOND( nullptr != ppIDataObject, "invalid parameter" ); + OSL_PRECOND( GetCurrentThreadId( ) != m_uOleThreadId, "getClipboard from within clipboard sta thread called" ); + + if ( !WaitForThreadReady( ) ) + { + OSL_FAIL( "clipboard sta thread not ready" ); + return E_FAIL; + } + + CAutoComInit comAutoInit; + + LPSTREAM lpStream; + + *ppIDataObject = nullptr; + + MsgCtx aMsgCtx; + + const bool bWaitSuccess = postMessage(MSG_GETCLIPBOARD, reinterpret_cast<WPARAM>(&lpStream), + reinterpret_cast<LPARAM>(&aMsgCtx)) + && aMsgCtx.aCondition.wait(m_hEvtWndDisposed); + + HRESULT hr = bWaitSuccess ? aMsgCtx.hr : E_ABORT; + + if ( SUCCEEDED( hr ) ) + { + hr = UnmarshalIDataObjectAndReleaseStream( lpStream, ppIDataObject ); + OSL_ENSURE( SUCCEEDED( hr ), "unmarshalling clipboard data object failed" ); + } + + return hr; +} + +// this is an asynchronous method that's why we don't wait until the +// request is completed + +HRESULT CMtaOleClipboard::setClipboard( IDataObject* pIDataObject ) +{ + if ( !WaitForThreadReady( ) ) + { + OSL_FAIL( "clipboard sta thread not ready" ); + return E_FAIL; + } + + CAutoComInit comAutoInit; + + OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId, "setClipboard from within the clipboard sta thread called" ); + + // because we marshall this request + // into the sta thread we better + // acquire the interface here so + // that the object will not be + // destroyed before the ole clipboard + // can acquire it + // remember: pIDataObject may be NULL + // which is a request to clear the + // current clipboard content + if ( pIDataObject ) + pIDataObject->AddRef( ); + + postMessage( + MSG_SETCLIPBOARD, + reinterpret_cast< WPARAM >( pIDataObject ) ); + + // because this is an asynchronous function + // the return value is useless + return S_OK; +} + +// register a clipboard viewer + +bool CMtaOleClipboard::registerClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback ) +{ + if ( !WaitForThreadReady( ) ) + { + OSL_FAIL( "clipboard sta thread not ready" ); + return false; + } + + OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId, "registerClipViewer from within the OleThread called" ); + + MsgCtx aMsgCtx; + + if (postMessage(MSG_REGCLIPVIEWER, reinterpret_cast<WPARAM>(pfncClipViewerCallback), + reinterpret_cast<LPARAM>(&aMsgCtx))) + aMsgCtx.aCondition.wait(m_hEvtWndDisposed); + + return false; +} + +// register a clipboard viewer + +bool CMtaOleClipboard::onRegisterClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback ) +{ + bool bRet = false; + + // we need exclusive access because the clipboard changed notifier + // thread also accesses this variable + std::unique_lock aGuard( m_pfncClipViewerCallbackMutex ); + + // register if not yet done + if ( ( nullptr != pfncClipViewerCallback ) && ( nullptr == m_pfncClipViewerCallback ) ) + { + // SetClipboardViewer sends a WM_DRAWCLIPBOARD message we ignore + // this message if we register ourself as clip viewer + m_bInRegisterClipViewer = true; + bRet = AddClipboardFormatListener(m_hwndMtaOleReqWnd); + m_bInRegisterClipViewer = false; + + // save the new callback function + m_pfncClipViewerCallback = pfncClipViewerCallback; + } + else if ( ( nullptr == pfncClipViewerCallback ) && ( nullptr != m_pfncClipViewerCallback ) ) + { + m_pfncClipViewerCallback = nullptr; + + // unregister if input parameter is NULL and we previously registered + // as clipboard viewer + bRet = RemoveClipboardFormatListener(m_hwndMtaOleReqWnd); + } + + return bRet; +} + +HRESULT CMtaOleClipboard::onSetClipboard( IDataObject* pIDataObject ) +{ + return sal::systools::RetryIfFailed(10, 100, + [pIDataObject] { return OleSetClipboard(pIDataObject); }); +} + +HRESULT CMtaOleClipboard::onGetClipboard( LPSTREAM* ppStream ) +{ + OSL_ASSERT(nullptr != ppStream); + + IDataObjectPtr pIDataObject; + + // forward the request to the OleClipboard + HRESULT hr + = sal::systools::RetryIfFailed(10, 100, [p = &pIDataObject] { return OleGetClipboard(p); }); + if ( SUCCEEDED( hr ) ) + { + hr = MarshalIDataObjectInStream(pIDataObject.get(), ppStream); + OSL_ENSURE(SUCCEEDED(hr), "marshalling clipboard data object failed"); + } + return hr; +} + +// flush the ole-clipboard + +HRESULT CMtaOleClipboard::onFlushClipboard( ) +{ + return sal::systools::RetryIfFailed(10, 100, [] { return OleFlushClipboard(); }); +} + +// handle clipboard update event + +LRESULT CMtaOleClipboard::onClipboardUpdate() +{ + // we don't send a notification if we are + // registering ourself as clipboard + if ( !m_bInRegisterClipViewer ) + { + std::unique_lock aGuard( m_ClipboardChangedEventCountMutex ); + + m_ClipboardChangedEventCount++; + SetEvent( m_hClipboardChangedEvent ); + } + + return 0; +} + +// SendMessage so we don't need to supply the HWND if we send +// something to our wrapped window + +LRESULT CMtaOleClipboard::sendMessage( UINT msg, WPARAM wParam, LPARAM lParam ) +{ + return ::SendMessageW( m_hwndMtaOleReqWnd, msg, wParam, lParam ); +} + +// PostMessage so we don't need to supply the HWND if we send +// something to our wrapped window + +bool CMtaOleClipboard::postMessage( UINT msg, WPARAM wParam, LPARAM lParam ) +{ + bool const ret = PostMessageW(m_hwndMtaOleReqWnd, msg, wParam, lParam); + SAL_WARN_IF(!ret, "vcl.win.dtrans", "ERROR: PostMessage() failed!"); + return ret; +} + +// the window proc + +LRESULT CALLBACK CMtaOleClipboard::mtaOleReqWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + LRESULT lResult = 0; + + // get a connection to the class-instance via the static member + CMtaOleClipboard* pImpl = CMtaOleClipboard::s_theMtaOleClipboardInst; + OSL_ASSERT( nullptr != pImpl ); + + switch( uMsg ) + { + case MSG_SETCLIPBOARD: + { + IDataObject* pIDataObject = reinterpret_cast< IDataObject* >( wParam ); + CMtaOleClipboard::onSetClipboard( pIDataObject ); + + // in setClipboard we did acquire the + // interface pointer in order to prevent + // destruction of the object before the + // ole clipboard can acquire the interface + // now we release the interface so that + // our lostOwnership mechanism works + // remember: pIDataObject may be NULL + if ( pIDataObject ) + pIDataObject->Release( ); + } + break; + + case MSG_GETCLIPBOARD: + { + MsgCtx* aMsgCtx = reinterpret_cast< MsgCtx* >( lParam ); + OSL_ASSERT( aMsgCtx ); + + aMsgCtx->hr = CMtaOleClipboard::onGetClipboard( reinterpret_cast< LPSTREAM* >(wParam) ); + aMsgCtx->aCondition.set( ); + } + break; + + case MSG_FLUSHCLIPBOARD: + { + MsgCtx* aMsgCtx = reinterpret_cast< MsgCtx* >( lParam ); + OSL_ASSERT( aMsgCtx ); + + aMsgCtx->hr = CMtaOleClipboard::onFlushClipboard( ); + aMsgCtx->aCondition.set( ); + } + break; + + case MSG_REGCLIPVIEWER: + { + MsgCtx* pMsgCtx = reinterpret_cast<MsgCtx*>(lParam); + SAL_WARN_IF(!pMsgCtx, "vcl.win.dtrans", "pMsgCtx is nullptr"); + + pImpl->onRegisterClipViewer( + reinterpret_cast<CMtaOleClipboard::LPFNC_CLIPVIEWER_CALLBACK_t>(wParam)); + pMsgCtx->aCondition.set(); + } + break; + + case WM_CLIPBOARDUPDATE: + lResult = pImpl->onClipboardUpdate(); + break; + + case MSG_SHUTDOWN: + DestroyWindow( pImpl->m_hwndMtaOleReqWnd ); + break; + + // force the sta thread to end + case WM_DESTROY: + SetEvent(pImpl->m_hEvtWndDisposed); // stop waiting for conditions set by this wndproc + PostQuitMessage( 0 ); + break; + + default: + lResult = DefWindowProcW( hWnd, uMsg, wParam, lParam ); + break; + } + + return lResult; +} + +void CMtaOleClipboard::createMtaOleReqWnd( ) +{ + WNDCLASSEXW wcex; + + SalData* pSalData = GetSalData(); + OSL_ASSERT(nullptr != pSalData->mhInst); + + ZeroMemory( &wcex, sizeof(wcex) ); + + wcex.cbSize = sizeof(wcex); + wcex.style = 0; + wcex.lpfnWndProc = CMtaOleClipboard::mtaOleReqWndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = pSalData->mhInst; + wcex.hIcon = nullptr; + wcex.hCursor = nullptr; + wcex.hbrBackground = nullptr; + wcex.lpszMenuName = nullptr; + wcex.lpszClassName = g_szWndClsName; + wcex.hIconSm = nullptr; + + m_MtaOleReqWndClassAtom = RegisterClassExW( &wcex ); + + if ( 0 != m_MtaOleReqWndClassAtom ) + m_hwndMtaOleReqWnd = CreateWindowW( + g_szWndClsName, nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, pSalData->mhInst, nullptr ); +} + +unsigned int CMtaOleClipboard::run( ) +{ + HRESULT hr = OleInitialize( nullptr ); + OSL_ASSERT( SUCCEEDED( hr ) ); + + createMtaOleReqWnd( ); + + unsigned int nRet = ~0U; // = error + + if ( IsWindow( m_hwndMtaOleReqWnd ) ) + { + if ( nullptr != m_hEvtThrdReady ) + SetEvent( m_hEvtThrdReady ); + + nRet = 0; + + // pumping messages + for (;;) + { + MSG msg; + int const bRet = GetMessageW(&msg, nullptr, 0, 0); + if (bRet == 0) + { + break; + } + if (-1 == bRet) + { + SAL_WARN("vcl.win.dtrans", "GetMessageW failed: " << WindowsErrorString(GetLastError())); + nRet = ~0U; + break; + } + DispatchMessageW(&msg); + } + } + + OleUninitialize( ); + + return nRet; +} + +unsigned __stdcall CMtaOleClipboard::oleThreadProc( void* pParam ) +{ + osl_setThreadName("CMtaOleClipboard::run()"); + + CMtaOleClipboard* pInst = + static_cast<CMtaOleClipboard*>( pParam ); + OSL_ASSERT( nullptr != pInst ); + + return pInst->run( ); +} + +unsigned __stdcall CMtaOleClipboard::clipboardChangedNotifierThreadProc(void* pParam) +{ + osl_setThreadName("CMtaOleClipboard::clipboardChangedNotifierThreadProc()"); + CMtaOleClipboard* pInst = static_cast< CMtaOleClipboard* >( pParam ); + OSL_ASSERT( nullptr != pInst ); + + sal::systools::CoInitializeGuard aGuard(COINIT_APARTMENTTHREADED, false, + sal::systools::CoInitializeGuard::WhenFailed::NoThrow); + + // assuming we don't need a lock for + // a boolean variable like m_bRun... + while ( pInst->m_bRunClipboardNotifierThread ) + { + // process window messages because of CoInitializeEx + MSG Msg; + while (PeekMessageW(&Msg, nullptr, 0, 0, PM_REMOVE)) + DispatchMessageW(&Msg); + + // wait for clipboard changed or terminate event + MsgWaitForMultipleObjects(2, pInst->m_hClipboardChangedNotifierEvents, false, INFINITE, + QS_ALLINPUT | QS_ALLPOSTMESSAGE); + + std::unique_lock aGuard2( pInst->m_ClipboardChangedEventCountMutex ); + + if ( pInst->m_ClipboardChangedEventCount > 0 ) + { + pInst->m_ClipboardChangedEventCount--; + if ( 0 == pInst->m_ClipboardChangedEventCount ) + ResetEvent( pInst->m_hClipboardChangedEvent ); + + aGuard2.unlock( ); + + // nobody should touch m_pfncClipViewerCallback while we do + std::unique_lock aClipViewerGuard( pInst->m_pfncClipViewerCallbackMutex ); + + // notify all clipboard listener + if ( pInst->m_pfncClipViewerCallback ) + pInst->m_pfncClipViewerCallback( ); + } + else + aGuard2.unlock( ); + } + + return 0; +} + +bool CMtaOleClipboard::WaitForThreadReady( ) const +{ + bool bRet = false; + + if ( nullptr != m_hEvtThrdReady ) + { + DWORD dwResult = WaitForSingleObject( + m_hEvtThrdReady, MAX_WAITTIME ); + bRet = ( dwResult == WAIT_OBJECT_0 ); + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/MtaOleClipb.hxx b/vcl/win/dtrans/MtaOleClipb.hxx new file mode 100644 index 0000000000..f76becb50c --- /dev/null +++ b/vcl/win/dtrans/MtaOleClipb.hxx @@ -0,0 +1,110 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> +#include <mutex> + +#include <objidl.h> + +// the Mta-Ole clipboard class is for internal use only! +// only one instance of this class should be created, the +// user has to ensure this! +// the class is not thread-safe because it will be used +// only from within the clipboard service and the methods +// of the clipboard service are already synchronized + +class CMtaOleClipboard +{ +public: + typedef void ( WINAPI *LPFNC_CLIPVIEWER_CALLBACK_t )( void ); + +public: + CMtaOleClipboard( ); + ~CMtaOleClipboard( ); + + // clipboard functions + HRESULT setClipboard( IDataObject* pIDataObject ); + HRESULT getClipboard( IDataObject** ppIDataObject ); + HRESULT flushClipboard( ); + + // register/unregister a clipboard viewer; there can only + // be one at a time; parameter NULL means unregister + // a clipboard viewer + // returns true on success else false; use GetLastError( ) in + // false case + bool registerClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback ); + +private: + unsigned int run( ); + + // create a hidden window which serves as a request target; so we + // guarantee synchronization + void createMtaOleReqWnd( ); + + // message support + bool postMessage( UINT msg, WPARAM wParam = 0, LPARAM lParam = 0 ); + LRESULT sendMessage( UINT msg, WPARAM wParam = 0, LPARAM lParam = 0 ); + + // message handler functions; remember these functions are called + // from a different thread context! + + static HRESULT onSetClipboard( IDataObject* pIDataObject ); + static HRESULT onGetClipboard( LPSTREAM* ppStream ); + static HRESULT onFlushClipboard( ); + bool onRegisterClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback ); + + // win32 clipboard listener support + LRESULT onClipboardUpdate(); + + static LRESULT CALLBACK mtaOleReqWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + static unsigned __stdcall oleThreadProc(void* pParam); + + static unsigned __stdcall clipboardChangedNotifierThreadProc(void* pParam); + + bool WaitForThreadReady( ) const; + +private: + HANDLE m_hOleThread; + unsigned m_uOleThreadId; + HANDLE m_hEvtThrdReady; + HWND m_hwndMtaOleReqWnd; + HANDLE m_hEvtWndDisposed; + ATOM m_MtaOleReqWndClassAtom; + LPFNC_CLIPVIEWER_CALLBACK_t m_pfncClipViewerCallback; + bool m_bInRegisterClipViewer; + + bool m_bRunClipboardNotifierThread; + HANDLE m_hClipboardChangedNotifierThread; + HANDLE m_hClipboardChangedNotifierEvents[2]; + HANDLE& m_hClipboardChangedEvent; + HANDLE& m_hTerminateClipboardChangedNotifierEvent; + std::mutex m_ClipboardChangedEventCountMutex; + sal_Int32 m_ClipboardChangedEventCount; + + std::mutex m_pfncClipViewerCallbackMutex; + + static CMtaOleClipboard* s_theMtaOleClipboardInst; + + CMtaOleClipboard( const CMtaOleClipboard& ) = delete; + CMtaOleClipboard& operator=( const CMtaOleClipboard& ) = delete; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/TxtCnvtHlp.cxx b/vcl/win/dtrans/TxtCnvtHlp.cxx new file mode 100644 index 0000000000..d7ab386fc1 --- /dev/null +++ b/vcl/win/dtrans/TxtCnvtHlp.cxx @@ -0,0 +1,125 @@ +/* -*- 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 <osl/diagnose.h> + +#include "TxtCnvtHlp.hxx" +#include "DTransHelper.hxx" +#include "ImplHelper.hxx" + +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::uno; + +// assuming a '\0' terminated string if no length specified + +static int CalcBuffSizeForTextConversion( UINT code_page, LPCSTR lpMultiByteString, int nLen = -1 ) +{ + return ( MultiByteToWideChar( code_page, + 0, + lpMultiByteString, + nLen, + nullptr, + 0 ) * sizeof( sal_Unicode ) ); +} + +// assuming a '\0' terminated string if no length specified + +static int CalcBuffSizeForTextConversion( UINT code_page, LPCWSTR lpWideCharString, int nLen = -1 ) +{ + return WideCharToMultiByte( code_page, + 0, + lpWideCharString, + nLen, + nullptr, + 0, + nullptr, + nullptr ); +} + +// converts text in one code page into unicode text +// automatically calculates the necessary buffer size and allocates +// the buffer + +int MultiByteToWideCharEx( UINT cp_src, + LPCSTR lpMultiByteString, + int lenStr, + CStgTransferHelper& refDTransHelper, + BOOL bEnsureTrailingZero ) +{ + OSL_ASSERT( IsValidCodePage( cp_src ) ); + OSL_ASSERT( nullptr != lpMultiByteString ); + + // calculate the required buff size + int reqSize = CalcBuffSizeForTextConversion( cp_src, lpMultiByteString, lenStr ); + + if ( bEnsureTrailingZero ) + reqSize += sizeof( sal_Unicode ); + + // initialize the data-transfer helper + refDTransHelper.init( reqSize ); + + // setup a global memory pointer + CRawHGlobalPtr ptrHGlob( refDTransHelper ); + + // do the conversion and return + return MultiByteToWideChar( cp_src, + 0, + lpMultiByteString, + lenStr, + static_cast< LPWSTR >( ptrHGlob.GetMemPtr( ) ), + ptrHGlob.MemSize( ) ); +} + +// converts unicode text into text of the specified code page +// automatically calculates the necessary buffer size and allocates +// the buffer + +int WideCharToMultiByteEx( UINT cp_dest, + LPCWSTR lpWideCharString, + int lenStr, + CStgTransferHelper& refDTransHelper, + BOOL bEnsureTrailingZero ) +{ + OSL_ASSERT( IsValidCodePage( cp_dest ) ); + OSL_ASSERT( nullptr != lpWideCharString ); + + // calculate the required buff size + int reqSize = CalcBuffSizeForTextConversion( cp_dest, lpWideCharString, lenStr ); + + if ( bEnsureTrailingZero ) + reqSize += sizeof( sal_Int8 ); + + // initialize the data-transfer helper + refDTransHelper.init( reqSize ); + + // setup a global memory pointer + CRawHGlobalPtr ptrHGlob( refDTransHelper ); + + // do the conversion and return + return WideCharToMultiByte( cp_dest, + 0, + lpWideCharString, + lenStr, + static_cast< LPSTR >( ptrHGlob.GetMemPtr( ) ), + ptrHGlob.MemSize( ), + nullptr, + nullptr ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/TxtCnvtHlp.hxx b/vcl/win/dtrans/TxtCnvtHlp.hxx new file mode 100644 index 0000000000..5294879337 --- /dev/null +++ b/vcl/win/dtrans/TxtCnvtHlp.hxx @@ -0,0 +1,41 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/datatransfer/DataFlavor.hpp> + +#include "DTransHelper.hxx" + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +int MultiByteToWideCharEx( UINT cp_src, + LPCSTR lpMultiByteString, + int lenStr, + CStgTransferHelper& refDTransHelper, + BOOL bEnsureTrailingZero = TRUE ); + +int WideCharToMultiByteEx( UINT cp_dest, + LPCWSTR lpWideCharString, + int lenStr, + CStgTransferHelper& refDTransHelper, + BOOL bEnsureTrailingZero = TRUE ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/WinClip.hxx b/vcl/win/dtrans/WinClip.hxx new file mode 100644 index 0000000000..f90d5eea06 --- /dev/null +++ b/vcl/win/dtrans/WinClip.hxx @@ -0,0 +1,26 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> + +const sal_Int32 CF_INVALID = 0; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/WinClipboard.cxx b/vcl/win/dtrans/WinClipboard.cxx new file mode 100644 index 0000000000..1a8eaea151 --- /dev/null +++ b/vcl/win/dtrans/WinClipboard.cxx @@ -0,0 +1,383 @@ +/* -*- 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 <sal/config.h> + +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> +#include <com/sun/star/datatransfer/clipboard/ClipboardEvent.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <vcl/svapp.hxx> +#include <svdata.hxx> +#include <salinst.hxx> + +#include <com/sun/star/datatransfer/clipboard/RenderingCapabilities.hpp> +#include "XNotifyingDataObject.hxx" + +#include <systools/win32/comtools.hxx> +#include "DtObjFactory.hxx" +#include "APNDataObject.hxx" +#include "DOTransferable.hxx" +#include "WinClipboard.hxx" + +#if !defined WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <ole2.h> +#include <objidl.h> + +using namespace com::sun::star; + +namespace +{ +CWinClipboard* s_pCWinClipbImpl = nullptr; +osl::Mutex s_aClipboardSingletonMutex; +} + +/*XEventListener,*/ +CWinClipboard::CWinClipboard(const uno::Reference<uno::XComponentContext>& rxContext, + const OUString& aClipboardName) + : m_xContext(rxContext) + , m_itsName(aClipboardName) + , m_pCurrentClipContent(nullptr) +{ + // necessary to reassociate from + // the static callback function + { + osl::MutexGuard aGuard(s_aClipboardSingletonMutex); + s_pCWinClipbImpl = this; + } + + registerClipboardViewer(); +} + +CWinClipboard::~CWinClipboard() +{ + { + osl::MutexGuard aGuard(s_aClipboardSingletonMutex); + s_pCWinClipbImpl = nullptr; + } + + unregisterClipboardViewer(); +} + +void CWinClipboard::disposing(std::unique_lock<std::mutex>& mutex) +{ + { + osl::MutexGuard aGuard(s_aClipboardSingletonMutex); + s_pCWinClipbImpl = nullptr; + } + + unregisterClipboardViewer(); + + WeakComponentImplHelper::disposing(mutex); +} + +// XClipboard + +// to avoid unnecessary traffic we check first if there is a clipboard +// content which was set via setContent, in this case we don't need +// to query the content from the clipboard, create a new wrapper object +// and so on, we simply return the original XTransferable instead of our +// DOTransferable + +uno::Reference<datatransfer::XTransferable> SAL_CALL CWinClipboard::getContents() +{ + osl::MutexGuard aGuard(m_aContentMutex); + + if (m_bDisposed) + throw lang::DisposedException("object is already disposed", + static_cast<XClipboardEx*>(this)); + + // use the shortcut or create a transferable from + // system clipboard + { + osl::MutexGuard aGuard2(m_aContentCacheMutex); + + if (nullptr != m_pCurrentClipContent) + return m_pCurrentClipContent->m_XTransferable; + + // Content cached? + if (m_foreignContent.is()) + return m_foreignContent; + + // release the mutex, so that the variable may be + // changed by other threads + } + + uno::Reference<datatransfer::XTransferable> rClipContent; + + // get the current format list from clipboard + if (UINT nFormats; !GetUpdatedClipboardFormats(nullptr, 0, &nFormats) + && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + std::vector<UINT> aUINTFormats(nFormats); + if (GetUpdatedClipboardFormats(aUINTFormats.data(), nFormats, &nFormats)) + { + std::vector<sal_uInt32> aFormats(aUINTFormats.begin(), aUINTFormats.end()); + rClipContent = new CDOTransferable(m_xContext, this, aFormats); + + osl::MutexGuard aGuard2(m_aContentCacheMutex); + m_foreignContent = rClipContent; + } + } + + return rClipContent; +} + +IDataObjectPtr CWinClipboard::getIDataObject() +{ + osl::MutexGuard aGuard(m_aContentMutex); + + if (m_bDisposed) + throw lang::DisposedException("object is already disposed", + static_cast<XClipboardEx*>(this)); + + // get the current dataobject from clipboard + IDataObjectPtr pIDataObject; + HRESULT hr = m_MtaOleClipboard.getClipboard(&pIDataObject); + + if (SUCCEEDED(hr)) + { + // create an apartment neutral dataobject and initialize it with a + // com smart pointer to the IDataObject from clipboard + pIDataObject = new CAPNDataObject(pIDataObject); + } + + return pIDataObject; +} + +void SAL_CALL CWinClipboard::setContents( + const uno::Reference<datatransfer::XTransferable>& xTransferable, + const uno::Reference<datatransfer::clipboard::XClipboardOwner>& xClipboardOwner) +{ + osl::MutexGuard aGuard(m_aContentMutex); + + if (m_bDisposed) + throw lang::DisposedException("object is already disposed", + static_cast<XClipboardEx*>(this)); + + IDataObjectPtr pIDataObj; + + if (xTransferable.is()) + { + { + osl::MutexGuard aGuard2(m_aContentCacheMutex); + + m_foreignContent.clear(); + + m_pCurrentClipContent = new CXNotifyingDataObject( + CDTransObjFactory::createDataObjFromTransferable(m_xContext, xTransferable), + xTransferable, xClipboardOwner, this); + } + + pIDataObj = IDataObjectPtr(m_pCurrentClipContent); + } + + m_MtaOleClipboard.setClipboard(pIDataObj.get()); +} + +OUString SAL_CALL CWinClipboard::getName() +{ + if (m_bDisposed) + throw lang::DisposedException("object is already disposed", + static_cast<XClipboardEx*>(this)); + + return m_itsName; +} + +// XFlushableClipboard + +void SAL_CALL CWinClipboard::flushClipboard() +{ + osl::MutexGuard aGuard(m_aContentMutex); + + if (m_bDisposed) + throw lang::DisposedException("object is already disposed", + static_cast<XClipboardEx*>(this)); + + // actually it should be ClearableMutexGuard aGuard( m_aContentCacheMutex ); + // but it does not work since FlushClipboard does a callback and frees DataObject + // which results in a deadlock in onReleaseDataObject. + // FlushClipboard had to be synchron in order to prevent shutdown until all + // clipboard-formats are rendered. + // The request is needed to prevent flushing if we are not clipboard owner (it is + // not known what happens if we flush but aren't clipboard owner). + // It may be possible to move the request to the clipboard STA thread by saving the + // DataObject and call OleIsCurrentClipboard before flushing. + + if (nullptr != m_pCurrentClipContent) + m_MtaOleClipboard.flushClipboard(); +} + +// XClipboardEx + +sal_Int8 SAL_CALL CWinClipboard::getRenderingCapabilities() +{ + if (m_bDisposed) + throw lang::DisposedException("object is already disposed", + static_cast<XClipboardEx*>(this)); + + using namespace datatransfer::clipboard::RenderingCapabilities; + return (Delayed | Persistent); +} + +// XClipboardNotifier + +void SAL_CALL CWinClipboard::addClipboardListener( + const uno::Reference<datatransfer::clipboard::XClipboardListener>& listener) +{ + if (m_bDisposed) + throw lang::DisposedException("object is already disposed", + static_cast<XClipboardEx*>(this)); + + // check input parameter + if (!listener.is()) + throw lang::IllegalArgumentException("empty reference", static_cast<XClipboardEx*>(this), + 1); + + std::unique_lock aGuard(m_aMutex); + maClipboardListeners.addInterface(aGuard, listener); +} + +void SAL_CALL CWinClipboard::removeClipboardListener( + const uno::Reference<datatransfer::clipboard::XClipboardListener>& listener) +{ + if (m_bDisposed) + throw lang::DisposedException("object is already disposed", + static_cast<XClipboardEx*>(this)); + + // check input parameter + if (!listener.is()) + throw lang::IllegalArgumentException("empty reference", static_cast<XClipboardEx*>(this), + 1); + + std::unique_lock aGuard(m_aMutex); + maClipboardListeners.removeInterface(aGuard, listener); +} + +void CWinClipboard::notifyAllClipboardListener() +{ + if (m_bDisposed) + return; + + std::unique_lock aGuard(m_aMutex); + if (m_bDisposed) + return; + + if (!maClipboardListeners.getLength(aGuard)) + return; + + try + { + uno::Reference<datatransfer::XTransferable> rXTransf(getContents()); + datatransfer::clipboard::ClipboardEvent aClipbEvent(static_cast<XClipboard*>(this), + rXTransf); + maClipboardListeners.notifyEach( + aGuard, &datatransfer::clipboard::XClipboardListener::changedContents, aClipbEvent); + } + catch (const lang::DisposedException&) + { + OSL_FAIL("Service Manager disposed"); + + aGuard.unlock(); + // no further clipboard changed notifications + unregisterClipboardViewer(); + } +} + +// XServiceInfo + +OUString SAL_CALL CWinClipboard::getImplementationName() +{ + return "com.sun.star.datatransfer.clipboard.ClipboardW32"; +} + +sal_Bool SAL_CALL CWinClipboard::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +uno::Sequence<OUString> SAL_CALL CWinClipboard::getSupportedServiceNames() +{ + return { "com.sun.star.datatransfer.clipboard.SystemClipboard" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +dtrans_CWinClipboard_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const& args) +{ + // We run unit tests in parallel, which is a problem when touching a shared resource + // like the system clipboard, so rather use the dummy GenericClipboard. + static const bool bRunningUnitTest = getenv("LO_TESTNAME"); + + if (bRunningUnitTest) + { + SolarMutexGuard aGuard; + auto xClipboard = ImplGetSVData()->mpDefInst->CreateClipboard(args); + if (xClipboard.is()) + xClipboard->acquire(); + return xClipboard.get(); + } + else + { + return cppu::acquire(new CWinClipboard(context, "")); + } +} + +void CWinClipboard::onReleaseDataObject(CXNotifyingDataObject* theCaller) +{ + OSL_ASSERT(nullptr != theCaller); + + if (theCaller) + theCaller->lostOwnership(); + + // if the current caller is the one we currently hold, then set it to NULL + // because an external source must be the clipboardowner now + osl::MutexGuard aGuard(m_aContentCacheMutex); + + if (m_pCurrentClipContent == theCaller) + m_pCurrentClipContent = nullptr; +} + +void CWinClipboard::registerClipboardViewer() +{ + m_MtaOleClipboard.registerClipViewer(CWinClipboard::onClipboardContentChanged); +} + +void CWinClipboard::unregisterClipboardViewer() { m_MtaOleClipboard.registerClipViewer(nullptr); } + +void WINAPI CWinClipboard::onClipboardContentChanged() +{ + osl::MutexGuard aGuard(s_aClipboardSingletonMutex); + + // reassociation to instance through static member + if (nullptr != s_pCWinClipbImpl) + { + s_pCWinClipbImpl->m_foreignContent.clear(); + s_pCWinClipbImpl->notifyAllClipboardListener(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/WinClipboard.hxx b/vcl/win/dtrans/WinClipboard.hxx new file mode 100644 index 0000000000..fbaa1b2062 --- /dev/null +++ b/vcl/win/dtrans/WinClipboard.hxx @@ -0,0 +1,112 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <comphelper/compbase.hxx> +#include <comphelper/interfacecontainer4.hxx> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp> +#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp> +#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <osl/conditn.hxx> +#include <systools/win32/comtools.hxx> + +#include "MtaOleClipb.hxx" +#include "XNotifyingDataObject.hxx" + +// implements the XClipboard[Ex] ... interfaces +// for the clipboard viewer mechanism we need a static callback function +// and a static member to reassociate from this static function to the +// class instance +// watch out: we are using only one static member variable and not a list +// because we assume to be instantiated only once +// this will be assured by a OneInstanceFactory of the service and not +// by this class! + +class CWinClipboard final + : public comphelper::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard, + css::datatransfer::clipboard::XFlushableClipboard, + css::lang::XServiceInfo> +{ + friend STDMETHODIMP_(ULONG) CXNotifyingDataObject::Release(); + + css::uno::Reference<css::uno::XComponentContext> m_xContext; + const OUString m_itsName; + CMtaOleClipboard m_MtaOleClipboard; + CXNotifyingDataObject* m_pCurrentClipContent; + com::sun::star::uno::Reference<com::sun::star::datatransfer::XTransferable> m_foreignContent; + osl::Mutex m_aContentMutex; + osl::Mutex m_aContentCacheMutex; + comphelper::OInterfaceContainerHelper4<css::datatransfer::clipboard::XClipboardListener> + maClipboardListeners; + + void notifyAllClipboardListener(); + void onReleaseDataObject(CXNotifyingDataObject* theCaller); + + void registerClipboardViewer(); + void unregisterClipboardViewer(); + + static void WINAPI onClipboardContentChanged(); + +public: + CWinClipboard(const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const OUString& aClipboardName); + virtual ~CWinClipboard() override; + + // XClipboard + virtual css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override; + virtual void SAL_CALL setContents( + const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable, + const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner) + override; + virtual OUString SAL_CALL getName() override; + + // XFlushableClipboard + virtual void SAL_CALL flushClipboard() override; + + // XClipboardEx + virtual sal_Int8 SAL_CALL getRenderingCapabilities() override; + + // XClipboardNotifier + virtual void SAL_CALL addClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) + override; + virtual void SAL_CALL removeClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) + override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + IDataObjectPtr getIDataObject(); + + virtual void disposing(std::unique_lock<std::mutex>&) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/XNotifyingDataObject.cxx b/vcl/win/dtrans/XNotifyingDataObject.cxx new file mode 100644 index 0000000000..aab168f043 --- /dev/null +++ b/vcl/win/dtrans/XNotifyingDataObject.cxx @@ -0,0 +1,149 @@ +/* -*- 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 <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> +#include "XNotifyingDataObject.hxx" +#include "WinClipboard.hxx" + +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::clipboard; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::Reference; + +CXNotifyingDataObject::CXNotifyingDataObject( + const IDataObjectPtr& aIDataObject, + const Reference< XTransferable >& aXTransferable, + const Reference< XClipboardOwner >& aXClipOwner, + CWinClipboard* const theWinClipoard) : + m_nRefCnt( 0 ), + m_aIDataObject( aIDataObject ), + m_XTransferable( aXTransferable ), + m_XClipboardOwner( aXClipOwner ), + m_pWinClipImpl( theWinClipoard ) +{ +} + +STDMETHODIMP CXNotifyingDataObject::QueryInterface( REFIID iid, void** ppvObject ) +{ + if ( nullptr == ppvObject ) + return E_INVALIDARG; + + HRESULT hr = E_NOINTERFACE; + + *ppvObject = nullptr; + if ( ( __uuidof( IUnknown ) == iid ) || + ( __uuidof( IDataObject ) == iid ) ) + { + *ppvObject = static_cast< IUnknown* >( this ); + static_cast<LPUNKNOWN>(*ppvObject)->AddRef( ); + hr = S_OK; + } + + return hr; +} + +STDMETHODIMP_(ULONG) CXNotifyingDataObject::AddRef( ) +{ + return static_cast< ULONG >( InterlockedIncrement( &m_nRefCnt ) ); +} + +STDMETHODIMP_(ULONG) CXNotifyingDataObject::Release( ) +{ + ULONG nRefCnt = + static_cast< ULONG >( InterlockedDecrement( &m_nRefCnt ) ); + + if ( 0 == nRefCnt ) + { + if ( m_pWinClipImpl ) + m_pWinClipImpl->onReleaseDataObject( this ); + + delete this; + } + + return nRefCnt; +} + +STDMETHODIMP CXNotifyingDataObject::GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) +{ + return m_aIDataObject->GetData(pFormatetc, pmedium); +} + +STDMETHODIMP CXNotifyingDataObject::EnumFormatEtc( + DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) +{ + return m_aIDataObject->EnumFormatEtc(dwDirection, ppenumFormatetc); +} + +STDMETHODIMP CXNotifyingDataObject::QueryGetData( FORMATETC * pFormatetc ) +{ + return m_aIDataObject->QueryGetData(pFormatetc); +} + +STDMETHODIMP CXNotifyingDataObject::GetDataHere( FORMATETC * lpFetc, STGMEDIUM * lpStgMedium ) +{ + return m_aIDataObject->GetDataHere(lpFetc, lpStgMedium); +} + +STDMETHODIMP CXNotifyingDataObject::GetCanonicalFormatEtc( FORMATETC * lpFetc, FORMATETC * lpCanonicalFetc ) +{ + return m_aIDataObject->GetCanonicalFormatEtc(lpFetc, lpCanonicalFetc); +} + +STDMETHODIMP CXNotifyingDataObject::SetData( FORMATETC * lpFetc, STGMEDIUM * lpStgMedium, BOOL bRelease ) +{ + return m_aIDataObject->SetData( lpFetc, lpStgMedium, bRelease ); +} + +STDMETHODIMP CXNotifyingDataObject::DAdvise( + FORMATETC * lpFetc, DWORD advf, IAdviseSink * lpAdvSink, DWORD* pdwConnection ) +{ + return m_aIDataObject->DAdvise( lpFetc, advf, lpAdvSink, pdwConnection ); +} + +STDMETHODIMP CXNotifyingDataObject::DUnadvise( DWORD dwConnection ) +{ + return m_aIDataObject->DUnadvise( dwConnection ); +} + +STDMETHODIMP CXNotifyingDataObject::EnumDAdvise( IEnumSTATDATA ** ppenumAdvise ) +{ + return m_aIDataObject->EnumDAdvise( ppenumAdvise ); +} + +CXNotifyingDataObject::operator IDataObject*( ) +{ + return static_cast< IDataObject* >( this ); +} + +void CXNotifyingDataObject::lostOwnership( ) +{ + try + { + if (m_XClipboardOwner.is()) + m_XClipboardOwner->lostOwnership( + static_cast<XClipboardEx*>(m_pWinClipImpl), m_XTransferable); + } + catch(RuntimeException&) + { + TOOLS_WARN_EXCEPTION( "vcl", "" ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/XNotifyingDataObject.hxx b/vcl/win/dtrans/XNotifyingDataObject.hxx new file mode 100644 index 0000000000..408413a5de --- /dev/null +++ b/vcl/win/dtrans/XNotifyingDataObject.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <objidl.h> + +#include <systools/win32/comtools.hxx> + +/*-------------------------------------------------------------------------- + To implement the lostOwnership mechanism cleanly we need this wrapper + object +----------------------------------------------------------------------------*/ + +// forward +class CWinClipboard; + +class CXNotifyingDataObject final : public IDataObject +{ +public: + CXNotifyingDataObject( + const IDataObjectPtr& aIDataObject, + const css::uno::Reference< css::datatransfer::XTransferable >& aXTransferable, + const css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner >& aXClipOwner, + CWinClipboard* const theWinClipoard); + + virtual ~CXNotifyingDataObject() {} + + // ole interface implementation + + //IUnknown interface methods + STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override; + STDMETHODIMP_( ULONG ) AddRef( ) override; + STDMETHODIMP_( ULONG ) Release( ) override; + + // IDataObject interface methods + STDMETHODIMP GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override; + STDMETHODIMP GetDataHere( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override; + STDMETHODIMP QueryGetData( FORMATETC * pFormatetc ) override; + STDMETHODIMP GetCanonicalFormatEtc( FORMATETC * pFormatectIn, FORMATETC * pFormatetcOut ) override; + STDMETHODIMP SetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease ) override; + STDMETHODIMP EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) override; + STDMETHODIMP DAdvise( FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD* pdwConnection ) override; + STDMETHODIMP DUnadvise( DWORD dwConnection ) override; + STDMETHODIMP EnumDAdvise( IEnumSTATDATA** ppenumAdvise ) override; + + operator IDataObject*( ); + +private: + void lostOwnership( ); + + sal_Int32 m_nRefCnt; + IDataObjectPtr m_aIDataObject; + const css::uno::Reference< css::datatransfer::XTransferable > m_XTransferable; + const css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner > m_XClipboardOwner; + CWinClipboard* const m_pWinClipImpl; + + friend class CWinClipboard; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/XTDataObject.cxx b/vcl/win/dtrans/XTDataObject.cxx new file mode 100644 index 0000000000..b3f5ccb303 --- /dev/null +++ b/vcl/win/dtrans/XTDataObject.cxx @@ -0,0 +1,751 @@ +/* -*- 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 <osl/diagnose.h> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/safeint.hxx> + +#include "XTDataObject.hxx" +#include <com/sun/star/datatransfer/DataFlavor.hpp> +#include "ImplHelper.hxx" +#include "DTransHelper.hxx" +#include "TxtCnvtHlp.hxx" +#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp> +#include <com/sun/star/awt/AsyncCallback.hpp> +#include <com/sun/star/awt/XCallback.hpp> +#include "FmtFilter.hxx" +#include <cppuhelper/implbase.hxx> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <shlobj.h> + +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::clipboard; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; + +namespace { + +void setupStgMedium( const FORMATETC& fetc, + CStgTransferHelper& stgTransHlp, + STGMEDIUM& stgmedium ) +{ + stgmedium.pUnkForRelease = nullptr; + + if ( fetc.cfFormat == CF_METAFILEPICT ) + { + stgmedium.tymed = TYMED_MFPICT; + stgmedium.hMetaFilePict = static_cast< HMETAFILEPICT >( stgTransHlp.getHGlobal( ) ); + } + else if ( fetc.cfFormat == CF_ENHMETAFILE ) + { + stgmedium.tymed = TYMED_ENHMF; + stgmedium.hEnhMetaFile = static_cast< HENHMETAFILE >( stgTransHlp.getHGlobal( ) ); + } + else if ( fetc.tymed & TYMED_HGLOBAL ) + { + stgmedium.tymed = TYMED_HGLOBAL; + stgmedium.hGlobal = stgTransHlp.getHGlobal( ); + } + else if ( fetc.tymed & TYMED_ISTREAM ) + { + stgmedium.tymed = TYMED_ISTREAM; + stgTransHlp.getIStream( &stgmedium.pstm ); + } + else + OSL_ASSERT( false ); +} + +/** + We need to destroy XTransferable in the main thread to avoid dead lock + when locking in the clipboard thread. So we transfer the ownership of the + XTransferable reference to this object and release it when the callback + is executed in main thread. +*/ +class AsyncDereference : public cppu::WeakImplHelper<css::awt::XCallback> +{ + Reference<XTransferable> maTransferable; + +public: + AsyncDereference(css::uno::Reference<css::datatransfer::XTransferable> const & rTransferable) + : maTransferable(rTransferable) + {} + + virtual void SAL_CALL notify(css::uno::Any const &) override + { + maTransferable.clear(); + } +}; + +// a helper class that will be thrown by the function validateFormatEtc + +class CInvalidFormatEtcException +{ +public: + HRESULT m_hr; + explicit CInvalidFormatEtcException( HRESULT hr ) : m_hr( hr ) {}; +}; + +void validateFormatEtc( LPFORMATETC lpFormatEtc ) +{ + OSL_ASSERT( lpFormatEtc ); + + if ( lpFormatEtc->lindex != -1 ) + throw CInvalidFormatEtcException( DV_E_LINDEX ); + + if ( !(lpFormatEtc->dwAspect & DVASPECT_CONTENT) && + !(lpFormatEtc->dwAspect & DVASPECT_SHORTNAME) ) + throw CInvalidFormatEtcException( DV_E_DVASPECT ); + + if ( !(lpFormatEtc->tymed & TYMED_HGLOBAL) && + !(lpFormatEtc->tymed & TYMED_ISTREAM) && + !(lpFormatEtc->tymed & TYMED_MFPICT) && + !(lpFormatEtc->tymed & TYMED_ENHMF) ) + throw CInvalidFormatEtcException( DV_E_TYMED ); + + if ( lpFormatEtc->cfFormat == CF_METAFILEPICT && + !(lpFormatEtc->tymed & TYMED_MFPICT) ) + throw CInvalidFormatEtcException( DV_E_TYMED ); + + if ( lpFormatEtc->cfFormat == CF_ENHMETAFILE && + !(lpFormatEtc->tymed & TYMED_ENHMF) ) + throw CInvalidFormatEtcException( DV_E_TYMED ); +} + +void invalidateStgMedium( STGMEDIUM& stgmedium ) +{ + stgmedium.tymed = TYMED_NULL; +} + +HRESULT translateStgExceptionCode( HRESULT hr ) +{ + HRESULT hrTransl; + + switch( hr ) + { + case STG_E_MEDIUMFULL: + hrTransl = hr; + break; + + default: + hrTransl = E_UNEXPECTED; + break; + } + + return hrTransl; +} + +// inline +void renderDataAndSetupStgMedium( + const sal_Int8* lpStorage, const FORMATETC& fetc, sal_uInt32 nInitStgSize, + sal_uInt32 nBytesToTransfer, STGMEDIUM& stgmedium ) +{ + OSL_PRECOND( !nInitStgSize || (nInitStgSize >= nBytesToTransfer), + "Memory size less than number of bytes to transfer" ); + + CStgTransferHelper stgTransfHelper( AUTO_INIT ); + + // setup storage size + if ( nInitStgSize > 0 ) + stgTransfHelper.init( nInitStgSize ); + +#if OSL_DEBUG_LEVEL > 0 + sal_uInt32 nBytesWritten = 0; + stgTransfHelper.write( lpStorage, nBytesToTransfer, &nBytesWritten ); + OSL_ASSERT( nBytesWritten == nBytesToTransfer ); +#else + stgTransfHelper.write( lpStorage, nBytesToTransfer ); +#endif + + setupStgMedium( fetc, stgTransfHelper, stgmedium ); +} + +} + +CXTDataObject::CXTDataObject( const Reference< XComponentContext >& rxContext, + const Reference< XTransferable >& aXTransferable ) + : m_nRefCnt( 0 ) + , m_XTransferable( aXTransferable ) + , m_XComponentContext( rxContext ) + , m_bFormatEtcContainerInitialized( false ) + , m_DataFormatTranslator( rxContext ) + , m_FormatRegistrar( rxContext, m_DataFormatTranslator ) +{ +} + +CXTDataObject::~CXTDataObject() +{ + css::awt::AsyncCallback::create(m_XComponentContext)->addCallback( + new AsyncDereference(m_XTransferable), + css::uno::Any()); +} + +// IUnknown->QueryInterface + +STDMETHODIMP CXTDataObject::QueryInterface( REFIID iid, void** ppvObject ) +{ + if ( nullptr == ppvObject ) + return E_INVALIDARG; + + HRESULT hr = E_NOINTERFACE; + + *ppvObject = nullptr; + if ( ( __uuidof( IUnknown ) == iid ) || + ( __uuidof( IDataObject ) == iid ) ) + { + *ppvObject = static_cast< IUnknown* >( this ); + static_cast<LPUNKNOWN>(*ppvObject)->AddRef( ); + hr = S_OK; + } + + return hr; +} + +// IUnknown->AddRef + +STDMETHODIMP_(ULONG) CXTDataObject::AddRef( ) +{ + return static_cast< ULONG >( InterlockedIncrement( &m_nRefCnt ) ); +} + +// IUnknown->Release + +STDMETHODIMP_(ULONG) CXTDataObject::Release( ) +{ + ULONG nRefCnt = + static_cast< ULONG >( InterlockedDecrement( &m_nRefCnt ) ); + + if ( 0 == nRefCnt ) + delete this; + + return nRefCnt; +} + +STDMETHODIMP CXTDataObject::GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) +{ + if ( !(pFormatetc && pmedium) ) + return E_INVALIDARG; + + try + { + // prepare data transfer + invalidateStgMedium( *pmedium ); + validateFormatEtc( pFormatetc ); + + // handle locale request, because locale is an artificial format for us + if ( CF_LOCALE == pFormatetc->cfFormat ) + renderLocaleAndSetupStgMedium( *pFormatetc, *pmedium ); + else if ( CF_UNICODETEXT == pFormatetc->cfFormat ) + renderUnicodeAndSetupStgMedium( *pFormatetc, *pmedium ); + else + renderAnyDataAndSetupStgMedium( *pFormatetc, *pmedium ); + } + catch(UnsupportedFlavorException&) + { + HRESULT hr = DV_E_FORMATETC; + + CFormatEtc aFormatetc(*pFormatetc); + if (CFormatRegistrar::isSynthesizeableFormat(aFormatetc)) + hr = renderSynthesizedFormatAndSetupStgMedium( *pFormatetc, *pmedium ); + + return hr; + } + catch( CInvalidFormatEtcException& ex ) + { + return ex.m_hr; + } + catch( CStgTransferHelper::CStgTransferException& ex ) + { + return translateStgExceptionCode( ex.m_hr ); + } + catch(...) + { + return E_UNEXPECTED; + } + + return S_OK; +} + +//inline +void CXTDataObject::renderLocaleAndSetupStgMedium( + FORMATETC const & fetc, STGMEDIUM& stgmedium ) +{ + if ( !m_FormatRegistrar.hasSynthesizedLocale( ) ) + throw CInvalidFormatEtcException( DV_E_FORMATETC ); + LCID lcid = CFormatRegistrar::getSynthesizedLocale( ); + renderDataAndSetupStgMedium( + reinterpret_cast< sal_Int8* >( &lcid ), + fetc, + 0, + sizeof( LCID ), + stgmedium ); +} + +void CXTDataObject::renderUnicodeAndSetupStgMedium( + FORMATETC const & fetc, STGMEDIUM& stgmedium ) +{ + DataFlavor aFlavor = formatEtcToDataFlavor( fetc ); + + Any aAny = m_XTransferable->getTransferData( aFlavor ); + + // unfortunately not all transferables fulfill the + // spec. and do throw an UnsupportedFlavorException + // so we must check the any + if ( !aAny.hasValue( ) ) + { + OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" ); + throw UnsupportedFlavorException( ); + } + + OUString aText; + aAny >>= aText; + + sal_uInt32 nBytesToTransfer = aText.getLength( ) * sizeof( sal_Unicode ); + + // to be sure there is an ending 0 + sal_uInt32 nRequiredMemSize = nBytesToTransfer + sizeof( sal_Unicode ); + + renderDataAndSetupStgMedium( + reinterpret_cast< const sal_Int8* >( aText.getStr( ) ), + fetc, + nRequiredMemSize, + nBytesToTransfer, + stgmedium ); +} + +void CXTDataObject::renderAnyDataAndSetupStgMedium( + FORMATETC& fetc, STGMEDIUM& stgmedium ) +{ + DataFlavor aFlavor = formatEtcToDataFlavor( fetc ); + + Any aAny = m_XTransferable->getTransferData( aFlavor ); + + // unfortunately not all transferables fulfill the + // spec. and do throw an UnsupportedFlavorException + // so we must check the any + if ( !aAny.hasValue( ) ) + { + OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" ); + throw UnsupportedFlavorException( ); + } + + Sequence< sal_Int8 > clipDataStream; + aAny >>= clipDataStream; + + sal_uInt32 nRequiredMemSize = 0; + if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc.cfFormat ) ) + nRequiredMemSize = sizeof( sal_Int8 ) * clipDataStream.getLength( ) + 1; + + // prepare data for transmission + // #i124085# DIBV5 should not happen for now, but keep as hint here + if ( CF_DIBV5 == fetc.cfFormat || CF_DIB == fetc.cfFormat ) + { +#ifdef DBG_UTIL + if(CF_DIBV5 == fetc.cfFormat) + { + OSL_ENSURE(o3tl::make_unsigned(clipDataStream.getLength()) > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV5HEADER)), "Wrong size on CF_DIBV5 data (!)"); + } + else // CF_DIB == fetc.cfFormat + { + OSL_ENSURE(o3tl::make_unsigned(clipDataStream.getLength()) > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)), "Wrong size on CF_DIB data (!)"); + } +#endif + + // remove BITMAPFILEHEADER + clipDataStream = OOBmpToWinDIB( clipDataStream ); + } + + if ( CF_METAFILEPICT == fetc.cfFormat ) + { + stgmedium.tymed = TYMED_MFPICT; + stgmedium.hMetaFilePict = OOMFPictToWinMFPict( clipDataStream ); + stgmedium.pUnkForRelease = nullptr; + } + else if( CF_ENHMETAFILE == fetc.cfFormat ) + { + stgmedium.tymed = TYMED_ENHMF; + stgmedium.hMetaFilePict = OOMFPictToWinENHMFPict( clipDataStream ); + stgmedium.pUnkForRelease = nullptr; + } + else + renderDataAndSetupStgMedium( + clipDataStream.getArray( ), + fetc, + nRequiredMemSize, + clipDataStream.getLength( ), + stgmedium ); +} + +HRESULT CXTDataObject::renderSynthesizedFormatAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium ) +{ + HRESULT hr = S_OK; + + try + { + if ( CF_UNICODETEXT == fetc.cfFormat ) + // the transferable seems to have only text + renderSynthesizedUnicodeAndSetupStgMedium( fetc, stgmedium ); + else if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc.cfFormat ) ) + // the transferable seems to have only unicode text + renderSynthesizedTextAndSetupStgMedium( fetc, stgmedium ); + else + // the transferable seems to have only text/html + renderSynthesizedHtmlAndSetupStgMedium( fetc, stgmedium ); + } + catch(UnsupportedFlavorException&) + { + hr = DV_E_FORMATETC; + } + catch( CInvalidFormatEtcException& ) + { + OSL_FAIL( "Unexpected exception" ); + } + catch( CStgTransferHelper::CStgTransferException& ex ) + { + return translateStgExceptionCode( ex.m_hr ); + } + catch(...) + { + hr = E_UNEXPECTED; + } + + return hr; +} + +// the transferable must have only text, so we will synthesize unicode text + +void CXTDataObject::renderSynthesizedUnicodeAndSetupStgMedium( FORMATETC const & fetc, STGMEDIUM& stgmedium ) +{ + OSL_ASSERT( CF_UNICODETEXT == fetc.cfFormat ); + + Any aAny = m_XTransferable->getTransferData( m_FormatRegistrar.getRegisteredTextFlavor( ) ); + + // unfortunately not all transferables fulfill the + // spec. and do throw an UnsupportedFlavorException + // so we must check the any + if ( !aAny.hasValue( ) ) + { + OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" ); + throw UnsupportedFlavorException( ); + } + + Sequence< sal_Int8 > aText; + aAny >>= aText; + + CStgTransferHelper stgTransfHelper; + + MultiByteToWideCharEx( + CFormatRegistrar::getRegisteredTextCodePage( ), + reinterpret_cast< char* >( aText.getArray( ) ), + aText.getLength( ), + stgTransfHelper ); + + setupStgMedium( fetc, stgTransfHelper, stgmedium ); +} + +// the transferable must have only unicode text so we will synthesize text + +void CXTDataObject::renderSynthesizedTextAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium ) +{ + OSL_ASSERT( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc.cfFormat ) ); + + DataFlavor aFlavor = formatEtcToDataFlavor( + CDataFormatTranslator::getFormatEtcForClipformat( CF_UNICODETEXT ) ); + + Any aAny = m_XTransferable->getTransferData( aFlavor ); + + // unfortunately not all transferables fulfill the + // spec. and do throw an UnsupportedFlavorException + // so we must check the any + if ( !aAny.hasValue( ) ) + { + OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" ); + throw UnsupportedFlavorException( ); + } + + OUString aUnicodeText; + aAny >>= aUnicodeText; + + CStgTransferHelper stgTransfHelper; + + WideCharToMultiByteEx( + GetACP( ), + o3tl::toW( aUnicodeText.getStr( ) ), + aUnicodeText.getLength( ), + stgTransfHelper ); + + setupStgMedium( fetc, stgTransfHelper, stgmedium ); +} + +void CXTDataObject::renderSynthesizedHtmlAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium ) +{ + OSL_ASSERT( CDataFormatTranslator::isHTMLFormat( fetc.cfFormat ) ); + + DataFlavor aFlavor; + + // creating a DataFlavor on the fly + aFlavor.MimeType = "text/html"; + aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get(); + + Any aAny = m_XTransferable->getTransferData( aFlavor ); + + // unfortunately not all transferables fulfill the + // spec. and do throw an UnsupportedFlavorException + // so we must check the any + if ( !aAny.hasValue( ) ) + { + OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" ); + throw UnsupportedFlavorException( ); + } + + Sequence< sal_Int8 > aTextHtmlSequence; + aAny >>= aTextHtmlSequence; + + Sequence< sal_Int8 > aHTMLFormatSequence = TextHtmlToHTMLFormat( aTextHtmlSequence ); + + sal_uInt32 nBytesToTransfer = aHTMLFormatSequence.getLength( ); + + renderDataAndSetupStgMedium( + reinterpret_cast< const sal_Int8* >( aHTMLFormatSequence.getArray( ) ), + fetc, + 0, + nBytesToTransfer, + stgmedium ); +} + +// IDataObject->EnumFormatEtc + +STDMETHODIMP CXTDataObject::EnumFormatEtc( + DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) +{ + if ( nullptr == ppenumFormatetc ) + return E_INVALIDARG; + + if ( DATADIR_SET == dwDirection ) + return E_NOTIMPL; + + *ppenumFormatetc = nullptr; + + InitializeFormatEtcContainer( ); + + HRESULT hr; + if ( DATADIR_GET == dwDirection ) + { + *ppenumFormatetc = new CEnumFormatEtc( this, m_FormatEtcContainer ); + static_cast< LPUNKNOWN >( *ppenumFormatetc )->AddRef( ); + + hr = S_OK; + } + else + hr = E_INVALIDARG; + + return hr; +} + +// IDataObject->QueryGetData + +STDMETHODIMP CXTDataObject::QueryGetData( FORMATETC * pFormatetc ) +{ + if ( (nullptr == pFormatetc) || IsBadReadPtr( pFormatetc, sizeof( FORMATETC ) ) ) + return E_INVALIDARG; + + InitializeFormatEtcContainer( ); + + CFormatEtc aFormatetc(*pFormatetc); + return m_FormatEtcContainer.hasFormatEtc(aFormatetc) ? S_OK : S_FALSE; +} + +// IDataObject->GetDataHere + +STDMETHODIMP CXTDataObject::GetDataHere( FORMATETC *, STGMEDIUM * ) +{ + return E_NOTIMPL; +} + +// IDataObject->GetCanonicalFormatEtc + +STDMETHODIMP CXTDataObject::GetCanonicalFormatEtc( FORMATETC *, FORMATETC * ) +{ + return E_NOTIMPL; +} + +// IDataObject->SetData + +STDMETHODIMP CXTDataObject::SetData( FORMATETC *, STGMEDIUM *, BOOL ) +{ + return E_NOTIMPL; +} + +// IDataObject->DAdvise + +STDMETHODIMP CXTDataObject::DAdvise( FORMATETC *, DWORD, IAdviseSink *, DWORD * ) +{ + return E_NOTIMPL; +} + +// IDataObject->DUnadvise + +STDMETHODIMP CXTDataObject::DUnadvise( DWORD ) +{ + return E_NOTIMPL; +} + +// IDataObject->EnumDAdvise + +STDMETHODIMP CXTDataObject::EnumDAdvise( IEnumSTATDATA ** ) +{ + return E_NOTIMPL; +} + +// for our convenience + +CXTDataObject::operator IDataObject*( ) +{ + return static_cast< IDataObject* >( this ); +} + +inline +DataFlavor CXTDataObject::formatEtcToDataFlavor( const FORMATETC& aFormatEtc ) const +{ + DataFlavor aFlavor; + + if ( m_FormatRegistrar.hasSynthesizedLocale( ) ) + aFlavor = m_DataFormatTranslator.getDataFlavorFromFormatEtc( + aFormatEtc.cfFormat, CFormatRegistrar::getSynthesizedLocale()); + else + aFlavor = m_DataFormatTranslator.getDataFlavorFromFormatEtc(aFormatEtc.cfFormat); + + if ( !aFlavor.MimeType.getLength( ) ) + throw UnsupportedFlavorException( ); + + return aFlavor; +} + +inline void CXTDataObject::InitializeFormatEtcContainer( ) +{ + if ( !m_bFormatEtcContainerInitialized ) + { + m_FormatRegistrar.RegisterFormats( m_XTransferable, m_FormatEtcContainer ); + m_bFormatEtcContainerInitialized = true; + } +} + +CEnumFormatEtc::CEnumFormatEtc( LPUNKNOWN lpUnkOuter, const CFormatEtcContainer& aFormatEtcContainer ) : + m_nRefCnt( 0 ), + m_lpUnkOuter( lpUnkOuter ), + m_FormatEtcContainer( aFormatEtcContainer ) +{ + Reset( ); +} + +// IUnknown->QueryInterface + +STDMETHODIMP CEnumFormatEtc::QueryInterface( REFIID iid, void** ppvObject ) +{ + if ( nullptr == ppvObject ) + return E_INVALIDARG; + + HRESULT hr = E_NOINTERFACE; + + *ppvObject = nullptr; + + if ( ( __uuidof( IUnknown ) == iid ) || + ( __uuidof( IEnumFORMATETC ) == iid ) ) + { + *ppvObject = static_cast< IUnknown* >( this ); + static_cast< LPUNKNOWN >( *ppvObject )->AddRef( ); + hr = S_OK; + } + + return hr; +} + +// IUnknown->AddRef + +STDMETHODIMP_(ULONG) CEnumFormatEtc::AddRef( ) +{ + // keep the dataobject alive + m_lpUnkOuter->AddRef( ); + return InterlockedIncrement( &m_nRefCnt ); +} + +// IUnknown->Release + +STDMETHODIMP_(ULONG) CEnumFormatEtc::Release( ) +{ + // release the outer dataobject + m_lpUnkOuter->Release( ); + + ULONG nRefCnt = InterlockedDecrement( &m_nRefCnt ); + if ( 0 == nRefCnt ) + delete this; + + return nRefCnt; +} + +// IEnumFORMATETC->Next + +STDMETHODIMP CEnumFormatEtc::Next( ULONG nRequested, FORMATETC * lpDest, ULONG* lpFetched ) +{ + if ( ( nRequested < 1 ) || + (( nRequested > 1 ) && ( nullptr == lpFetched )) || + IsBadWritePtr( lpDest, sizeof( FORMATETC ) * nRequested ) ) + return E_INVALIDARG; + + sal_uInt32 nFetched = m_FormatEtcContainer.nextFormatEtc( lpDest, nRequested ); + + if ( nullptr != lpFetched ) + *lpFetched = nFetched; + + return (nFetched == nRequested) ? S_OK : S_FALSE; +} + +// IEnumFORMATETC->Skip + +STDMETHODIMP CEnumFormatEtc::Skip( ULONG celt ) +{ + return m_FormatEtcContainer.skipFormatEtc( celt ) ? S_OK : S_FALSE; +} + +// IEnumFORMATETC->Reset + +STDMETHODIMP CEnumFormatEtc::Reset( ) +{ + m_FormatEtcContainer.beginEnumFormatEtc( ); + return S_OK; +} + +// IEnumFORMATETC->Clone + +STDMETHODIMP CEnumFormatEtc::Clone( IEnumFORMATETC** ppenum ) +{ + if ( nullptr == ppenum ) + return E_INVALIDARG; + + *ppenum = new CEnumFormatEtc( m_lpUnkOuter, m_FormatEtcContainer ); + static_cast< LPUNKNOWN >( *ppenum )->AddRef( ); + + return S_OK; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/XTDataObject.hxx b/vcl/win/dtrans/XTDataObject.hxx new file mode 100644 index 0000000000..77f8c53f29 --- /dev/null +++ b/vcl/win/dtrans/XTDataObject.hxx @@ -0,0 +1,134 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp> + +#include "DataFmtTransl.hxx" + +#include "FetcList.hxx" + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <ole2.h> +#include <objidl.h> + +/*-------------------------------------------------------------------------- + - the function principle of the windows clipboard: + a data provider offers all formats he can deliver on the clipboard + a clipboard client ask for the available formats on the clipboard + and decides if there is a format he can use + if there is one, he requests the data in this format + + - This class inherits from IDataObject and so can be placed on the + OleClipboard. The class wraps a transferable object which is the + original DataSource + - DataFlavors offered by this transferable will be translated into + appropriate clipboard formats + - if the transferable contains text data always text and unicodetext + will be offered or vice versa + - text data will be automatically converted between text and unicode text + - although the transferable may support text in different charsets + (codepages) only text in one codepage can be offered by the clipboard + +----------------------------------------------------------------------------*/ + +class CStgTransferHelper; + +class CXTDataObject : public IDataObject +{ +public: + CXTDataObject( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::datatransfer::XTransferable >& aXTransferable ); + virtual ~CXTDataObject(); + + // ole interface implementation + + //IUnknown interface methods + STDMETHODIMP QueryInterface(REFIID iid, void** ppvObject) override; + STDMETHODIMP_( ULONG ) AddRef( ) override; + STDMETHODIMP_( ULONG ) Release( ) override; + + // IDataObject interface methods + STDMETHODIMP GetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override; + STDMETHODIMP GetDataHere( FORMATETC * pFormatetc, STGMEDIUM * pmedium ) override; + STDMETHODIMP QueryGetData( FORMATETC * pFormatetc ) override; + STDMETHODIMP GetCanonicalFormatEtc( FORMATETC * pFormatectIn, FORMATETC * pFormatetcOut ) override; + STDMETHODIMP SetData( FORMATETC * pFormatetc, STGMEDIUM * pmedium, BOOL fRelease ) override; + STDMETHODIMP EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC** ppenumFormatetc ) override; + STDMETHODIMP DAdvise( FORMATETC * pFormatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD* pdwConnection ) override; + STDMETHODIMP DUnadvise( DWORD dwConnection ) override; + STDMETHODIMP EnumDAdvise( IEnumSTATDATA** ppenumAdvise ) override; + + operator IDataObject*( ); + +private: + css::datatransfer::DataFlavor formatEtcToDataFlavor( const FORMATETC& aFormatEtc ) const; + + void renderLocaleAndSetupStgMedium( FORMATETC const & fetc, STGMEDIUM& stgmedium ); + void renderUnicodeAndSetupStgMedium( FORMATETC const & fetc, STGMEDIUM& stgmedium ); + void renderAnyDataAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium ); + + HRESULT renderSynthesizedFormatAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium ); + void renderSynthesizedUnicodeAndSetupStgMedium( FORMATETC const & fetc, STGMEDIUM& stgmedium ); + void renderSynthesizedTextAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium ); + void renderSynthesizedHtmlAndSetupStgMedium( FORMATETC& fetc, STGMEDIUM& stgmedium ); + + inline void InitializeFormatEtcContainer( ); + +private: + LONG m_nRefCnt; + css::uno::Reference< css::datatransfer::XTransferable > m_XTransferable; + css::uno::Reference< css::uno::XComponentContext> m_XComponentContext; + CFormatEtcContainer m_FormatEtcContainer; + bool m_bFormatEtcContainerInitialized; + CDataFormatTranslator m_DataFormatTranslator; + CFormatRegistrar m_FormatRegistrar; +}; + +class CEnumFormatEtc : public IEnumFORMATETC +{ +public: + CEnumFormatEtc( LPUNKNOWN lpUnkOuter, const CFormatEtcContainer& aFormatEtcContainer ); + virtual ~CEnumFormatEtc() {} + + // IUnknown + STDMETHODIMP QueryInterface( REFIID iid, void** ppvObject ) override; + STDMETHODIMP_( ULONG ) AddRef( ) override; + STDMETHODIMP_( ULONG ) Release( ) override; + + //IEnumFORMATETC + STDMETHODIMP Next( ULONG nRequested, FORMATETC * lpDest, ULONG* lpFetched ) override; + STDMETHODIMP Skip( ULONG celt ) override; + STDMETHODIMP Reset( ) override; + STDMETHODIMP Clone( IEnumFORMATETC** ppenum ) override; + +private: + LONG m_nRefCnt; + LPUNKNOWN m_lpUnkOuter; + CFormatEtcContainer m_FormatEtcContainer; +}; + +typedef CEnumFormatEtc *PCEnumFormatEtc; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/clipboardmanager.cxx b/vcl/win/dtrans/clipboardmanager.cxx new file mode 100644 index 0000000000..bff5aec49f --- /dev/null +++ b/vcl/win/dtrans/clipboardmanager.cxx @@ -0,0 +1,198 @@ +/* -*- 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 "clipboardmanager.hxx" +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequence.hxx> +#include <rtl/ref.hxx> + +using namespace com::sun::star::container; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::clipboard; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace cppu; +using namespace osl; + +using ::dtrans::ClipboardManager; + +static std::mutex g_InstanceGuard; +static rtl::Reference<ClipboardManager> g_Instance; +static bool g_Disposed = false; + + +ClipboardManager::ClipboardManager(): + m_aDefaultName(OUString("default")) +{ +} + +ClipboardManager::~ClipboardManager() +{ +} + +OUString SAL_CALL ClipboardManager::getImplementationName( ) +{ + return "com.sun.star.comp.datatransfer.ClipboardManager"; +} + +sal_Bool SAL_CALL ClipboardManager::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL ClipboardManager::getSupportedServiceNames( ) +{ + return { "com.sun.star.datatransfer.clipboard.ClipboardManager" }; +} + +Reference< XClipboard > SAL_CALL ClipboardManager::getClipboard( const OUString& aName ) +{ + std::unique_lock aGuard(m_aMutex); + + // object is disposed already + if (m_bDisposed) + throw DisposedException("object is disposed.", + static_cast < XClipboardManager * > (this)); + + ClipboardMap::iterator iter = + m_aClipboardMap.find(aName.getLength() ? aName : m_aDefaultName); + + if (iter != m_aClipboardMap.end()) + return iter->second; + + throw NoSuchElementException(aName, static_cast < XClipboardManager * > (this)); +} + +void SAL_CALL ClipboardManager::addClipboard( const Reference< XClipboard >& xClipboard ) +{ + OSL_ASSERT(xClipboard.is()); + + // check parameter + if (!xClipboard.is()) + throw IllegalArgumentException("empty reference", + static_cast < XClipboardManager * > (this), 1); + + // the name "default" is reserved for internal use + OUString aName = xClipboard->getName(); + if ( m_aDefaultName == aName ) + throw IllegalArgumentException("name reserved", + static_cast < XClipboardManager * > (this), 1); + + // try to add new clipboard to the list + std::unique_lock aGuard(m_aMutex); + if (!m_bDisposed) + { + std::pair< const OUString, Reference< XClipboard > > value ( + aName.getLength() ? aName : m_aDefaultName, + xClipboard ); + + std::pair< ClipboardMap::iterator, bool > p = m_aClipboardMap.insert(value); + aGuard.unlock(); + + // insert failed, element must exist already + if (!p.second) + throw ElementExistException(aName, static_cast < XClipboardManager * > (this)); + + // request disposing notifications + Reference< XComponent > xComponent(xClipboard, UNO_QUERY); + if (xComponent.is()) + xComponent->addEventListener(static_cast < XEventListener * > (this)); + } +} + +void SAL_CALL ClipboardManager::removeClipboard( const OUString& aName ) +{ + std::unique_lock aGuard(m_aMutex); + if (!m_bDisposed) + m_aClipboardMap.erase(aName.getLength() ? aName : m_aDefaultName ); +} + +Sequence< OUString > SAL_CALL ClipboardManager::listClipboardNames() +{ + std::unique_lock aGuard(m_aMutex); + + if (m_bDisposed) + throw DisposedException("object is disposed.", + static_cast < XClipboardManager * > (this)); + + return comphelper::mapKeysToSequence(m_aClipboardMap); +} + +void ClipboardManager::disposing(std::unique_lock<std::mutex>& rGuard) +{ + rGuard.unlock(); + + { + std::unique_lock aGuard(g_InstanceGuard); + g_Instance.clear(); + g_Disposed = true; + } + + // removeClipboard is still allowed here, so make a copy of the + // list (to ensure integrity) and clear the original. + rGuard.lock(); + ClipboardMap aCopy; + std::swap(aCopy, m_aClipboardMap); + rGuard.unlock(); + + // dispose all clipboards still in list + for (auto const& elem : aCopy) + { + Reference< XComponent > xComponent(elem.second, UNO_QUERY); + if (xComponent.is()) + { + try + { + xComponent->removeEventListener(static_cast < XEventListener * > (this)); + xComponent->dispose(); + } + catch (const Exception&) + { + // exceptions can be safely ignored here. + } + } + } +} + +void SAL_CALL ClipboardManager::disposing( const EventObject& event ) +{ + Reference < XClipboard > xClipboard(event.Source, UNO_QUERY); + + if (xClipboard.is()) + removeClipboard(xClipboard->getName()); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +dtrans_ClipboardManager_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + std::unique_lock aGuard(g_InstanceGuard); + if (g_Disposed) + return nullptr; + if (!g_Instance) + g_Instance.set(new ClipboardManager()); + return cppu::acquire(g_Instance.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/clipboardmanager.hxx b/vcl/win/dtrans/clipboardmanager.hxx new file mode 100644 index 0000000000..27f9ddbdae --- /dev/null +++ b/vcl/win/dtrans/clipboardmanager.hxx @@ -0,0 +1,89 @@ +/* -*- 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 . + */ + +#pragma once + +#include <comphelper/compbase.hxx> + +#include <com/sun/star/datatransfer/clipboard/XClipboardManager.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include <map> + +typedef std::map< OUString, css::uno::Reference< css::datatransfer::clipboard::XClipboard > > ClipboardMap; + +namespace dtrans +{ + + class ClipboardManager : public ::comphelper::WeakComponentImplHelper < + css::datatransfer::clipboard::XClipboardManager, + css::lang::XEventListener, + css::lang::XServiceInfo > + { + ClipboardMap m_aClipboardMap; + + const OUString m_aDefaultName; + + virtual ~ClipboardManager() override; + protected: + using WeakComponentImplHelperBase::disposing; + public: + + ClipboardManager(); + + /* + * XServiceInfo + */ + + virtual OUString SAL_CALL getImplementationName( ) override; + + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + /* + * XComponent + */ + + virtual void disposing(std::unique_lock<std::mutex>& rGuard) override; + + /* + * XEventListener + */ + + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + /* + * XClipboardManager + */ + + virtual css::uno::Reference< css::datatransfer::clipboard::XClipboard > SAL_CALL getClipboard( const OUString& aName ) override; + + virtual void SAL_CALL addClipboard( const css::uno::Reference< css::datatransfer::clipboard::XClipboard >& xClipboard ) override; + + virtual void SAL_CALL removeClipboard( const OUString& aName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL listClipboardNames( ) override; + + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/ftransl.cxx b/vcl/win/dtrans/ftransl.cxx new file mode 100644 index 0000000000..20056bba0e --- /dev/null +++ b/vcl/win/dtrans/ftransl.cxx @@ -0,0 +1,550 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> + +#include "ftransl.hxx" +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/datatransfer/XMimeContentType.hpp> +#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include "ImplHelper.hxx" + +#include <shlobj.h> + +#define CPPUTYPE_SEQSALINT8 cppu::UnoType<Sequence< sal_Int8 >>::get() +#define CPPUTYPE_DEFAULT CPPUTYPE_SEQSALINT8 + +constexpr OUString Windows_FormatName = u"windows_formatname"_ustr; +const css::uno::Type CppuType_ByteSequence = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get(); +const css::uno::Type CppuType_String = ::cppu::UnoType<OUString>::get(); + +using namespace osl; +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::container; + +namespace +{ + +struct FormatEntry +{ + FormatEntry( + const char *mime_content_type, + const char *human_presentable_name, + const char *native_format_name, + CLIPFORMAT std_clipboard_format_id, + css::uno::Type const & cppu_type + ); + + css::datatransfer::DataFlavor aDataFlavor; + OUString aNativeFormatName; + sal_Int32 aStandardFormatId; +}; + +} + +FormatEntry::FormatEntry( + const char *mime_content_type, + const char *human_presentable_name, + const char *native_format_name, + CLIPFORMAT std_clipboard_format_id, + css::uno::Type const & cppu_type) + : aDataFlavor( OUString::createFromAscii(mime_content_type), OUString::createFromAscii(human_presentable_name), cppu_type) +{ + if (native_format_name) + aNativeFormatName = OUString::createFromAscii(native_format_name); + else + aNativeFormatName = OUString::createFromAscii(human_presentable_name); + + aStandardFormatId = std_clipboard_format_id; +} + +// to optimize searching we add all entries with a +// standard clipboard format number first, in the +// table before the entries with CF_INVALID +// if we are searching for a standard clipboard +// format number we can stop if we find the first +// CF_INVALID + +const std::vector< FormatEntry > g_TranslTable { + //SotClipboardFormatId::DIF + FormatEntry("application/x-openoffice-dif;windows_formatname=\"DIF\"", "DIF", "DIF", CF_DIF, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::BITMAP + + // #i124085# CF_DIBV5 disabled, leads to problems at export. To fully support, using + // an own mime-type may be necessary. I have tried that, but saw no real advantages + // with different apps when exchanging bitmap-based data. So, disabled for now. At + // the same time increased png format exchange for better interoperability + // FormatEntry("application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"", "Bitmap", "Bitmap", CF_DIBV5, CPPUTYPE_DEFAULT), + + FormatEntry("application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"", "Bitmap", "Bitmap", CF_DIB, CPPUTYPE_DEFAULT), + FormatEntry("application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"", "Bitmap", "Bitmap", CF_BITMAP, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STRING + FormatEntry("text/plain;charset=utf-16", "Unicode-Text", "", CF_UNICODETEXT, CppuType_String), + // Format Locale - for internal use + FormatEntry("application/x-openoffice-locale;windows_formatname=\"Locale\"", "Locale", "Locale", CF_LOCALE, CPPUTYPE_DEFAULT), + // SOT_FORMAT_WMF + FormatEntry("application/x-openoffice-wmf;windows_formatname=\"Image WMF\"", "Windows MetaFile", "Image WMF", CF_METAFILEPICT, CPPUTYPE_DEFAULT), + // SOT_FORMAT_EMF + FormatEntry("application/x-openoffice-emf;windows_formatname=\"Image EMF\"", "Windows Enhanced MetaFile", "Image EMF", CF_ENHMETAFILE, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::FILE_LIST + FormatEntry("application/x-openoffice-filelist;windows_formatname=\"FileList\"", "FileList", "FileList", CF_HDROP, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SYLK + FormatEntry("application/x-openoffice-sylk;windows_formatname=\"Sylk\"", "Sylk", "Sylk", CF_SYLK, CPPUTYPE_DEFAULT ), + // SotClipboardFormatId::GDIMETAFILE + FormatEntry("application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"", "GDIMetaFile", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::PRIVATE + FormatEntry("application/x-openoffice-private;windows_formatname=\"Private\"", "Private", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::SIMPLE_FILE + FormatEntry("application/x-openoffice-file;windows_formatname=\"FileNameW\"", "FileName", nullptr, CF_INVALID, CppuType_String), + // SotClipboardFormatId::RTF + FormatEntry("text/rtf", "Rich Text Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::DRAWING + FormatEntry("application/x-openoffice-drawing;windows_formatname=\"Drawing Format\"", "Drawing Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::SVXB + FormatEntry("application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\"", "SVXB (StarView Bitmap/Animation)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::SVIM + FormatEntry("application/x-openoffice-svim;windows_formatname=\"SVIM (StarView ImageMap)\"", "SVIM (StarView ImageMap)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::XFA + FormatEntry("application/x-libreoffice-xfa;windows_formatname=\"XFA (XOutDev FillAttr Any)\"", "XFA (XOutDev FillAttr Any)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT + FormatEntry("application/vnd.oasis.opendocument.text-flat-xml", "EditEngine ODF", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::INTERNALLINK_STATE + FormatEntry("application/x-openoffice-internallink-state;windows_formatname=\"StatusInfo of SvxInternalLink\"", "StatusInfo of SvxInternalLink", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::SOLK + FormatEntry("application/x-openoffice-solk;windows_formatname=\"SOLK (StarOffice Link)\"", "SOLK (StarOffice Link)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::NETSCAPE_BOOKMARK + FormatEntry("application/x-openoffice-netscape-bookmark;windows_formatname=\"Netscape Bookmark\"", "Netscape Bookmark", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::TREELISTBOX + FormatEntry("application/x-openoffice-treelistbox;windows_formatname=\"SV_LBOX_DD_FORMAT\"", "SV_LBOX_DD_FORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::NATIVE + FormatEntry("application/x-openoffice-native;windows_formatname=\"Native\"", "Native", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::OWNERLINK + FormatEntry("application/x-openoffice-ownerlink;windows_formatname=\"OwnerLink\"", "OwnerLink", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STARSERVER + FormatEntry("application/x-openoffice-starserver;windows_formatname=\"StarServerFormat\"", "StarServerFormat", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STAROBJECT + FormatEntry("application/x-openoffice-starobject;windows_formatname=\"StarObjectFormat\"", "StarObjectFormat", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::APPLETOBJECT + FormatEntry("application/x-openoffice-appletobject;windows_formatname=\"Applet Object\"", "Applet Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::PLUGIN_OBJECT + FormatEntry("application/x-openoffice-plugin-object;windows_formatname=\"PlugIn Object\"", "PlugIn Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STARWRITER_30 + FormatEntry("application/x-openoffice-starwriter-30;windows_formatname=\"StarWriter 3.0\"", "StarWriter 3.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWRITER_40 + FormatEntry("application/x-openoffice-starwriter-40;windows_formatname=\"StarWriter 4.0\"", "StarWriter 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWRITER_50 + FormatEntry("application/x-openoffice-starwriter-50;windows_formatname=\"StarWriter 5.0\"", "StarWriter 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWRITERWEB_40 + FormatEntry("application/x-openoffice-starwriterweb-40;windows_formatname=\"StarWriter/Web 4.0\"", "StarWriter/Web 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWRITERWEB_50 + FormatEntry("application/x-openoffice-starwriterweb-50;windows_formatname=\"StarWriter/Web 5.0\"", "StarWriter/Web 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWRITERGLOB_40 + FormatEntry("application/x-openoffice-starwriterglob-40;windows_formatname=\"StarWriter/Global 4.0\"", "StarWriter/Global 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STARWRITERGLOB_50 + FormatEntry("application/x-openoffice-starwriterglob-50;windows_formatname=\"StarWriter/Global 5.0\"", "StarWriter/Global 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARDRAW + FormatEntry("application/x-openoffice-stardraw;windows_formatname=\"StarDrawDocument\"", "StarDrawDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARDRAW_40 + FormatEntry("application/x-openoffice-stardraw-40;windows_formatname=\"StarDrawDocument 4.0\"", "StarDrawDocument 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARIMPRESS_50 + FormatEntry("application/x-openoffice-starimpress-50;windows_formatname=\"StarImpress 5.0\"", "StarImpress 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STARDRAW_50 + FormatEntry("application/x-openoffice-stardraw-50;windows_formatname=\"StarDraw 5.0\"", "StarDraw 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARCALC + FormatEntry("application/x-openoffice-starcalc;windows_formatname=\"StarCalcDocument\"", "StarCalcDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARCALC_40 + FormatEntry("application/x-openoffice-starcalc-40;windows_formatname=\"StarCalc 4.0\"", "StarCalc 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STARCALC_50 + FormatEntry("application/x-openoffice-starcalc-50;windows_formatname=\"StarCalc 5.0\"", "StarCalc 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STARCHART + FormatEntry("application/x-openoffice-starchart;windows_formatname=\"StarChartDocument\"", "StarChartDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STARCHART_40 + FormatEntry("application/x-openoffice-starchart-40;windows_formatname=\"StarChartDocument 4.0\"", "StarChartDocument 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::STARCHART_50 + FormatEntry("application/x-openoffice-starchart-50;windows_formatname=\"StarChart 5.0\"", "StarChart 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARIMAGE + FormatEntry("application/x-openoffice-starimage;windows_formatname=\"StarImageDocument\"", "StarImageDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARIMAGE_40 + FormatEntry("application/x-openoffice-starimage-40;windows_formatname=\"StarImageDocument 4.0\"", "StarImageDocument 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARIMAGE_50 + FormatEntry("application/x-openoffice-starimage-50;windows_formatname=\"StarImage 5.0\"", "StarImage 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARMATH + FormatEntry("application/x-openoffice-starmath;windows_formatname=\"StarMath\"", "StarMath", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARMATH_40 + FormatEntry("application/x-openoffice-starmath-40;windows_formatname=\"StarMathDocument 4.0\"", "StarMathDocument 4.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARMATH_50 + FormatEntry("application/x-openoffice-starmath-50;windows_formatname=\"StarMath 5.0\"", "StarMath 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STAROBJECT_PAINTDOC + FormatEntry("application/x-openoffice-starobject-paintdoc;windows_formatname=\"StarObjectPaintDocument\"", "StarObjectPaintDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::FILLED_AREA + FormatEntry("application/x-openoffice-filled-area;windows_formatname=\"FilledArea\"", "FilledArea", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::HTML + FormatEntry("text/html", "HTML (HyperText Markup Language)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::HTML_SIMPLE + FormatEntry("application/x-openoffice-html-simple;windows_formatname=\"HTML Format\"", "HTML Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::CHAOS + FormatEntry("application/x-openoffice-chaos;windows_formatname=\"FORMAT_CHAOS\"", "FORMAT_CHAOS", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::CNT_MSGATTACHFILE + FormatEntry("application/x-openoffice-msgattachfile;windows_formatname=\"CNT_MSGATTACHFILE_FORMAT\"", "CNT_MSGATTACHFILE_FORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::BIFF_5 + FormatEntry("application/x-openoffice-biff5;windows_formatname=\"Biff5\"", "Biff5", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::BIFF__5 + FormatEntry("application/x-openoffice-biff-5;windows_formatname=\"Biff 5\"", "Biff 5", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::BIFF_8 + FormatEntry("application/x-openoffice-biff-8;windows_formatname=\"Biff8\"", "Biff8", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SYLK_BIGCAPS + FormatEntry("application/x-openoffice-sylk-bigcaps;windows_formatname=\"SYLK\"", "SYLK", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::LINK + FormatEntry("application/x-openoffice-link;windows_formatname=\"Link\"", "Link", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARDRAW_TABBAR + FormatEntry("application/x-openoffice-stardraw-tabbar;windows_formatname=\"StarDraw TabBar\"", "StarDraw TabBar", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SONLK + FormatEntry("application/x-openoffice-sonlk;windows_formatname=\"SONLK (StarOffice Navi Link)\"", "SONLK (StarOffice Navi Link)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::MSWORD_DOC + FormatEntry("application/msword", "MSWordDoc", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STAR_FRAMESET_DOC + FormatEntry("application/x-openoffice-star-frameset-doc;windows_formatname=\"StarFrameSetDocument\"", "StarFrameSetDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::OFFICE_DOC + FormatEntry("application/x-openoffice-office-doc;windows_formatname=\"OfficeDocument\"", "OfficeDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::NOTES_DOCINFO + FormatEntry("application/x-openoffice-notes-docinfo;windows_formatname=\"NotesDocInfo\"", "NotesDocInfo", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::NOTES_HNOTE + FormatEntry("application/x-openoffice-notes-hnote;windows_formatname=\"NoteshNote\"", "NoteshNote", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::NOTES_NATIVE + FormatEntry("application/x-openoffice-notes-native;windows_formatname=\"Native\"", "Native", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SFX_DOC + FormatEntry("application/x-openoffice-sfx-doc;windows_formatname=\"SfxDocument\"", "SfxDocument", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::EVDF + FormatEntry("application/x-openoffice-evdf;windows_formatname=\"EVDF (Explorer View Dummy Format)\"", "EVDF (Explorer View Dummy Format)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::ESDF + FormatEntry("application/x-openoffice-esdf;windows_formatname=\"ESDF (Explorer Search Dummy Format)\"", "ESDF (Explorer Search Dummy Format)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::IDF + FormatEntry("application/x-openoffice-idf;windows_formatname=\"IDF (Iconview Dummy Format)\"", "IDF (Iconview Dummy Format)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::EFTP + FormatEntry("application/x-openoffice-eftp;windows_formatname=\"EFTP (Explorer Ftp File)\"", "EFTP (Explorer Ftp File)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::EFD + FormatEntry("application/x-openoffice-efd;windows_formatname=\"EFD (Explorer Ftp Dir)\"", "EFD (Explorer Ftp Dir)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SVX_FORMFIELDEXCH + FormatEntry("application/x-openoffice-svx-formfieldexch;windows_formatname=\"SvxFormFieldExch\"", "SvxFormFieldExch", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::EXTENDED_TABBAR + FormatEntry("application/x-openoffice-extended-tabbar;windows_formatname=\"ExtendedTabBar\"", "ExtendedTabBar", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_DATAEXCHANGE + FormatEntry("application/x-openoffice-sba-dataexchange;windows_formatname=\"SBA-DATAFORMAT\"", "SBA-DATAFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_FIELDDATAEXCHANGE + FormatEntry("application/x-openoffice-sba-fielddataexchange;windows_formatname=\"SBA-FIELDFORMAT\"", "SBA-FIELDFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_PRIVATE_URL + FormatEntry("application/x-openoffice-sba-private-url;windows_formatname=\"SBA-PRIVATEURLFORMAT\"", "SBA-PRIVATEURLFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_TABED + FormatEntry("application/x-openoffice-sba-tabed;windows_formatname=\"Tabed\"", "Tabed", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_TABID + FormatEntry("application/x-openoffice-sba-tabid;windows_formatname=\"Tabid\"", "Tabid", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_JOIN + FormatEntry("application/x-openoffice-sba-join;windows_formatname=\"SBA-JOINFORMAT\"", "SBA-JOINFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::OBJECTDESCRIPTOR + FormatEntry("application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"", "Star Object Descriptor (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::LINKSRCDESCRIPTOR + FormatEntry("application/x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link Source Descriptor (XML)\"", "Star Link Source Descriptor (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::EMBED_SOURCE + FormatEntry("application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"", "Star Embed Source (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::LINK_SOURCE + FormatEntry("application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\"", "Star Link Source (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::EMBEDDED_OBJ + FormatEntry("application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\"", "Star Embedded Object (XML)", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::FILECONTENT + FormatEntry("application/x-openoffice-filecontent;windows_formatname=\"" CFSTR_FILECONTENTS "\"", CFSTR_FILECONTENTS, nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::FILEGRPDESCRIPTOR + FormatEntry("application/x-openoffice-filegrpdescriptor;windows_formatname=\"" CFSTR_FILEDESCRIPTOR "\"", CFSTR_FILEDESCRIPTOR, nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::FILENAME + FormatEntry("application/x-openoffice-filename;windows_formatname=\"" CFSTR_FILENAME "\"", CFSTR_FILENAME, nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SD_OLE + FormatEntry("application/x-openoffice-sd-ole;windows_formatname=\"SD-OLE\"", "SD-OLE", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::EMBEDDED_OBJ_OLE + FormatEntry("application/x-openoffice-embedded-obj-ole;windows_formatname=\"Embedded Object\"", "Embedded Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::EMBED_SOURCE_OLE + FormatEntry("application/x-openoffice-embed-source-ole;windows_formatname=\"Embed Source\"", "Embed Source", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::OBJECTDESCRIPTOR_OLE + FormatEntry("application/x-openoffice-objectdescriptor-ole;windows_formatname=\"Object Descriptor\"", "Object Descriptor", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::LINKSRCDESCRIPTOR_OLE + FormatEntry("application/x-openoffice-linkdescriptor-ole;windows_formatname=\"Link Source Descriptor\"", "Link Source Descriptor", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::LINK_SOURCE_OLE + FormatEntry("application/x-openoffice-link-source-ole;windows_formatname=\"Link Source\"", "Link Source", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_CTRLDATAEXCHANGE + FormatEntry("application/x-openoffice-sba-ctrldataexchange;windows_formatname=\"SBA-CTRLFORMAT\"", "SBA-CTRLFORMAT", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::OUTPLACE_OBJ + FormatEntry("application/x-openoffice-outplace-obj;windows_formatname=\"OutPlace Object\"", "OutPlace Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::CNT_OWN_CLIP + FormatEntry("application/x-openoffice-cnt-own-clip;windows_formatname=\"CntOwnClipboard\"", "CntOwnClipboard", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::INET_IMAGE + FormatEntry("application/x-openoffice-inet-image;windows_formatname=\"SO-INet-Image\"", "SO-INet-Image", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::NETSCAPE_IMAGE + FormatEntry("application/x-openoffice-netscape-image;windows_formatname=\"Netscape Image Format\"", "Netscape Image Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_FORMEXCHANGE + FormatEntry("application/x-openoffice-sba-formexchange;windows_formatname=\"SBA_FORMEXCHANGE\"", "SBA_FORMEXCHANGE", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::SBA_REPORTEXCHANGE + FormatEntry("application/x-openoffice-sba-reportexchange;windows_formatname=\"SBA_REPORTEXCHANGE\"", "SBA_REPORTEXCHANGE", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::UNIFORMRESOURCELOCATOR + FormatEntry("application/x-openoffice-uniformresourcelocator;windows_formatname=\"UniformResourceLocatorW\"", "UniformResourceLocator", nullptr, CF_INVALID, CppuType_String), + //SotClipboardFormatId::STARCHARTDOCUMENT_50 + FormatEntry("application/x-openoffice-starchartdocument-50;windows_formatname=\"StarChartDocument 5.0\"", "StarChartDocument 5.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::GRAPHOBJ + FormatEntry("application/x-openoffice-graphobj;windows_formatname=\"Graphic Object\"", "Graphic Object", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWRITER_60 + FormatEntry("application/vnd.sun.xml.writer", "Writer 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWRITERWEB_60 + FormatEntry("application/vnd.sun.xml.writer.web", "Writer/Web 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWRITERGLOB_60 + FormatEntry("application/vnd.sun.xml.writer.global", "Writer/Global 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARWDRAW_60 + FormatEntry("application/vnd.sun.xml.draw", "Draw 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARIMPRESS_60 + FormatEntry("application/vnd.sun.xml.impress", "Impress 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARCALC_60 + FormatEntry("application/vnd.sun.xml.calc", "Calc 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARCHART_60 + FormatEntry("application/vnd.sun.xml.chart", "Chart 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::STARMATH_60 + FormatEntry("application/vnd.sun.xml.math", "Math 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::DIALOG_60 + FormatEntry("application/vnd.sun.xml.dialog", "Dialog 6.0", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::BMP + FormatEntry("image/bmp", "Windows Bitmap", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::PNG + FormatEntry("image/png", "PNG", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::MATHML + FormatEntry("application/mathml+xml", "MathML", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::DUMMY3 + FormatEntry("application/x-openoffice-dummy3;windows_formatname=\"SO_DUMMYFORMAT_3\"", "SO_DUMMYFORMAT_3", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + //SotClipboardFormatId::DUMMY4 + FormatEntry("application/x-openoffice-dummy4;windows_formatname=\"SO_DUMMYFORMAT_4\"", "SO_DUMMYFORMAT_4", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + // SotClipboardFormatId::RICHTEXT + FormatEntry("text/richtext", "Richtext Format", nullptr, CF_INVALID, CPPUTYPE_DEFAULT), + }; + +namespace { + +void findDataFlavorForStandardFormatId( sal_Int32 aStandardFormatId, DataFlavor& aDataFlavor ) +{ + /* + we stop search if we find the first CF_INVALID + because in the translation table the entries with a + standard clipboard format id appear before the other + entries with CF_INVALID + */ + std::vector< FormatEntry >::const_iterator citer = std::find_if(g_TranslTable.begin(), g_TranslTable.end(), + [&aStandardFormatId](const FormatEntry& rEntry) { + return rEntry.aStandardFormatId == aStandardFormatId + || rEntry.aStandardFormatId == CF_INVALID; + }); + if (citer != g_TranslTable.end() && citer->aStandardFormatId == aStandardFormatId) + aDataFlavor = citer->aDataFlavor; +} + +void findDataFlavorForNativeFormatName( const OUString& aNativeFormatName, DataFlavor& aDataFlavor ) +{ + std::vector< FormatEntry >::const_iterator citer = std::find_if(g_TranslTable.begin(), g_TranslTable.end(), + [&aNativeFormatName](const FormatEntry& rEntry) { + return aNativeFormatName.equalsIgnoreAsciiCase(rEntry.aNativeFormatName); }); + if (citer != g_TranslTable.end()) + aDataFlavor = citer->aDataFlavor; +} + +void findStandardFormatIdForCharset( const OUString& aCharset, Any& aAny ) +{ + if ( aCharset.equalsIgnoreAsciiCase( "utf-16" ) ) + aAny <<= static_cast< sal_Int32 >( CF_UNICODETEXT ); + else + { + sal_Int32 wincp = getWinCPFromMimeCharset( aCharset ); + if ( IsOEMCP ( wincp ) ) + aAny <<= static_cast< sal_Int32 >( CF_OEMTEXT ); + } +} + +void setStandardFormatIdForNativeFormatName( const OUString& aNativeFormatName, Any& aAny ) +{ + std::vector< FormatEntry >::const_iterator citer = std::find_if(g_TranslTable.begin(), g_TranslTable.end(), + [&aNativeFormatName](const FormatEntry& rEntry) { + return aNativeFormatName.equalsIgnoreAsciiCase(rEntry.aNativeFormatName) + && (CF_INVALID != rEntry.aStandardFormatId); + }); + if (citer != g_TranslTable.end()) + aAny <<= citer->aStandardFormatId; +} + +void findStdFormatIdOrNativeFormatNameForFullMediaType( + const Reference< XMimeContentTypeFactory >& aRefXMimeFactory, + const OUString& aFullMediaType, + Any& aAny ) +{ + std::vector< FormatEntry >::const_iterator citer = std::find_if(g_TranslTable.begin(), g_TranslTable.end(), + [&aRefXMimeFactory, &aFullMediaType](const FormatEntry& rEntry) { + Reference<XMimeContentType> refXMime( aRefXMimeFactory->createMimeContentType(rEntry.aDataFlavor.MimeType) ); + return aFullMediaType.equalsIgnoreAsciiCase(refXMime->getFullMediaType()); + }); + if (citer != g_TranslTable.end()) + { + sal_Int32 cf = citer->aStandardFormatId; + if ( CF_INVALID != cf ) + aAny <<= cf; + else + { + OSL_ENSURE( citer->aNativeFormatName.getLength( ), + "Invalid standard format id and empty native format name in translation table" ); + aAny <<= citer->aNativeFormatName; + } + } +} + +bool isTextPlainMediaType( std::u16string_view fullMediaType ) +{ + return o3tl::equalsIgnoreAsciiCase(fullMediaType, u"text/plain"); +} + +DataFlavor mkDataFlv(const OUString& cnttype, const OUString& hpname, Type dtype) +{ + DataFlavor dflv; + dflv.MimeType = cnttype; + dflv.HumanPresentableName = hpname; + dflv.DataType = dtype; + return dflv; +} + +} + +CDataFormatTranslatorUNO::CDataFormatTranslatorUNO( const Reference< XComponentContext >& rxContext ) : + m_xContext( rxContext ) +{ +} + +Any SAL_CALL CDataFormatTranslatorUNO::getSystemDataTypeFromDataFlavor( const DataFlavor& aDataFlavor ) +{ + Any aAny; + + try + { + Reference< XMimeContentTypeFactory > refXMimeCntFactory = MimeContentTypeFactory::create( m_xContext ); + + Reference< XMimeContentType > + refXMimeCntType( refXMimeCntFactory->createMimeContentType( aDataFlavor.MimeType ) ); + + OUString fullMediaType = refXMimeCntType->getFullMediaType( ); + if ( isTextPlainMediaType( fullMediaType ) ) + { + // default is CF_TEXT + aAny <<= static_cast< sal_Int32 >( CF_TEXT ); + + if ( refXMimeCntType->hasParameter( "charset" ) ) + { + // but maybe it is unicode text or oem text + OUString charset = refXMimeCntType->getParameterValue( "charset" ); + findStandardFormatIdForCharset( charset, aAny ); + } + } + else + { + if (refXMimeCntType->hasParameter(Windows_FormatName)) + { + OUString winfmtname = refXMimeCntType->getParameterValue(Windows_FormatName); + aAny <<= winfmtname; + + setStandardFormatIdForNativeFormatName( winfmtname, aAny ); + } + else + findStdFormatIdOrNativeFormatNameForFullMediaType( refXMimeCntFactory, fullMediaType, aAny ); + } + } + catch( IllegalArgumentException& ) + { + OSL_FAIL( "Invalid content-type detected!" ); + } + catch( NoSuchElementException& ) + { + OSL_FAIL( "Illegal content-type parameter" ); + } + catch( ... ) + { + OSL_FAIL( "Unexpected error" ); + throw; + } + + return aAny; +} + +DataFlavor SAL_CALL CDataFormatTranslatorUNO::getDataFlavorFromSystemDataType( const Any& aSysDataType ) +{ + OSL_PRECOND( aSysDataType.hasValue( ), "Empty system data type delivered" ); + + DataFlavor aFlavor = mkDataFlv( OUString(), OUString(), CPPUTYPE_SEQSALINT8 ); + + if ( aSysDataType.getValueType( ) == cppu::UnoType<sal_Int32>::get() ) + { + sal_Int32 clipformat = CF_INVALID; + aSysDataType >>= clipformat; + if ( CF_INVALID != clipformat ) + findDataFlavorForStandardFormatId( clipformat, aFlavor ); + } + else if ( aSysDataType.getValueType( ) == cppu::UnoType<OUString>::get() ) + { + OUString nativeFormatName; + aSysDataType >>= nativeFormatName; + + findDataFlavorForNativeFormatName( nativeFormatName, aFlavor ); + } + else + OSL_FAIL( "Invalid data type received" ); + + return aFlavor; +} + +// XServiceInfo + +OUString SAL_CALL CDataFormatTranslatorUNO::getImplementationName( ) +{ + return "com.sun.star.datatransfer.DataFormatTranslator"; +} + +sal_Bool SAL_CALL CDataFormatTranslatorUNO::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL CDataFormatTranslatorUNO::getSupportedServiceNames( ) +{ + return { "com.sun.star.datatransfer.DataFormatTranslator" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +dtrans_CDataFormatTranslatorUNO_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new CDataFormatTranslatorUNO(context)); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/ftransl.hxx b/vcl/win/dtrans/ftransl.hxx new file mode 100644 index 0000000000..7a5041be5f --- /dev/null +++ b/vcl/win/dtrans/ftransl.hxx @@ -0,0 +1,59 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/datatransfer/XDataFormatTranslator.hpp> +#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include "WinClip.hxx" + +#include <vector> + +class CDataFormatTranslatorUNO : public + cppu::WeakImplHelper< css::datatransfer::XDataFormatTranslator, + css::lang::XServiceInfo > +{ + +public: + explicit CDataFormatTranslatorUNO( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + // XDataFormatTranslator + + virtual css::uno::Any SAL_CALL getSystemDataTypeFromDataFlavor( const css::datatransfer::DataFlavor& aDataFlavor ) override; + + virtual css::datatransfer::DataFlavor SAL_CALL getDataFlavorFromSystemDataType( const css::uno::Any& aSysDataType ) override; + + // XServiceInfo + + virtual OUString SAL_CALL getImplementationName( ) override; + + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +private: + const css::uno::Reference< css::uno::XComponentContext > m_xContext; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/generic_clipboard.cxx b/vcl/win/dtrans/generic_clipboard.cxx new file mode 100644 index 0000000000..fd822f091e --- /dev/null +++ b/vcl/win/dtrans/generic_clipboard.cxx @@ -0,0 +1,136 @@ +/* -*- 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 "generic_clipboard.hxx" +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/datatransfer/clipboard/RenderingCapabilities.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::clipboard; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace cppu; +using namespace osl; + +using ::dtrans::GenericClipboard; + +GenericClipboard::GenericClipboard() : + m_bInitialized(false) +{ +} + +GenericClipboard::~GenericClipboard() +{ +} + +void SAL_CALL GenericClipboard::initialize( const Sequence< Any >& aArguments ) +{ + if (!m_bInitialized) + { + for (Any const & arg : aArguments) + if (arg.getValueType() == cppu::UnoType<OUString>::get()) + { + arg >>= m_aName; + break; + } + } +} + +OUString SAL_CALL GenericClipboard::getImplementationName( ) +{ + return "com.sun.star.comp.datatransfer.clipboard.GenericClipboard"; +} + +sal_Bool SAL_CALL GenericClipboard::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL GenericClipboard::getSupportedServiceNames( ) +{ + return { "com.sun.star.datatransfer.clipboard.GenericClipboard" }; +} + +Reference< XTransferable > SAL_CALL GenericClipboard::getContents() +{ + std::unique_lock aGuard(m_aMutex); + return m_aContents; +} + +void SAL_CALL GenericClipboard::setContents(const Reference< XTransferable >& xTrans, + const Reference< XClipboardOwner >& xClipboardOwner ) +{ + // remember old values for callbacks before setting the new ones. + std::unique_lock aGuard(m_aMutex); + + Reference< XClipboardOwner > oldOwner(m_aOwner); + m_aOwner = xClipboardOwner; + + Reference< XTransferable > oldContents(m_aContents); + m_aContents = xTrans; + + aGuard.unlock(); + + // notify old owner on loss of ownership + if( oldOwner.is() ) + oldOwner->lostOwnership(static_cast < XClipboard * > (this), oldContents); + + // notify all listeners on content changes + aGuard.lock(); + ClipboardEvent aEvent(static_cast < XClipboard * > (this), m_aContents); + maClipboardListeners.notifyEach(aGuard, &XClipboardListener::changedContents, aEvent); +} + +OUString SAL_CALL GenericClipboard::getName() +{ + return m_aName; +} + +sal_Int8 SAL_CALL GenericClipboard::getRenderingCapabilities() +{ + return RenderingCapabilities::Delayed; +} + +void SAL_CALL GenericClipboard::addClipboardListener( const Reference< XClipboardListener >& listener ) +{ + std::unique_lock aGuard( m_aMutex ); + OSL_ENSURE( !m_bDisposed, "object is disposed" ); + if (!m_bDisposed) + maClipboardListeners.addInterface( aGuard, listener ); +} + +void SAL_CALL GenericClipboard::removeClipboardListener( const Reference< XClipboardListener >& listener ) +{ + std::unique_lock aGuard( m_aMutex ); + OSL_ENSURE( !m_bDisposed, "object is disposed" ); + if (!m_bDisposed) + maClipboardListeners.removeInterface( aGuard, listener ); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +dtrans_GenericClipboard_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new GenericClipboard()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/generic_clipboard.hxx b/vcl/win/dtrans/generic_clipboard.hxx new file mode 100644 index 0000000000..cc1ad976b3 --- /dev/null +++ b/vcl/win/dtrans/generic_clipboard.hxx @@ -0,0 +1,101 @@ +/* -*- 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 . + */ + +#pragma once + +#include <comphelper/compbase.hxx> + +#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp> + +#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> + +namespace dtrans +{ + + class GenericClipboard : public ::comphelper::WeakComponentImplHelper < + css::datatransfer::clipboard::XClipboardEx, + css::datatransfer::clipboard::XClipboardNotifier, + css::lang::XServiceInfo, + css::lang::XInitialization > + { + OUString m_aName; + + css::uno::Reference< css::datatransfer::XTransferable > m_aContents; + css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner > m_aOwner; + comphelper::OInterfaceContainerHelper4<css::datatransfer::clipboard::XClipboardListener> maClipboardListeners; + + bool m_bInitialized; + virtual ~GenericClipboard() override; + + public: + + GenericClipboard(); + + /* + * XInitialization + */ + + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + /* + * XServiceInfo + */ + + virtual OUString SAL_CALL getImplementationName( ) override; + + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + /* + * XClipboard + */ + + virtual css::uno::Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override; + + virtual void SAL_CALL setContents( + const css::uno::Reference< css::datatransfer::XTransferable >& xTrans, + const css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override; + + virtual OUString SAL_CALL getName() override; + + /* + * XClipboardEx + */ + + virtual sal_Int8 SAL_CALL getRenderingCapabilities() override; + + /* + * XClipboardNotifier + */ + + virtual void SAL_CALL addClipboardListener( + const css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override; + + virtual void SAL_CALL removeClipboardListener( + const css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override; + + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/globals.cxx b/vcl/win/dtrans/globals.cxx new file mode 100644 index 0000000000..fcddef22e6 --- /dev/null +++ b/vcl/win/dtrans/globals.cxx @@ -0,0 +1,130 @@ +/* -*- 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 <com/sun/star/datatransfer/dnd/DNDConstants.hpp> + +#include "globals.hxx" + +//--> TRA +#include <com/sun/star/datatransfer/XTransferable.hpp> + +// used as shortcut when drag-source and drop-target are the same +css::uno::Reference< css::datatransfer::XTransferable > g_XTransferable; + +//<-- TRA + +using namespace com::sun::star::datatransfer::dnd::DNDConstants; + +sal_Int8 dndOleKeysToAction( DWORD grfKeyState, sal_Int8 nSourceActions) +{ + sal_Int8 ret= 0; + + // no MK_ALT, MK_CONTROL, MK_SHIFT + if( !(grfKeyState & MK_CONTROL) && + !(grfKeyState & MK_ALT) && + !(grfKeyState & MK_RBUTTON) && + !(grfKeyState & MK_SHIFT)) + { + if( nSourceActions & ACTION_MOVE ) + { + ret= ACTION_DEFAULT | ACTION_MOVE; + } + + else if( nSourceActions & ACTION_COPY ) + { + ret= ACTION_COPY; + } + + else if( nSourceActions & ACTION_LINK ) + { + ret= ACTION_LINK; + } + + else + ret = 0; + } + else if( grfKeyState & MK_SHIFT && + !(grfKeyState & MK_CONTROL)) + { + ret= ACTION_MOVE; + } + else if ( grfKeyState & MK_CONTROL && + !(grfKeyState & MK_SHIFT) ) + { + ret= ACTION_COPY; + } + else if ( grfKeyState & MK_CONTROL && + grfKeyState & MK_SHIFT) + { + ret= ACTION_LINK; + } + else if ( grfKeyState & MK_RBUTTON || + grfKeyState & MK_ALT) + { + ret= ACTION_COPY_OR_MOVE | ACTION_LINK; + } + return ret; +} + +sal_Int8 dndOleDropEffectsToActions( DWORD dwEffect) +{ + sal_Int8 ret= ACTION_NONE; + if( dwEffect & DROPEFFECT_COPY) + ret |= ACTION_COPY; + if( dwEffect & DROPEFFECT_MOVE) + ret |= ACTION_MOVE; + if( dwEffect & DROPEFFECT_LINK) + ret |= ACTION_LINK; + + return ret; +} + +DWORD dndActionsToDropEffects( sal_Int8 actions) +{ + DWORD ret= DROPEFFECT_NONE; + if( actions & ACTION_MOVE) + ret |= DROPEFFECT_MOVE; + if( actions & ACTION_COPY) + ret |= DROPEFFECT_COPY; + if( actions & ACTION_LINK) + ret |= DROPEFFECT_LINK; + if( actions & ACTION_DEFAULT) + ret |= DROPEFFECT_COPY; + return ret; +} + +DWORD dndActionsToSingleDropEffect( sal_Int8 actions) +{ + DWORD effects= dndActionsToDropEffects( actions); + + sal_Int8 countEffect= 0; + + if( effects & DROPEFFECT_MOVE) + countEffect++; + if( effects & DROPEFFECT_COPY) + countEffect++; + if( effects & DROPEFFECT_LINK) + countEffect++; + + // DROPEFFECT_MOVE is the default effect + DWORD retVal= countEffect > 1 ? DROPEFFECT_MOVE : effects; + return retVal; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/globals.hxx b/vcl/win/dtrans/globals.hxx new file mode 100644 index 0000000000..9bb174d0b6 --- /dev/null +++ b/vcl/win/dtrans/globals.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ +#pragma once + +#include <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <osl/mutex.hxx> + +namespace com::sun::star::datatransfer +{ +class XTransferable; +} + +#include <wtypes.h> +#include <sal/types.h> + +// This maps key states as occur as parameter, e.g. in IDropTarget::DragEnter, +// IDropSource::QueryContinueDrag, to actions as are declared in +// css::datatransfer::dnd::DNDConstants ( ACTION_MOVE etc). +// If the grfKeyState indicates the ALt or right mousebutton then the returned +// values combines all possible actions. This is because those keys and buttons are +// used when the user expect a menu to appear when he drops. The menu then +// contains entries, such as move, copy, link, cancel. +// An odd fact is that the argument grfKeyState in IDropTarget::Drop does not +// contain mouse buttons (winnt 4 SP6). That indicates that the right mouse button +// is not considered relevant in a drag operation. Contrarily the file explorer +// gives that button a special meaning: the user has to select the effect from +// a context menu on drop. +sal_Int8 dndOleKeysToAction(DWORD grfKeyState, sal_Int8 sourceActions); + +// The function maps a windows DROPEFFECTs to actions +// ( css::datatransfer::dnd::DNDConstants). +// The argument can be a combination of different DROPEFFECTS, +// In that case the return value is also a combination of the +// appropriate actions. +sal_Int8 dndOleDropEffectsToActions(DWORD dwEffect); + +// The function maps actions ( css::datatransfer::dnd::DNDConstants) +// to window DROPEFFECTs. +// The argument can be a combination of different actions +// In that case the return value is also a combination of the +// appropriate DROPEFFECTS. +DWORD dndActionsToDropEffects(sal_Int8 actions); + +// If the argument constitutes only one action then it is mapped to the +// corresponding DROPEFFECT otherwise DROPEFFECT_MOVE is returned. This is +// why move is the default effect (no modifiers pressed, or right mouse button +// or Alt). +DWORD dndActionsToSingleDropEffect(sal_Int8 actions); + +extern css::uno::Reference<css::datatransfer::XTransferable> g_XTransferable; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/idroptarget.cxx b/vcl/win/dtrans/idroptarget.cxx new file mode 100644 index 0000000000..8b403eb7a3 --- /dev/null +++ b/vcl/win/dtrans/idroptarget.cxx @@ -0,0 +1,96 @@ +/* -*- 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 "idroptarget.hxx" + +IDropTargetImpl::IDropTargetImpl( DropTarget& pTarget): m_nRefCount( 0), + m_rDropTarget( pTarget) +{ +} + +IDropTargetImpl::~IDropTargetImpl() +{ +} + +//IDropTarget +HRESULT STDMETHODCALLTYPE IDropTargetImpl::QueryInterface( REFIID riid, void **ppvObject) +{ + if( !ppvObject) + return E_POINTER; + *ppvObject= nullptr; + + if( riid == __uuidof( IUnknown)) + *ppvObject= static_cast<IUnknown*>( this); + else if ( riid == __uuidof( IDropTarget)) + *ppvObject= static_cast<IDropTarget*>( this); + + if(*ppvObject) + { + AddRef(); + return S_OK; + } + else + return E_NOINTERFACE; + +} + +ULONG STDMETHODCALLTYPE IDropTargetImpl::AddRef() +{ + return InterlockedIncrement( &m_nRefCount); +} + +ULONG STDMETHODCALLTYPE IDropTargetImpl::Release() +{ + LONG count= InterlockedDecrement( &m_nRefCount); + if( m_nRefCount == 0 ) + delete this; + return count; +} + +STDMETHODIMP IDropTargetImpl::DragEnter( IDataObject __RPC_FAR *pDataObj, + DWORD grfKeyState, + POINTL pt, + DWORD *pdwEffect) +{ + return m_rDropTarget.DragEnter( pDataObj, grfKeyState, + pt, pdwEffect); +} + +STDMETHODIMP IDropTargetImpl::DragOver( DWORD grfKeyState, + POINTL pt, + DWORD *pdwEffect) +{ + return m_rDropTarget.DragOver( grfKeyState, pt, pdwEffect); +} + +STDMETHODIMP IDropTargetImpl::DragLeave() +{ + return m_rDropTarget.DragLeave(); +} + +STDMETHODIMP IDropTargetImpl::Drop( IDataObject *pDataObj, + DWORD grfKeyState, + POINTL pt, + DWORD __RPC_FAR *pdwEffect) +{ + return m_rDropTarget.Drop( pDataObj, grfKeyState, + pt, pdwEffect); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/idroptarget.hxx b/vcl/win/dtrans/idroptarget.hxx new file mode 100644 index 0000000000..5871373c6c --- /dev/null +++ b/vcl/win/dtrans/idroptarget.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ +#pragma once + +#include <win/dnd_target.hxx> + +class IDropTargetImpl: public IDropTarget +{ + LONG m_nRefCount; + // Calls to IDropTarget functions are delegated to a DropTarget. + DropTarget& m_rDropTarget; + + virtual ~IDropTargetImpl(); // delete is only called by IUnknown::Release + IDropTargetImpl( const IDropTargetImpl& ); + IDropTargetImpl& operator=( const IDropTargetImpl& ); +public: + explicit IDropTargetImpl(DropTarget& rTarget); + + // IDropTarget + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + /* [in] */ REFIID riid, + /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) override; + + virtual ULONG STDMETHODCALLTYPE AddRef( ) override; + + virtual ULONG STDMETHODCALLTYPE Release( ) override; + + virtual HRESULT STDMETHODCALLTYPE DragEnter( + /* [unique][in] */ IDataObject __RPC_FAR *pDataObj, + /* [in] */ DWORD grfKeyState, + /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect) override; + + virtual HRESULT STDMETHODCALLTYPE DragOver( + /* [in] */ DWORD grfKeyState, + /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect) override; + + virtual HRESULT STDMETHODCALLTYPE DragLeave( ) override; + + virtual HRESULT STDMETHODCALLTYPE Drop( + /* [unique][in] */ IDataObject __RPC_FAR *pDataObj, + /* [in] */ DWORD grfKeyState, + /* [in] */ POINTL pt, + /* [out][in] */ DWORD __RPC_FAR *pdwEffect) override; + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/source.cxx b/vcl/win/dtrans/source.cxx new file mode 100644 index 0000000000..94124b23dd --- /dev/null +++ b/vcl/win/dtrans/source.cxx @@ -0,0 +1,373 @@ +/* -*- 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 <com/sun/star/datatransfer/dnd/DNDConstants.hpp> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/awt/MouseEvent.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/any.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <process.h> +#include <memory> + +#include <win/dnd_source.hxx> +#include "globals.hxx" +#include "sourcecontext.hxx" +#include "DtObjFactory.hxx" + +#include <rtl/ustring.h> +#include <osl/thread.h> +#include <winuser.h> +#include <stdio.h> + +using namespace cppu; +using namespace osl; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::dnd; +using namespace com::sun::star::datatransfer::dnd::DNDConstants; +using namespace com::sun::star::uno; +using namespace com::sun::star::awt::MouseButton; +using namespace com::sun::star::awt; +using namespace com::sun::star::lang; + +static unsigned __stdcall DndOleSTAFunc(void* pParams); + +DragSource::DragSource( const Reference<XComponentContext>& rxContext): + WeakComponentImplHelper< XDragSource, XInitialization, XServiceInfo >(m_aMutex), + m_xContext( rxContext ), +// m_pcurrentContext_impl(0), + m_hAppWindow(nullptr), + m_MouseButton(0), + m_RunningDndOperationCount(0) +{ +} + +DragSource::~DragSource() +{ +} + +/** First start a new drag and drop thread if + the last one has finished + + ???? + Do we really need a separate thread for + every Dnd operation or only if the source + thread is an MTA thread + ???? +*/ +void DragSource::StartDragImpl( + const DragGestureEvent& trigger, + sal_Int8 sourceActions, + sal_Int32 /*cursor*/, + sal_Int32 /*image*/, + const Reference<XTransferable >& trans, + const Reference<XDragSourceListener >& listener ) +{ + // The actions supported by the drag source + m_sourceActions= sourceActions; + // We need to know which mouse button triggered the operation. + // If it was the left one, then the drop occurs when that button + // has been released and if it was the right one then the drop + // occurs when the right button has been released. If the event is not + // set then we assume that the left button is pressed. + MouseEvent evtMouse; + trigger.Event >>= evtMouse; + m_MouseButton= evtMouse.Buttons; + + // The SourceContext class administers the XDragSourceListener s and + // fires events to them. An instance only exists in the scope of this + // function. However, the drag and drop operation causes callbacks + // to the IDropSource interface implemented in this class (but only + // while this function executes). The source context is also used + // in DragSource::QueryContinueDrag. + m_currentContext = new SourceContext(this, listener); + + // Convert the XTransferable data object into an IDataObject object; + + //--> TRA + g_XTransferable = trans; + //<-- TRA + + m_spDataObject= CDTransObjFactory::createDataObjFromTransferable( + m_xContext, trans); + + // Obtain the id of the thread that created the window + DWORD processId; + m_threadIdWindow= GetWindowThreadProcessId( m_hAppWindow, &processId); + + // hold the instance for the DnD thread, it's too late + // to acquire at the start of the thread procedure + // the thread procedure is responsible for the release + acquire(); + + // The thread accesses members of this instance but does not call acquire. + // Hopefully this instance is not destroyed before the thread has terminated. + HANDLE hThread + = reinterpret_cast<HANDLE>(_beginthreadex(nullptr, 0, DndOleSTAFunc, this, 0, nullptr)); + + // detach from thread + CloseHandle(hThread); +} + +// XInitialization +/** aArguments contains a machine id */ +void SAL_CALL DragSource::initialize( const Sequence< Any >& aArguments ) +{ + if( aArguments.getLength() >=2) + m_hAppWindow= reinterpret_cast<HWND>(static_cast<sal_uIntPtr>(*o3tl::doAccess<sal_uInt64>(aArguments[1]))); + OSL_ASSERT( IsWindow( m_hAppWindow) ); +} + +/** XDragSource */ +sal_Bool SAL_CALL DragSource::isDragImageSupported( ) +{ + return false; +} + +sal_Int32 SAL_CALL DragSource::getDefaultCursor( sal_Int8 /*dragAction*/ ) +{ + return 0; +} + +/** Notifies the XDragSourceListener by + calling dragDropEnd */ +void SAL_CALL DragSource::startDrag( + const DragGestureEvent& trigger, + sal_Int8 sourceActions, + sal_Int32 cursor, + sal_Int32 image, + const Reference<XTransferable >& trans, + const Reference<XDragSourceListener >& listener ) +{ + // Allow only one running dnd operation at a time, + // see XDragSource documentation + + LONG cnt = InterlockedIncrement(&m_RunningDndOperationCount); + + if (1 == cnt) + { + StartDragImpl(trigger, sourceActions, cursor, image, trans, listener); + } + else + { + cnt = InterlockedDecrement(&m_RunningDndOperationCount); + + DragSourceDropEvent dsde; + + dsde.DropAction = ACTION_NONE; + dsde.DropSuccess = false; + + try + { + listener->dragDropEnd(dsde); + } + catch(RuntimeException&) + { + TOOLS_WARN_EXCEPTION( "vcl", "Runtime exception during event dispatching"); + } + } +} + +/** IDropTarget */ +HRESULT STDMETHODCALLTYPE DragSource::QueryInterface( REFIID riid, void **ppvObject) +{ + if( !ppvObject) + return E_POINTER; + *ppvObject= nullptr; + + if( riid == __uuidof( IUnknown) ) + *ppvObject= static_cast<IUnknown*>( this); + else if ( riid == __uuidof( IDropSource) ) + *ppvObject= static_cast<IDropSource*>( this); + + if(*ppvObject) + { + AddRef(); + return S_OK; + } + else + return E_NOINTERFACE; + +} + +ULONG STDMETHODCALLTYPE DragSource::AddRef() +{ + acquire(); + return static_cast<ULONG>(m_refCount); +} + +ULONG STDMETHODCALLTYPE DragSource::Release() +{ + ULONG ref= m_refCount; + release(); + return --ref; +} + +/** IDropSource */ +HRESULT STDMETHODCALLTYPE DragSource::QueryContinueDrag( +/* [in] */ BOOL fEscapePressed, +/* [in] */ DWORD grfKeyState) +{ +#if defined DBG_CONSOLE_OUT + printf("\nDragSource::QueryContinueDrag"); +#endif + + HRESULT retVal= S_OK; // default continue DnD + + if (fEscapePressed) + { + retVal= DRAGDROP_S_CANCEL; + } + else + { + if( ( m_MouseButton == MouseButton::RIGHT && !(grfKeyState & MK_RBUTTON) ) || + ( m_MouseButton == MouseButton::MIDDLE && !(grfKeyState & MK_MBUTTON) ) || + ( m_MouseButton == MouseButton::LEFT && !(grfKeyState & MK_LBUTTON) ) || + ( m_MouseButton == 0 && !(grfKeyState & MK_LBUTTON) ) ) + { + retVal= DRAGDROP_S_DROP; + } + } + + // fire dropActionChanged event. + // this is actually done by the context, which also detects whether the action + // changed at all + sal_Int8 dropAction= fEscapePressed ? ACTION_NONE : + dndOleKeysToAction( grfKeyState, m_sourceActions); + + sal_Int8 userAction= fEscapePressed ? ACTION_NONE : + dndOleKeysToAction( grfKeyState, -1 ); + + static_cast<SourceContext*>(m_currentContext.get())->fire_dropActionChanged( + dropAction, userAction); + + return retVal; +} + +HRESULT STDMETHODCALLTYPE DragSource::GiveFeedback( +/* [in] */ DWORD +#if defined DBG_CONSOLE_OUT +dwEffect +#endif +) +{ +#if defined DBG_CONSOLE_OUT + printf("\nDragSource::GiveFeedback %d", dwEffect); +#endif + + return DRAGDROP_S_USEDEFAULTCURSORS; +} + +// XServiceInfo +OUString SAL_CALL DragSource::getImplementationName( ) +{ + return "com.sun.star.comp.datatransfer.dnd.OleDragSource_V1"; +} +// XServiceInfo +sal_Bool SAL_CALL DragSource::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL DragSource::getSupportedServiceNames( ) +{ + return { "com.sun.star.datatransfer.dnd.OleDragSource" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +dtrans_DragSource_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new DragSource(context)); +} + +/** This function is called as extra thread from + DragSource::executeDrag. The function + carries out a drag and drop operation by calling + DoDragDrop. The thread also notifies all + XSourceListener. */ +unsigned __stdcall DndOleSTAFunc(void* pParams) +{ + osl_setThreadName("DragSource DndOleSTAFunc"); + + // The structure contains all arguments for DoDragDrop and other + DragSource *pSource= static_cast<DragSource*>(pParams); + + // Drag and drop only works in a thread in which OleInitialize is called. + HRESULT hr= OleInitialize( nullptr); + + if(SUCCEEDED(hr)) + { + // We force the creation of a thread message queue. This is necessary + // for a later call to AttachThreadInput + MSG msgtemp; + PeekMessageW( &msgtemp, nullptr, WM_USER, WM_USER, PM_NOREMOVE); + + DWORD threadId= GetCurrentThreadId(); + + // This thread is attached to the thread that created the window. Hence + // this thread also receives all mouse and keyboard messages which are + // needed by DoDragDrop + AttachThreadInput( threadId , pSource->m_threadIdWindow, TRUE ); + + DWORD dwEffect= 0; + hr= DoDragDrop( + pSource->m_spDataObject.get(), + static_cast<IDropSource*>(pSource), + dndActionsToDropEffects( pSource->m_sourceActions), + &dwEffect); + + // #105428 detach my message queue from the other threads + // message queue before calling fire_dragDropEnd else + // the office may appear to hang sometimes + AttachThreadInput( threadId, pSource->m_threadIdWindow, FALSE); + + //--> TRA + // clear the global transferable again + g_XTransferable.clear(); + //<-- TRA + + OSL_ENSURE( hr != E_INVALIDARG, "IDataObject impl does not contain valid data"); + + //Fire event + sal_Int8 action= hr == DRAGDROP_S_DROP ? dndOleDropEffectsToActions( dwEffect) : ACTION_NONE; + + static_cast<SourceContext*>(pSource->m_currentContext.get())->fire_dragDropEnd( + hr == DRAGDROP_S_DROP, action); + + // Destroy SourceContextslkfgj + pSource->m_currentContext= nullptr; + // Destroy the XTransferable wrapper + pSource->m_spDataObject=nullptr; + + OleUninitialize(); + } + + InterlockedDecrement(&pSource->m_RunningDndOperationCount); + + // the DragSource was manually acquired by + // thread starting method DelayedStartDrag + pSource->release(); + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/sourcecontext.cxx b/vcl/win/dtrans/sourcecontext.cxx new file mode 100644 index 0000000000..c0e6371c5b --- /dev/null +++ b/vcl/win/dtrans/sourcecontext.cxx @@ -0,0 +1,134 @@ +/* -*- 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 <com/sun/star/datatransfer/dnd/DNDConstants.hpp> + +#include "sourcecontext.hxx" + +using namespace com::sun::star::datatransfer::dnd; +using namespace com::sun::star::datatransfer::dnd::DNDConstants; + +SourceContext::SourceContext( DragSource* pSource, + const Reference<XDragSourceListener>& listener): + WeakComponentImplHelper<XDragSourceContext>(m_aMutex), + m_pDragSource( pSource), + m_dragSource( static_cast<XDragSource*>( m_pDragSource) ) +{ +#if OSL_DEBUG_LEVEL > 1 + if( listener.is()) +#endif + rBHelper.addListener( cppu::UnoType<decltype(listener)>::get(), listener ); +} + +SourceContext::~SourceContext() +{ +} + +void SourceContext::addDragSourceListener( + const Reference<XDragSourceListener >& ) +{ +} + +void SourceContext::removeDragSourceListener( + const Reference<XDragSourceListener >& ) +{ +} + +sal_Int32 SAL_CALL SourceContext::getCurrentCursor( ) +{ + return 0; +} + +void SAL_CALL SourceContext::setCursor( sal_Int32 /*cursorId*/ ) +{ +} + +void SAL_CALL SourceContext::setImage( sal_Int32 /*imageId*/ ) +{ +} + +void SAL_CALL SourceContext::transferablesFlavorsChanged( ) +{ +} + +// non -interface functions +// Fires XDragSourceListener::dragDropEnd events. +void SourceContext::fire_dragDropEnd( bool success, sal_Int8 effect) +{ + + DragSourceDropEvent e; + + if( success ) + { + e.DropAction= effect; + e.DropSuccess= true; + } + else + { + e.DropAction= ACTION_NONE; + e.DropSuccess= false; + } + e.DragSource= m_dragSource; + e.DragSourceContext= static_cast<XDragSourceContext*>( this); + e.Source.set( static_cast<XDragSourceContext*>( this), UNO_QUERY); + + OInterfaceContainerHelper* pContainer= rBHelper.getContainer( + cppu::UnoType<XDragSourceListener>::get()); + + if( pContainer) + { + OInterfaceIteratorHelper iter( *pContainer); + while( iter.hasMoreElements()) + { + Reference<XDragSourceListener> listener( + static_cast<XDragSourceListener*>( iter.next())); + listener->dragDropEnd( e); + } + } +} + +void SourceContext::fire_dropActionChanged( sal_Int8 dropAction, sal_Int8 userAction) +{ + if( m_currentAction != dropAction) + { + m_currentAction= dropAction; + DragSourceDragEvent e; + e.DropAction= dropAction; + e.UserAction= userAction; + e.DragSource= m_dragSource; + e.DragSourceContext= static_cast<XDragSourceContext*>( this); + e.Source.set( static_cast<XDragSourceContext*>( this), UNO_QUERY); + + OInterfaceContainerHelper* pContainer= rBHelper.getContainer( + cppu::UnoType<XDragSourceListener>::get()); + + if( pContainer) + { + OInterfaceIteratorHelper iter( *pContainer); + while( iter.hasMoreElements()) + { + Reference<XDragSourceListener> listener( + static_cast<XDragSourceListener*>( iter.next())); + listener->dropActionChanged( e); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/sourcecontext.hxx b/vcl/win/dtrans/sourcecontext.hxx new file mode 100644 index 0000000000..4471747956 --- /dev/null +++ b/vcl/win/dtrans/sourcecontext.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ +#pragma once + +#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <win/dnd_source.hxx> + +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::datatransfer::dnd; +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + +// This class fires events to XDragSourceListener implementations. +// Of that interface only dragDropEnd and dropActionChanged are called. +// The functions dragEnter, dragExit and dragOver are not supported +// currently. +// An instance of SourceContext only lives as long as the drag and drop +// operation lasts. +class SourceContext : public cppu::BaseMutex, public WeakComponentImplHelper<XDragSourceContext> +{ + DragSource* m_pDragSource; + Reference<XDragSource> m_dragSource; + // the action ( copy, move etc) + sal_Int8 m_currentAction; + +public: + SourceContext(DragSource* pSource, const Reference<XDragSourceListener>& listener); + ~SourceContext() override; + SourceContext(const SourceContext&) = delete; + SourceContext& operator=(const SourceContext&) = delete; + + /// @throws RuntimeException + virtual void addDragSourceListener(const Reference<XDragSourceListener>& dsl); + /// @throws RuntimeException + virtual void removeDragSourceListener(const Reference<XDragSourceListener>& dsl); + virtual sal_Int32 SAL_CALL getCurrentCursor() override; + virtual void SAL_CALL setCursor(sal_Int32 cursorId) override; + virtual void SAL_CALL setImage(sal_Int32 imageId) override; + virtual void SAL_CALL transferablesFlavorsChanged() override; + + // non - interface functions + void fire_dragDropEnd(bool success, sal_Int8 byte); + void fire_dropActionChanged(sal_Int8 dropAction, sal_Int8 userAction); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/target.cxx b/vcl/win/dtrans/target.cxx new file mode 100644 index 0000000000..abe53379ea --- /dev/null +++ b/vcl/win/dtrans/target.cxx @@ -0,0 +1,646 @@ +/* -*- 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 <com/sun/star/datatransfer/dnd/DNDConstants.hpp> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/any.hxx> + +#include <stdio.h> +#include <win/dnd_target.hxx> +#include "idroptarget.hxx" +#include "globals.hxx" +#include "targetdropcontext.hxx" +#include "targetdragcontext.hxx" +#include <rtl/ustring.h> +#include <osl/thread.h> +#include <sal/log.hxx> +#include <comphelper/windowserrorstring.hxx> + +#include "DOTransferable.hxx" + +using namespace cppu; +using namespace osl; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::dnd; +using namespace com::sun::star::datatransfer::dnd::DNDConstants; + +#define WM_REGISTERDRAGDROP WM_USER + 1 +#define WM_REVOKEDRAGDROP WM_USER + 2 + +unsigned __stdcall DndTargetOleSTAFunc(void* pParams); + +DropTarget::DropTarget( const Reference<XComponentContext>& rxContext): + WeakComponentImplHelper<XInitialization,XDropTarget, XServiceInfo>(m_aMutex), + m_hWnd( nullptr), + m_threadIdWindow(0), + m_threadIdTarget(0), + m_hOleThread(nullptr), + m_oleThreadId( 0), + m_pDropTarget( nullptr), + m_xContext( rxContext ), + m_bActive(true), + m_nDefaultActions(ACTION_COPY|ACTION_MOVE|ACTION_LINK|ACTION_DEFAULT), + m_nCurrentDropAction( ACTION_NONE), + m_nLastDropAction(0), + m_bDropComplete(false) +{ +} + +DropTarget::~DropTarget() +{ +} +// called from WeakComponentImplHelperX::dispose +// WeakComponentImplHelper calls disposing before it destroys +// itself. +// NOTE: RevokeDragDrop decrements the ref count on the IDropTarget +// interface. (m_pDropTarget) +// If the HWND is invalid then it doesn't decrement and +// the IDropTarget object will live on. MEMORY LEAK +void SAL_CALL DropTarget::disposing() +{ + if( m_threadIdTarget) + { + // Call RevokeDragDrop and wait for the OLE thread to die; + PostThreadMessageW( m_threadIdTarget, WM_REVOKEDRAGDROP, reinterpret_cast<WPARAM>(this), 0); + WaitForSingleObject( m_hOleThread, INFINITE); + CloseHandle( m_hOleThread); + //OSL_ENSURE( SUCCEEDED( hr), "HWND not valid!" ); + } + else + { + RevokeDragDrop( m_hWnd); + m_hWnd= nullptr; + } + if( m_pDropTarget) + { + CoLockObjectExternal( m_pDropTarget, FALSE, TRUE); + m_pDropTarget->Release(); + m_pDropTarget = nullptr; + } + + if( m_oleThreadId) + { + if( m_oleThreadId == CoGetCurrentProcess() ) + OleUninitialize(); + } + +} + +void SAL_CALL DropTarget::initialize( const Sequence< Any >& aArguments ) +{ + // The window must be registered for Dnd by RegisterDragDrop. We must ensure + // that RegisterDragDrop is called from an STA ( OleInitialize) thread. + // As long as the window is registered we need to receive OLE messages in + // an OLE thread. That is to say, if DropTarget::initialize was called from an + // MTA thread then we create an OLE thread in which the window is registered. + // The thread will stay alive until aver RevokeDragDrop has been called. + + // Additionally even if RegisterDragDrop is called from an STA thread we have + // to ensure that it is called from the same thread that created the Window + // otherwise messages sent during DND won't reach the windows message queue. + // Calling AttachThreadInput first would resolve this problem but would block + // the message queue of the calling thread. So if the current thread + // (even if it's an STA thread) and the thread that created the window are not + // identical we need to create a new thread as we do when the calling thread is + // an MTA thread. + + if( aArguments.getLength() > 0) + { + // Get the window handle from aArgument. It is needed for RegisterDragDrop. + m_hWnd= reinterpret_cast<HWND>(static_cast<sal_uIntPtr>(*o3tl::doAccess<sal_uInt64>(aArguments[0]))); + OSL_ASSERT( IsWindow( m_hWnd) ); + + // Obtain the id of the thread that created the window + m_threadIdWindow= GetWindowThreadProcessId( m_hWnd, nullptr); + + HRESULT hr= OleInitialize( nullptr); + + // Current thread is MTA or Current thread and Window thread are not identical + if( hr == RPC_E_CHANGED_MODE || GetCurrentThreadId() != m_threadIdWindow ) + { + OSL_ENSURE( ! m_threadIdTarget,"initialize was called twice"); + // create the IDropTargetImplementation + m_pDropTarget= new IDropTargetImpl( *this ); + m_pDropTarget->AddRef(); + + // Obtain the id of the thread that created the window + m_threadIdWindow= GetWindowThreadProcessId( m_hWnd, nullptr); + // The event is set by the thread that we will create momentarily. + // It indicates that the thread is ready to receive messages. + HANDLE m_evtThreadReady= CreateEventW( nullptr, FALSE, FALSE, nullptr); + + m_hOleThread = reinterpret_cast<HANDLE>(_beginthreadex(nullptr, 0, DndTargetOleSTAFunc, + &m_evtThreadReady, 0, &m_threadIdTarget)); + WaitForSingleObject( m_evtThreadReady, INFINITE); + CloseHandle( m_evtThreadReady); + PostThreadMessageW( m_threadIdTarget, WM_REGISTERDRAGDROP, reinterpret_cast<WPARAM>(this), 0); + } + else if( hr == S_OK || hr == S_FALSE) + { + // current thread is STA + // If OleInitialize has been called by the caller then we must not call + // OleUninitialize + if( hr == S_OK) + { + // caller did not call OleInitialize, so we call OleUninitialize + // remember the thread that will call OleUninitialize + m_oleThreadId= CoGetCurrentProcess(); // get a unique thread id + } + + // Get the window handle from aArgument. It is needed for RegisterDragDrop. + // create the IDropTargetImplementation + m_pDropTarget= new IDropTargetImpl( *this ); + m_pDropTarget->AddRef(); + // CoLockObjectExternal is prescribed by the protocol. It bumps up the ref count + if( SUCCEEDED( CoLockObjectExternal( m_pDropTarget, TRUE, FALSE))) + { + if( FAILED( RegisterDragDrop( m_hWnd, m_pDropTarget) ) ) + { + // do clean up if drag and drop is not possible + CoLockObjectExternal( m_pDropTarget, FALSE, FALSE); + m_pDropTarget->Release(); + m_pDropTarget = nullptr; + m_hWnd= nullptr; + } + } + } + else + throw Exception("OleInitialize failed with " + OUString::number(hr), nullptr); + + } +} + +// This function is called as extra thread from DragSource::startDrag. +// The function carries out a drag and drop operation by calling +// DoDragDrop. The thread also notifies all XSourceListener. +unsigned __stdcall DndTargetOleSTAFunc(void* pParams) +{ + osl_setThreadName("DropTarget DndTargetOleSTAFunc"); + + HRESULT hr= OleInitialize( nullptr); + if( SUCCEEDED( hr) ) + { + MSG msg; + // force the creation of a message queue + PeekMessageW( &msg, nullptr, 0, 0, PM_NOREMOVE); + // Signal the creator ( DropTarget::initialize) that the thread is + // ready to receive messages. + SetEvent( *static_cast<HANDLE*>(pParams)); + // Thread id is needed for attaching this message queue to the one of the + // thread where the window was created. + DWORD threadId= GetCurrentThreadId(); + // We force the creation of a thread message queue. This is necessary + // for a later call to AttachThreadInput + for (;;) + { + int const bRet = GetMessageW(&msg, nullptr, 0, 0); + if (bRet == 0) + { + break; + } + if (-1 == bRet) + { + SAL_WARN("vcl.win.dtrans", "GetMessageW failed: " << WindowsErrorString(GetLastError())); + break; + } + if( msg.message == WM_REGISTERDRAGDROP) + { + DropTarget *pTarget= reinterpret_cast<DropTarget*>(msg.wParam); + // This thread is attached to the thread that created the window. Hence + // this thread also receives all mouse and keyboard messages which are + // needed + AttachThreadInput( threadId , pTarget->m_threadIdWindow, TRUE ); + + if( SUCCEEDED( CoLockObjectExternal(pTarget-> m_pDropTarget, TRUE, FALSE))) + { + if( FAILED( RegisterDragDrop( pTarget-> m_hWnd, pTarget-> m_pDropTarget) ) ) + { + // do clean up if drag and drop is not possible + CoLockObjectExternal( pTarget->m_pDropTarget, FALSE, FALSE); + pTarget->m_pDropTarget->Release(); + pTarget->m_pDropTarget = nullptr; + pTarget->m_hWnd= nullptr; + } + } + } + else if( msg.message == WM_REVOKEDRAGDROP) + { + DropTarget *pTarget= reinterpret_cast<DropTarget*>(msg.wParam); + RevokeDragDrop( pTarget-> m_hWnd); + // Detach this thread from the window thread + AttachThreadInput( threadId, pTarget->m_threadIdWindow, FALSE); + pTarget->m_hWnd= nullptr; + break; + } + TranslateMessage( &msg); + DispatchMessageW( &msg); + } + OleUninitialize(); + } + return 0; +} + +// XServiceInfo +OUString SAL_CALL DropTarget::getImplementationName( ) +{ + return "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1"; +} +// XServiceInfo +sal_Bool SAL_CALL DropTarget::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL DropTarget::getSupportedServiceNames( ) +{ + return { "com.sun.star.datatransfer.dnd.OleDropTarget" }; +} + +// XDropTarget +void SAL_CALL DropTarget::addDropTargetListener( const Reference< XDropTargetListener >& dtl ) +{ + rBHelper.addListener( cppu::UnoType<decltype(dtl)>::get(), dtl ); +} + +void SAL_CALL DropTarget::removeDropTargetListener( const Reference< XDropTargetListener >& dtl ) +{ + rBHelper.removeListener( cppu::UnoType<decltype(dtl)>::get(), dtl ); +} + +sal_Bool SAL_CALL DropTarget::isActive( ) +{ + return m_bActive; //m_bDropTargetRegistered; +} + +void SAL_CALL DropTarget::setActive( sal_Bool _b ) +{ + MutexGuard g(m_aMutex); + m_bActive= _b; +} + +sal_Int8 SAL_CALL DropTarget::getDefaultActions( ) +{ + return m_nDefaultActions; +} + +void SAL_CALL DropTarget::setDefaultActions( sal_Int8 actions ) +{ + OSL_ENSURE( actions < 8, "No valid default actions"); + m_nDefaultActions= actions; +} + +HRESULT DropTarget::DragEnter( IDataObject *pDataObj, + DWORD grfKeyState, + POINTL pt, + DWORD *pdwEffect) +{ +#if defined DBG_CONSOLE_OUT + printf("\nDropTarget::DragEnter state: %x effect %d", grfKeyState, *pdwEffect); +#endif + if( m_bActive ) + { + // Intersection of pdwEffect and the allowed actions ( setDefaultActions) + m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect); + // m_nLastDropAction has to be set by a listener. If no listener calls + //XDropTargetDragContext::acceptDrag and specifies an action then pdwEffect + // will be DROPEFFECT_NONE throughout + m_nLastDropAction= ACTION_DEFAULT | ACTION_MOVE; + + m_currentDragContext = new TargetDragContext(this); + + //--> TRA + + // shortcut + if ( g_XTransferable.is( ) ) + m_currentData = g_XTransferable; + else + { + // Convert the IDataObject to a XTransferable + m_currentData = new CDOTransferable(m_xContext, IDataObjectPtr(pDataObj)); + } + + //<-- TRA + + if( m_nCurrentDropAction != ACTION_NONE) + { + DropTargetDragEnterEvent e; + e.SupportedDataFlavors= m_currentData->getTransferDataFlavors(); + e.DropAction= m_nCurrentDropAction; + e.Source.set( static_cast<XDropTarget*>(this),UNO_QUERY); + e.Context= m_currentDragContext; + POINT point={ pt.x, pt.y}; + ScreenToClient( m_hWnd, &point); + e.LocationX= point.x; + e.LocationY= point.y; + e.SourceActions= dndOleDropEffectsToActions( *pdwEffect); + + fire_dragEnter( e); + // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set + // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set + // in pdwEffect. The listener notification is asynchronous, that is we cannot expect that the listener + // has already reacted to the notification. + // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed + // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed. + // On drop the target should present the user a dialog from which the user may change the action. + sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect); + *pdwEffect= dndActionsToSingleDropEffect( m_nLastDropAction & allowedActions); + } + else + { + *pdwEffect= DROPEFFECT_NONE; + } + } + return S_OK; +} + +HRESULT DropTarget::DragOver( DWORD grfKeyState, + POINTL pt, + DWORD *pdwEffect) +{ + if( m_bActive) + { + m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect); + + if( m_nCurrentDropAction) + { + DropTargetDragEvent e; + e.DropAction= m_nCurrentDropAction; + e.Source.set(static_cast<XDropTarget*>(this),UNO_QUERY); + e.Context= m_currentDragContext; + POINT point={ pt.x, pt.y}; + ScreenToClient( m_hWnd, &point); + e.LocationX= point.x; + e.LocationY= point.y; + e.SourceActions= dndOleDropEffectsToActions( *pdwEffect); + + // if grfKeyState has changed since the last DragOver then fire events. + // A listener might change m_nCurrentDropAction by calling the + // XDropTargetDragContext::acceptDrag function. But this is not important + // because in the afterwards fired dragOver event the action reflects + // grgKeyState again. + if( m_nLastDropAction != m_nCurrentDropAction) + fire_dropActionChanged( e); + + // The Event contains a XDropTargetDragContext implementation. + fire_dragOver( e); + // Check if the action derived from grfKeyState (m_nCurrentDropAction) or the action set + // by the listener (m_nCurrentDropAction) is allowed by the source. Only an allowed action is set + // in pdwEffect. The listener notification is asynchronous, that is we cannot expect that the listener + // has already reacted to the notification. + // If there is more than one valid action which is the case when ALT or RIGHT MOUSE BUTTON is pressed + // then getDropEffect returns DROPEFFECT_MOVE which is the default value if no other modifier is pressed. + // On drop the target should present the user a dialog from which the user may change the action. + sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect); + // set the last action to the current if listener has not changed the value yet + *pdwEffect= dndActionsToSingleDropEffect( m_nLastDropAction & allowedActions); + } + else + { + *pdwEffect= DROPEFFECT_NONE; + } + } +#if defined DBG_CONSOLE_OUT + printf("\nDropTarget::DragOver %d", *pdwEffect ); +#endif + return S_OK; +} + +HRESULT DropTarget::DragLeave() +{ +#if defined DBG_CONSOLE_OUT + printf("\nDropTarget::DragLeave"); +#endif + if( m_bActive) + { + + m_currentData=nullptr; + m_currentDragContext= nullptr; + m_currentDropContext= nullptr; + m_nLastDropAction= 0; + + if( m_nDefaultActions != ACTION_NONE) + { + DropTargetEvent e; + e.Source= static_cast<XDropTarget*>(this); + + fire_dragExit( e); + } + } + return S_OK; +} + +HRESULT DropTarget::Drop( IDataObject * /*pDataObj*/, + DWORD grfKeyState, + POINTL pt, + DWORD *pdwEffect) +{ +#if defined DBG_CONSOLE_OUT + printf("\nDropTarget::Drop"); +#endif + if( m_bActive) + { + + m_bDropComplete= false; + + m_nCurrentDropAction= getFilteredActions( grfKeyState, *pdwEffect); + m_currentDropContext = new TargetDropContext(this); + if( m_nCurrentDropAction) + { + DropTargetDropEvent e; + e.DropAction= m_nCurrentDropAction; + e.Source.set( static_cast<XDropTarget*>(this), UNO_QUERY); + e.Context= m_currentDropContext; + POINT point={ pt.x, pt.y}; + ScreenToClient( m_hWnd, &point); + e.LocationX= point.x; + e.LocationY= point.y; + e.SourceActions= dndOleDropEffectsToActions( *pdwEffect); + e.Transferable= m_currentData; + fire_drop( e); + + //if fire_drop returns than a listener might have modified m_nCurrentDropAction + if( m_bDropComplete ) + { + sal_Int8 allowedActions= dndOleDropEffectsToActions( *pdwEffect); + *pdwEffect= dndActionsToSingleDropEffect( m_nCurrentDropAction & allowedActions); + } + else + *pdwEffect= DROPEFFECT_NONE; + } + else + *pdwEffect= DROPEFFECT_NONE; + + m_currentData= nullptr; + m_currentDragContext= nullptr; + m_currentDropContext= nullptr; + m_nLastDropAction= 0; + } + return S_OK; +} + +void DropTarget::fire_drop( const DropTargetDropEvent& dte) +{ + OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + if( pContainer) + { + OInterfaceIteratorHelper iter( *pContainer); + while( iter.hasMoreElements()) + { + Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next())); + listener->drop( dte); + } + } +} + +void DropTarget::fire_dragEnter( const DropTargetDragEnterEvent& e ) +{ + OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + if( pContainer) + { + OInterfaceIteratorHelper iter( *pContainer); + while( iter.hasMoreElements()) + { + Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next())); + listener->dragEnter( e); + } + } +} + +void DropTarget::fire_dragExit( const DropTargetEvent& dte ) +{ + OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + + if( pContainer) + { + OInterfaceIteratorHelper iter( *pContainer); + while( iter.hasMoreElements()) + { + Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next())); + listener->dragExit( dte); + } + } +} + +void DropTarget::fire_dragOver( const DropTargetDragEvent& dtde ) +{ + OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + if( pContainer) + { + OInterfaceIteratorHelper iter( *pContainer ); + while( iter.hasMoreElements()) + { + Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next())); + listener->dragOver( dtde); + } + } +} + +void DropTarget::fire_dropActionChanged( const DropTargetDragEvent& dtde ) +{ + OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + if( pContainer) + { + OInterfaceIteratorHelper iter( *pContainer); + while( iter.hasMoreElements()) + { + Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next())); + listener->dropActionChanged( dtde); + } + } +} + +// Non - interface functions +// DropTarget fires events to XDropTargetListeners. The event object contains an +// XDropTargetDropContext implementation. When the listener calls on that interface +// then the calls are delegated from DropContext (XDropTargetDropContext) to these +// functions. +// Only one listener which visible area is affected is allowed to call on +// XDropTargetDropContext +// Returning sal_False would cause the XDropTargetDropContext or ..DragContext implementation +// to throw an InvalidDNDOperationException, meaning that a Drag is not currently performed. +// return sal_False results in throwing an InvalidDNDOperationException in the caller. + +void DropTarget::_acceptDrop(sal_Int8 dropOperation, const Reference<XDropTargetDropContext>& context) +{ + if( context == m_currentDropContext) + { + m_nCurrentDropAction= dropOperation; + } +} + +void DropTarget::_rejectDrop( const Reference<XDropTargetDropContext>& context) +{ + if( context == m_currentDropContext) + { + m_nCurrentDropAction= ACTION_NONE; + } +} + +void DropTarget::_dropComplete(bool success, const Reference<XDropTargetDropContext>& context) +{ + if(context == m_currentDropContext) + { + m_bDropComplete= success; + } +} + +// DropTarget fires events to XDropTargetListeners. The event object can contains an +// XDropTargetDragContext implementation. When the listener calls on that interface +// then the calls are delegated from DragContext (XDropTargetDragContext) to these +// functions. +// Only one listener which visible area is affected is allowed to call on +// XDropTargetDragContext +void DropTarget::_acceptDrag( sal_Int8 dragOperation, const Reference<XDropTargetDragContext>& context) +{ + if( context == m_currentDragContext) + { + m_nLastDropAction= dragOperation; + } +} + +void DropTarget::_rejectDrag( const Reference<XDropTargetDragContext>& context) +{ + if(context == m_currentDragContext) + { + m_nLastDropAction= ACTION_NONE; + } +} + +// This function determines the action dependent on the pressed +// key modifiers ( CTRL, SHIFT, ALT, Right Mouse Button). The result +// is then checked against the allowed actions which can be set through +// XDropTarget::setDefaultActions. Only those values which are also +// default actions are returned. If setDefaultActions has not been called +// beforehand the default actions comprise all possible actions. +// params: grfKeyState - the modifier keys and mouse buttons currently pressed +inline sal_Int8 DropTarget::getFilteredActions( DWORD grfKeyState, DWORD dwEffect) +{ + sal_Int8 actions= dndOleKeysToAction( grfKeyState, dndOleDropEffectsToActions( dwEffect)); + return actions & m_nDefaultActions; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +dtrans_DropTarget_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new DropTarget(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/targetdragcontext.cxx b/vcl/win/dtrans/targetdragcontext.cxx new file mode 100644 index 0000000000..71e345b74c --- /dev/null +++ b/vcl/win/dtrans/targetdragcontext.cxx @@ -0,0 +1,39 @@ +/* -*- 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 "targetdragcontext.hxx" + +TargetDragContext::TargetDragContext(DropTarget* p) +{ + m_pDropTarget = p; + p->acquire(); +} + +TargetDragContext::~TargetDragContext() { m_pDropTarget->release(); } + +void SAL_CALL TargetDragContext::acceptDrag(sal_Int8 dragOperation) +{ + m_pDropTarget->_acceptDrag(dragOperation, static_cast<XDropTargetDragContext*>(this)); +} +void SAL_CALL TargetDragContext::rejectDrag() +{ + m_pDropTarget->_rejectDrag(static_cast<XDropTargetDragContext*>(this)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/targetdragcontext.hxx b/vcl/win/dtrans/targetdragcontext.hxx new file mode 100644 index 0000000000..a64ab6a30b --- /dev/null +++ b/vcl/win/dtrans/targetdragcontext.hxx @@ -0,0 +1,50 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp> +#include <com/sun/star/datatransfer/DataFlavor.hpp> + +#include <win/dnd_target.hxx> + +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::datatransfer::dnd; +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + +class TargetDragContext : public WeakImplHelper<XDropTargetDragContext> +{ + // some calls to the functions of XDropTargetDragContext are delegated + // to non-interface functions of m_pDropTarget + DropTarget* m_pDropTarget; + +public: + explicit TargetDragContext(DropTarget* pTarget); + ~TargetDragContext() override; + TargetDragContext(const TargetDragContext&) = delete; + TargetDragContext& operator=(const TargetDragContext&) = delete; + + virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override; + virtual void SAL_CALL rejectDrag() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/targetdropcontext.cxx b/vcl/win/dtrans/targetdropcontext.cxx new file mode 100644 index 0000000000..fe65389431 --- /dev/null +++ b/vcl/win/dtrans/targetdropcontext.cxx @@ -0,0 +1,50 @@ +/* -*- 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 "targetdropcontext.hxx" + +using namespace ::com::sun::star::datatransfer::dnd; +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + +TargetDropContext::TargetDropContext(DropTarget* p) +{ + m_pDropTarget = p; + p->acquire(); +} + +TargetDropContext::~TargetDropContext() { m_pDropTarget->release(); } + +void SAL_CALL TargetDropContext::acceptDrop(sal_Int8 dropOperation) +{ + m_pDropTarget->_acceptDrop(dropOperation, static_cast<XDropTargetDropContext*>(this)); +} + +void SAL_CALL TargetDropContext::rejectDrop() +{ + m_pDropTarget->_rejectDrop(static_cast<XDropTargetDropContext*>(this)); +} + +void SAL_CALL TargetDropContext::dropComplete(sal_Bool success) +{ + m_pDropTarget->_dropComplete(success, static_cast<XDropTargetDropContext*>(this)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/dtrans/targetdropcontext.hxx b/vcl/win/dtrans/targetdropcontext.hxx new file mode 100644 index 0000000000..938a23f850 --- /dev/null +++ b/vcl/win/dtrans/targetdropcontext.hxx @@ -0,0 +1,52 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/datatransfer/dnd/XDropTargetDropContext.hpp> + +#include <win/dnd_target.hxx> + +using namespace ::com::sun::star::datatransfer::dnd; +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + +class TargetDropContext : public WeakImplHelper<XDropTargetDropContext> +{ + // calls to the functions of XDropTargetDropContext are delegated + // to non-interface functions of m_pDropTarget + DropTarget* m_pDropTarget; + +public: + explicit TargetDropContext(DropTarget* pTarget); + ~TargetDropContext() override; + TargetDropContext(const TargetDropContext&) = delete; + TargetDropContext& operator=(const TargetDropContext&) = delete; + + // XDropTargetDragContext + virtual void SAL_CALL acceptDrop(sal_Int8 dropOperation) override; + virtual void SAL_CALL rejectDrop() override; + + // XDropTargetDropContext (inherits XDropTargetDragContext) + virtual void SAL_CALL dropComplete(sal_Bool success) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/DWriteTextRenderer.cxx b/vcl/win/gdi/DWriteTextRenderer.cxx new file mode 100644 index 0000000000..3d3dac83c6 --- /dev/null +++ b/vcl/win/gdi/DWriteTextRenderer.cxx @@ -0,0 +1,341 @@ +/* -*- 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 <sal/config.h> + +#include <win/salgdi.h> +#include <win/saldata.hxx> +#include <ImplOutDevData.hxx> + +#include <win/DWriteTextRenderer.hxx> + +#include <sft.hxx> +#include <sallayout.hxx> + +#include <shlwapi.h> +#include <winver.h> + +#include <comphelper/windowserrorstring.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> + +namespace +{ + +D2DTextAntiAliasMode lclGetSystemTextAntiAliasMode() +{ + D2DTextAntiAliasMode eMode = D2DTextAntiAliasMode::Default; + + BOOL bFontSmoothing; + if (!SystemParametersInfoW(SPI_GETFONTSMOOTHING, 0, &bFontSmoothing, 0)) + return eMode; + + if (bFontSmoothing) + { + eMode = D2DTextAntiAliasMode::AntiAliased; + + UINT nType; + if (SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &nType, 0) && nType == FE_FONTSMOOTHINGCLEARTYPE) + eMode = D2DTextAntiAliasMode::ClearType; + } + else + { + eMode = D2DTextAntiAliasMode::Aliased; + } + + return eMode; +} + +IDWriteRenderingParams* lclSetRenderingMode(IDWriteFactory* pDWriteFactory, DWRITE_RENDERING_MODE eRenderingMode) +{ + IDWriteRenderingParams* pDefaultParameters = nullptr; + pDWriteFactory->CreateRenderingParams(&pDefaultParameters); + + IDWriteRenderingParams* pParameters = nullptr; + pDWriteFactory->CreateCustomRenderingParams( + pDefaultParameters->GetGamma(), + pDefaultParameters->GetEnhancedContrast(), + pDefaultParameters->GetClearTypeLevel(), + pDefaultParameters->GetPixelGeometry(), + eRenderingMode, + &pParameters); + return pParameters; +} + +#ifdef SAL_LOG_WARN +HRESULT checkResult(HRESULT hr, const char* file, size_t line) +{ + if (FAILED(hr)) + { + OUString sLocationString = OUString::createFromAscii(file) + ":" + OUString::number(line) + " "; + SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_WARN, ::SAL_DETAIL_LOG_LEVEL_WARN, + "vcl.gdi", sLocationString.toUtf8().getStr(), + "HRESULT failed with: 0x" << OUString::number(hr, 16) << ": " << WindowsErrorStringFromHRESULT(hr)); + } + return hr; +} + +#define CHECKHR(funct) checkResult(funct, __FILE__, __LINE__) +#else +#define CHECKHR(funct) (funct) +#endif + + +} // end anonymous namespace + +D2DWriteTextOutRenderer::D2DWriteTextOutRenderer(bool bRenderingModeNatural) + : mpD2DFactory(nullptr), + mpRT(nullptr), + mRTProps(D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), + 0, 0)), + mbRenderingModeNatural(bRenderingModeNatural), + meTextAntiAliasMode(D2DTextAntiAliasMode::Default) +{ + WinSalGraphics::getDWriteFactory(&mpDWriteFactory); + HRESULT hr = S_OK; + hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), nullptr, reinterpret_cast<void **>(&mpD2DFactory)); + if (SUCCEEDED(hr)) + hr = CreateRenderTarget(bRenderingModeNatural); + meTextAntiAliasMode = lclGetSystemTextAntiAliasMode(); +} + +D2DWriteTextOutRenderer::~D2DWriteTextOutRenderer() +{ + if (mpRT) + mpRT->Release(); + if (mpD2DFactory) + mpD2DFactory->Release(); +} + +void D2DWriteTextOutRenderer::applyTextAntiAliasMode(bool bRenderingModeNatural) +{ + D2D1_TEXT_ANTIALIAS_MODE eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + DWRITE_RENDERING_MODE eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT; + switch (meTextAntiAliasMode) + { + case D2DTextAntiAliasMode::Default: + eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT; + eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + break; + case D2DTextAntiAliasMode::Aliased: + eRenderingMode = DWRITE_RENDERING_MODE_ALIASED; + eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED; + break; + case D2DTextAntiAliasMode::AntiAliased: + eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC; + eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; + break; + case D2DTextAntiAliasMode::ClearType: + eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC; + eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; + break; + default: + break; + } + + if (bRenderingModeNatural) + eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL; + + mpRT->SetTextRenderingParams(lclSetRenderingMode(mpDWriteFactory, eRenderingMode)); + mpRT->SetTextAntialiasMode(eTextAAMode); +} + +HRESULT D2DWriteTextOutRenderer::CreateRenderTarget(bool bRenderingModeNatural) +{ + if (mpRT) + { + mpRT->Release(); + mpRT = nullptr; + } + HRESULT hr = CHECKHR(mpD2DFactory->CreateDCRenderTarget(&mRTProps, &mpRT)); + if (SUCCEEDED(hr)) + applyTextAntiAliasMode(bRenderingModeNatural); + return hr; +} + +bool D2DWriteTextOutRenderer::Ready() const +{ + return mpRT; +} + +HRESULT D2DWriteTextOutRenderer::BindDC(HDC hDC, tools::Rectangle const & rRect) +{ + RECT const rc = { + o3tl::narrowing<LONG>(rRect.Left()), o3tl::narrowing<LONG>(rRect.Top()), + o3tl::narrowing<LONG>(rRect.Right()), o3tl::narrowing<LONG>(rRect.Bottom()) }; + return CHECKHR(mpRT->BindDC(hDC, &rc)); +} + +bool D2DWriteTextOutRenderer::operator()(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool bRenderingModeNatural) +{ + bool bRetry = false; + bool bResult = false; + int nCount = 0; + do + { + bRetry = false; + bResult = performRender(rLayout, rGraphics, hDC, bRetry, bRenderingModeNatural); + nCount++; + } while (bRetry && nCount < 3); + return bResult; +} + +bool D2DWriteTextOutRenderer::performRender(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool& bRetry, bool bRenderingModeNatural) +{ + if (!Ready()) + return false; + + HRESULT hr = S_OK; + hr = BindDC(hDC); + + if (hr == D2DERR_RECREATE_TARGET) + { + CreateRenderTarget(bRenderingModeNatural); + bRetry = true; + return false; + } + if (FAILED(hr)) + { + // If for any reason we can't bind fallback to legacy APIs. + return ExTextOutRenderer()(rLayout, rGraphics, hDC, bRenderingModeNatural); + } + + const WinFontInstance& rWinFont = static_cast<const WinFontInstance&>(rLayout.GetFont()); + float fHScale = rWinFont.getHScale(); + + float lfEmHeight = 0; + IDWriteFontFace* pFontFace = GetDWriteFace(rWinFont, &lfEmHeight); + if (!pFontFace) + return false; + + tools::Rectangle bounds; + bool succeeded = rLayout.GetBoundRect(bounds); + if (succeeded) + { + hr = BindDC(hDC, bounds); // Update the bounding rect. + succeeded &= SUCCEEDED(hr); + } + + ID2D1SolidColorBrush* pBrush = nullptr; + if (succeeded) + { + COLORREF bgrTextColor = GetTextColor(hDC); + D2D1::ColorF aD2DColor(GetRValue(bgrTextColor) / 255.0f, GetGValue(bgrTextColor) / 255.0f, GetBValue(bgrTextColor) / 255.0f); + succeeded &= SUCCEEDED(CHECKHR(mpRT->CreateSolidColorBrush(aD2DColor, &pBrush))); + } + + if (succeeded) + { + mpRT->BeginDraw(); + + int nStart = 0; + basegfx::B2DPoint aPos; + const GlyphItem* pGlyph; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + UINT16 glyphIndices[] = { static_cast<UINT16>(pGlyph->glyphId()) }; + FLOAT glyphAdvances[] = { static_cast<FLOAT>(pGlyph->newWidth()) / fHScale }; + DWRITE_GLYPH_OFFSET glyphOffsets[] = { { 0.0f, 0.0f }, }; + D2D1_POINT_2F baseline = { static_cast<FLOAT>(aPos.getX() - bounds.Left()) / fHScale, + static_cast<FLOAT>(aPos.getY() - bounds.Top()) }; + WinFontTransformGuard aTransformGuard(mpRT, fHScale, rLayout, baseline, pGlyph->IsVertical()); + DWRITE_GLYPH_RUN glyphs = { + pFontFace, + lfEmHeight, + 1, + glyphIndices, + glyphAdvances, + glyphOffsets, + false, + 0 + }; + + mpRT->DrawGlyphRun(baseline, &glyphs, pBrush); + } + + hr = CHECKHR(mpRT->EndDraw()); + } + + if (pBrush) + pBrush->Release(); + + if (hr == D2DERR_RECREATE_TARGET) + { + CreateRenderTarget(bRenderingModeNatural); + bRetry = true; + } + + return succeeded; +} + +IDWriteFontFace* D2DWriteTextOutRenderer::GetDWriteFace(const WinFontInstance& rWinFont, + float* lfSize) const +{ + auto pFontFace = rWinFont.GetDWFontFace(); + if (pFontFace) + { + LOGFONTW aLogFont; + HFONT hFont = rWinFont.GetHFONT(); + + GetObjectW(hFont, sizeof(LOGFONTW), &aLogFont); + float dpix, dpiy; + mpRT->GetDpi(&dpix, &dpiy); + *lfSize = aLogFont.lfHeight * 96.0f / dpiy; + + assert(*lfSize < 0); + *lfSize *= -1; + } + + return pFontFace; +} + +WinFontTransformGuard::WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, float fHScale, + const GenericSalLayout& rLayout, + const D2D1_POINT_2F& rBaseline, + bool bIsVertical) + : mpRenderTarget(pRenderTarget) +{ + pRenderTarget->GetTransform(&maTransform); + D2D1::Matrix3x2F aTransform = maTransform; + if (fHScale != 1.0f) + { + aTransform + = aTransform * D2D1::Matrix3x2F::Scale(D2D1::Size(fHScale, 1.0f), D2D1::Point2F(0, 0)); + } + + Degree10 angle = rLayout.GetOrientation(); + + if (bIsVertical) + angle += 900_deg10; + + if (angle) + { + // DWrite angle is in clockwise degrees, our orientation is in counter-clockwise 10th + // degrees. + aTransform = aTransform + * D2D1::Matrix3x2F::Rotation( + -toDegrees(angle), rBaseline); + } + mpRenderTarget->SetTransform(aTransform); +} + +WinFontTransformGuard::~WinFontTransformGuard() { mpRenderTarget->SetTransform(maTransform); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/dw-extra.h b/vcl/win/gdi/dw-extra.h new file mode 100644 index 0000000000..4c07d81d21 --- /dev/null +++ b/vcl/win/gdi/dw-extra.h @@ -0,0 +1,141 @@ +// +// copied from: +// https://hg.mozilla.org/mozilla-central/file/704f09a557a4dfc9057f1672b711789f64f74a82/gfx/2d/dw-extra.h +// + +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/. */ + +/* + * New DirectWrite interfaces based on Win10 Fall Creators Update versions + * of dwrite_3.h and dcommon.h (from SDK 10.0.17061.0). This particular + * subset of declarations is intended to be just sufficient to compile the + * Gecko DirectWrite font code; it omits many other new interfaces, etc. + */ + +#ifndef DWRITE_EXTRA_H +#define DWRITE_EXTRA_H + +#pragma once + +interface IDWriteFontResource; +interface IDWriteFontFaceReference1; + +enum DWRITE_GLYPH_IMAGE_FORMATS { + DWRITE_GLYPH_IMAGE_FORMATS_NONE = 0x00000000, + DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE = 0x00000001, + DWRITE_GLYPH_IMAGE_FORMATS_CFF = 0x00000002, + DWRITE_GLYPH_IMAGE_FORMATS_COLR = 0x00000004, + DWRITE_GLYPH_IMAGE_FORMATS_SVG = 0x00000008, + DWRITE_GLYPH_IMAGE_FORMATS_PNG = 0x00000010, + DWRITE_GLYPH_IMAGE_FORMATS_JPEG = 0x00000020, + DWRITE_GLYPH_IMAGE_FORMATS_TIFF = 0x00000040, + DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8 = 0x00000080, +}; + +#ifdef DEFINE_ENUM_FLAG_OPERATORS +DEFINE_ENUM_FLAG_OPERATORS(DWRITE_GLYPH_IMAGE_FORMATS); +#endif + +#define DWRITE_MAKE_FONT_AXIS_TAG(a, b, c, d) \ + (static_cast<DWRITE_FONT_AXIS_TAG>(DWRITE_MAKE_OPENTYPE_TAG(a, b, c, d))) + +enum DWRITE_FONT_AXIS_TAG : UINT32 { + DWRITE_FONT_AXIS_TAG_WEIGHT = DWRITE_MAKE_FONT_AXIS_TAG('w', 'g', 'h', 't'), + DWRITE_FONT_AXIS_TAG_WIDTH = DWRITE_MAKE_FONT_AXIS_TAG('w', 'd', 't', 'h'), + DWRITE_FONT_AXIS_TAG_SLANT = DWRITE_MAKE_FONT_AXIS_TAG('s', 'l', 'n', 't'), + DWRITE_FONT_AXIS_TAG_OPTICAL_SIZE = + DWRITE_MAKE_FONT_AXIS_TAG('o', 'p', 's', 'z'), + DWRITE_FONT_AXIS_TAG_ITALIC = DWRITE_MAKE_FONT_AXIS_TAG('i', 't', 'a', 'l'), +}; + +enum DWRITE_FONT_AXIS_ATTRIBUTES { + DWRITE_FONT_AXIS_ATTRIBUTES_NONE = 0x0000, + DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE = 0x0001, + DWRITE_FONT_AXIS_ATTRIBUTES_HIDDEN = 0x0002, +}; + +struct DWRITE_FONT_AXIS_VALUE { + DWRITE_FONT_AXIS_TAG axisTag; + FLOAT value; +}; + +struct DWRITE_FONT_AXIS_RANGE { + DWRITE_FONT_AXIS_TAG axisTag; + FLOAT minValue; + FLOAT maxValue; +}; + +struct DWRITE_GLYPH_IMAGE_DATA { + const void* imageData; + UINT32 imageDataSize; + UINT32 uniqueDataId; + UINT32 pixelsPerEm; + D2D1_SIZE_U pixelSize; + D2D1_POINT_2L horizontalLeftOrigin; + D2D1_POINT_2L horizontalRightOrigin; + D2D1_POINT_2L verticalTopOrigin; + D2D1_POINT_2L verticalBottomOrigin; +}; + +interface DWRITE_DECLARE_INTERFACE("27F2A904-4EB8-441D-9678-0563F53E3E2F") + IDWriteFontFace4 : public IDWriteFontFace3 { + STDMETHOD_(DWRITE_GLYPH_IMAGE_FORMATS, GetGlyphImageFormats)() PURE; + STDMETHOD(GetGlyphImageFormats) + (UINT16 glyphId, UINT32 pixelsPerEmFirst, UINT32 pixelsPerEmLast, + _Out_ DWRITE_GLYPH_IMAGE_FORMATS* glyphImageFormats) PURE; + STDMETHOD(GetGlyphImageData) + (_In_ UINT16 glyphId, UINT32 pixelsPerEm, + DWRITE_GLYPH_IMAGE_FORMATS glyphImageFormat, + _Out_ DWRITE_GLYPH_IMAGE_DATA* glyphData, + _Outptr_result_maybenull_ void** glyphDataContext) PURE; + STDMETHOD_(void, ReleaseGlyphImageData)(void* glyphDataContext) PURE; +}; + +interface DWRITE_DECLARE_INTERFACE("98EFF3A5-B667-479A-B145-E2FA5B9FDC29") + IDWriteFontFace5 : public IDWriteFontFace4 { + STDMETHOD_(UINT32, GetFontAxisValueCount)() PURE; + STDMETHOD(GetFontAxisValues) + (_Out_writes_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE* fontAxisValues, + UINT32 fontAxisValueCount) PURE; + STDMETHOD_(BOOL, HasVariations)() PURE; + STDMETHOD(GetFontResource) + (_COM_Outptr_ IDWriteFontResource** fontResource) PURE; + STDMETHOD_(BOOL, Equals)(IDWriteFontFace* fontFace) PURE; +}; + +interface DWRITE_DECLARE_INTERFACE("1F803A76-6871-48E8-987F-B975551C50F2") + IDWriteFontResource : public IUnknown { + STDMETHOD(GetFontFile)(_COM_Outptr_ IDWriteFontFile** fontFile) PURE; + STDMETHOD_(UINT32, GetFontFaceIndex)() PURE; + STDMETHOD_(UINT32, GetFontAxisCount)() PURE; + STDMETHOD(GetDefaultFontAxisValues) + (_Out_writes_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE* fontAxisValues, + UINT32 fontAxisValueCount) PURE; + STDMETHOD(GetFontAxisRanges) + (_Out_writes_(fontAxisRangeCount) DWRITE_FONT_AXIS_RANGE* fontAxisRanges, + UINT32 fontAxisRangeCount) PURE; + STDMETHOD_(DWRITE_FONT_AXIS_ATTRIBUTES, GetFontAxisAttributes) + (UINT32 axisIndex) PURE; + STDMETHOD(GetAxisNames) + (UINT32 axisIndex, _COM_Outptr_ IDWriteLocalizedStrings** names) PURE; + STDMETHOD_(UINT32, GetAxisValueNameCount)(UINT32 axisIndex) PURE; + STDMETHOD(GetAxisValueNames) + (UINT32 axisIndex, UINT32 axisValueIndex, + _Out_ DWRITE_FONT_AXIS_RANGE* fontAxisRange, + _COM_Outptr_ IDWriteLocalizedStrings** names) PURE; + STDMETHOD_(BOOL, HasVariations)() PURE; + STDMETHOD(CreateFontFace) + (DWRITE_FONT_SIMULATIONS fontSimulations, + _In_reads_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE const* fontAxisValues, + UINT32 fontAxisValueCount, _COM_Outptr_ IDWriteFontFace5** fontFace) PURE; + STDMETHOD(CreateFontFaceReference) + (DWRITE_FONT_SIMULATIONS fontSimulations, + _In_reads_(fontAxisValueCount) DWRITE_FONT_AXIS_VALUE const* fontAxisValues, + UINT32 fontAxisValueCount, + _COM_Outptr_ IDWriteFontFaceReference1** fontFaceReference) PURE; +}; + +#endif /* DWRITE_EXTRA_H */ diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx new file mode 100644 index 0000000000..5cb6a05bda --- /dev/null +++ b/vcl/win/gdi/gdiimpl.cxx @@ -0,0 +1,2731 @@ +/* -*- 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 <sal/config.h> + +#include <cstdlib> +#include <memory> +#include <numeric> + +#include <svsys.h> + +#include "gdiimpl.hxx" + +#include <string.h> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <tools/poly.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salgdi.h> +#include <win/salbmp.h> +#include <win/scoped_gdi.hxx> +#include <vcl/BitmapAccessMode.hxx> +#include <vcl/BitmapBuffer.hxx> +#include <vcl/BitmapPalette.hxx> +#include <win/salframe.h> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/systemdependentdata.hxx> + +#include <win/salids.hrc> +#include <ControlCacheKey.hxx> + +#include <prewin.h> + +#include <gdiplus.h> +#include <gdiplusenums.h> +#include <gdipluscolor.h> + +#include <postwin.h> + +#define SAL_POLYPOLYCOUNT_STACKBUF 8 +#define SAL_POLYPOLYPOINTS_STACKBUF 64 + +#define SAL_POLY_STACKBUF 32 + +namespace { + +// #100127# Fill point and flag memory from array of points which +// might also contain bezier control points for the PolyDraw() GDI method +// Make sure pWinPointAry and pWinFlagAry are big enough +void ImplPreparePolyDraw( bool bCloseFigures, + sal_uLong nPoly, + const sal_uInt32* pPoints, + const Point* const* pPtAry, + const PolyFlags* const* pFlgAry, + POINT* pWinPointAry, + BYTE* pWinFlagAry ) +{ + sal_uLong nCurrPoly; + for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly ) + { + const Point* pCurrPoint = *pPtAry++; + const PolyFlags* pCurrFlag = *pFlgAry++; + const sal_uInt32 nCurrPoints = *pPoints++; + const bool bHaveFlagArray( pCurrFlag ); + sal_uLong nCurrPoint; + + if( nCurrPoints ) + { + // start figure + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_MOVETO; + ++pCurrFlag; + + for( nCurrPoint=1; nCurrPoint<nCurrPoints; ) + { + // #102067# Check existence of flag array + if( bHaveFlagArray && + ( nCurrPoint + 2 ) < nCurrPoints ) + { + PolyFlags P4( pCurrFlag[ 2 ] ); + + if( ( PolyFlags::Control == pCurrFlag[ 0 ] ) && + ( PolyFlags::Control == pCurrFlag[ 1 ] ) && + ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) ) + { + // control point one + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + // control point two + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + // end point + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + nCurrPoint += 3; + pCurrFlag += 3; + continue; + } + } + + // regular line point + *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) }; + pCurrPoint++; + *pWinFlagAry++ = PT_LINETO; + ++pCurrFlag; + ++nCurrPoint; + } + + // end figure? + if( bCloseFigures ) + pWinFlagAry[-1] |= PT_CLOSEFIGURE; + } + } +} + +Color ImplGetROPColor( SalROPColor nROPColor ) +{ + Color nColor; + if ( nROPColor == SalROPColor::N0 ) + nColor = Color( 0, 0, 0 ); + else + nColor = Color( 255, 255, 255 ); + return nColor; +} + +bool IsDitherColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + constexpr sal_uInt8 DITHER_PAL_DELTA = 51; + + return !(nRed % DITHER_PAL_DELTA) && + !(nGreen % DITHER_PAL_DELTA) && + !(nBlue % DITHER_PAL_DELTA); +} + +bool IsPaletteColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + static const PALETTEENTRY aImplSalSysPalEntryAry[] = + { + { 0, 0, 0, 0 }, + { 0, 0, 0x80, 0 }, + { 0, 0x80, 0, 0 }, + { 0, 0x80, 0x80, 0 }, + { 0x80, 0, 0, 0 }, + { 0x80, 0, 0x80, 0 }, + { 0x80, 0x80, 0, 0 }, + { 0x80, 0x80, 0x80, 0 }, + { 0xC0, 0xC0, 0xC0, 0 }, + { 0, 0, 0xFF, 0 }, + { 0, 0xFF, 0, 0 }, + { 0, 0xFF, 0xFF, 0 }, + { 0xFF, 0, 0, 0 }, + { 0xFF, 0, 0xFF, 0 }, + { 0xFF, 0xFF, 0, 0 }, + { 0xFF, 0xFF, 0xFF, 0 } + }; + + for (const auto& rPalEntry : aImplSalSysPalEntryAry) + { + if(rPalEntry.peRed == nRed && + rPalEntry.peGreen == nGreen && + rPalEntry.peBlue == nBlue) + { + return true; + } + } + + return false; +} + +bool IsExtraColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + return (nRed == 0) && (nGreen == 184) && (nBlue == 255); +} + +bool ImplIsPaletteEntry(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + return IsDitherColor(nRed, nGreen, nBlue) || + IsPaletteColor(nRed, nGreen, nBlue) || + IsExtraColor(nRed, nGreen, nBlue); +} + +} // namespace + +WinSalGraphicsImpl::WinSalGraphicsImpl(WinSalGraphics& rParent): + mrParent(rParent), + mbXORMode(false), + mbPen(false), + mhPen(nullptr), + mbStockPen(false), + mbBrush(false), + mbStockBrush(false), + mhBrush(nullptr) +{ +} + +WinSalGraphicsImpl::~WinSalGraphicsImpl() +{ + if ( mhPen ) + { + if ( !mbStockPen ) + DeletePen( mhPen ); + } + + if ( mhBrush ) + { + if ( !mbStockBrush ) + DeleteBrush( mhBrush ); + } +} + +void WinSalGraphicsImpl::Init() +{ +} + +void WinSalGraphicsImpl::freeResources() +{ +} + +bool WinSalGraphicsImpl::drawEPS(tools::Long, tools::Long, tools::Long, tools::Long, void*, sal_uInt32) +{ + return false; +} + +void WinSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + HDC hSrcDC; + DWORD nRop; + + if ( pSrcGraphics ) + hSrcDC = static_cast<WinSalGraphics*>(pSrcGraphics)->getHDC(); + else + hSrcDC = mrParent.getHDC(); + + if ( mbXORMode ) + nRop = SRCINVERT; + else + nRop = SRCCOPY; + + if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) && + (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) ) + { + BitBlt( mrParent.getHDC(), + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hSrcDC, + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + nRop ); + } + else + { + int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS ); + StretchBlt( mrParent.getHDC(), + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hSrcDC, + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + nRop ); + SetStretchBltMode( mrParent.getHDC(), nOldStretchMode ); + } +} + +namespace +{ + +void MakeInvisibleArea(const RECT& rSrcRect, + int nLeft, int nTop, int nRight, int nBottom, + HRGN& rhInvalidateRgn) +{ + if (!rhInvalidateRgn) + { + rhInvalidateRgn = CreateRectRgnIndirect(&rSrcRect); + } + + ScopedHRGN hTempRgn(CreateRectRgn(nLeft, nTop, nRight, nBottom)); + CombineRgn(rhInvalidateRgn, rhInvalidateRgn, hTempRgn.get(), RGN_DIFF); +} + +void ImplCalcOutSideRgn( const RECT& rSrcRect, + int nLeft, int nTop, int nRight, int nBottom, + HRGN& rhInvalidateRgn ) +{ + // calculate area outside the visible region + if (rSrcRect.left < nLeft) + { + MakeInvisibleArea(rSrcRect, -31999, 0, nLeft, 31999, rhInvalidateRgn); + } + if (rSrcRect.top < nTop) + { + MakeInvisibleArea(rSrcRect, 0, -31999, 31999, nTop, rhInvalidateRgn); + } + if (rSrcRect.right > nRight) + { + MakeInvisibleArea(rSrcRect, nRight, 0, 31999, 31999, rhInvalidateRgn); + } + if (rSrcRect.bottom > nBottom) + { + MakeInvisibleArea(rSrcRect, 0, nBottom, 31999, 31999, rhInvalidateRgn); + } +} + +} // namespace + +void WinSalGraphicsImpl::copyArea( tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, + bool bWindowInvalidate ) +{ + bool bRestoreClipRgn = false; + HRGN hOldClipRgn = nullptr; + int nOldClipRgnType = ERROR; + HRGN hInvalidateRgn = nullptr; + + // do we have to invalidate also the overlapping regions? + if ( bWindowInvalidate && mrParent.isWindow() ) + { + // compute and invalidate those parts that were either off-screen or covered by other windows + // while performing the above BitBlt + // those regions then have to be invalidated as they contain useless/wrong data + RECT aSrcRect; + RECT aClipRect; + RECT aTempRect; + RECT aTempRect2; + HRGN hTempRgn; + HWND hWnd; + + // restrict srcRect to this window (calc intersection) + aSrcRect.left = static_cast<int>(nSrcX); + aSrcRect.top = static_cast<int>(nSrcY); + aSrcRect.right = aSrcRect.left+static_cast<int>(nSrcWidth); + aSrcRect.bottom = aSrcRect.top+static_cast<int>(nSrcHeight); + GetClientRect( mrParent.gethWnd(), &aClipRect ); + if ( IntersectRect( &aSrcRect, &aSrcRect, &aClipRect ) ) + { + // transform srcRect to screen coordinates + POINT aPt; + aPt.x = 0; + aPt.y = 0; + ClientToScreen( mrParent.gethWnd(), &aPt ); + aSrcRect.left += aPt.x; + aSrcRect.top += aPt.y; + aSrcRect.right += aPt.x; + aSrcRect.bottom += aPt.y; + hInvalidateRgn = nullptr; + + // compute the parts that are off screen (ie invisible) + RECT theScreen; + ImplSalGetWorkArea( nullptr, &theScreen, nullptr ); // find the screen area taking multiple monitors into account + ImplCalcOutSideRgn( aSrcRect, theScreen.left, theScreen.top, theScreen.right, theScreen.bottom, hInvalidateRgn ); + + // calculate regions that are covered by other windows + HRGN hTempRgn2 = nullptr; + HWND hWndTopWindow = mrParent.gethWnd(); + // Find the TopLevel Window, because only Windows which are in + // in the foreground of our TopLevel window must be considered + if ( GetWindowStyle( hWndTopWindow ) & WS_CHILD ) + { + RECT aTempRect3 = aSrcRect; + do + { + hWndTopWindow = ::GetParent( hWndTopWindow ); + + // Test if the Parent clips our window + GetClientRect( hWndTopWindow, &aTempRect ); + POINT aPt2; + aPt2.x = 0; + aPt2.y = 0; + ClientToScreen( hWndTopWindow, &aPt2 ); + aTempRect.left += aPt2.x; + aTempRect.top += aPt2.y; + aTempRect.right += aPt2.x; + aTempRect.bottom += aPt2.y; + IntersectRect( &aTempRect3, &aTempRect3, &aTempRect ); + } + while ( GetWindowStyle( hWndTopWindow ) & WS_CHILD ); + + // If one or more Parents clip our window, then we must + // calculate the outside area + if ( !EqualRect( &aSrcRect, &aTempRect3 ) ) + { + ImplCalcOutSideRgn( aSrcRect, + aTempRect3.left, aTempRect3.top, + aTempRect3.right, aTempRect3.bottom, + hInvalidateRgn ); + } + } + // retrieve the top-most (z-order) child window + hWnd = GetWindow( GetDesktopWindow(), GW_CHILD ); + while ( hWnd ) + { + if ( hWnd == hWndTopWindow ) + break; + if ( IsWindowVisible( hWnd ) && !IsIconic( hWnd ) ) + { + GetWindowRect( hWnd, &aTempRect ); + if ( IntersectRect( &aTempRect2, &aSrcRect, &aTempRect ) ) + { + // hWnd covers part or all of aSrcRect + if ( !hInvalidateRgn ) + hInvalidateRgn = CreateRectRgnIndirect( &aSrcRect ); + + // get full bounding box of hWnd + hTempRgn = CreateRectRgnIndirect( &aTempRect ); + + // get region of hWnd (the window may be shaped) + if ( !hTempRgn2 ) + hTempRgn2 = CreateRectRgn( 0, 0, 0, 0 ); + int nRgnType = GetWindowRgn( hWnd, hTempRgn2 ); + if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) ) + { + // convert window region to screen coordinates + OffsetRgn( hTempRgn2, aTempRect.left, aTempRect.top ); + // and intersect with the window's bounding box + CombineRgn( hTempRgn, hTempRgn, hTempRgn2, RGN_AND ); + } + // finally compute that part of aSrcRect which is not covered by any parts of hWnd + CombineRgn( hInvalidateRgn, hInvalidateRgn, hTempRgn, RGN_DIFF ); + DeleteRegion( hTempRgn ); + } + } + // retrieve the next window in the z-order, i.e. the window below hwnd + hWnd = GetWindow( hWnd, GW_HWNDNEXT ); + } + if ( hTempRgn2 ) + DeleteRegion( hTempRgn2 ); + if ( hInvalidateRgn ) + { + // hInvalidateRgn contains the fully visible parts of the original srcRect + hTempRgn = CreateRectRgnIndirect( &aSrcRect ); + // subtract it from the original rect to get the occluded parts + int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_DIFF ); + DeleteRegion( hTempRgn ); + + if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) ) + { + // move the occluded parts to the destination pos + int nOffX = static_cast<int>(nDestX-nSrcX); + int nOffY = static_cast<int>(nDestY-nSrcY); + OffsetRgn( hInvalidateRgn, nOffX-aPt.x, nOffY-aPt.y ); + + // by excluding hInvalidateRgn from the system's clip region + // we will prevent bitblt from copying useless data + // especially now shadows from overlapping windows will appear (#i36344) + hOldClipRgn = CreateRectRgn( 0, 0, 0, 0 ); + nOldClipRgnType = GetClipRgn( mrParent.getHDC(), hOldClipRgn ); + + bRestoreClipRgn = true; // indicate changed clipregion and force invalidate + ExtSelectClipRgn( mrParent.getHDC(), hInvalidateRgn, RGN_DIFF ); + } + } + } + } + + BitBlt( mrParent.getHDC(), + static_cast<int>(nDestX), static_cast<int>(nDestY), + static_cast<int>(nSrcWidth), static_cast<int>(nSrcHeight), + mrParent.getHDC(), + static_cast<int>(nSrcX), static_cast<int>(nSrcY), + SRCCOPY ); + + if( bRestoreClipRgn ) + { + // restore old clip region + if( nOldClipRgnType != ERROR ) + SelectClipRgn( mrParent.getHDC(), hOldClipRgn); + DeleteRegion( hOldClipRgn ); + + // invalidate regions that were not copied + bool bInvalidate = true; + + // Combine Invalidate vcl::Region with existing ClipRegion + HRGN hTempRgn = CreateRectRgn( 0, 0, 0, 0 ); + if ( GetClipRgn( mrParent.getHDC(), hTempRgn ) == 1 ) + { + int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_AND ); + if ( (nRgnType == ERROR) || (nRgnType == NULLREGION) ) + bInvalidate = false; + } + DeleteRegion( hTempRgn ); + + if ( bInvalidate ) + { + InvalidateRgn( mrParent.gethWnd(), hInvalidateRgn, TRUE ); + // here we only initiate an update if this is the MainThread, + // so that there is no deadlock when handling the Paint event, + // as the SolarMutex is already held by this Thread + SalData* pSalData = GetSalData(); + DWORD nCurThreadId = GetCurrentThreadId(); + if ( pSalData->mnAppThreadId == nCurThreadId ) + UpdateWindow( mrParent.gethWnd() ); + } + + DeleteRegion( hInvalidateRgn ); + } + +} + +namespace { + +void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap, + bool bPrinter, int nDrawMode ) +{ + if( hDC ) + { + HGLOBAL hDrawDIB; + HBITMAP hDrawDDB = rSalBitmap.ImplGethDDB(); + std::unique_ptr<WinSalBitmap> xTmpSalBmp; + bool bPrintDDB = ( bPrinter && hDrawDDB ); + + if( bPrintDDB ) + { + xTmpSalBmp.reset(new WinSalBitmap); + xTmpSalBmp->Create(rSalBitmap, vcl::bitDepthToPixelFormat(rSalBitmap.GetBitCount())); + hDrawDIB = xTmpSalBmp->ImplGethDIB(); + } + else + hDrawDIB = rSalBitmap.ImplGethDIB(); + + if( hDrawDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB )); + PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + + WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD ); + const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS ); + + StretchDIBits( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + pBits, pBI, DIB_RGB_COLORS, nDrawMode ); + + GlobalUnlock( hDrawDIB ); + SetStretchBltMode( hDC, nOldStretchMode ); + } + else if( hDrawDDB && !bPrintDDB ) + { + ScopedCachedHDC<CACHED_HDC_DRAW> hBmpDC(hDrawDDB); + + COLORREF nOldBkColor = RGB(0xFF,0xFF,0xFF); + COLORREF nOldTextColor = RGB(0,0,0); + bool bMono = ( rSalBitmap.GetBitCount() == 1 ); + + if( bMono ) + { + COLORREF nBkColor = RGB( 0xFF, 0xFF, 0xFF ); + COLORREF nTextColor = RGB( 0x00, 0x00, 0x00 ); + //fdo#33455 handle 1 bit depth pngs with palette entries + //to set fore/back colors + if (BitmapBuffer* pBitmapBuffer = const_cast<WinSalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Info)) + { + const BitmapPalette& rPalette = pBitmapBuffer->maPalette; + if (rPalette.GetEntryCount() == 2) + { + Color nCol = rPalette[0]; + nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() ); + nCol = rPalette[1]; + nBkColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() ); + } + const_cast<WinSalBitmap&>(rSalBitmap).ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Info); + } + nOldBkColor = SetBkColor( hDC, nBkColor ); + nOldTextColor = ::SetTextColor( hDC, nTextColor ); + } + + if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) && + (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) ) + { + BitBlt( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hBmpDC.get(), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + nDrawMode ); + } + else + { + const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS ); + + StretchBlt( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hBmpDC.get(), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + nDrawMode ); + + SetStretchBltMode( hDC, nOldStretchMode ); + } + + if( bMono ) + { + SetBkColor( hDC, nOldBkColor ); + ::SetTextColor( hDC, nOldTextColor ); + } + } + } +} + +} // namespace + +void WinSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) +{ + bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode); + + if(bTryDirectPaint) + { + // only paint direct when no scaling and no MapMode, else the + // more expensive conversions may be done for short-time Bitmap/BitmapEx + // used for buffering only + if(rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight) + { + bTryDirectPaint = false; + } + } + + // try to draw using GdiPlus directly + if(bTryDirectPaint && TryDrawBitmapGDIPlus(rPosAry, rSalBitmap)) + { + return; + } + + // fall back old stuff + assert(dynamic_cast<const WinSalBitmap*>(&rSalBitmap)); + + ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast<const WinSalBitmap&>(rSalBitmap), + mrParent.isPrinter(), + mbXORMode ? SRCINVERT : SRCCOPY ); +} + +void WinSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSSalBitmap, + const SalBitmap& rSTransparentBitmap ) +{ + SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" ); + bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode); + + // try to draw using GdiPlus directly + if(bTryDirectPaint && drawAlphaBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap)) + { + return; + } + + assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap)); + assert(dynamic_cast<const WinSalBitmap*>(&rSTransparentBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap); + const WinSalBitmap& rTransparentBitmap = static_cast<const WinSalBitmap&>(rSTransparentBitmap); + + SalTwoRect aPosAry = rPosAry; + int nDstX = static_cast<int>(aPosAry.mnDestX); + int nDstY = static_cast<int>(aPosAry.mnDestY); + int nDstWidth = static_cast<int>(aPosAry.mnDestWidth); + int nDstHeight = static_cast<int>(aPosAry.mnDestHeight); + HDC hDC = mrParent.getHDC(); + + ScopedHBITMAP hMemBitmap; + ScopedHBITMAP hMaskBitmap; + + if( ( nDstWidth > CACHED_HDC_DEFEXT ) || ( nDstHeight > CACHED_HDC_DEFEXT ) ) + { + hMemBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight)); + hMaskBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight)); + } + + ScopedCachedHDC<CACHED_HDC_1> hMemDC(hMemBitmap.get()); + ScopedCachedHDC<CACHED_HDC_2> hMaskDC(hMaskBitmap.get()); + + aPosAry.mnDestX = aPosAry.mnDestY = 0; + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hDC, nDstX, nDstY, SRCCOPY ); + + // WIN/WNT seems to have a minor problem mapping the correct color of the + // mask to the palette if we draw the DIB directly ==> draw DDB + if( ( GetBitCount() <= 8 ) && rTransparentBitmap.ImplGethDIB() && rTransparentBitmap.GetBitCount() == 1 ) + { + WinSalBitmap aTmp; + + if( aTmp.Create( rTransparentBitmap, &mrParent ) ) + ImplDrawBitmap( hMaskDC.get(), aPosAry, aTmp, false, SRCCOPY ); + } + else + ImplDrawBitmap( hMaskDC.get(), aPosAry, rTransparentBitmap, false, SRCCOPY ); + + // now MemDC contains background, MaskDC the transparency mask + + // #105055# Respect XOR mode + if( mbXORMode ) + { + ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE ); + // now MaskDC contains the bitmap area with black background + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCINVERT ); + // now MemDC contains background XORed bitmap area on top + } + else + { + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCAND ); + // now MemDC contains background with masked-out bitmap area + ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE ); + // now MaskDC contains the bitmap area with black background + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCPAINT ); + // now MemDC contains background and bitmap merged together + } + // copy to output DC + BitBlt( hDC, nDstX, nDstY, nDstWidth, nDstHeight, hMemDC.get(), 0, 0, SRCCOPY ); +} + +bool WinSalGraphicsImpl::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight, sal_uInt8 nTransparency ) +{ + if( mbPen || !mbBrush || mbXORMode ) + return false; // can only perform solid fills without XOR. + + ScopedCachedHDC<CACHED_HDC_1> hMemDC(nullptr); + SetPixel( hMemDC.get(), int(0), int(0), mnBrushColor ); + + BLENDFUNCTION aFunc = { + AC_SRC_OVER, + 0, + sal::static_int_cast<sal_uInt8>(255 - 255L*nTransparency/100), + 0 + }; + + // hMemDC contains a 1x1 bitmap of the right color - stretch-blit + // that to dest hdc + bool bRet = GdiAlphaBlend(mrParent.getHDC(), nX, nY, nWidth, nHeight, + hMemDC.get(), 0,0,1,1, + aFunc ) == TRUE; + + return bRet; +} + +void WinSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, + const SalBitmap& rSSalBitmap, + Color nMaskColor) +{ + SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" ); + + assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap); + + SalTwoRect aPosAry = rPosAry; + const HDC hDC = mrParent.getHDC(); + + ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(RGB(nMaskColor.GetRed(), + nMaskColor.GetGreen(), + nMaskColor.GetBlue()))); + + // WIN/WNT seems to have a minor problem mapping the correct color of the + // mask to the palette if we draw the DIB directly ==> draw DDB + if( ( GetBitCount() <= 8 ) && rSalBitmap.ImplGethDIB() && rSalBitmap.GetBitCount() == 1 ) + { + WinSalBitmap aTmp; + + if( aTmp.Create( rSalBitmap, &mrParent ) ) + ImplDrawBitmap( hDC, aPosAry, aTmp, false, 0x00B8074AUL ); + } + else + ImplDrawBitmap( hDC, aPosAry, rSalBitmap, false, 0x00B8074AUL ); +} + +std::shared_ptr<SalBitmap> WinSalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY ) +{ + SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No ::GetBitmap() from printer possible!" ); + + std::shared_ptr<WinSalBitmap> pSalBitmap; + + nDX = std::abs( nDX ); + nDY = std::abs( nDY ); + + HDC hDC = mrParent.getHDC(); + HBITMAP hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY ); + bool bRet; + + { + ScopedCachedHDC<CACHED_HDC_1> hBmpDC(hBmpBitmap); + + bRet = BitBlt(hBmpDC.get(), 0, 0, + static_cast<int>(nDX), static_cast<int>(nDY), hDC, + static_cast<int>(nX), static_cast<int>(nY), SRCCOPY) ? TRUE : FALSE; + } + + if( bRet ) + { + pSalBitmap = std::make_shared<WinSalBitmap>(); + + if( !pSalBitmap->Create( hBmpBitmap ) ) + { + pSalBitmap.reset(); + } + } + else + { + // #124826# avoid resource leak! Happens when running without desktop access (remote desktop, service, may be screensavers) + DeleteBitmap( hBmpBitmap ); + } + + return pSalBitmap; +} + +Color WinSalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY ) +{ + COLORREF aWinCol = ::GetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY) ); + + if ( CLR_INVALID == aWinCol ) + return Color( 0, 0, 0 ); + else + return Color( GetRValue( aWinCol ), + GetGValue( aWinCol ), + GetBValue( aWinCol ) ); +} + +namespace +{ + +HBRUSH Get50PercentBrush() +{ + SalData* pSalData = GetSalData(); + if ( !pSalData->mh50Brush ) + { + if ( !pSalData->mh50Bmp ) + pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 ); + pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp ); + } + + return pSalData->mh50Brush; +} + +} // namespace + +void WinSalGraphicsImpl::invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags ) +{ + if ( nFlags & SalInvert::TrackFrame ) + { + HPEN hDotPen = CreatePen( PS_DOT, 0, 0 ); + HPEN hOldPen = SelectPen( mrParent.getHDC(), hDotPen ); + HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), GetStockBrush( NULL_BRUSH ) ); + int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT ); + + Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) ); + + SetROP2( mrParent.getHDC(), nOldROP ); + SelectPen( mrParent.getHDC(), hOldPen ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + DeletePen( hDotPen ); + } + else if ( nFlags & SalInvert::N50 ) + { + COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 ); + HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), Get50PercentBrush() ); + PatBlt( mrParent.getHDC(), nX, nY, nWidth, nHeight, PATINVERT ); + ::SetTextColor( mrParent.getHDC(), nOldTextColor ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + } + else + { + RECT aRect; + aRect.left = static_cast<int>(nX); + aRect.top = static_cast<int>(nY); + aRect.right = static_cast<int>(nX)+nWidth; + aRect.bottom = static_cast<int>(nY)+nHeight; + ::InvertRect( mrParent.getHDC(), &aRect ); + } +} + +void WinSalGraphicsImpl::invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags ) +{ + HPEN hPen; + HPEN hOldPen; + HBRUSH hBrush; + HBRUSH hOldBrush = nullptr; + COLORREF nOldTextColor RGB(0,0,0); + int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT ); + + if ( nSalFlags & SalInvert::TrackFrame ) + hPen = CreatePen( PS_DOT, 0, 0 ); + else + { + + if ( nSalFlags & SalInvert::N50 ) + hBrush = Get50PercentBrush(); + else + hBrush = GetStockBrush( BLACK_BRUSH ); + + hPen = GetStockPen( NULL_PEN ); + nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 ); + hOldBrush = SelectBrush( mrParent.getHDC(), hBrush ); + } + hOldPen = SelectPen( mrParent.getHDC(), hPen ); + + std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]); + for (sal_uInt32 i=0; i<nPoints; ++i) + pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) }; + + // for Windows 95 and its maximum number of points + if ( nSalFlags & SalInvert::TrackFrame ) + { + if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); + } + else + { + if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polygon( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); + } + + SetROP2( mrParent.getHDC(), nOldROP ); + SelectPen( mrParent.getHDC(), hOldPen ); + + if ( nSalFlags & SalInvert::TrackFrame ) + DeletePen( hPen ); + else + { + ::SetTextColor( mrParent.getHDC(), nOldTextColor ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + } +} + +sal_uInt16 WinSalGraphicsImpl::GetBitCount() const +{ + return static_cast<sal_uInt16>(GetDeviceCaps( mrParent.getHDC(), BITSPIXEL )); +} + +tools::Long WinSalGraphicsImpl::GetGraphicsWidth() const +{ + if( mrParent.gethWnd() && IsWindow( mrParent.gethWnd() ) ) + { + WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() ); + if( pFrame ) + { + if (pFrame->maGeometry.width()) + return pFrame->maGeometry.width(); + else + { + // TODO: perhaps not needed, maGeometry should always be up-to-date + RECT aRect; + GetClientRect( mrParent.gethWnd(), &aRect ); + return aRect.right; + } + } + } + + return 0; +} + +void WinSalGraphicsImpl::ResetClipRegion() +{ + if ( mrParent.mhRegion ) + { + DeleteRegion( mrParent.mhRegion ); + mrParent.mhRegion = nullptr; + } + + SelectClipRgn( mrParent.getHDC(), nullptr ); +} + +static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon& rCandidate) +{ + if(rCandidate.areControlPointsUsed()) + { + return false; + } + + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount < 2) + { + return true; + } + + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount); + basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0)); + + for(sal_uInt32 a(1); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex(a % nPointCount); + const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex)); + + if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY())) + { + return false; + } + + aLast = aCurrent; + } + + return true; +} + +static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon& rCandidate) +{ + if(rCandidate.areControlPointsUsed()) + { + return false; + } + + for(auto const& rPolygon : rCandidate) + { + if(!containsOnlyHorizontalAndVerticalEdges(rPolygon)) + { + return false; + } + } + + return true; +} + +void WinSalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip ) +{ + if ( mrParent.mhRegion ) + { + DeleteRegion( mrParent.mhRegion ); + mrParent.mhRegion = nullptr; + } + + bool bUsePolygon(i_rClip.HasPolyPolygonOrB2DPolyPolygon()); + static bool bTryToAvoidPolygon(true); + + // #i122149# try to avoid usage of tools::PolyPolygon ClipRegions when tools::PolyPolygon is no curve + // and only contains horizontal/vertical edges. In that case, use the fallback + // in GetRegionRectangles which will use vcl::Region::GetAsRegionBand() which will do + // the correct polygon-to-RegionBand transformation. + // Background is that when using the same Rectangle as rectangle or as Polygon + // clip region will lead to different results; the polygon-based one will be + // one pixel less to the right and down (see GDI docu for CreatePolygonRgn). This + // again is because of the polygon-nature and it's classic handling when filling. + // This also means that all cases which use a 'true' polygon-based incarnation of + // a vcl::Region should know what they do - it may lead to repaint errors. + if(bUsePolygon && bTryToAvoidPolygon) + { + const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() ); + + if(!aPolyPolygon.areControlPointsUsed()) + { + if(containsOnlyHorizontalAndVerticalEdges(aPolyPolygon)) + { + bUsePolygon = false; + } + } + } + + if(bUsePolygon) + { + // #i122149# check the comment above to know that this may lead to potential repaint + // problems. It may be solved (if needed) by scaling the polygon by one in X + // and Y. Currently the workaround to only use it if really unavoidable will + // solve most cases. When someone is really using polygon-based Regions he + // should know what he is doing. + // Added code to do that scaling to check if it works, testing it. + const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() ); + const sal_uInt32 nCount(aPolyPolygon.count()); + + if( nCount ) + { + std::vector< POINT > aPolyPoints; + aPolyPoints.reserve( 1024 ); + std::vector< INT > aPolyCounts( nCount, 0 ); + basegfx::B2DHomMatrix aExpand; + sal_uInt32 nTargetCount(0); + static bool bExpandByOneInXandY(true); + + if(bExpandByOneInXandY) + { + const basegfx::B2DRange aRangeS(aPolyPolygon.getB2DRange()); + const basegfx::B2DRange aRangeT(aRangeS.getMinimum(), aRangeS.getMaximum() + basegfx::B2DTuple(1.0, 1.0)); + aExpand = basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT); + } + + for(auto const& rPolygon : aPolyPolygon) + { + const basegfx::B2DPolygon aPoly( + basegfx::utils::adaptiveSubdivideByDistance( + rPolygon, + 1)); + const sal_uInt32 nPoints(aPoly.count()); + + // tdf#40863 For CustomShapes there is a hack (see + // f64ef72743e55389e446e0d4bc6febd475011023) that adds polygons + // with a single point in top-left and bottom-right corner + // of the BoundRect to be able to determine the correct BoundRect + // in the slideshow. Unfortunately, CreatePolyPolygonRgn below + // fails with polygons containing a single pixel, so clipping is + // lost. For now, use only polygons with more than two points - the + // ones that may have an area. + // Note: polygons with one point which are curves may have an area, + // but the polygon is already subdivided here, so no need to test + // this. + if(nPoints > 2) + { + aPolyCounts[nTargetCount] = nPoints; + nTargetCount++; + + for( sal_uInt32 b = 0; b < nPoints; b++ ) + { + basegfx::B2DPoint aPt(aPoly.getB2DPoint(b)); + + if(bExpandByOneInXandY) + { + aPt = aExpand * aPt; + } + + POINT aPOINT; + // #i122149# do correct rounding + aPOINT.x = basegfx::fround(aPt.getX()); + aPOINT.y = basegfx::fround(aPt.getY()); + aPolyPoints.push_back( aPOINT ); + } + } + } + + if(nTargetCount) + { + mrParent.mhRegion = CreatePolyPolygonRgn( aPolyPoints.data(), aPolyCounts.data(), nTargetCount, ALTERNATE ); + } + } + } + else + { + RectangleVector aRectangles; + i_rClip.GetRegionRectangles(aRectangles); + + sal_uLong nRectBufSize = sizeof(RECT)*aRectangles.size(); + if ( aRectangles.size() < SAL_CLIPRECT_COUNT ) + { + if ( !mrParent.mpStdClipRgnData ) + mrParent.mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]); + mrParent.mpClipRgnData = mrParent.mpStdClipRgnData; + } + else + mrParent.mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]); + mrParent.mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER ); + mrParent.mpClipRgnData->rdh.iType = RDH_RECTANGLES; + mrParent.mpClipRgnData->rdh.nCount = aRectangles.size(); + mrParent.mpClipRgnData->rdh.nRgnSize = nRectBufSize; + RECT* pBoundRect = &(mrParent.mpClipRgnData->rdh.rcBound); + SetRectEmpty( pBoundRect ); + RECT* pNextClipRect = reinterpret_cast<RECT*>(&(mrParent.mpClipRgnData->Buffer)); + bool bFirstClipRect = true; + + for (auto const& rectangle : aRectangles) + { + const tools::Long nW(rectangle.GetWidth()); + const tools::Long nH(rectangle.GetHeight()); + + if(nW && nH) + { + const tools::Long nRight(rectangle.Left() + nW); + const tools::Long nBottom(rectangle.Top() + nH); + + if(bFirstClipRect) + { + pBoundRect->left = rectangle.Left(); + pBoundRect->top = rectangle.Top(); + pBoundRect->right = nRight; + pBoundRect->bottom = nBottom; + bFirstClipRect = false; + } + else + { + if(rectangle.Left() < pBoundRect->left) + { + pBoundRect->left = static_cast<int>(rectangle.Left()); + } + + if(rectangle.Top() < pBoundRect->top) + { + pBoundRect->top = static_cast<int>(rectangle.Top()); + } + + if(nRight > pBoundRect->right) + { + pBoundRect->right = static_cast<int>(nRight); + } + + if(nBottom > pBoundRect->bottom) + { + pBoundRect->bottom = static_cast<int>(nBottom); + } + } + + pNextClipRect->left = static_cast<int>(rectangle.Left()); + pNextClipRect->top = static_cast<int>(rectangle.Top()); + pNextClipRect->right = static_cast<int>(nRight); + pNextClipRect->bottom = static_cast<int>(nBottom); + pNextClipRect++; + } + else + { + mrParent.mpClipRgnData->rdh.nCount--; + mrParent.mpClipRgnData->rdh.nRgnSize -= sizeof( RECT ); + } + } + + // create clip region from ClipRgnData + if(0 == mrParent.mpClipRgnData->rdh.nCount) + { + // #i123585# region is empty; this may happen when e.g. a tools::PolyPolygon is given + // that contains no polygons or only empty ones (no width/height). This is + // perfectly fine and we are done, except setting it (see end of method) + } + else if(1 == mrParent.mpClipRgnData->rdh.nCount) + { + RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound); + mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, + pRect->right, pRect->bottom ); + } + else if(mrParent.mpClipRgnData->rdh.nCount > 1) + { + sal_uLong nSize = mrParent.mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER); + mrParent.mhRegion = ExtCreateRegion( nullptr, nSize, mrParent.mpClipRgnData ); + + // if ExtCreateRegion(...) is not supported + if( !mrParent.mhRegion ) + { + RGNDATAHEADER const & pHeader = mrParent.mpClipRgnData->rdh; + + if( pHeader.nCount ) + { + RECT* pRect = reinterpret_cast<RECT*>(mrParent.mpClipRgnData->Buffer); + mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom ); + pRect++; + + for( sal_uLong n = 1; n < pHeader.nCount; n++, pRect++ ) + { + ScopedHRGN hRgn(CreateRectRgn(pRect->left, pRect->top, pRect->right, pRect->bottom)); + CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn.get(), RGN_OR ); + } + } + } + + if ( mrParent.mpClipRgnData != mrParent.mpStdClipRgnData ) + delete [] reinterpret_cast<BYTE*>(mrParent.mpClipRgnData); + } + } + + if( mrParent.mhRegion ) + { + SelectClipRgn( mrParent.getHDC(), mrParent.mhRegion ); + + // debug code if you want to check range of the newly applied ClipRegion + //RECT aBound; + //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound); + + //bool bBla = true; + } + else + { + // #i123585# See above, this is a valid case, execute it + SelectClipRgn( mrParent.getHDC(), nullptr ); + } +} + +void WinSalGraphicsImpl::SetLineColor() +{ + ResetPen(GetStockPen(NULL_PEN)); + + // set new data + mbPen = false; + mbStockPen = true; +} + +void WinSalGraphicsImpl::SetLineColor(Color nColor) +{ + COLORREF nPenColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + bool bStockPen = false; + + HPEN hNewPen = SearchStockPen(nPenColor); + if (hNewPen) + bStockPen = true; + else + hNewPen = MakePen(nColor); + + ResetPen(hNewPen); + + // set new data + mnPenColor = nPenColor; + maLineColor = nColor; + mbPen = true; + mbStockPen = bStockPen; +} + +HPEN WinSalGraphicsImpl::SearchStockPen(COLORREF nPenColor) +{ + // Only screen, because printer has problems, when we use stock objects. + if (!mrParent.isPrinter()) + { + const SalData* pSalData = GetSalData(); + + for (sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++) + { + if (nPenColor == pSalData->maStockPenColorAry[i]) + return pSalData->mhStockPenAry[i]; + } + } + + return nullptr; +} + +HPEN WinSalGraphicsImpl::MakePen(Color nColor) +{ + COLORREF nPenColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + + if (!mrParent.isPrinter()) + { + if (GetSalData()->mhDitherPal && ImplIsSysColorEntry(nColor)) + { + nPenColor = PALRGB_TO_RGB(nPenColor); + } + } + + return CreatePen(PS_SOLID, mrParent.mnPenWidth, nPenColor); +} + +void WinSalGraphicsImpl::ResetPen(HPEN hNewPen) +{ + HPEN hOldPen = SelectPen(mrParent.getHDC(), hNewPen); + + if (mhPen) + { + if (!mbStockPen) + { + DeletePen(mhPen); + } + } + else + { + mrParent.mhDefPen = hOldPen; + } + + mhPen = hNewPen; +} + +void WinSalGraphicsImpl::SetFillColor() +{ + ResetBrush(GetStockBrush(NULL_BRUSH)); + + // set new data + mbBrush = false; + mbStockBrush = true; +} + +void WinSalGraphicsImpl::SetFillColor(Color nColor) +{ + COLORREF nBrushColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + bool bStockBrush = false; + + HBRUSH hNewBrush = SearchStockBrush(nBrushColor); + if (hNewBrush) + bStockBrush = true; + else + hNewBrush = MakeBrush(nColor); + + ResetBrush(hNewBrush); + + // set new data + mnBrushColor = nBrushColor; + maFillColor = nColor; + mbBrush = true; + mbStockBrush = bStockBrush; +} + +HBRUSH WinSalGraphicsImpl::SearchStockBrush(COLORREF nBrushColor) +{ + // Only screen, because printer has problems, when we use stock objects. + if (!mrParent.isPrinter()) + { + const SalData* pSalData = GetSalData(); + + for (sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++) + { + if (nBrushColor == pSalData->maStockBrushColorAry[i]) + return pSalData->mhStockBrushAry[i]; + } + } + + return nullptr; +} + +namespace +{ + +BYTE GetDitherMappingValue(BYTE nVal, BYTE nThres, const SalData* pSalData) +{ + return (pSalData->mpDitherDiff[nVal] > nThres) ? + pSalData->mpDitherHigh[nVal] : pSalData->mpDitherLow[nVal]; +} + +HBRUSH Make16BitDIBPatternBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + + static const BYTE aOrdDither16Bit[8][8] = + { + { 0, 6, 1, 7, 0, 6, 1, 7 }, + { 4, 2, 5, 3, 4, 2, 5, 3 }, + { 1, 7, 0, 6, 1, 7, 0, 6 }, + { 5, 3, 4, 2, 5, 3, 4, 2 }, + { 0, 6, 1, 7, 0, 6, 1, 7 }, + { 4, 2, 5, 3, 4, 2, 5, 3 }, + { 1, 7, 0, 6, 1, 7, 0, 6 }, + { 5, 3, 4, 2, 5, 3, 4, 2 } + }; + + BYTE* pTmp = pSalData->mpDitherDIBData; + + for(int nY = 0; nY < 8; ++nY) + { + for(int nX = 0; nX < 8; ++nX) + { + const BYTE nThres = aOrdDither16Bit[nY][nX]; + *pTmp++ = GetDitherMappingValue(nBlue, nThres, pSalData); + *pTmp++ = GetDitherMappingValue(nGreen, nThres, pSalData); + *pTmp++ = GetDitherMappingValue(nRed, nThres, pSalData); + } + } + + return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_RGB_COLORS); +} + +HBRUSH Make8BitDIBPatternBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + + static const BYTE aOrdDither8Bit[8][8] = + { + { 0, 38, 9, 48, 2, 40, 12, 50 }, + { 25, 12, 35, 22, 28, 15, 37, 24 }, + { 6, 44, 3, 41, 8, 47, 5, 44 }, + { 32, 19, 28, 16, 34, 21, 31, 18 }, + { 1, 40, 11, 49, 0, 39, 10, 48 }, + { 27, 14, 36, 24, 26, 13, 36, 23 }, + { 8, 46, 4, 43, 7, 45, 4, 42 }, + { 33, 20, 30, 17, 32, 20, 29, 16 } + }; + + BYTE* pTmp = pSalData->mpDitherDIBData; + + for (int nY = 0; nY < 8; ++nY) + { + for (int nX = 0; nX < 8; ++nX) + { + const BYTE nThres = aOrdDither8Bit[nY][nX]; + *pTmp = GetDitherMappingValue(nRed, nThres, pSalData) + + GetDitherMappingValue(nGreen, nThres, pSalData) * 6 + + GetDitherMappingValue(nBlue, nThres, pSalData) * 36; + pTmp++; + } + } + + return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_PAL_COLORS); +} + +} // namespace + +HBRUSH WinSalGraphicsImpl::MakeBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + const COLORREF nBrushColor = PALETTERGB(nRed, nGreen, nBlue); + + if (mrParent.isPrinter() || !pSalData->mhDitherDIB) + return CreateSolidBrush(nBrushColor); + + if (24 == reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB)->biBitCount) + return Make16BitDIBPatternBrush(nColor); + + if (ImplIsSysColorEntry(nColor)) + return CreateSolidBrush(PALRGB_TO_RGB(nBrushColor)); + + if (ImplIsPaletteEntry(nRed, nGreen, nBlue)) + return CreateSolidBrush(nBrushColor); + + return Make8BitDIBPatternBrush(nColor); +} + +void WinSalGraphicsImpl::ResetBrush(HBRUSH hNewBrush) +{ + HBRUSH hOldBrush = SelectBrush(mrParent.getHDC(), hNewBrush); + + if (mhBrush) + { + if (!mbStockBrush) + { + DeleteBrush(mhBrush); + } + } + else + { + mrParent.mhDefBrush = hOldBrush; + } + + mhBrush = hNewBrush; +} + +void WinSalGraphicsImpl::SetXORMode( bool bSet, bool ) +{ + mbXORMode = bSet; + ::SetROP2( mrParent.getHDC(), bSet ? R2_XORPEN : R2_COPYPEN ); +} + +void WinSalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor ) +{ + SetLineColor( ImplGetROPColor( nROPColor ) ); +} + +void WinSalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor ) +{ + SetFillColor( ImplGetROPColor( nROPColor ) ); +} + +void WinSalGraphicsImpl::DrawPixelImpl( tools::Long nX, tools::Long nY, COLORREF crColor ) +{ + const HDC hDC = mrParent.getHDC(); + + if (!mbXORMode) + { + SetPixel(hDC, static_cast<int>(nX), static_cast<int>(nY), crColor); + return; + } + + ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(crColor)); + PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT); +} + +void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY ) +{ + DrawPixelImpl( nX, nY, mnPenColor ); +} + +void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor ) +{ + COLORREF nCol = PALETTERGB( nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue() ); + + if ( !mrParent.isPrinter() && + GetSalData()->mhDitherPal && + ImplIsSysColorEntry( nColor ) ) + nCol = PALRGB_TO_RGB( nCol ); + + DrawPixelImpl( nX, nY, nCol ); +} + +void WinSalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) +{ + MoveToEx( mrParent.getHDC(), static_cast<int>(nX1), static_cast<int>(nY1), nullptr ); + + LineTo( mrParent.getHDC(), static_cast<int>(nX2), static_cast<int>(nY2) ); + + // LineTo doesn't draw the last pixel + if ( !mrParent.isPrinter() ) + DrawPixelImpl( nX2, nY2, mnPenColor ); +} + +void WinSalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + if ( !mbPen ) + { + if ( !mrParent.isPrinter() ) + { + PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight), + mbXORMode ? PATINVERT : PATCOPY ); + } + else + { + RECT aWinRect; + aWinRect.left = nX; + aWinRect.top = nY; + aWinRect.right = nX+nWidth; + aWinRect.bottom = nY+nHeight; + ::FillRect( mrParent.getHDC(), &aWinRect, mhBrush ); + } + } + else + Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) ); +} + +void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) +{ + std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]); + for (sal_uInt32 i=0; i<nPoints; ++i) + pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) }; + + // for Windows 95 and its maximum number of points + if ( !Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polyline( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); + + // Polyline seems to uses LineTo, which doesn't paint the last pixel (see 87eb8f8ee) + if ( !mrParent.isPrinter() ) + DrawPixelImpl( pWinPtAry[nPoints-1].x, pWinPtAry[nPoints-1].y, mnPenColor ); +} + +void WinSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) +{ + std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]); + for (sal_uInt32 i=0; i<nPoints; ++i) + pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) }; + + // for Windows 95 and its maximum number of points + if ( !Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polygon( mrParent.getHDC(), pWinPtAry.get(), MAX_64KSALPOINTS ); +} + +void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, + const Point** pPtAry ) +{ + UINT aWinPointAry[SAL_POLYPOLYCOUNT_STACKBUF]; + UINT* pWinPointAry; + UINT nPolyPolyPoints = 0; + UINT nPoints; + UINT i; + + if ( nPoly <= SAL_POLYPOLYCOUNT_STACKBUF ) + pWinPointAry = aWinPointAry; + else + pWinPointAry = new UINT[nPoly]; + + for ( i = 0; i < static_cast<UINT>(nPoly); i++ ) + { + nPoints = static_cast<UINT>(pPoints[i])+1; + pWinPointAry[i] = nPoints; + nPolyPolyPoints += nPoints; + } + + POINT aWinPointAryAry[SAL_POLYPOLYPOINTS_STACKBUF]; + POINT* pWinPointAryAry; + if ( nPolyPolyPoints <= SAL_POLYPOLYPOINTS_STACKBUF ) + pWinPointAryAry = aWinPointAryAry; + else + pWinPointAryAry = new POINT[nPolyPolyPoints]; + UINT n = 0; + for ( i = 0; i < static_cast<UINT>(nPoly); i++ ) + { + nPoints = pWinPointAry[i]; + const Point* pPolyAry = pPtAry[i]; + for (sal_uInt32 j=0; j<nPoints-1; ++j) + pWinPointAryAry[n+j] = POINT { static_cast<LONG>(pPolyAry[j].getX()), static_cast<LONG>(pPolyAry[j].getY()) }; + pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n]; + n += nPoints; + } + + if ( !PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), static_cast<UINT>(nPoly) ) && + (nPolyPolyPoints > MAX_64KSALPOINTS) ) + { + nPolyPolyPoints = 0; + nPoly = 0; + do + { + nPolyPolyPoints += pWinPointAry[static_cast<UINT>(nPoly)]; + nPoly++; + } + while ( nPolyPolyPoints < MAX_64KSALPOINTS ); + nPoly--; + if ( pWinPointAry[static_cast<UINT>(nPoly)] > MAX_64KSALPOINTS ) + pWinPointAry[static_cast<UINT>(nPoly)] = MAX_64KSALPOINTS; + if ( nPoly == 1 ) + Polygon( mrParent.getHDC(), pWinPointAryAry, *pWinPointAry ); + else + PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), nPoly ); + } + + if ( pWinPointAry != aWinPointAry ) + delete [] pWinPointAry; + if ( pWinPointAryAry != aWinPointAryAry ) + delete [] pWinPointAryAry; +} + +bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) +{ + // #100127# draw an array of points which might also contain bezier control points + if (!nPoints) + return true; + + const HDC hdc = mrParent.getHDC(); + + // TODO: profile whether the following options are faster: + // a) look ahead and draw consecutive bezier or line segments by PolyBezierTo/PolyLineTo resp. + // b) convert our flag array to window's and use PolyDraw + MoveToEx(hdc, static_cast<LONG>(pPtAry->getX()), static_cast<LONG>(pPtAry->getY()), nullptr); + ++pPtAry; + ++pFlgAry; + + for(sal_uInt32 i = 1; i < nPoints; ++i) + { + if(*pFlgAry != PolyFlags::Control) + { + LineTo(hdc, pPtAry->getX(), pPtAry->getY()); + } + else if(nPoints - i > 2) + { + POINT bezierPoints[] = { + POINT { static_cast<LONG>(pPtAry[0].getX()), static_cast<LONG>(pPtAry[0].getY()) }, + POINT { static_cast<LONG>(pPtAry[1].getX()), static_cast<LONG>(pPtAry[1].getY()) }, + POINT { static_cast<LONG>(pPtAry[2].getX()), static_cast<LONG>(pPtAry[2].getY()) }, + }; + PolyBezierTo(hdc, bezierPoints, 3); + i += 2; + pPtAry += 2; + pFlgAry += 2; + } + + ++pPtAry; + ++pFlgAry; + } + + return true; +} + +bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) +{ + POINT aStackAry1[SAL_POLY_STACKBUF]; + BYTE aStackAry2[SAL_POLY_STACKBUF]; + POINT* pWinPointAry; + BYTE* pWinFlagAry; + if( nPoints > SAL_POLY_STACKBUF ) + { + pWinPointAry = new POINT[ nPoints ]; + pWinFlagAry = new BYTE[ nPoints ]; + } + else + { + pWinPointAry = aStackAry1; + pWinFlagAry = aStackAry2; + } + + sal_uInt32 nPoints_i32(nPoints); + ImplPreparePolyDraw(true, 1, &nPoints_i32, &pPtAry, &pFlgAry, pWinPointAry, pWinFlagAry); + + bool bRet( false ); + + if( BeginPath( mrParent.getHDC() ) ) + { + PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nPoints); + + if( EndPath( mrParent.getHDC() ) ) + { + if( StrokeAndFillPath( mrParent.getHDC() ) ) + bRet = true; + } + } + + if( pWinPointAry != aStackAry1 ) + { + delete [] pWinPointAry; + delete [] pWinFlagAry; + } + + return bRet; +} + +bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, + const Point* const* pPtAry, const PolyFlags* const* pFlgAry ) +{ + sal_uLong nCurrPoly, nTotalPoints; + const sal_uInt32* pCurrPoints = pPoints; + for( nCurrPoly=0, nTotalPoints=0; nCurrPoly<nPoly; ++nCurrPoly ) + nTotalPoints += *pCurrPoints++; + + POINT aStackAry1[SAL_POLY_STACKBUF]; + BYTE aStackAry2[SAL_POLY_STACKBUF]; + POINT* pWinPointAry; + BYTE* pWinFlagAry; + if( nTotalPoints > SAL_POLY_STACKBUF ) + { + pWinPointAry = new POINT[ nTotalPoints ]; + pWinFlagAry = new BYTE[ nTotalPoints ]; + } + else + { + pWinPointAry = aStackAry1; + pWinFlagAry = aStackAry2; + } + + ImplPreparePolyDraw(true, nPoly, pPoints, pPtAry, pFlgAry, pWinPointAry, pWinFlagAry); + + bool bRet( false ); + + if( BeginPath( mrParent.getHDC() ) ) + { + PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nTotalPoints); + + if( EndPath( mrParent.getHDC() ) ) + { + if( StrokeAndFillPath( mrParent.getHDC() ) ) + bRet = true; + } + } + + if( pWinPointAry != aStackAry1 ) + { + delete [] pWinPointAry; + delete [] pWinFlagAry; + } + + return bRet; +} + +static basegfx::B2DPoint impPixelSnap( + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if(bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint( + bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + if(rObjectToDeviceInv.isIdentity()) + { + rObjectToDeviceInv = rObjectToDevice; + rObjectToDeviceInv.invert(); + } + + aSnappedPoint *= rObjectToDeviceInv; + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +static void impAddB2DPolygonToGDIPlusGraphicsPathReal( + Gdiplus::GraphicsPath& rGraphicsPath, + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + bool bNoLineJoin, + bool bPixelSnapHairline) +{ + sal_uInt32 nCount(rPolygon.count()); + + if(nCount) + { + const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1); + + if(nEdgeCount) + { + const bool bControls(rPolygon.areControlPointsUsed()); + basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0)); + basegfx::B2DHomMatrix aObjectToDeviceInv; + + if(bPixelSnapHairline) + { + aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0); + } + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nCount); + basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex)); + const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a)); + const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex)); + + if(bPixelSnapHairline) + { + aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex); + } + + if(b1stControlPointUsed || b2ndControlPointUsed) + { + basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a)); + basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex)); + + // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines + // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has + // no 1st or 2nd control point, despite that these are mathematically correct definitions + // (basegfx can handle that). + // Caution: This error (and it's correction) might be necessary for other graphical + // sub-systems in a similar way. + // tdf#101026 The 1st attempt to create a mathematically correct replacement control + // vector was wrong. Best alternative is one as close as possible which means short. + if(!b1stControlPointUsed) + { + aCa = aCurr + ((aCb - aCurr) * 0.0005); + } + else if(!b2ndControlPointUsed) + { + aCb = aNext + ((aCa - aNext) * 0.0005); + } + + rGraphicsPath.AddBezier( + static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), + static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()), + static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()), + static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); + } + else + { + rGraphicsPath.AddLine( + static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), + static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); + } + + if(a + 1 < nEdgeCount) + { + aCurr = aNext; + + if(bNoLineJoin) + { + rGraphicsPath.StartFigure(); + } + } + } + } + } +} + +namespace { + +class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData +{ +private: + // the path data itself + std::shared_ptr<Gdiplus::GraphicsPath> mpGraphicsPath; + + // all other values the triangulation is based on and + // need to be compared with to check for data validity + bool mbNoLineJoin; + std::vector< double > maStroke; + +public: + SystemDependentData_GraphicsPath( + std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath, + bool bNoLineJoin, + const std::vector< double >* pStroke); // MM01 + + // read access + std::shared_ptr<Gdiplus::GraphicsPath>& getGraphicsPath() { return mpGraphicsPath; } + bool getNoLineJoin() const { return mbNoLineJoin; } + const std::vector< double >& getStroke() const { return maStroke; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +} + +SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath( + std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath, + bool bNoLineJoin, + const std::vector< double >* pStroke) +: basegfx::SystemDependentData(Application::GetSystemDependentDataManager()), + mpGraphicsPath(rpGraphicsPath), + mbNoLineJoin(bNoLineJoin), + maStroke() +{ + if(nullptr != pStroke) + { + maStroke = *pStroke; + } +} + +sal_Int64 SystemDependentData_GraphicsPath::estimateUsageInBytes() const +{ + sal_Int64 nRetval(0); + + if(mpGraphicsPath) + { + const INT nPointCount(mpGraphicsPath->GetPointCount()); + + if(0 != nPointCount) + { + // Each point has + // - 2 x sizeof(Gdiplus::REAL) + // - 1 byte (see GetPathTypes in docu) + nRetval = nPointCount * ((2 * sizeof(Gdiplus::REAL)) + 1); + } + } + + return nRetval; +} + +void WinSalGraphicsImpl::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + const sal_uInt32 nCount(rPolyPolygon.count()); + + if(!mbBrush || 0 == nCount || fTransparency < 0.0 || fTransparency > 1.0) + { + return; + } + + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + const sal_uInt8 aTrans(sal_uInt8(255) - static_cast<sal_uInt8>(basegfx::fround(fTransparency * 255.0))); + const Gdiplus::Color aTestColor(aTrans, maFillColor.GetRed(), maFillColor.GetGreen(), maFillColor.GetBlue()); + const Gdiplus::SolidBrush aSolidBrush(aTestColor.GetValue()); + + // Set full (Object-to-Device) transformation - if used + if(rObjectToDevice.isIdentity()) + { + aGraphics.ResetTransform(); + } + else + { + Gdiplus::Matrix aMatrix; + + aMatrix.SetElements( + rObjectToDevice.get(0, 0), + rObjectToDevice.get(1, 0), + rObjectToDevice.get(0, 1), + rObjectToDevice.get(1, 1), + rObjectToDevice.get(0, 2), + rObjectToDevice.get(1, 2)); + aGraphics.SetTransform(&aMatrix); + } + + // prepare local instance of Gdiplus::GraphicsPath + std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath; + + // try to access buffered data + std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath( + rPolyPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>()); + + if(pSystemDependentData_GraphicsPath) + { + // copy buffered data + pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath(); + } + else + { + // Note: In principle we could use the same buffered geometry at line + // and fill polygons. Checked that in a first try, used + // GraphicsPath::AddPath from Gdiplus combined with below used + // StartFigure/CloseFigure, worked well (thus the line-draw version + // may create non-closed partial Polygon data). + // + // But in current reality it gets not used due to e.g. + // SdrPathPrimitive2D::create2DDecomposition creating transformed + // line and fill polygon-primitives (what could be changed). + // + // There will probably be more hindrances here in other rendering paths + // which could all be found - intention to do this would be: Use more + // transformations, less modifications of B2DPolygons/B2DPolyPolygons. + // + // A fix for SdrPathPrimitive2D would be to create the sub-geometry + // and embed into a TransformPrimitive2D containing the transformation. + // + // A 2nd problem is that the NoLineJoin mode (basegfx::B2DLineJoin::NONE + // && !bIsHairline) creates polygon fill infos that are not reusable + // for the fill case (see ::drawPolyLine below) - thus we would need a + // bool and/or two system-dependent paths buffered - doable, but complicated. + // + // All in all: Make B2DPolyPolygon a SystemDependentDataProvider and buffer + // the whole to-be-filled PolyPolygon independent from evtl. line-polygon + // (at least for now...) + + // create data + pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>(); + + for(sal_uInt32 a(0); a < nCount; a++) + { + if(0 != a) + { + // #i101491# not needed for first run + pGraphicsPath->StartFigure(); + } + + impAddB2DPolygonToGDIPlusGraphicsPathReal( + *pGraphicsPath, + rPolyPolygon.getB2DPolygon(a), + rObjectToDevice, // not used due to the two 'false' values below, but to not forget later + false, + false); + + pGraphicsPath->CloseFigure(); + } + + // add to buffering mechanism + rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>( + pGraphicsPath, + false, + nullptr); + } + + if(mrParent.getAntiAlias()) + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + else + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone); + } + + if(mrParent.isPrinter()) + { + // #i121591# + // Normally GdiPlus should not be used for printing at all since printers cannot + // print transparent filled polygon geometry and normally this does not happen + // since OutputDevice::RemoveTransparenciesFromMetaFile is used as preparation + // and no transparent parts should remain for printing. But this can be overridden + // by the user and thus happens. This call can only come (currently) from + // OutputDevice::DrawTransparent, see comments there with the same TaskID. + // If it is used, the mapping for the printer is wrong and needs to be corrected. I + // checked that there is *no* transformation set and estimated that a stable factor + // dependent of the printer's DPI is used. Create and set a transformation here to + // correct this. + const Gdiplus::REAL aDpiX(aGraphics.GetDpiX()); + const Gdiplus::REAL aDpiY(aGraphics.GetDpiY()); + + // Now the transformation maybe/is already used (see above), so do + // modify it without resetting to not destroy it. + // I double-checked with MS docu that Gdiplus::MatrixOrderAppend does what + // we need - in our notation, would be a multiply from left to execute + // current transform first and this scale last. + // I tried to trigger this code using Print from the menu and various + // targets, but got no hit, thus maybe obsolete anyways. If someone knows + // more, feel free to remove it. + // One more hint: This *may* also be needed now in ::drawPolyLine below + // since it also uses transformations now. + // + // aGraphics.ResetTransform(); + + aGraphics.ScaleTransform( + Gdiplus::REAL(100.0) / aDpiX, + Gdiplus::REAL(100.0) / aDpiY, + Gdiplus::MatrixOrderAppend); + } + + // use created or buffered data + aGraphics.FillPath( + &aSolidBrush, + &(*pGraphicsPath)); +} + +bool WinSalGraphicsImpl::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + // MM01 check done for simple reasons + if(!mbPen || !rPolygon.count() || fTransparency < 0.0 || fTransparency > 1.0) + { + return true; + } + + // need to check/handle LineWidth when ObjectToDevice transformation is used + const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity()); + const bool bIsHairline(fLineWidth == 0); + + // tdf#124848 calculate-back logical LineWidth for a hairline + // since this implementation hands over the transformation to + // the graphic sub-system + if(bIsHairline) + { + fLineWidth = 1.0; + + if(!bObjectToDeviceIsIdentity) + { + basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice); + aObjectToDeviceInv.invert(); + fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength(); + } + } + + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) )); + const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue()); + Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(fLineWidth)); + bool bNoLineJoin(false); + + // Set full (Object-to-Device) transformation - if used + if(bObjectToDeviceIsIdentity) + { + aGraphics.ResetTransform(); + } + else + { + Gdiplus::Matrix aMatrix; + + aMatrix.SetElements( + rObjectToDevice.get(0, 0), + rObjectToDevice.get(1, 0), + rObjectToDevice.get(0, 1), + rObjectToDevice.get(1, 1), + rObjectToDevice.get(0, 2), + rObjectToDevice.get(1, 2)); + aGraphics.SetTransform(&aMatrix); + } + + switch(eLineJoin) + { + case basegfx::B2DLineJoin::NONE : + { + if(!bIsHairline) + { + bNoLineJoin = true; + } + break; + } + case basegfx::B2DLineJoin::Bevel : + { + aPen.SetLineJoin(Gdiplus::LineJoinBevel); + break; + } + case basegfx::B2DLineJoin::Miter : + { + const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0)); + + aPen.SetMiterLimit(aMiterLimit); + // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional + // graphics, somewhere clipped in some distance from the edge point, dependent + // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use + // that instead + aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped); + break; + } + case basegfx::B2DLineJoin::Round : + { + aPen.SetLineJoin(Gdiplus::LineJoinRound); + break; + } + } + + switch(eLineCap) + { + default: /*css::drawing::LineCap_BUTT*/ + { + // nothing to do + break; + } + case css::drawing::LineCap_ROUND: + { + aPen.SetStartCap(Gdiplus::LineCapRound); + aPen.SetEndCap(Gdiplus::LineCapRound); + break; + } + case css::drawing::LineCap_SQUARE: + { + aPen.SetStartCap(Gdiplus::LineCapSquare); + aPen.SetEndCap(Gdiplus::LineCapSquare); + break; + } + } + + // prepare local instance of Gdiplus::GraphicsPath + std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath; + + // try to access buffered data + std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath( + rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>()); + + // MM01 need to do line dashing as fallback stuff here now + const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); + const bool bStrokeUsed(0.0 != fDotDashLength); + assert(!bStrokeUsed || (bStrokeUsed && pStroke)); + + // MM01 decide if to stroke directly + static bool bDoDirectGDIPlusStroke(true); + + // activate to stroke directly + if(bDoDirectGDIPlusStroke && bStrokeUsed) + { + // tdf#124848 the fix of tdf#130478 that was needed here before + // gets much easier when already handling the hairline case above, + // the back-calculated logical linewidth is already here, just use it. + // Still be careful - a zero LineWidth *should* not happen, but... + std::vector<Gdiplus::REAL> aDashArray(pStroke->size()); + const double fFactor(fLineWidth == 0 ? 1.0 : 1.0 / fLineWidth); + + // tdf#134128. ODF adds caps to the dashes and dots, but GDI makes caps from the + // dash or dot themselves. We tweak aDashArray to look the same in GDI (e.g. Impress edit mode) + // and other renders (e.g. Impress slide show), while keeping the total length of the + // pattern. + // Patterns are always a sequence dash space dash space ... + if (eLineCap != css::drawing::LineCap_BUTT) + { + size_t nSize = pStroke->size(); + // We want to treat dash and space in pairs. There should be no odd size. If so, we ignore + // last item. + nSize /= 2; + for(size_t a(0); a < nSize; a++) + { + double fDashLengthRel = (*pStroke)[2 * a] * fFactor; + double fSpaceLengthRel = (*pStroke)[2 * a + 1] * fFactor; + // GDI allows only positive lengths for space, Skia negative lengths too. Thus the + // appearance is different, in case space is too small. + double fCorrect = fSpaceLengthRel - 1.0 <= 0 ? fSpaceLengthRel - 0.01 : 1.0; + aDashArray[2 * a] = Gdiplus::REAL(fDashLengthRel + fCorrect); + aDashArray[2 * a + 1] = Gdiplus::REAL(fSpaceLengthRel - fCorrect); + } + } + else + { + for(size_t a(0); a < pStroke->size(); a++) + { + aDashArray[a] = Gdiplus::REAL((*pStroke)[a] * fFactor); + } + } + if (eLineCap == css::drawing::LineCap_ROUND) + aPen.SetDashCap(Gdiplus::DashCapRound); + else + aPen.SetDashCap(Gdiplus::DashCapFlat); // "square" doesn't exist in Gdiplus + aPen.SetDashOffset(Gdiplus::REAL(0.0)); + aPen.SetDashPattern(aDashArray.data(), aDashArray.size()); + } + + if(!bDoDirectGDIPlusStroke && pSystemDependentData_GraphicsPath) + { + // MM01 - check on stroke change. Used against not used, or if oth used, + // equal or different? Triangulation geometry creation depends heavily + // on stroke, independent of being transformation independent + const bool bStrokeWasUsed(!pSystemDependentData_GraphicsPath->getStroke().empty()); + + if(bStrokeWasUsed != bStrokeUsed + || (bStrokeUsed && *pStroke != pSystemDependentData_GraphicsPath->getStroke())) + { + // data invalid, forget + pSystemDependentData_GraphicsPath.reset(); + } + } + + if(pSystemDependentData_GraphicsPath) + { + // check data validity + if (pSystemDependentData_GraphicsPath->getNoLineJoin() != bNoLineJoin + || bPixelSnapHairline /*tdf#124700*/) + { + // data invalid, forget + pSystemDependentData_GraphicsPath.reset(); + } + } + + if(pSystemDependentData_GraphicsPath) + { + // copy buffered data + pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath(); + } + else + { + // fill data of buffered data + pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>(); + + if(!bDoDirectGDIPlusStroke && bStrokeUsed) + { + // MM01 need to do line dashing as fallback stuff here now + basegfx::B2DPolyPolygon aPolyPolygonLine; + + // apply LineStyle + basegfx::utils::applyLineDashing( + rPolygon, // source + *pStroke, // pattern + &aPolyPolygonLine, // target for lines + nullptr, // target for gaps + fDotDashLength); // full length if available + + // MM01 checked/verified, ok + for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++) + { + const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a)); + pGraphicsPath->StartFigure(); + impAddB2DPolygonToGDIPlusGraphicsPathReal( + *pGraphicsPath, + aPolyLine, + rObjectToDevice, + bNoLineJoin, + bPixelSnapHairline); + } + } + else + { + // no line dashing or direct stroke, just copy + impAddB2DPolygonToGDIPlusGraphicsPathReal( + *pGraphicsPath, + rPolygon, + rObjectToDevice, + bNoLineJoin, + bPixelSnapHairline); + + if(rPolygon.isClosed() && !bNoLineJoin) + { + // #i101491# needed to create the correct line joins + pGraphicsPath->CloseFigure(); + } + } + + // add to buffering mechanism + if (!bPixelSnapHairline /*tdf#124700*/) + { + rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>( + pGraphicsPath, + bNoLineJoin, + pStroke); + } + } + + if(mrParent.getAntiAlias()) + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + else + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone); + } + + if(mrParent.isPrinter()) + { + // tdf#122384 As mentioned above in WinSalGraphicsImpl::drawPolyPolygon + // (look for 'One more hint: This *may* also be needed now in'...). + // See comments in same spot above *urgently* before doing changes here, + // these comments are *still fully valid* at this place (!) + const Gdiplus::REAL aDpiX(aGraphics.GetDpiX()); + const Gdiplus::REAL aDpiY(aGraphics.GetDpiY()); + + aGraphics.ScaleTransform( + Gdiplus::REAL(100.0) / aDpiX, + Gdiplus::REAL(100.0) / aDpiY, + Gdiplus::MatrixOrderAppend); + } + + aGraphics.DrawPath( + &aPen, + &(*pGraphicsPath)); + + return true; +} + +static void paintToGdiPlus( + Gdiplus::Graphics& rGraphics, + const SalTwoRect& rTR, + Gdiplus::Bitmap& rBitmap) +{ + // only parts of source are used + Gdiplus::PointF aDestPoints[3]; + Gdiplus::ImageAttributes aAttributes; + + // define target region as parallelogram + aDestPoints[0].X = Gdiplus::REAL(rTR.mnDestX); + aDestPoints[0].Y = Gdiplus::REAL(rTR.mnDestY); + aDestPoints[1].X = Gdiplus::REAL(rTR.mnDestX + rTR.mnDestWidth); + aDestPoints[1].Y = Gdiplus::REAL(rTR.mnDestY); + aDestPoints[2].X = Gdiplus::REAL(rTR.mnDestX); + aDestPoints[2].Y = Gdiplus::REAL(rTR.mnDestY + rTR.mnDestHeight); + + aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY); + + rGraphics.DrawImage( + &rBitmap, + aDestPoints, + 3, + Gdiplus::REAL(rTR.mnSrcX), + Gdiplus::REAL(rTR.mnSrcY), + Gdiplus::REAL(rTR.mnSrcWidth), + Gdiplus::REAL(rTR.mnSrcHeight), + Gdiplus::UnitPixel, + &aAttributes); +} + +static void setInterpolationMode( + Gdiplus::Graphics& rGraphics, + tools::Long rSrcWidth, + tools::Long rDestWidth, + tools::Long rSrcHeight, + tools::Long rDestHeight) +{ + const bool bSameWidth(rSrcWidth == rDestWidth); + const bool bSameHeight(rSrcHeight == rDestHeight); + + if(bSameWidth && bSameHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeInvalid); + } + else if(rDestWidth > rSrcWidth && rDestHeight > rSrcHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault); + } + else if(rDestWidth < rSrcWidth && rDestHeight < rSrcHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeBicubic); + } + else + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault); + } +} + +bool WinSalGraphicsImpl::TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap) +{ + if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight) + { + assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap()); + + if(aARGB) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + + setInterpolationMode( + aGraphics, + rTR.mnSrcWidth, + rTR.mnDestWidth, + rTR.mnSrcHeight, + rTR.mnDestHeight); + + paintToGdiPlus( + aGraphics, + rTR, + *aARGB); + + return true; + } + } + + return false; +} + +bool WinSalGraphicsImpl::blendBitmap( + const SalTwoRect&, + const SalBitmap&) +{ + return false; +} + +bool WinSalGraphicsImpl::blendAlphaBitmap( + const SalTwoRect&, + const SalBitmap&, + const SalBitmap&, + const SalBitmap&) +{ + return false; +} + +bool WinSalGraphicsImpl::drawAlphaBitmap( + const SalTwoRect& rTR, + const SalBitmap& rSrcBitmap, + const SalBitmap& rAlphaBmp) +{ + if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight) + { + assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap)); + assert(dynamic_cast<const WinSalBitmap*>(&rAlphaBmp)); + + const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap); + const WinSalBitmap& rSalAlpha = static_cast< const WinSalBitmap& >(rAlphaBmp); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(&rSalAlpha)); + + if(aARGB) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + + setInterpolationMode( + aGraphics, + rTR.mnSrcWidth, + rTR.mnDestWidth, + rTR.mnSrcHeight, + rTR.mnDestHeight); + + paintToGdiPlus( + aGraphics, + rTR, + *aARGB); + + return true; + } + } + + return false; +} + +bool WinSalGraphicsImpl::drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, + double fAlpha) +{ + assert(dynamic_cast<const WinSalBitmap*>(&rSourceBitmap)); + assert(!pAlphaBitmap || dynamic_cast<const WinSalBitmap*>(pAlphaBitmap)); + + if( fAlpha != 1.0 ) + return false; + + const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSourceBitmap); + const WinSalBitmap* pSalAlpha = static_cast< const WinSalBitmap* >(pAlphaBitmap); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(pSalAlpha)); + + if(aARGB) + { + const tools::Long nSrcWidth(aARGB->GetWidth()); + const tools::Long nSrcHeight(aARGB->GetHeight()); + + if(nSrcWidth && nSrcHeight) + { + const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength())); + const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength())); + + if(nDestWidth && nDestHeight) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + Gdiplus::PointF aDestPoints[3]; + Gdiplus::ImageAttributes aAttributes; + + setInterpolationMode( + aGraphics, + nSrcWidth, + nDestWidth, + nSrcHeight, + nDestHeight); + + // this mode is only capable of drawing the whole bitmap to a parallelogram + aDestPoints[0].X = Gdiplus::REAL(rNull.getX()); + aDestPoints[0].Y = Gdiplus::REAL(rNull.getY()); + aDestPoints[1].X = Gdiplus::REAL(rX.getX()); + aDestPoints[1].Y = Gdiplus::REAL(rX.getY()); + aDestPoints[2].X = Gdiplus::REAL(rY.getX()); + aDestPoints[2].Y = Gdiplus::REAL(rY.getY()); + + aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY); + + aGraphics.DrawImage( + aARGB.get(), + aDestPoints, + 3, + Gdiplus::REAL(0.0), + Gdiplus::REAL(0.0), + Gdiplus::REAL(nSrcWidth), + Gdiplus::REAL(nSrcHeight), + Gdiplus::UnitPixel, + &aAttributes); + } + } + + return true; + } + + return false; +} + +bool WinSalGraphicsImpl::hasFastDrawTransformedBitmap() const +{ + return false; +} + +bool WinSalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/, + const Gradient& /*rGradient*/) +{ + return false; +} + +bool WinSalGraphicsImpl::implDrawGradient(basegfx::B2DPolyPolygon const & /*rPolyPolygon*/, + SalGradient const & /*rGradient*/) +{ + return false; +} + +bool WinSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const +{ + bool bRet = false; + + switch (eType) + { + case OutDevSupportType::TransparentRect: + bRet = mrParent.mbVirDev || mrParent.mbWindow; + break; + default: + break; + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/gdiimpl.hxx b/vcl/win/gdi/gdiimpl.hxx new file mode 100644 index 0000000000..9d4fa32233 --- /dev/null +++ b/vcl/win/gdi/gdiimpl.hxx @@ -0,0 +1,253 @@ +/* -*- 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 . + */ + +#pragma once + +#include <salgdiimpl.hxx> +#include <tools/long.hxx> +#include <win/salgdi.h> +#include <win/wingdiimpl.hxx> + +#include <vcl/gradient.hxx> + +#include <svsys.h> +#include <ControlCacheKey.hxx> + +class WinSalGraphics; + +class WinSalGraphicsImpl : public SalGraphicsImpl, public WinSalGraphicsImplBase +{ +private: + + WinSalGraphics& mrParent; + bool mbXORMode : 1; // _every_ output with RasterOp XOR + bool mbPen : 1; // is Pen (FALSE == NULL_PEN) + HPEN mhPen; // Pen + bool mbStockPen : 1; // is Pen a stockpen + bool mbBrush : 1; // is Brush (FALSE == NULL_BRUSH) + bool mbStockBrush : 1; // is Brush a stockbrush + HBRUSH mhBrush; // Brush + COLORREF mnPenColor; // PenColor + COLORREF mnBrushColor; // BrushColor + + // remember RGB values for SetLineColor/SetFillColor + Color maLineColor; + Color maFillColor; + + bool TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap); + void DrawPixelImpl(tools::Long nX, tools::Long nY, COLORREF crColor); + + HPEN SearchStockPen(COLORREF nPenColor); + HPEN MakePen(Color nColor); + void ResetPen(HPEN hNewPen); + + HBRUSH SearchStockBrush(COLORREF nBrushColor); + HBRUSH MakeBrush(Color nColor); + void ResetBrush(HBRUSH hNewBrush); +public: + + explicit WinSalGraphicsImpl(WinSalGraphics& rParent); + + virtual ~WinSalGraphicsImpl() override; + + virtual void Init() override; + + virtual void freeResources() override; + + virtual OUString getRenderBackendName() const override { return "gdi"; } + + virtual void setClipRegion( const vcl::Region& ) override; + // + // get the depth of the device + virtual sal_uInt16 GetBitCount() const override; + + // get the width of the device + virtual tools::Long GetGraphicsWidth() const override; + + // set the clip region to empty + virtual void ResetClipRegion() override; + + // set the line color to transparent (= don't draw lines) + + virtual void SetLineColor() override; + + // set the line color to a specific color + virtual void SetLineColor( Color nColor ) override; + + // set the fill color to transparent (= don't fill) + virtual void SetFillColor() override; + + // set the fill color to a specific color, shapes will be + // filled accordingly + virtual void SetFillColor( Color nColor ) override; + + // enable/disable XOR drawing + virtual void SetXORMode( bool bSet, bool bInvertOnly ) override; + + // set line color for raster operations + virtual void SetROPLineColor( SalROPColor nROPColor ) override; + + // set fill color for raster operations + virtual void SetROPFillColor( SalROPColor nROPColor ) override; + + // draw --> LineColor and FillColor and RasterOp and ClipRegion + virtual void drawPixel( tools::Long nX, tools::Long nY ) override; + virtual void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) override; + + virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) override; + + virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override; + + virtual void drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) override; + + virtual void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) override; + + virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry ) override; + + virtual void drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon&, + double fTransparency) override; + + virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon&, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin, + css::drawing::LineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; + + virtual bool drawPolyLineBezier( + sal_uInt32 nPoints, + const Point* pPtAry, + const PolyFlags* pFlgAry ) override; + + virtual bool drawPolygonBezier( + sal_uInt32 nPoints, + const Point* pPtAry, + const PolyFlags* pFlgAry ) override; + + virtual bool drawPolyPolygonBezier( + sal_uInt32 nPoly, + const sal_uInt32* pPoints, + const Point* const* pPtAry, + const PolyFlags* const* pFlgAry ) override; + + // CopyArea --> No RasterOp, but ClipRegion + virtual void copyArea( + tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, bool bWindowInvalidate ) override; + + // CopyBits and DrawBitmap --> RasterOp and ClipRegion + // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics + virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override; + + virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) override; + + virtual void drawBitmap( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rMaskBitmap ) override; + + virtual void drawMask( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + Color nMaskColor ) override; + + virtual std::shared_ptr<SalBitmap> getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override; + + virtual Color getPixel( tools::Long nX, tools::Long nY ) override; + + // invert --> ClipRegion (only Windows or VirDevs) + virtual void invert( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + SalInvert nFlags) override; + + virtual void invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags ) override; + + virtual bool drawEPS( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + void* pPtr, + sal_uInt32 nSize ) override; + + virtual bool blendBitmap( + const SalTwoRect&, + const SalBitmap& rBitmap ) override; + + virtual bool blendAlphaBitmap( + const SalTwoRect&, + const SalBitmap& rSrcBitmap, + const SalBitmap& rMaskBitmap, + const SalBitmap& rAlphaBitmap ) override; + + /** Render bitmap with alpha channel + + @param rSourceBitmap + Source bitmap to blit + + @param rAlphaBitmap + Alpha channel to use for blitting + + @return true, if the operation succeeded, and false + otherwise. In this case, clients should try to emulate alpha + compositing themselves + */ + virtual bool drawAlphaBitmap( + const SalTwoRect&, + const SalBitmap& rSourceBitmap, + const SalBitmap& rAlphaBitmap ) override; + + /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */ + virtual bool drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, + double fAlpha) override; + + virtual bool hasFastDrawTransformedBitmap() const override; + + /** Render solid rectangle with given transparency + + @param nTransparency + Transparency value (0-255) to use. 0 blits and opaque, 255 a + fully transparent rectangle + */ + virtual bool drawAlphaRect( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + sal_uInt8 nTransparency ) override; + + + virtual bool drawGradient(const tools::PolyPolygon& rPolygon, + const Gradient& rGradient) override; + virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, + SalGradient const & rGradient) override; + + virtual bool supportsOperation(OutDevSupportType eType) const override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salbmp.cxx b/vcl/win/gdi/salbmp.cxx new file mode 100644 index 0000000000..71c099e952 --- /dev/null +++ b/vcl/win/gdi/salbmp.cxx @@ -0,0 +1,910 @@ +/* + * 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 <svsys.h> +#include <vcl/bitmap.hxx> +#include <vcl/BitmapAccessMode.hxx> +#include <vcl/BitmapBuffer.hxx> +#include <vcl/BitmapPalette.hxx> +#include <vcl/ColorMask.hxx> +#include <vcl/Scanline.hxx> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <win/wincomp.hxx> +#include <win/salgdi.h> +#include <win/saldata.hxx> +#include <win/salbmp.h> +#include <string.h> +#include <vcl/timer.hxx> +#include <cppuhelper/basemutex.hxx> +#include <sal/log.hxx> +#include <tools/helpers.hxx> +#include <map> + +#include <prewin.h> +#include <gdiplus.h> +#include <postwin.h> + +#if defined _MSC_VER +#undef min +#undef max +#endif + +WinSalBitmap::WinSalBitmap() +: SalBitmap(), + basegfx::SystemDependentDataHolder(), + maSize(), + mhDIB(nullptr), + mhDDB(nullptr), + mnBitCount(0) +{ +} + +WinSalBitmap::~WinSalBitmap() +{ + Destroy(); +} + +void WinSalBitmap::Destroy() +{ + if( mhDIB ) + GlobalFree( mhDIB ); + else if( mhDDB ) + DeleteObject( mhDDB ); + + maSize = Size(); + mnBitCount = 0; +} + +namespace { + +class SystemDependentData_GdiPlusBitmap : public basegfx::SystemDependentData +{ +private: + std::shared_ptr<Gdiplus::Bitmap> mpGdiPlusBitmap; + const WinSalBitmap* mpAssociatedAlpha; + +public: + SystemDependentData_GdiPlusBitmap( + const std::shared_ptr<Gdiplus::Bitmap>& rGdiPlusBitmap, + const WinSalBitmap* pAssociatedAlpha); + + const WinSalBitmap* getAssociatedAlpha() const { return mpAssociatedAlpha; } + const std::shared_ptr<Gdiplus::Bitmap>& getGdiPlusBitmap() const { return mpGdiPlusBitmap; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +} + +SystemDependentData_GdiPlusBitmap::SystemDependentData_GdiPlusBitmap( + const std::shared_ptr<Gdiplus::Bitmap>& rGdiPlusBitmap, + const WinSalBitmap* pAssociatedAlpha) +: basegfx::SystemDependentData(Application::GetSystemDependentDataManager()), + mpGdiPlusBitmap(rGdiPlusBitmap), + mpAssociatedAlpha(pAssociatedAlpha) +{ +} + +sal_Int64 SystemDependentData_GdiPlusBitmap::estimateUsageInBytes() const +{ + sal_Int64 nRetval(0); + + if(mpGdiPlusBitmap) + { + const UINT nWidth(mpGdiPlusBitmap->GetWidth()); + const UINT nHeight(mpGdiPlusBitmap->GetHeight()); + + if(0 != nWidth && 0 != nHeight) + { + nRetval = nWidth * nHeight; + + switch(mpGdiPlusBitmap->GetPixelFormat()) + { + case PixelFormat1bppIndexed: + nRetval /= 8; + break; + case PixelFormat4bppIndexed: + nRetval /= 4; + break; + case PixelFormat16bppGrayScale: + case PixelFormat16bppRGB555: + case PixelFormat16bppRGB565: + case PixelFormat16bppARGB1555: + nRetval *= 2; + break; + case PixelFormat24bppRGB: + nRetval *= 3; + break; + case PixelFormat32bppRGB: + case PixelFormat32bppARGB: + case PixelFormat32bppPARGB: + case PixelFormat32bppCMYK: + nRetval *= 4; + break; + case PixelFormat48bppRGB: + nRetval *= 6; + break; + case PixelFormat64bppARGB: + case PixelFormat64bppPARGB: + nRetval *= 8; + break; + default: + case PixelFormat8bppIndexed: + break; + } + } + } + + return nRetval; +} + +std::shared_ptr< Gdiplus::Bitmap > WinSalBitmap::ImplGetGdiPlusBitmap(const WinSalBitmap* pAlphaSource) const +{ + std::shared_ptr< Gdiplus::Bitmap > aRetval; + + // try to access buffered data + std::shared_ptr<SystemDependentData_GdiPlusBitmap> pSystemDependentData_GdiPlusBitmap( + getSystemDependentData<SystemDependentData_GdiPlusBitmap>()); + + if(pSystemDependentData_GdiPlusBitmap) + { + // check data validity + if(pSystemDependentData_GdiPlusBitmap->getAssociatedAlpha() != pAlphaSource + || 0 == maSize.Width() + || 0 == maSize.Height()) + { + // #122350# if associated alpha with which the GDIPlus was constructed has changed + // it is necessary to remove it from buffer, reset reference to it and reconstruct + // data invalid, forget + pSystemDependentData_GdiPlusBitmap.reset(); + } + } + + if(pSystemDependentData_GdiPlusBitmap) + { + // use from buffer + aRetval = pSystemDependentData_GdiPlusBitmap->getGdiPlusBitmap(); + } + else if(!maSize.IsEmpty()) + { + // create and set data + const WinSalBitmap* pAssociatedAlpha(nullptr); + + if(pAlphaSource) + { + aRetval = const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap(*pAlphaSource); + pAssociatedAlpha = pAlphaSource; + } + else + { + aRetval = const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap(); + pAssociatedAlpha = nullptr; + } + + // add to buffering mechanism + addOrReplaceSystemDependentData<SystemDependentData_GdiPlusBitmap>( + aRetval, + pAssociatedAlpha); + } + + return aRetval; +} + +std::shared_ptr<Gdiplus::Bitmap> WinSalBitmap::ImplCreateGdiPlusBitmap() +{ + std::shared_ptr<Gdiplus::Bitmap> pRetval; + WinSalBitmap* pSalRGB = this; + std::unique_ptr<WinSalBitmap> pExtraWinSalRGB; + + if(!pSalRGB->ImplGethDIB()) + { + // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap + pExtraWinSalRGB.reset(new WinSalBitmap()); + pExtraWinSalRGB->Create(*pSalRGB, vcl::bitDepthToPixelFormat(pSalRGB->GetBitCount())); + pSalRGB = pExtraWinSalRGB.get(); + } + + BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(BitmapAccessMode::Read); + std::optional<BitmapBuffer> pExtraRGB; + + if(pRGB && ScanlineFormat::N24BitTcBgr != RemoveScanline(pRGB->mnFormat)) + { + // convert source bitmap to BMP_FORMAT_24BIT_TC_BGR format if not yet in that format + SalTwoRect aSalTwoRect(0, 0, pRGB->mnWidth, pRGB->mnHeight, 0, 0, pRGB->mnWidth, pRGB->mnHeight); + pExtraRGB = StretchAndConvert( + *pRGB, + aSalTwoRect, + ScanlineFormat::N24BitTcBgr); + + pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Write); + pRGB = pExtraRGB ? &*pExtraRGB : nullptr; + } + + if(pRGB + && pRGB->mnWidth > 0 + && pRGB->mnHeight > 0 + && ScanlineFormat::N24BitTcBgr == RemoveScanline(pRGB->mnFormat)) + { + const sal_uInt32 nW(pRGB->mnWidth); + const sal_uInt32 nH(pRGB->mnHeight); + + pRetval = std::make_shared<Gdiplus::Bitmap>(nW, nH, PixelFormat24bppRGB); + + if ( pRetval->GetLastStatus() == Gdiplus::Ok ) + { + sal_uInt8* pSrcRGB(pRGB->mpBits); + const sal_uInt32 nExtraRGB(pRGB->mnScanlineSize - (nW * 3)); + const bool bTopDown(pRGB->mnFormat & ScanlineFormat::TopDown); + const Gdiplus::Rect aAllRect(0, 0, nW, nH); + Gdiplus::BitmapData aGdiPlusBitmapData; + pRetval->LockBits(&aAllRect, Gdiplus::ImageLockModeWrite, PixelFormat24bppRGB, &aGdiPlusBitmapData); + + // copy data to Gdiplus::Bitmap; format is BGR here in both cases, so memcpy is possible + for(sal_uInt32 y(0); y < nH; y++) + { + const sal_uInt32 nYInsert(bTopDown ? y : nH - y - 1); + sal_uInt8* targetPixels = static_cast<sal_uInt8*>(aGdiPlusBitmapData.Scan0) + (nYInsert * aGdiPlusBitmapData.Stride); + + memcpy(targetPixels, pSrcRGB, nW * 3); + pSrcRGB += nW * 3 + nExtraRGB; + } + + pRetval->UnlockBits(&aGdiPlusBitmapData); + } + else + { + pRetval.reset(); + } + } + + if(pExtraRGB) + { + // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling + // in its destructor, this *has to be done by hand*. Doing it here now + delete[] pExtraRGB->mpBits; + pExtraRGB.reset(); + } + else + { + pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read); + } + + return pRetval; +} + +std::shared_ptr<Gdiplus::Bitmap> WinSalBitmap::ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource) +{ + std::shared_ptr<Gdiplus::Bitmap> pRetval; + WinSalBitmap* pSalRGB = this; + std::unique_ptr<WinSalBitmap> pExtraWinSalRGB; + + if(!pSalRGB->ImplGethDIB()) + { + // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap + pExtraWinSalRGB.reset(new WinSalBitmap()); + pExtraWinSalRGB->Create(*pSalRGB, vcl::bitDepthToPixelFormat(pSalRGB->GetBitCount())); + pSalRGB = pExtraWinSalRGB.get(); + } + + BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(BitmapAccessMode::Read); + std::optional<BitmapBuffer> pExtraRGB; + + if(pRGB && ScanlineFormat::N24BitTcBgr != RemoveScanline(pRGB->mnFormat)) + { + // convert source bitmap to canlineFormat::N24BitTcBgr format if not yet in that format + SalTwoRect aSalTwoRect(0, 0, pRGB->mnWidth, pRGB->mnHeight, 0, 0, pRGB->mnWidth, pRGB->mnHeight); + pExtraRGB = StretchAndConvert( + *pRGB, + aSalTwoRect, + ScanlineFormat::N24BitTcBgr); + + pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read); + pRGB = pExtraRGB ? &*pExtraRGB : nullptr; + } + + WinSalBitmap* pSalA = const_cast< WinSalBitmap* >(&rAlphaSource); + std::unique_ptr<WinSalBitmap> pExtraWinSalA; + + if(!pSalA->ImplGethDIB()) + { + // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap + pExtraWinSalA.reset(new WinSalBitmap()); + pExtraWinSalA->Create(*pSalA, vcl::bitDepthToPixelFormat(pSalA->GetBitCount())); + pSalA = pExtraWinSalA.get(); + } + + BitmapBuffer* pA = pSalA->AcquireBuffer(BitmapAccessMode::Read); + std::optional<BitmapBuffer> pExtraA; + + if(pA && ScanlineFormat::N8BitPal != RemoveScanline(pA->mnFormat)) + { + // convert alpha bitmap to ScanlineFormat::N8BitPal format if not yet in that format + SalTwoRect aSalTwoRect(0, 0, pA->mnWidth, pA->mnHeight, 0, 0, pA->mnWidth, pA->mnHeight); + const BitmapPalette& rTargetPalette = Bitmap::GetGreyPalette(256); + + pExtraA = StretchAndConvert( + *pA, + aSalTwoRect, + ScanlineFormat::N8BitPal, + rTargetPalette); + + pSalA->ReleaseBuffer(pA, BitmapAccessMode::Read); + pA = pExtraA ? &*pExtraA : nullptr; + } + + if(pRGB + && pA + && pRGB->mnWidth > 0 + && pRGB->mnHeight > 0 + && pRGB->mnWidth == pA->mnWidth + && pRGB->mnHeight == pA->mnHeight + && ScanlineFormat::N24BitTcBgr == RemoveScanline(pRGB->mnFormat) + && ScanlineFormat::N8BitPal == RemoveScanline(pA->mnFormat)) + { + // we have alpha and bitmap in known formats, create GdiPlus Bitmap as 32bit ARGB + const sal_uInt32 nW(pRGB->mnWidth); + const sal_uInt32 nH(pRGB->mnHeight); + + pRetval = std::make_shared<Gdiplus::Bitmap>(nW, nH, PixelFormat32bppARGB); + + if ( pRetval->GetLastStatus() == Gdiplus::Ok ) // 2nd place to secure with new Gdiplus::Bitmap + { + sal_uInt8* pSrcRGB(pRGB->mpBits); + sal_uInt8* pSrcA(pA->mpBits); + const sal_uInt32 nExtraRGB(pRGB->mnScanlineSize - (nW * 3)); + const sal_uInt32 nExtraA(pA->mnScanlineSize - nW); + const bool bTopDown(pRGB->mnFormat & ScanlineFormat::TopDown); + const Gdiplus::Rect aAllRect(0, 0, nW, nH); + Gdiplus::BitmapData aGdiPlusBitmapData; + pRetval->LockBits(&aAllRect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &aGdiPlusBitmapData); + + // copy data to Gdiplus::Bitmap; format is BGRA; need to mix BGR from Bitmap and + // A from alpha, so inner loop is needed (who invented BitmapEx..?) + for(sal_uInt32 y(0); y < nH; y++) + { + const sal_uInt32 nYInsert(bTopDown ? y : nH - y - 1); + sal_uInt8* targetPixels = static_cast<sal_uInt8*>(aGdiPlusBitmapData.Scan0) + (nYInsert * aGdiPlusBitmapData.Stride); + + for(sal_uInt32 x(0); x < nW; x++) + { + *targetPixels++ = *pSrcRGB++; + *targetPixels++ = *pSrcRGB++; + *targetPixels++ = *pSrcRGB++; + *targetPixels++ = *pSrcA++; + } + + pSrcRGB += nExtraRGB; + pSrcA += nExtraA; + } + + pRetval->UnlockBits(&aGdiPlusBitmapData); + } + else + { + pRetval.reset(); + } + } + + if(pExtraA) + { + // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling + // in its destructor, this *has to be done handish*. Doing it here now + delete[] pExtraA->mpBits; + pExtraA.reset(); + } + else + { + pSalA->ReleaseBuffer(pA, BitmapAccessMode::Read); + } + + pExtraWinSalA.reset(); + + if(pExtraRGB) + { + // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling + // in its destructor, this *has to be done by hand*. Doing it here now + delete[] pExtraRGB->mpBits; + pExtraRGB.reset(); + } + else + { + pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read); + } + + pExtraWinSalRGB.reset(); + + return pRetval; +} + +bool WinSalBitmap::Create( HANDLE hBitmap ) +{ + bool bRet = true; + + mhDDB = static_cast<HBITMAP>( hBitmap ); + + if( mhDIB ) + { + PBITMAPINFOHEADER pBIH = static_cast<PBITMAPINFOHEADER>(GlobalLock( mhDIB )); + + maSize = Size( pBIH->biWidth, pBIH->biHeight ); + mnBitCount = pBIH->biBitCount; + + if( mnBitCount ) + mnBitCount = ( mnBitCount <= 1 ) ? 1 : ( mnBitCount <= 4 ) ? 4 : ( mnBitCount <= 8 ) ? 8 : 24; + + GlobalUnlock( mhDIB ); + } + else if( mhDDB ) + { + BITMAP aDDBInfo; + + if( GetObjectW( mhDDB, sizeof( aDDBInfo ), &aDDBInfo ) ) + { + maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight ); + mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel; + + if( mnBitCount ) + { + mnBitCount = ( mnBitCount <= 1 ) ? 1 : + ( mnBitCount <= 4 ) ? 4 : + ( mnBitCount <= 8 ) ? 8 : 24; + } + } + else + { + mhDDB = nullptr; + bRet = false; + } + } + else + bRet = false; + + return bRet; +} + +bool WinSalBitmap::Create(const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal) +{ + bool bRet = false; + + mhDIB = ImplCreateDIB(rSize, ePixelFormat, rPal); + + if( mhDIB ) + { + maSize = rSize; + mnBitCount = vcl::pixelFormatBitCount(ePixelFormat); + bRet = true; + } + + return bRet; +} + +bool WinSalBitmap::Create( const SalBitmap& rSSalBitmap ) +{ + bool bRet = false; + const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap); + + if ( rSalBitmap.mhDIB || rSalBitmap.mhDDB ) + { + HANDLE hNewHdl = ImplCopyDIBOrDDB( rSalBitmap.mhDIB ? rSalBitmap.mhDIB : rSalBitmap.mhDDB, + rSalBitmap.mhDIB != nullptr ); + + if ( hNewHdl ) + { + if( rSalBitmap.mhDIB ) + mhDIB = static_cast<HGLOBAL>(hNewHdl); + else if( rSalBitmap.mhDDB ) + mhDDB = static_cast<HBITMAP>(hNewHdl); + + maSize = rSalBitmap.maSize; + mnBitCount = rSalBitmap.mnBitCount; + + bRet = true; + } + } + + return bRet; +} + +bool WinSalBitmap::Create( const SalBitmap& rSSalBmp, SalGraphics* pSGraphics ) +{ + bool bRet = false; + + const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp); + WinSalGraphics* pGraphics = static_cast<WinSalGraphics*>(pSGraphics); + + if( rSalBmp.mhDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( rSalBmp.mhDIB )); + HDC hDC = pGraphics->getHDC(); + HBITMAP hNewDDB; + BITMAP aDDBInfo; + PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + + ImplGetDIBColorCount( rSalBmp.mhDIB ) * sizeof( RGBQUAD ); + + if( pBI->bmiHeader.biBitCount == 1 ) + { + hNewDDB = CreateBitmap( pBI->bmiHeader.biWidth, pBI->bmiHeader.biHeight, 1, 1, nullptr ); + + if( hNewDDB ) + SetDIBits( hDC, hNewDDB, 0, pBI->bmiHeader.biHeight, pBits, pBI, DIB_RGB_COLORS ); + } + else + hNewDDB = CreateDIBitmap( hDC, &pBI->bmiHeader, CBM_INIT, pBits, pBI, DIB_RGB_COLORS ); + + GlobalUnlock( rSalBmp.mhDIB ); + + if( hNewDDB && GetObjectW( hNewDDB, sizeof( aDDBInfo ), &aDDBInfo ) ) + { + mhDDB = hNewDDB; + maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight ); + mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel; + + bRet = true; + } + else if( hNewDDB ) + DeleteObject( hNewDDB ); + } + + return bRet; +} + +bool WinSalBitmap::Create(const SalBitmap& rSSalBmp, vcl::PixelFormat eNewPixelFormat) +{ + bool bRet = false; + + const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp); + + if( rSalBmp.mhDDB ) + { + mhDIB = ImplCreateDIB( rSalBmp.maSize, eNewPixelFormat, BitmapPalette() ); + + if( mhDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB )); + const int nLines = static_cast<int>(rSalBmp.maSize.Height()); + HDC hDC = GetDC( nullptr ); + PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + + ImplGetDIBColorCount( mhDIB ) * sizeof( RGBQUAD ); + SalData* pSalData = GetSalData(); + HPALETTE hOldPal = nullptr; + + if ( pSalData->mhDitherPal ) + { + hOldPal = SelectPalette( hDC, pSalData->mhDitherPal, TRUE ); + RealizePalette( hDC ); + } + + if( GetDIBits( hDC, rSalBmp.mhDDB, 0, nLines, pBits, pBI, DIB_RGB_COLORS ) == nLines ) + { + GlobalUnlock( mhDIB ); + maSize = rSalBmp.maSize; + mnBitCount = vcl::pixelFormatBitCount(eNewPixelFormat); + bRet = true; + } + else + { + GlobalUnlock( mhDIB ); + GlobalFree( mhDIB ); + mhDIB = nullptr; + } + + if( hOldPal ) + SelectPalette( hDC, hOldPal, TRUE ); + + ReleaseDC( nullptr, hDC ); + } + } + + return bRet; +} + +bool WinSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas, Size& /*rSize*/, bool bMask ) +{ + css::uno::Reference< css::beans::XFastPropertySet > + xFastPropertySet( rBitmapCanvas, css::uno::UNO_QUERY ); + + if( xFastPropertySet ) { + css::uno::Sequence< css::uno::Any > args; + + if( xFastPropertySet->getFastPropertyValue(bMask ? 2 : 1) >>= args ) { + sal_Int64 aHBmp64; + + if( args[0] >>= aHBmp64 ) { + return Create( reinterpret_cast<HANDLE>(aHBmp64) ); + } + } + } + return false; +} + +sal_uInt16 WinSalBitmap::ImplGetDIBColorCount( HGLOBAL hDIB ) +{ + sal_uInt16 nColors = 0; + + if( hDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDIB )); + + if ( pBI->bmiHeader.biSize != sizeof( BITMAPCOREHEADER ) ) + { + if( pBI->bmiHeader.biBitCount <= 8 ) + { + if ( pBI->bmiHeader.biClrUsed ) + nColors = static_cast<sal_uInt16>(pBI->bmiHeader.biClrUsed); + else + nColors = 1 << pBI->bmiHeader.biBitCount; + } + } + else if( reinterpret_cast<PBITMAPCOREHEADER>(pBI)->bcBitCount <= 8 ) + nColors = 1 << reinterpret_cast<PBITMAPCOREHEADER>(pBI)->bcBitCount; + + GlobalUnlock( hDIB ); + } + + return nColors; +} + +HGLOBAL WinSalBitmap::ImplCreateDIB(const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal) +{ + HGLOBAL hDIB = nullptr; + + if( rSize.IsEmpty() ) + return hDIB; + + const auto nBits = vcl::pixelFormatBitCount(ePixelFormat); + + // calculate bitmap size in Bytes + const sal_uLong nAlignedWidth4Bytes = AlignedWidth4Bytes(nBits * rSize.Width()); + const sal_uLong nImageSize = nAlignedWidth4Bytes * rSize.Height(); + bool bOverflow = (nImageSize / nAlignedWidth4Bytes) != static_cast<sal_uLong>(rSize.Height()); + if( bOverflow ) + return hDIB; + + // allocate bitmap memory including header and palette + sal_uInt16 nColors = 0; + if (ePixelFormat <= vcl::PixelFormat::N8_BPP) + nColors = vcl::numberOfColors(ePixelFormat); + + const sal_uLong nHeaderSize = sizeof( BITMAPINFOHEADER ) + nColors * sizeof( RGBQUAD ); + bOverflow = (nHeaderSize + nImageSize) < nImageSize; + if( bOverflow ) + return hDIB; + + hDIB = GlobalAlloc( GHND, nHeaderSize + nImageSize ); + if( !hDIB ) + return hDIB; + + PBITMAPINFO pBI = static_cast<PBITMAPINFO>( GlobalLock( hDIB ) ); + PBITMAPINFOHEADER pBIH = reinterpret_cast<PBITMAPINFOHEADER>( pBI ); + + pBIH->biSize = sizeof( BITMAPINFOHEADER ); + pBIH->biWidth = rSize.Width(); + pBIH->biHeight = rSize.Height(); + pBIH->biPlanes = 1; + pBIH->biBitCount = nBits; + pBIH->biCompression = BI_RGB; + pBIH->biSizeImage = nImageSize; + pBIH->biXPelsPerMeter = 0; + pBIH->biYPelsPerMeter = 0; + pBIH->biClrUsed = 0; + pBIH->biClrImportant = 0; + + if( nColors ) + { + // copy the palette entries if any + const sal_uInt16 nMinCount = std::min( nColors, rPal.GetEntryCount() ); + if( nMinCount ) + memcpy( pBI->bmiColors, rPal.ImplGetColorBuffer(), nMinCount * sizeof(RGBQUAD) ); + } + + GlobalUnlock( hDIB ); + + return hDIB; +} + +HANDLE WinSalBitmap::ImplCopyDIBOrDDB( HANDLE hHdl, bool bDIB ) +{ + HANDLE hCopy = nullptr; + + if ( bDIB && hHdl ) + { + const sal_uLong nSize = GlobalSize( hHdl ); + + if ( (hCopy = GlobalAlloc( GHND, nSize )) != nullptr ) + { + memcpy( GlobalLock( hCopy ), GlobalLock( hHdl ), nSize ); + + GlobalUnlock( hCopy ); + GlobalUnlock( hHdl ); + } + } + else if ( hHdl ) + { + BITMAP aBmp; + + // find out size of source bitmap + GetObjectW( hHdl, sizeof( aBmp ), &aBmp ); + + // create destination bitmap + if ( (hCopy = CreateBitmapIndirect( &aBmp )) != nullptr ) + { + HDC hBmpDC = CreateCompatibleDC( nullptr ); + HBITMAP hBmpOld = static_cast<HBITMAP>(SelectObject( hBmpDC, hHdl )); + HDC hCopyDC = CreateCompatibleDC( hBmpDC ); + HBITMAP hCopyOld = static_cast<HBITMAP>(SelectObject( hCopyDC, hCopy )); + + BitBlt( hCopyDC, 0, 0, aBmp.bmWidth, aBmp.bmHeight, hBmpDC, 0, 0, SRCCOPY ); + + SelectObject( hCopyDC, hCopyOld ); + DeleteDC( hCopyDC ); + + SelectObject( hBmpDC, hBmpOld ); + DeleteDC( hBmpDC ); + } + } + + return hCopy; +} + +BitmapBuffer* WinSalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ ) +{ + std::unique_ptr<BitmapBuffer> pBuffer; + + if( mhDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB )); + PBITMAPINFOHEADER pBIH = &pBI->bmiHeader; + + if( pBIH->biPlanes == 1 ) + { + pBuffer.reset(new BitmapBuffer); + + pBuffer->mnFormat = pBIH->biBitCount == 1 ? ScanlineFormat::N1BitMsbPal : + pBIH->biBitCount == 8 ? ScanlineFormat::N8BitPal : + pBIH->biBitCount == 24 ? ScanlineFormat::N24BitTcBgr : + pBIH->biBitCount == 32 ? ScanlineFormat::N32BitTcMask : + ScanlineFormat::NONE; + + if( RemoveScanline( pBuffer->mnFormat ) != ScanlineFormat::NONE ) + { + pBuffer->mnWidth = maSize.Width(); + pBuffer->mnHeight = maSize.Height(); + pBuffer->mnScanlineSize = AlignedWidth4Bytes( maSize.Width() * pBIH->biBitCount ); + pBuffer->mnBitCount = static_cast<sal_uInt16>(pBIH->biBitCount); + + if( pBuffer->mnBitCount <= 8 ) + { + const sal_uInt16 nPalCount = ImplGetDIBColorCount( mhDIB ); + + pBuffer->maPalette.SetEntryCount( nPalCount ); + memcpy( pBuffer->maPalette.ImplGetColorBuffer(), pBI->bmiColors, nPalCount * sizeof( RGBQUAD ) ); + pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + nPalCount * sizeof( RGBQUAD ); + } + else if( ( pBIH->biBitCount == 16 ) || ( pBIH->biBitCount == 32 ) ) + { + sal_uLong nOffset = 0; + + if( pBIH->biCompression == BI_BITFIELDS ) + { + nOffset = 3 * sizeof( RGBQUAD ); + ColorMaskElement aRedMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 0 ])); + aRedMask.CalcMaskShift(); + ColorMaskElement aGreenMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 1 ])); + aGreenMask.CalcMaskShift(); + ColorMaskElement aBlueMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 2 ])); + aBlueMask.CalcMaskShift(); + pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask); + } + else if( pBIH->biBitCount == 16 ) + { + ColorMaskElement aRedMask(0x00007c00UL); + aRedMask.CalcMaskShift(); + ColorMaskElement aGreenMask(0x000003e0UL); + aGreenMask.CalcMaskShift(); + ColorMaskElement aBlueMask(0x0000001fUL); + aBlueMask.CalcMaskShift(); + pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask); + } + else + { + ColorMaskElement aRedMask(0x00ff0000UL); + aRedMask.CalcMaskShift(); + ColorMaskElement aGreenMask(0x0000ff00UL); + aGreenMask.CalcMaskShift(); + ColorMaskElement aBlueMask(0x000000ffUL); + aBlueMask.CalcMaskShift(); + pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask); + } + + pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + nOffset; + } + else + pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize; + } + else + { + GlobalUnlock( mhDIB ); + pBuffer.reset(); + } + } + else + GlobalUnlock( mhDIB ); + } + + return pBuffer.release(); +} + +void WinSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode ) +{ + if( pBuffer ) + { + if( mhDIB ) + { + if( nMode == BitmapAccessMode::Write && !!pBuffer->maPalette ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB )); + const sal_uInt16 nCount = pBuffer->maPalette.GetEntryCount(); + const sal_uInt16 nDIBColorCount = ImplGetDIBColorCount( mhDIB ); + memcpy( pBI->bmiColors, pBuffer->maPalette.ImplGetColorBuffer(), std::min( nDIBColorCount, nCount ) * sizeof( RGBQUAD ) ); + GlobalUnlock( mhDIB ); + } + + GlobalUnlock( mhDIB ); + } + + delete pBuffer; + } + if( nMode == BitmapAccessMode::Write ) + InvalidateChecksum(); +} + +bool WinSalBitmap::GetSystemData( BitmapSystemData& rData ) +{ + bool bRet = false; + if( mhDIB || mhDDB ) + { + bRet = true; + rData.pDIB = mhDIB; + const Size& rSize = GetSize (); + rData.mnWidth = rSize.Width(); + rData.mnHeight = rSize.Height(); + } + return bRet; +} + +bool WinSalBitmap::ScalingSupported() const +{ + return false; +} + +bool WinSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ ) +{ + return false; +} + +bool WinSalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ ) +{ + return false; +} + +const basegfx::SystemDependentDataHolder* WinSalBitmap::accessSystemDependentDataHolder() const +{ + return this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salfont.cxx b/vcl/win/gdi/salfont.cxx new file mode 100644 index 0000000000..51ddcce741 --- /dev/null +++ b/vcl/win/gdi/salfont.cxx @@ -0,0 +1,1377 @@ +/* -*- 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 <sal/config.h> + +#include <sal/types.h> +#include <config_folders.h> + +#include <algorithm> +#include <map> +#include <memory> +#include <mutex> +#include <set> +#include <string.h> +#include <string_view> +#include <svsys.h> +#include <vector> + +#include <dwrite_3.h> +// Currently, we build with _WIN32_WINNT=0x0601 (Windows 7), which means newer +// declarations in dwrite_3.h will not be visible. +#if WINVER < 0x0A00 +# include "dw-extra.h" +#endif + +#include <o3tl/lru_map.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <osl/file.hxx> +#include <osl/process.h> +#include <rtl/bootstrap.hxx> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <tools/helpers.hxx> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <unotools/fontcfg.hxx> +#include <vcl/settings.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/metric.hxx> +#include <vcl/fontcharmap.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/windowserrorstring.hxx> + +#include <font/FontSelectPattern.hxx> +#include <font/PhysicalFontCollection.hxx> +#include <font/PhysicalFontFaceCollection.hxx> +#include <font/PhysicalFontFace.hxx> +#include <font/fontsubstitution.hxx> +#include <sft.hxx> +#include <win/saldata.hxx> +#include <win/salgdi.h> +#include <win/winlayout.hxx> +#include <win/wingdiimpl.hxx> +#include <impfontcharmap.hxx> +#include <font/FontMetricData.hxx> +#include <impglyphitem.hxx> + +#if HAVE_FEATURE_SKIA +#include <vcl/skia/SkiaHelper.hxx> +#include <skia/win/font.hxx> +#endif + +using namespace vcl; + +static FIXED FixedFromDouble( double d ) +{ + const tools::Long l = static_cast<tools::Long>( d * 65536. ); + return *reinterpret_cast<FIXED const *>(&l); +} + +static int IntTimes256FromFixed(FIXED f) +{ + int nFixedTimes256 = (f.value << 8) + ((f.fract+0x80) >> 8); + return nFixedTimes256; +} + +// platform specific font substitution hooks for glyph fallback enhancement + +namespace { + +class WinPreMatchFontSubstititution +: public vcl::font::PreMatchFontSubstitution +{ +public: + bool FindFontSubstitute(vcl::font::FontSelectPattern&) const override; +}; + +class WinGlyphFallbackSubstititution +: public vcl::font::GlyphFallbackFontSubstitution +{ +public: + bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingChars) const override; +}; + +// does a font face hold the given missing characters? +bool HasMissingChars(vcl::font::PhysicalFontFace* pFace, OUString& rMissingChars) +{ + FontCharMapRef xFontCharMap = pFace->GetFontCharMap(); + + // avoid fonts with unknown CMAP subtables for glyph fallback + if( !xFontCharMap.is() || xFontCharMap->IsDefaultMap() ) + return false; + + int nMatchCount = 0; + std::vector<sal_UCS4> rRemainingCodes; + const sal_Int32 nStrLen = rMissingChars.getLength(); + sal_Int32 nStrIdx = 0; + while (nStrIdx < nStrLen) + { + const sal_UCS4 uChar = rMissingChars.iterateCodePoints( &nStrIdx ); + if (xFontCharMap->HasChar(uChar)) + nMatchCount++; + else + rRemainingCodes.push_back(uChar); + } + + xFontCharMap = nullptr; + + if (nMatchCount > 0) + rMissingChars = OUString(rRemainingCodes.data(), rRemainingCodes.size()); + + return nMatchCount > 0; +} + + //used by 2-level font fallback + vcl::font::PhysicalFontFamily* findDevFontListByLocale(const vcl::font::PhysicalFontCollection &rFontCollection, + const LanguageTag& rLanguageTag ) + { + // get the default font for a specified locale + const utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get(); + const OUString aDefault = rDefaults.getUserInterfaceFont(rLanguageTag); + return rFontCollection.FindFontFamilyByTokenNames(aDefault); + } +} + +// These are Win 3.1 bitmap fonts using "FON" font format +// which is not supported with DirectWrite so let's substitute them +// with a font that is supported and always available. +// Based on: +// https://dxr.mozilla.org/mozilla-esr10/source/gfx/thebes/gfxDWriteFontList.cpp#1057 +const std::map<OUString, OUString> aBitmapFontSubs = +{ + { "MS Sans Serif", "Microsoft Sans Serif" }, + { "MS Serif", "Times New Roman" }, + { "Small Fonts", "Arial" }, + { "Courier", "Courier New" }, + { "Roman", "Times New Roman" }, + { "Script", "Mistral" } +}; + +// TODO: See if Windows have API that we can use here to improve font fallback. +bool WinPreMatchFontSubstititution::FindFontSubstitute(vcl::font::FontSelectPattern& rFontSelData) const +{ + if (rFontSelData.IsMicrosoftSymbolEncoded() || IsOpenSymbol(rFontSelData.maSearchName)) + return false; + + for (const auto& aSub : aBitmapFontSubs) + { + if (rFontSelData.maSearchName == GetEnglishSearchFontName(aSub.first)) + { + rFontSelData.maSearchName = aSub.second; + return true; + } + } + + return false; +} + +// find a fallback font for missing characters +// TODO: should stylistic matches be searched and preferred? +bool WinGlyphFallbackSubstititution::FindFontSubstitute(vcl::font::FontSelectPattern& rFontSelData, LogicalFontInstance* /*pLogicalFont*/, OUString& rMissingChars) const +{ + // guess a locale matching to the missing chars + LanguageType eLang = rFontSelData.meLanguage; + LanguageTag aLanguageTag( eLang); + + // fall back to default UI locale if the font language is inconclusive + if( eLang == LANGUAGE_DONTKNOW ) + aLanguageTag = Application::GetSettings().GetUILanguageTag(); + + // first level fallback: + // try use the locale specific default fonts defined in VCL.xcu + const vcl::font::PhysicalFontCollection* pFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList.get(); + vcl::font::PhysicalFontFamily* pFontFamily = findDevFontListByLocale(*pFontCollection, aLanguageTag); + if( pFontFamily ) + { + vcl::font::PhysicalFontFace* pFace = pFontFamily->FindBestFontFace( rFontSelData ); + if( HasMissingChars( pFace, rMissingChars ) ) + { + rFontSelData.maSearchName = pFontFamily->GetSearchName(); + return true; + } + } + + // are the missing characters symbols? + pFontFamily = pFontCollection->FindFontFamilyByAttributes( ImplFontAttrs::Symbol, + rFontSelData.GetWeight(), + rFontSelData.GetWidthType(), + rFontSelData.GetItalic(), + rFontSelData.maSearchName ); + if( pFontFamily ) + { + vcl::font::PhysicalFontFace* pFace = pFontFamily->FindBestFontFace( rFontSelData ); + if( HasMissingChars( pFace, rMissingChars ) ) + { + rFontSelData.maSearchName = pFontFamily->GetSearchName(); + return true; + } + } + + // last level fallback, check each font type face one by one + std::unique_ptr<vcl::font::PhysicalFontFaceCollection> pTestFontList = pFontCollection->GetFontFaceCollection(); + // limit the count of fonts to be checked to prevent hangs + static const int MAX_GFBFONT_COUNT = 600; + int nTestFontCount = pTestFontList->Count(); + if( nTestFontCount > MAX_GFBFONT_COUNT ) + nTestFontCount = MAX_GFBFONT_COUNT; + + bool bFound = false; + for( int i = 0; i < nTestFontCount; ++i ) + { + vcl::font::PhysicalFontFace* pFace = pTestFontList->Get( i ); + bFound = HasMissingChars( pFace, rMissingChars ); + if( !bFound ) + continue; + rFontSelData.maSearchName = pFace->GetFamilyName(); + break; + } + + return bFound; +} + +namespace { + +struct ImplEnumInfo +{ + HDC mhDC; + vcl::font::PhysicalFontCollection* mpList; + OUString* mpName; + LOGFONTW* mpLogFont; + bool mbPrinter; + int mnFontCount; +}; + +} + +static rtl_TextEncoding ImplCharSetToSal( BYTE nCharSet ) +{ + rtl_TextEncoding eTextEncoding; + + if ( nCharSet == OEM_CHARSET ) + { + UINT nCP = static_cast<sal_uInt16>(GetOEMCP()); + switch ( nCP ) + { + // It is unclear why these two (undefined?) code page numbers are + // handled specially here: + case 1004: eTextEncoding = RTL_TEXTENCODING_MS_1252; break; + case 65400: eTextEncoding = RTL_TEXTENCODING_SYMBOL; break; + default: + eTextEncoding = rtl_getTextEncodingFromWindowsCodePage(nCP); + break; + } + } + else + { + if( nCharSet ) + eTextEncoding = rtl_getTextEncodingFromWindowsCharset( nCharSet ); + else + eTextEncoding = RTL_TEXTENCODING_UNICODE; + } + + return eTextEncoding; +} + +static FontFamily ImplFamilyToSal( BYTE nFamily ) +{ + switch ( nFamily & 0xF0 ) + { + case FF_DECORATIVE: + return FAMILY_DECORATIVE; + + case FF_MODERN: + return FAMILY_MODERN; + + case FF_ROMAN: + return FAMILY_ROMAN; + + case FF_SCRIPT: + return FAMILY_SCRIPT; + + case FF_SWISS: + return FAMILY_SWISS; + + default: + break; + } + + return FAMILY_DONTKNOW; +} + +static BYTE ImplFamilyToWin( FontFamily eFamily ) +{ + switch ( eFamily ) + { + case FAMILY_DECORATIVE: + return FF_DECORATIVE; + + case FAMILY_MODERN: + return FF_MODERN; + + case FAMILY_ROMAN: + return FF_ROMAN; + + case FAMILY_SCRIPT: + return FF_SCRIPT; + + case FAMILY_SWISS: + return FF_SWISS; + + case FAMILY_SYSTEM: + return FF_SWISS; + + default: + break; + } + + return FF_DONTCARE; +} + +static FontWeight ImplWeightToSal( int nWeight ) +{ + if ( nWeight <= FW_THIN ) + return WEIGHT_THIN; + else if ( nWeight <= FW_ULTRALIGHT ) + return WEIGHT_ULTRALIGHT; + else if ( nWeight <= FW_LIGHT ) + return WEIGHT_LIGHT; + else if ( nWeight < FW_MEDIUM ) + return WEIGHT_NORMAL; + else if ( nWeight == FW_MEDIUM ) + return WEIGHT_MEDIUM; + else if ( nWeight <= FW_SEMIBOLD ) + return WEIGHT_SEMIBOLD; + else if ( nWeight <= FW_BOLD ) + return WEIGHT_BOLD; + else if ( nWeight <= FW_ULTRABOLD ) + return WEIGHT_ULTRABOLD; + else + return WEIGHT_BLACK; +} + +static int ImplWeightToWin( FontWeight eWeight ) +{ + switch ( eWeight ) + { + case WEIGHT_THIN: + return FW_THIN; + + case WEIGHT_ULTRALIGHT: + return FW_ULTRALIGHT; + + case WEIGHT_LIGHT: + return FW_LIGHT; + + case WEIGHT_SEMILIGHT: + case WEIGHT_NORMAL: + return FW_NORMAL; + + case WEIGHT_MEDIUM: + return FW_MEDIUM; + + case WEIGHT_SEMIBOLD: + return FW_SEMIBOLD; + + case WEIGHT_BOLD: + return FW_BOLD; + + case WEIGHT_ULTRABOLD: + return FW_ULTRABOLD; + + case WEIGHT_BLACK: + return FW_BLACK; + + default: + break; + } + + return 0; +} + +static FontPitch ImplLogPitchToSal( BYTE nPitch ) +{ + if ( nPitch & FIXED_PITCH ) + return PITCH_FIXED; + else + return PITCH_VARIABLE; +} + +static FontPitch ImplMetricPitchToSal( BYTE nPitch ) +{ + // Grrrr! See NT help + if ( !(nPitch & TMPF_FIXED_PITCH) ) + return PITCH_FIXED; + else + return PITCH_VARIABLE; +} + +static BYTE ImplPitchToWin( FontPitch ePitch ) +{ + if ( ePitch == PITCH_FIXED ) + return FIXED_PITCH; + else if ( ePitch == PITCH_VARIABLE ) + return VARIABLE_PITCH; + else + return DEFAULT_PITCH; +} + +static FontAttributes WinFont2DevFontAttributes( const ENUMLOGFONTEXW& rEnumFont, + const NEWTEXTMETRICW& rMetric) +{ + FontAttributes aDFA; + + const LOGFONTW rLogFont = rEnumFont.elfLogFont; + + // get font face attributes + aDFA.SetFamilyType(ImplFamilyToSal( rLogFont.lfPitchAndFamily )); + aDFA.SetWidthType(WIDTH_DONTKNOW); + aDFA.SetWeight(ImplWeightToSal( rLogFont.lfWeight )); + aDFA.SetItalic((rLogFont.lfItalic) ? ITALIC_NORMAL : ITALIC_NONE); + aDFA.SetPitch(ImplLogPitchToSal( rLogFont.lfPitchAndFamily )); + aDFA.SetMicrosoftSymbolEncoded(rLogFont.lfCharSet == SYMBOL_CHARSET); + + // get the font face name + aDFA.SetFamilyName(OUString(o3tl::toU(rLogFont.lfFaceName))); + + // use the face's style name only if it looks reasonable + const wchar_t* pStyleName = rEnumFont.elfStyle; + const wchar_t* pEnd = pStyleName + sizeof(rEnumFont.elfStyle)/sizeof(*rEnumFont.elfStyle); + const wchar_t* p = pStyleName; + for(; *p && (p < pEnd); ++p ) + if( *p < 0x0020 ) + break; + if( p < pEnd ) + aDFA.SetStyleName(OUString(o3tl::toU(pStyleName))); + + // heuristics for font quality + // - opentypeTT > truetype + aDFA.SetQuality( 0 ); + if( rMetric.tmPitchAndFamily & TMPF_TRUETYPE ) + aDFA.IncreaseQualityBy( 50 ); + if( 0 != (rMetric.ntmFlags & (NTM_TT_OPENTYPE | NTM_PS_OPENTYPE)) ) + aDFA.IncreaseQualityBy( 10 ); + + // TODO: add alias names + return aDFA; +} + +void ImplSalLogFontToFontW( HDC hDC, const LOGFONTW& rLogFont, Font& rFont ) +{ + OUString aFontName( o3tl::toU(rLogFont.lfFaceName) ); + if (!aFontName.isEmpty()) + { + rFont.SetFamilyName( aFontName ); + rFont.SetCharSet( ImplCharSetToSal( rLogFont.lfCharSet ) ); + rFont.SetFamily( ImplFamilyToSal( rLogFont.lfPitchAndFamily ) ); + rFont.SetPitch( ImplLogPitchToSal( rLogFont.lfPitchAndFamily ) ); + rFont.SetWeight( ImplWeightToSal( rLogFont.lfWeight ) ); + + tools::Long nFontHeight = rLogFont.lfHeight; + if ( nFontHeight < 0 ) + nFontHeight = -nFontHeight; + tools::Long nDPIY = GetDeviceCaps( hDC, LOGPIXELSY ); + if( !nDPIY ) + nDPIY = 600; + nFontHeight *= 72; + nFontHeight += nDPIY/2; + nFontHeight /= nDPIY; + rFont.SetFontSize( Size( 0, nFontHeight ) ); + rFont.SetOrientation( Degree10(static_cast<sal_Int16>(rLogFont.lfEscapement)) ); + if ( rLogFont.lfItalic ) + rFont.SetItalic( ITALIC_NORMAL ); + else + rFont.SetItalic( ITALIC_NONE ); + if ( rLogFont.lfUnderline ) + rFont.SetUnderline( LINESTYLE_SINGLE ); + else + rFont.SetUnderline( LINESTYLE_NONE ); + if ( rLogFont.lfStrikeOut ) + rFont.SetStrikeout( STRIKEOUT_SINGLE ); + else + rFont.SetStrikeout( STRIKEOUT_NONE ); + } +} + +WinFontFace::WinFontFace(const ENUMLOGFONTEXW& rEnumFont, const NEWTEXTMETRICW& rMetric) +: vcl::font::PhysicalFontFace(WinFont2DevFontAttributes(rEnumFont, rMetric)), + mnId( 0 ), + meWinCharSet(rEnumFont.elfLogFont.lfCharSet), + mnPitchAndFamily(rMetric.tmPitchAndFamily), + maLogFont(rEnumFont.elfLogFont) +{ +} + +WinFontFace::~WinFontFace() +{ +} + +sal_IntPtr WinFontFace::GetFontId() const +{ + return mnId; +} + +rtl::Reference<LogicalFontInstance> WinFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + return new SkiaWinFontInstance(*this, rFSD); +#endif + return new WinFontInstance(*this, rFSD); +} + +const std::vector<hb_variation_t>& +WinFontFace::GetVariations(const LogicalFontInstance& rFont) const +{ + if (!mxVariations) + { + mxVariations.emplace(); + auto pDWFontFace = static_cast<const WinFontInstance&>(rFont).GetDWFontFace(); + if (pDWFontFace) + { + sal::systools::COMReference<IDWriteFontFace5> xDWFontFace5; + auto hr = pDWFontFace->QueryInterface(__uuidof(IDWriteFontFace5), + reinterpret_cast<void**>(&xDWFontFace5)); + if (SUCCEEDED(hr) && xDWFontFace5->HasVariations()) + { + std::vector<DWRITE_FONT_AXIS_VALUE> aAxisValues( + xDWFontFace5->GetFontAxisValueCount()); + hr = xDWFontFace5->GetFontAxisValues(aAxisValues.data(), aAxisValues.size()); + if (SUCCEEDED(hr)) + { + mxVariations->reserve(aAxisValues.size()); + for (auto& rAxisValue : aAxisValues) + mxVariations->push_back( + { OSL_NETDWORD(rAxisValue.axisTag), rAxisValue.value }); + } + } + } + } + + return *mxVariations; +} + +namespace +{ +struct BlobReference +{ + hb_blob_t* mpBlob; + BlobReference(hb_blob_t* pBlob) + : mpBlob(pBlob) + { + hb_blob_reference(mpBlob); + } + BlobReference(BlobReference&& other) noexcept + : mpBlob(other.mpBlob) + { + other.mpBlob = nullptr; + } + BlobReference& operator=(BlobReference&& other) + { + std::swap(mpBlob, other.mpBlob); + return *this; + } + BlobReference(const BlobReference& other) = delete; + BlobReference& operator=(BlobReference& other) = delete; + ~BlobReference() { hb_blob_destroy(mpBlob); } +}; +} + +using BlobCacheKey = std::pair<sal_IntPtr, hb_tag_t>; + +namespace +{ +struct BlobCacheKeyHash +{ + std::size_t operator()(BlobCacheKey const& rKey) const + { + std::size_t seed = 0; + o3tl::hash_combine(seed, rKey.first); + o3tl::hash_combine(seed, rKey.second); + return seed; + } +}; +} + +hb_blob_t* WinFontFace::GetHbTable(hb_tag_t nTag) const +{ + static o3tl::lru_map<BlobCacheKey, BlobReference, BlobCacheKeyHash> gCache(50); + BlobCacheKey aCacheKey{ GetFontId(), nTag }; + auto it = gCache.find(aCacheKey); + if (it != gCache.end()) + { + hb_blob_reference(it->second.mpBlob); + return it->second.mpBlob; + } + + sal_uLong nLength = 0; + unsigned char* pBuffer = nullptr; + + HDC hDC(::GetDC(nullptr)); + HFONT hFont = ::CreateFontIndirectW(&maLogFont); + HFONT hOldFont = ::SelectFont(hDC, hFont); + + nLength = ::GetFontData(hDC, OSL_NETDWORD(nTag), 0, nullptr, 0); + if (nLength > 0 && nLength != GDI_ERROR) + { + pBuffer = new unsigned char[nLength]; + ::GetFontData(hDC, OSL_NETDWORD(nTag), 0, pBuffer, nLength); + } + + ::SelectFont(hDC, hOldFont); + ::DeleteFont(hFont); + ::ReleaseDC(nullptr, hDC); + + hb_blob_t* pBlob = nullptr; + + if (pBuffer) + pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY, + pBuffer, [](void* data) { delete[] static_cast<unsigned char*>(data); }); + + gCache.insert({ aCacheKey, BlobReference(pBlob) }); + return pBlob; +} + +void WinSalGraphics::SetTextColor( Color nColor ) +{ + COLORREF aCol = PALETTERGB( nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue() ); + + if( !mbPrinter && + GetSalData()->mhDitherPal && + ImplIsSysColorEntry( nColor ) ) + { + aCol = PALRGB_TO_RGB( aCol ); + } + + ::SetTextColor( getHDC(), aCol ); +} + +static int CALLBACK SalEnumQueryFontProcExW( const LOGFONTW*, const TEXTMETRICW*, DWORD, LPARAM lParam ) +{ + *reinterpret_cast<bool*>(lParam) = true; + return 0; +} + +void ImplGetLogFontFromFontSelect( const vcl::font::FontSelectPattern& rFont, + const vcl::font::PhysicalFontFace* pFontFace, + LOGFONTW& rLogFont ) +{ + OUString aName; + if (pFontFace) + aName = pFontFace->GetFamilyName(); + else + aName = rFont.GetFamilyName().getToken( 0, ';' ); + + UINT nNameLen = aName.getLength(); + if (nNameLen >= LF_FACESIZE) + nNameLen = LF_FACESIZE - 1; + memcpy( rLogFont.lfFaceName, aName.getStr(), nNameLen*sizeof( wchar_t ) ); + rLogFont.lfFaceName[nNameLen] = 0; + + if (pFontFace) + { + const WinFontFace* pWinFontData = static_cast<const WinFontFace*>(pFontFace); + rLogFont.lfCharSet = pWinFontData->GetCharSet(); + rLogFont.lfPitchAndFamily = pWinFontData->GetPitchAndFamily(); + } + else + { + rLogFont.lfCharSet = rFont.IsMicrosoftSymbolEncoded() ? SYMBOL_CHARSET : DEFAULT_CHARSET; + rLogFont.lfPitchAndFamily = ImplPitchToWin( rFont.GetPitch() ) + | ImplFamilyToWin( rFont.GetFamilyType() ); + } + + rLogFont.lfWeight = ImplWeightToWin( rFont.GetWeight() ); + rLogFont.lfHeight = static_cast<LONG>(-rFont.mnHeight); + rLogFont.lfWidth = static_cast<LONG>(rFont.mnWidth); + rLogFont.lfUnderline = 0; + rLogFont.lfStrikeOut = 0; + rLogFont.lfItalic = BYTE(rFont.GetItalic() != ITALIC_NONE); + rLogFont.lfEscapement = rFont.mnOrientation.get(); + rLogFont.lfOrientation = rLogFont.lfEscapement; + rLogFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + rLogFont.lfQuality = DEFAULT_QUALITY; + rLogFont.lfOutPrecision = OUT_TT_PRECIS; + if ( rFont.mnOrientation ) + rLogFont.lfClipPrecision |= CLIP_LH_ANGLES; + + // disable antialiasing if requested + if ( rFont.mbNonAntialiased ) + rLogFont.lfQuality = NONANTIALIASED_QUALITY; + +} + +std::tuple<HFONT,bool,sal_Int32> WinSalGraphics::ImplDoSetFont(HDC hDC, vcl::font::FontSelectPattern const & i_rFont, + const vcl::font::PhysicalFontFace * i_pFontFace, + HFONT& o_rOldFont) +{ + HFONT hNewFont = nullptr; + + LOGFONTW aLogFont; + ImplGetLogFontFromFontSelect( i_rFont, i_pFontFace, aLogFont ); + + bool bIsCJKVerticalFont = false; + // select vertical mode for printing if requested and available + if ( i_rFont.mbVertical && mbPrinter ) + { + constexpr size_t nLen = sizeof(aLogFont.lfFaceName) - sizeof(aLogFont.lfFaceName[0]); + // vertical fonts start with an '@' + memmove( &aLogFont.lfFaceName[1], &aLogFont.lfFaceName[0], nLen ); + aLogFont.lfFaceName[0] = '@'; + aLogFont.lfFaceName[LF_FACESIZE - 1] = 0; + + // check availability of vertical mode for this font + EnumFontFamiliesExW( getHDC(), &aLogFont, SalEnumQueryFontProcExW, + reinterpret_cast<LPARAM>(&bIsCJKVerticalFont), 0 ); + if( !bIsCJKVerticalFont ) + { + // restore non-vertical name if not vertical mode isn't available + memcpy( &aLogFont.lfFaceName[0], &aLogFont.lfFaceName[1], nLen ); + aLogFont.lfFaceName[LF_FACESIZE - 1] = 0; + } + } + + hNewFont = ::CreateFontIndirectW( &aLogFont ); + + HDC hdcScreen = nullptr; + if( mbVirDev ) + // only required for virtual devices, see below for details + hdcScreen = GetDC(nullptr); + if( hdcScreen ) + { + // select font into screen hdc first to get an antialiased font + // and instantly restore the default font! + // see knowledge base article 305290: + // "PRB: Fonts Not Drawn Antialiased on Device Context for DirectDraw Surface" + SelectFont( hdcScreen, SelectFont( hdcScreen , hNewFont ) ); + } + o_rOldFont = ::SelectFont(hDC, hNewFont); + + TEXTMETRICW aTextMetricW; + if (!::GetTextMetricsW(hDC, &aTextMetricW)) + { + // the selected font doesn't work => try a replacement + // TODO: use its font fallback instead + lstrcpynW( aLogFont.lfFaceName, L"Courier New", 12 ); + aLogFont.lfPitchAndFamily = FIXED_PITCH; + HFONT hNewFont2 = CreateFontIndirectW( &aLogFont ); + SelectFont(hDC, hNewFont2); + DeleteFont( hNewFont ); + hNewFont = hNewFont2; + bIsCJKVerticalFont = false; + } + + if( hdcScreen ) + ::ReleaseDC( nullptr, hdcScreen ); + + return std::make_tuple(hNewFont, bIsCJKVerticalFont, static_cast<sal_Int32>(aTextMetricW.tmDescent)); +} + +void WinSalGraphics::SetFont(LogicalFontInstance* pFont, int nFallbackLevel) +{ + assert(nFallbackLevel >= 0 && nFallbackLevel < MAX_FALLBACK); + + // return early if there is no new font + if( !pFont ) + { + if (!mpWinFontEntry[nFallbackLevel].is()) + return; + + // DeInitGraphics doesn't free the cached fonts, so mhDefFont might be nullptr + if (mhDefFont) + { + ::SelectFont(getHDC(), mhDefFont); + mhDefFont = nullptr; + } + + // release no longer referenced font handles + for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i ) + mpWinFontEntry[i] = nullptr; + return; + } + + WinFontInstance *pFontInstance = static_cast<WinFontInstance*>(pFont); + mpWinFontEntry[ nFallbackLevel ] = pFontInstance; + + HFONT hOldFont = nullptr; + HFONT hNewFont = pFontInstance->GetHFONT(); + if (!hNewFont) + { + pFontInstance->SetGraphics(this); + hNewFont = pFontInstance->GetHFONT(); + } + hOldFont = ::SelectFont(getHDC(), hNewFont); + + // keep default font + if( !mhDefFont ) + mhDefFont = hOldFont; + else + { + // release no longer referenced font handles + for( int i = nFallbackLevel + 1; i < MAX_FALLBACK && mpWinFontEntry[i].is(); ++i ) + mpWinFontEntry[i] = nullptr; + } +} + +void WinSalGraphics::GetFontMetric( FontMetricDataRef& rxFontMetric, int nFallbackLevel ) +{ + // temporarily change the HDC to the font in the fallback level + rtl::Reference<WinFontInstance> pFontInstance = mpWinFontEntry[nFallbackLevel]; + const HFONT hOldFont = SelectFont(getHDC(), pFontInstance->GetHFONT()); + + wchar_t aFaceName[LF_FACESIZE+60]; + if( GetTextFaceW( getHDC(), SAL_N_ELEMENTS(aFaceName), aFaceName ) ) + rxFontMetric->SetFamilyName(OUString(o3tl::toU(aFaceName))); + + rxFontMetric->SetMinKashida(pFontInstance->GetKashidaWidth()); + rxFontMetric->ImplCalcLineSpacing(pFontInstance.get()); + rxFontMetric->ImplInitBaselines(pFontInstance.get()); + + // get the font metric + OUTLINETEXTMETRICW aOutlineMetric; + const bool bOK = GetOutlineTextMetricsW(getHDC(), sizeof(aOutlineMetric), &aOutlineMetric); + // restore the HDC to the font in the base level + SelectFont( getHDC(), hOldFont ); + if( !bOK ) + return; + + TEXTMETRICW aWinMetric = aOutlineMetric.otmTextMetrics; + + // device independent font attributes + rxFontMetric->SetFamilyType(ImplFamilyToSal( aWinMetric.tmPitchAndFamily )); + rxFontMetric->SetMicrosoftSymbolEncoded(aWinMetric.tmCharSet == SYMBOL_CHARSET); + rxFontMetric->SetWeight(ImplWeightToSal( aWinMetric.tmWeight )); + rxFontMetric->SetPitch(ImplMetricPitchToSal( aWinMetric.tmPitchAndFamily )); + rxFontMetric->SetItalic(aWinMetric.tmItalic ? ITALIC_NORMAL : ITALIC_NONE); + rxFontMetric->SetSlant( 0 ); + + // transformation dependent font metrics + rxFontMetric->SetWidth(aWinMetric.tmAveCharWidth); +} + +FontCharMapRef WinSalGraphics::GetFontCharMap() const +{ + if (!mpWinFontEntry[0]) + { + return FontCharMapRef( new FontCharMap() ); + } + return mpWinFontEntry[0]->GetFontFace()->GetFontCharMap(); +} + +bool WinSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + if (!mpWinFontEntry[0]) + return false; + return mpWinFontEntry[0]->GetFontFace()->GetFontCapabilities(rFontCapabilities); +} + +static int CALLBACK SalEnumFontsProcExW( const LOGFONTW* lpelfe, + const TEXTMETRICW* lpntme, + DWORD nFontType, LPARAM lParam ) +{ + ENUMLOGFONTEXW const * pLogFont + = reinterpret_cast<ENUMLOGFONTEXW const *>(lpelfe); + NEWTEXTMETRICEXW const * pMetric + = reinterpret_cast<NEWTEXTMETRICEXW const *>(lpntme); + ImplEnumInfo* pInfo = reinterpret_cast<ImplEnumInfo*>(lParam); + if ( !pInfo->mpName ) + { + // Ignore vertical fonts + if ( pLogFont->elfLogFont.lfFaceName[0] != '@' ) + { + OUString aName(o3tl::toU(pLogFont->elfLogFont.lfFaceName)); + pInfo->mpName = &aName; + memcpy(pInfo->mpLogFont->lfFaceName, pLogFont->elfLogFont.lfFaceName, (aName.getLength()+1)*sizeof(wchar_t)); + pInfo->mpLogFont->lfCharSet = pLogFont->elfLogFont.lfCharSet; + EnumFontFamiliesExW(pInfo->mhDC, pInfo->mpLogFont, SalEnumFontsProcExW, + reinterpret_cast<LPARAM>(pInfo), 0); + pInfo->mpLogFont->lfFaceName[0] = '\0'; + pInfo->mpLogFont->lfCharSet = DEFAULT_CHARSET; + pInfo->mpName = nullptr; + } + } + else + { + // Ignore non-device fonts on printers. + if (pInfo->mbPrinter) + { + if ((nFontType & RASTER_FONTTYPE) && !(nFontType & DEVICE_FONTTYPE)) + { + SAL_INFO("vcl.fonts", "Unsupported printer font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName))); + return 1; + } + } + // Only SFNT fonts are supported, ignore anything else. + else if (!(nFontType & TRUETYPE_FONTTYPE) && + !(pMetric->ntmTm.ntmFlags & NTM_PS_OPENTYPE) && + !(pMetric->ntmTm.ntmFlags & NTM_TT_OPENTYPE)) + { + SAL_INFO("vcl.fonts", "Unsupported font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName))); + return 1; + } + + rtl::Reference<WinFontFace> pData = new WinFontFace(*pLogFont, pMetric->ntmTm); + pData->SetFontId( sal_IntPtr( pInfo->mnFontCount++ ) ); + + pInfo->mpList->Add( pData.get() ); + SAL_INFO("vcl.fonts", "SalEnumFontsProcExW: font added: " << pData->GetFamilyName() << " " << pData->GetStyleName()); + } + + return 1; +} + +struct TempFontItem +{ + OUString maFontResourcePath; + TempFontItem* mpNextItem; +}; + +static int lcl_AddFontResource(SalData& rSalData, const OUString& rFontFileURL, bool bShared) +{ + OUString aFontSystemPath; + OSL_VERIFY(!osl::FileBase::getSystemPathFromFileURL(rFontFileURL, aFontSystemPath)); + + int nRet = AddFontResourceExW(o3tl::toW(aFontSystemPath.getStr()), FR_PRIVATE, nullptr); + SAL_WARN_IF(nRet <= 0, "vcl.fonts", "AddFontResourceExW failed for " << rFontFileURL); + if (nRet > 0) + { + TempFontItem* pNewItem = new TempFontItem; + pNewItem->maFontResourcePath = aFontSystemPath; + if (bShared) + { + pNewItem->mpNextItem = rSalData.mpSharedTempFontItem; + rSalData.mpSharedTempFontItem = pNewItem; + } + else + { + pNewItem->mpNextItem = rSalData.mpOtherTempFontItem; + rSalData.mpOtherTempFontItem = pNewItem; + } + } + return nRet; +} + +void ImplReleaseTempFonts(SalData& rSalData, bool bAll) +{ + while (TempFontItem* p = rSalData.mpOtherTempFontItem) + { + RemoveFontResourceExW(o3tl::toW(p->maFontResourcePath.getStr()), FR_PRIVATE, nullptr); + rSalData.mpOtherTempFontItem = p->mpNextItem; + delete p; + } + + if (!bAll) + return; + + while (TempFontItem* p = rSalData.mpSharedTempFontItem) + { + RemoveFontResourceExW(o3tl::toW(p->maFontResourcePath.getStr()), FR_PRIVATE, nullptr); + rSalData.mpSharedTempFontItem = p->mpNextItem; + delete p; + } +} + +static OUString lcl_GetFontFamilyName(std::u16string_view rFontFileURL) +{ + // Create temporary file name + OUString aTempFileURL; + if (osl::File::E_None != osl::File::createTempFile(nullptr, nullptr, &aTempFileURL)) + return OUString(); + osl::File::remove(aTempFileURL); + OUString aResSystemPath; + osl::FileBase::getSystemPathFromFileURL(aTempFileURL, aResSystemPath); + + // Create font resource file (.fot) + // There is a limit of 127 characters for the full path passed via lpszFile, so we have to + // split the font URL and pass it as two parameters. As a result we can't use + // CreateScalableFontResource for renaming, as it now expects the font in the system path. + // But it's still good to use it for family name extraction, we're currently after. + // BTW: it doesn't help to prefix the lpszFile with \\?\ to support larger paths. + // TODO: use TTLoadEmbeddedFont (needs an EOT as input, so we have to add a header to the TTF) + // TODO: forward the EOT from the AddTempDevFont call side, if VCL supports it + INetURLObject aTTFUrl(rFontFileURL); + // GetBase() strips the extension + OUString aFilename = aTTFUrl.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + if (!CreateScalableFontResourceW(0, o3tl::toW(aResSystemPath.getStr()), + o3tl::toW(aFilename.getStr()), o3tl::toW(aTTFUrl.GetPath().getStr()))) + { + sal_uInt32 nError = GetLastError(); + SAL_WARN("vcl.fonts", "CreateScalableFontResource failed for " << aResSystemPath << " " + << aFilename << " " << aTTFUrl.GetPath() << " " << nError); + return OUString(); + } + + // Open and read the font resource file + osl::File aFotFile(aTempFileURL); + if (osl::FileBase::E_None != aFotFile.open(osl_File_OpenFlag_Read)) + return OUString(); + + sal_uInt64 nBytesRead = 0; + char aBuffer[4096]; + aFotFile.read( aBuffer, sizeof( aBuffer ), nBytesRead ); + // clean up temporary resource file + aFotFile.close(); + osl::File::remove(aTempFileURL); + + // retrieve font family name from byte offset 0x4F6 + static const sal_uInt64 nNameOfs = 0x4F6; + sal_uInt64 nPos = nNameOfs; + for (; (nPos < nBytesRead) && (aBuffer[nPos] != 0); nPos++); + if (nPos >= nBytesRead || (nPos == nNameOfs)) + return OUString(); + + return OUString(aBuffer + nNameOfs, nPos - nNameOfs, osl_getThreadTextEncoding()); +} + +bool WinSalGraphics::AddTempDevFont(vcl::font::PhysicalFontCollection* pFontCollection, + const OUString& rFontFileURL, const OUString& rFontName) +{ + OUString aFontFamily = lcl_GetFontFamilyName(rFontFileURL); + if (aFontFamily.isEmpty()) + { + SAL_WARN("vcl.fonts", "error extracting font family from " << rFontFileURL); + return false; + } + + if (rFontName != aFontFamily) + { + SAL_WARN("vcl.fonts", "font family renaming not implemented; skipping embedded " << rFontName); + return false; + } + + int nFonts = lcl_AddFontResource(*GetSalData(), rFontFileURL, false); + if (nFonts <= 0) + return false; + + ImplEnumInfo aInfo; + aInfo.mhDC = getHDC(); + aInfo.mpList = pFontCollection; + aInfo.mpName = &aFontFamily; + aInfo.mbPrinter = mbPrinter; + aInfo.mnFontCount = pFontCollection->Count(); + const int nExpectedFontCount = aInfo.mnFontCount + nFonts; + + LOGFONTW aLogFont = {}; + aLogFont.lfCharSet = DEFAULT_CHARSET; + aInfo.mpLogFont = &aLogFont; + + // add the font to the PhysicalFontCollection + EnumFontFamiliesExW(getHDC(), &aLogFont, + SalEnumFontsProcExW, reinterpret_cast<LPARAM>(&aInfo), 0); + + SAL_WARN_IF(nExpectedFontCount != pFontCollection->Count(), "vcl.fonts", + "temp font was registered but is not in enumeration: " << rFontFileURL); + + return true; +} + +void WinSalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection ) +{ + // make sure all LO shared fonts are registered temporarily + static std::once_flag init; + std::call_once(init, []() + { + auto registerFontsIn = [](const OUString& dir) { + // collect fonts in font path that could not be registered + osl::Directory aFontDir(dir); + osl::FileBase::RC rcOSL = aFontDir.open(); + if (rcOSL == osl::FileBase::E_None) + { + osl::DirectoryItem aDirItem; + SalData* pSalData = GetSalData(); + assert(pSalData); + + while (aFontDir.getNextItem(aDirItem, 10) == osl::FileBase::E_None) + { + osl::FileStatus aFileStatus(osl_FileStatus_Mask_FileURL); + rcOSL = aDirItem.getFileStatus(aFileStatus); + if (rcOSL == osl::FileBase::E_None) + lcl_AddFontResource(*pSalData, aFileStatus.getFileURL(), true); + } + } + }; + + // determine font path + // since we are only interested in fonts that could not be + // registered before because of missing administration rights + // only the font path of the user installation is needed + OUString aPath("$BRAND_BASE_DIR"); + rtl_bootstrap_expandMacros(&aPath.pData); + + // internal font resources, required for normal operation, like OpenSymbol + registerFontsIn(aPath + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts"); + + // collect fonts in font path that could not be registered + registerFontsIn(aPath + "/" LIBO_SHARE_FOLDER "/fonts/truetype"); + + return true; + }); + + ImplEnumInfo aInfo; + aInfo.mhDC = getHDC(); + aInfo.mpList = pFontCollection; + aInfo.mpName = nullptr; + aInfo.mbPrinter = mbPrinter; + aInfo.mnFontCount = 0; + + LOGFONTW aLogFont = {}; + aLogFont.lfCharSet = DEFAULT_CHARSET; + aInfo.mpLogFont = &aLogFont; + + // fill the PhysicalFontCollection + EnumFontFamiliesExW( getHDC(), &aLogFont, + SalEnumFontsProcExW, reinterpret_cast<LPARAM>(&aInfo), 0 ); + + // set glyph fallback hook + static WinGlyphFallbackSubstititution aSubstFallback; + static WinPreMatchFontSubstititution aPreMatchFont; + pFontCollection->SetFallbackHook( &aSubstFallback ); + pFontCollection->SetPreMatchHook(&aPreMatchFont); +} + +void WinSalGraphics::ClearDevFontCache() +{ + mWinSalGraphicsImplBase->ClearDevFontCache(); + ImplReleaseTempFonts(*GetSalData(), false); +} + +bool WinFontInstance::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool) const +{ + rB2DPolyPoly.clear(); + + assert(m_pGraphics); + HDC hDC = m_pGraphics->getHDC(); + const HFONT hOrigFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT)); + const HFONT hFont = GetHFONT(); + if (hFont != hOrigFont) + SelectObject(hDC, hFont); + + const ::comphelper::ScopeGuard aFontRestoreScopeGuard([hFont, hOrigFont, hDC]() + { if (hFont != hOrigFont) SelectObject(hDC, hOrigFont); }); + + // use unity matrix + MAT2 aMat; + aMat.eM11 = aMat.eM22 = FixedFromDouble( 1.0 ); + aMat.eM12 = aMat.eM21 = FixedFromDouble( 0.0 ); + + UINT nGGOFlags = GGO_NATIVE; + nGGOFlags |= GGO_GLYPH_INDEX; + + GLYPHMETRICS aGlyphMetrics; + const DWORD nSize1 = ::GetGlyphOutlineW(hDC, nId, nGGOFlags, &aGlyphMetrics, 0, nullptr, &aMat); + if( !nSize1 ) // blank glyphs are ok + return true; + else if( nSize1 == GDI_ERROR ) + return false; + + BYTE* pData = new BYTE[ nSize1 ]; + const DWORD nSize2 = ::GetGlyphOutlineW(hDC, nId, nGGOFlags, + &aGlyphMetrics, nSize1, pData, &aMat ); + + if( nSize1 != nSize2 ) + return false; + + // TODO: avoid tools polygon by creating B2DPolygon directly + int nPtSize = 512; + Point* pPoints = new Point[ nPtSize ]; + PolyFlags* pFlags = new PolyFlags[ nPtSize ]; + + TTPOLYGONHEADER* pHeader = reinterpret_cast<TTPOLYGONHEADER*>(pData); + while( reinterpret_cast<BYTE*>(pHeader) < pData+nSize2 ) + { + // only outline data is interesting + if( pHeader->dwType != TT_POLYGON_TYPE ) + break; + + // get start point; next start points are end points + // of previous segment + sal_uInt16 nPnt = 0; + + tools::Long nX = IntTimes256FromFixed( pHeader->pfxStart.x ); + tools::Long nY = IntTimes256FromFixed( pHeader->pfxStart.y ); + pPoints[ nPnt ] = Point( nX, nY ); + pFlags[ nPnt++ ] = PolyFlags::Normal; + + bool bHasOfflinePoints = false; + TTPOLYCURVE* pCurve = reinterpret_cast<TTPOLYCURVE*>( pHeader + 1 ); + pHeader = reinterpret_cast<TTPOLYGONHEADER*>( reinterpret_cast<BYTE*>(pHeader) + pHeader->cb ); + while( reinterpret_cast<BYTE*>(pCurve) < reinterpret_cast<BYTE*>(pHeader) ) + { + int nNeededSize = nPnt + 16 + 3 * pCurve->cpfx; + if( nPtSize < nNeededSize ) + { + Point* pOldPoints = pPoints; + PolyFlags* pOldFlags = pFlags; + nPtSize = 2 * nNeededSize; + pPoints = new Point[ nPtSize ]; + pFlags = new PolyFlags[ nPtSize ]; + for( sal_uInt16 i = 0; i < nPnt; ++i ) + { + pPoints[ i ] = pOldPoints[ i ]; + pFlags[ i ] = pOldFlags[ i ]; + } + delete[] pOldPoints; + delete[] pOldFlags; + } + + int i = 0; + if( TT_PRIM_LINE == pCurve->wType ) + { + while( i < pCurve->cpfx ) + { + nX = IntTimes256FromFixed( pCurve->apfx[ i ].x ); + nY = IntTimes256FromFixed( pCurve->apfx[ i ].y ); + ++i; + pPoints[ nPnt ] = Point( nX, nY ); + pFlags[ nPnt ] = PolyFlags::Normal; + ++nPnt; + } + } + else if( TT_PRIM_QSPLINE == pCurve->wType ) + { + bHasOfflinePoints = true; + while( i < pCurve->cpfx ) + { + // get control point of quadratic bezier spline + nX = IntTimes256FromFixed( pCurve->apfx[ i ].x ); + nY = IntTimes256FromFixed( pCurve->apfx[ i ].y ); + ++i; + Point aControlP( nX, nY ); + + // calculate first cubic control point + // P0 = 1/3 * (PBeg + 2 * PQControl) + nX = pPoints[ nPnt-1 ].X() + 2 * aControlP.X(); + nY = pPoints[ nPnt-1 ].Y() + 2 * aControlP.Y(); + pPoints[ nPnt+0 ] = Point( (2*nX+3)/6, (2*nY+3)/6 ); + pFlags[ nPnt+0 ] = PolyFlags::Control; + + // calculate endpoint of segment + nX = IntTimes256FromFixed( pCurve->apfx[ i ].x ); + nY = IntTimes256FromFixed( pCurve->apfx[ i ].y ); + + if ( i+1 >= pCurve->cpfx ) + { + // endpoint is either last point in segment => advance + ++i; + } + else + { + // or endpoint is the middle of two control points + nX += IntTimes256FromFixed( pCurve->apfx[ i-1 ].x ); + nY += IntTimes256FromFixed( pCurve->apfx[ i-1 ].y ); + nX = (nX + 1) / 2; + nY = (nY + 1) / 2; + // no need to advance, because the current point + // is the control point in next bezier spline + } + + pPoints[ nPnt+2 ] = Point( nX, nY ); + pFlags[ nPnt+2 ] = PolyFlags::Normal; + + // calculate second cubic control point + // P1 = 1/3 * (PEnd + 2 * PQControl) + nX = pPoints[ nPnt+2 ].X() + 2 * aControlP.X(); + nY = pPoints[ nPnt+2 ].Y() + 2 * aControlP.Y(); + pPoints[ nPnt+1 ] = Point( (2*nX+3)/6, (2*nY+3)/6 ); + pFlags[ nPnt+1 ] = PolyFlags::Control; + + nPnt += 3; + } + } + + // next curve segment + pCurve = reinterpret_cast<TTPOLYCURVE*>(&pCurve->apfx[ i ]); + } + + // end point is start point for closed contour + // disabled, because Polygon class closes the contour itself + // pPoints[nPnt++] = pPoints[0]; + // #i35928# + // Added again, but add only when not yet closed + if(pPoints[nPnt - 1] != pPoints[0]) + { + if( bHasOfflinePoints ) + pFlags[nPnt] = pFlags[0]; + + pPoints[nPnt++] = pPoints[0]; + } + + // convert y-coordinates W32 -> VCL + for( int i = 0; i < nPnt; ++i ) + pPoints[i].setY(-pPoints[i].Y()); + + // insert into polypolygon + tools::Polygon aPoly( nPnt, pPoints, (bHasOfflinePoints ? pFlags : nullptr) ); + // convert to B2DPolyPolygon + // TODO: get rid of the intermediate PolyPolygon + rB2DPolyPoly.append( aPoly.getB2DPolygon() ); + } + + delete[] pPoints; + delete[] pFlags; + + delete[] pData; + + // rescaling needed for the tools::PolyPolygon conversion + if( rB2DPolyPoly.count() ) + { + const double fFactor(1.0f/256); + rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fFactor, fFactor)); + } + + return true; +} + +IDWriteFontFace* WinFontInstance::GetDWFontFace() const +{ + if (!mxDWFontFace) + { + assert(m_pGraphics); + HDC hDC = m_pGraphics->getHDC(); + const HFONT hOrigFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT)); + const HFONT hFont = GetHFONT(); + if (hFont != hOrigFont) + SelectObject(hDC, hFont); + + const ::comphelper::ScopeGuard aFontRestoreScopeGuard([hFont, hOrigFont, hDC]() { + if (hFont != hOrigFont) + SelectObject(hDC, hOrigFont); + }); + + IDWriteGdiInterop* pDWriteGdiInterop; + WinSalGraphics::getDWriteFactory(nullptr, &pDWriteGdiInterop); + + HRESULT hr = pDWriteGdiInterop->CreateFontFaceFromHdc(hDC, &mxDWFontFace); + if (FAILED(hr)) + { + SAL_WARN("vcl.fonts", "HRESULT 0x" << OUString::number(hr, 16) << ": " + << WindowsErrorStringFromHRESULT(hr)); + mxDWFontFace = nullptr; + } + } + + return mxDWFontFace; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/win/gdi/salgdi.cxx b/vcl/win/gdi/salgdi.cxx new file mode 100644 index 0000000000..cb4b500a4a --- /dev/null +++ b/vcl/win/gdi/salgdi.cxx @@ -0,0 +1,1139 @@ +/* -*- 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 <string.h> +#include <svsys.h> +#include <rtl/strbuf.hxx> +#include <tools/poly.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <comphelper/windowserrorstring.hxx> +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salgdi.h> +#include <win/salframe.h> +#include <win/salvd.h> +#include <win/winlayout.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <salgdiimpl.hxx> +#include "gdiimpl.hxx" + +#include <config_features.h> +#include <vcl/skia/SkiaHelper.hxx> +#if HAVE_FEATURE_SKIA +#include <skia/win/gdiimpl.hxx> +#endif + + +#define DITHER_PAL_DELTA 51 +#define DITHER_PAL_STEPS 6 +#define DITHER_PAL_COUNT (DITHER_PAL_STEPS*DITHER_PAL_STEPS*DITHER_PAL_STEPS) +#define DITHER_MAX_SYSCOLOR 16 +#define DITHER_EXTRA_COLORS 1 + +namespace +{ + +struct SysColorEntry +{ + DWORD nRGB; + SysColorEntry* pNext; +}; + +SysColorEntry* pFirstSysColor = nullptr; +SysColorEntry* pActSysColor = nullptr; + +void DeleteSysColorList() +{ + SysColorEntry* pEntry = pFirstSysColor; + pActSysColor = pFirstSysColor = nullptr; + + while( pEntry ) + { + SysColorEntry* pTmp = pEntry->pNext; + delete pEntry; + pEntry = pTmp; + } +} + +} // namespace + +// Blue7 +static PALETTEENTRY aImplExtraColor1 = +{ + 0, 184, 255, 0 +}; + +static PALETTEENTRY aImplSalSysPalEntryAry[ DITHER_MAX_SYSCOLOR ] = +{ +{ 0, 0, 0, 0 }, +{ 0, 0, 0x80, 0 }, +{ 0, 0x80, 0, 0 }, +{ 0, 0x80, 0x80, 0 }, +{ 0x80, 0, 0, 0 }, +{ 0x80, 0, 0x80, 0 }, +{ 0x80, 0x80, 0, 0 }, +{ 0x80, 0x80, 0x80, 0 }, +{ 0xC0, 0xC0, 0xC0, 0 }, +{ 0, 0, 0xFF, 0 }, +{ 0, 0xFF, 0, 0 }, +{ 0, 0xFF, 0xFF, 0 }, +{ 0xFF, 0, 0, 0 }, +{ 0xFF, 0, 0xFF, 0 }, +{ 0xFF, 0xFF, 0, 0 }, +{ 0xFF, 0xFF, 0xFF, 0 } +}; + +// we must create pens with 1-pixel width; otherwise the S3-graphics card +// map has many paint problems when drawing polygons/polyLines and a +// complex is set +#define GSL_PEN_WIDTH 1 + +void ImplInitSalGDI() +{ + SalData* pSalData = GetSalData(); + + pSalData->mbResourcesAlreadyFreed = false; + + // init stock brushes + pSalData->maStockPenColorAry[0] = PALETTERGB( 0, 0, 0 ); + pSalData->maStockPenColorAry[1] = PALETTERGB( 0xFF, 0xFF, 0xFF ); + pSalData->maStockPenColorAry[2] = PALETTERGB( 0xC0, 0xC0, 0xC0 ); + pSalData->maStockPenColorAry[3] = PALETTERGB( 0x80, 0x80, 0x80 ); + pSalData->mhStockPenAry[0] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[0] ); + pSalData->mhStockPenAry[1] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[1] ); + pSalData->mhStockPenAry[2] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[2] ); + pSalData->mhStockPenAry[3] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[3] ); + pSalData->mnStockPenCount = 4; + + pSalData->maStockBrushColorAry[0] = PALETTERGB( 0, 0, 0 ); + pSalData->maStockBrushColorAry[1] = PALETTERGB( 0xFF, 0xFF, 0xFF ); + pSalData->maStockBrushColorAry[2] = PALETTERGB( 0xC0, 0xC0, 0xC0 ); + pSalData->maStockBrushColorAry[3] = PALETTERGB( 0x80, 0x80, 0x80 ); + pSalData->mhStockBrushAry[0] = CreateSolidBrush( pSalData->maStockBrushColorAry[0] ); + pSalData->mhStockBrushAry[1] = CreateSolidBrush( pSalData->maStockBrushColorAry[1] ); + pSalData->mhStockBrushAry[2] = CreateSolidBrush( pSalData->maStockBrushColorAry[2] ); + pSalData->mhStockBrushAry[3] = CreateSolidBrush( pSalData->maStockBrushColorAry[3] ); + pSalData->mnStockBrushCount = 4; + + // initialize temporary font lists + pSalData->mpSharedTempFontItem = nullptr; + pSalData->mpOtherTempFontItem = nullptr; + + // support palettes for 256 color displays + HDC hDC = GetDC( nullptr ); + int nBitsPixel = GetDeviceCaps( hDC, BITSPIXEL ); + int nPlanes = GetDeviceCaps( hDC, PLANES ); + int nRasterCaps = GetDeviceCaps( hDC, RASTERCAPS ); + int nBitCount = nBitsPixel * nPlanes; + + if ( (nBitCount > 8) && (nBitCount < 24) ) + { + // test if we have to dither + HDC hMemDC = ::CreateCompatibleDC( hDC ); + HBITMAP hMemBmp = ::CreateCompatibleBitmap( hDC, 8, 8 ); + HBITMAP hBmpOld = static_cast<HBITMAP>(::SelectObject( hMemDC, hMemBmp )); + HBRUSH hMemBrush = ::CreateSolidBrush( PALETTERGB( 175, 171, 169 ) ); + HBRUSH hBrushOld = static_cast<HBRUSH>(::SelectObject( hMemDC, hMemBrush )); + bool bDither16 = true; + + ::PatBlt( hMemDC, 0, 0, 8, 8, PATCOPY ); + const COLORREF aCol( ::GetPixel( hMemDC, 0, 0 ) ); + + for( int nY = 0; ( nY < 8 ) && bDither16; nY++ ) + for( int nX = 0; ( nX < 8 ) && bDither16; nX++ ) + if( ::GetPixel( hMemDC, nX, nY ) != aCol ) + bDither16 = false; + + ::SelectObject( hMemDC, hBrushOld ); + ::DeleteObject( hMemBrush ); + ::SelectObject( hMemDC, hBmpOld ); + ::DeleteObject( hMemBmp ); + ::DeleteDC( hMemDC ); + + if( bDither16 ) + { + // create DIBPattern for 16Bit dithering + tools::Long n; + + pSalData->mhDitherDIB = GlobalAlloc( GMEM_FIXED, sizeof( BITMAPINFOHEADER ) + 192 ); + pSalData->mpDitherDIB = static_cast<BYTE*>(GlobalLock( pSalData->mhDitherDIB )); + pSalData->mpDitherDiff.reset(new tools::Long[ 256 ]); + pSalData->mpDitherLow.reset(new BYTE[ 256 ]); + pSalData->mpDitherHigh.reset(new BYTE[ 256 ]); + pSalData->mpDitherDIBData = pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER ); + memset( pSalData->mpDitherDIB, 0, sizeof( BITMAPINFOHEADER ) ); + + BITMAPINFOHEADER* pBIH = reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB); + + pBIH->biSize = sizeof( BITMAPINFOHEADER ); + pBIH->biWidth = 8; + pBIH->biHeight = 8; + pBIH->biPlanes = 1; + pBIH->biBitCount = 24; + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherDiff[ n ] = n - ( n & 248L ); + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherLow[ n ] = static_cast<BYTE>( n & 248 ); + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherHigh[ n ] = static_cast<BYTE>(std::min( pSalData->mpDitherLow[ n ] + 8, 255 )); + } + } + else if ( (nRasterCaps & RC_PALETTE) && (nBitCount == 8) ) + { + BYTE nRed, nGreen, nBlue; + BYTE nR, nG, nB; + PALETTEENTRY* pPalEntry; + LOGPALETTE* pLogPal; + const sal_uInt16 nDitherPalCount = DITHER_PAL_COUNT; + sal_uLong nTotalCount = DITHER_MAX_SYSCOLOR + nDitherPalCount + DITHER_EXTRA_COLORS; + + // create logical palette + pLogPal = reinterpret_cast<LOGPALETTE*>(new char[ sizeof( LOGPALETTE ) + ( nTotalCount * sizeof( PALETTEENTRY ) ) ]); + pLogPal->palVersion = 0x0300; + pLogPal->palNumEntries = static_cast<sal_uInt16>(nTotalCount); + pPalEntry = pLogPal->palPalEntry; + + // Standard colors + memcpy( pPalEntry, aImplSalSysPalEntryAry, DITHER_MAX_SYSCOLOR * sizeof( PALETTEENTRY ) ); + pPalEntry += DITHER_MAX_SYSCOLOR; + + // own palette (6/6/6) + for( nB=0, nBlue=0; nB < DITHER_PAL_STEPS; nB++, nBlue += DITHER_PAL_DELTA ) + { + for( nG=0, nGreen=0; nG < DITHER_PAL_STEPS; nG++, nGreen += DITHER_PAL_DELTA ) + { + for( nR=0, nRed=0; nR < DITHER_PAL_STEPS; nR++, nRed += DITHER_PAL_DELTA ) + { + pPalEntry->peRed = nRed; + pPalEntry->peGreen = nGreen; + pPalEntry->peBlue = nBlue; + pPalEntry->peFlags = 0; + pPalEntry++; + } + } + } + + // insert special 'Blue' as standard drawing color + *pPalEntry++ = aImplExtraColor1; + + // create palette + pSalData->mhDitherPal = CreatePalette( pLogPal ); + delete[] reinterpret_cast<char*>(pLogPal); + + if( pSalData->mhDitherPal ) + { + // create DIBPattern for 8Bit dithering + tools::Long const nSize = sizeof( BITMAPINFOHEADER ) + ( 256 * sizeof( short ) ) + 64; + tools::Long n; + + pSalData->mhDitherDIB = GlobalAlloc( GMEM_FIXED, nSize ); + pSalData->mpDitherDIB = static_cast<BYTE*>(GlobalLock( pSalData->mhDitherDIB )); + pSalData->mpDitherDiff.reset(new tools::Long[ 256 ]); + pSalData->mpDitherLow.reset(new BYTE[ 256 ]); + pSalData->mpDitherHigh.reset(new BYTE[ 256 ]); + pSalData->mpDitherDIBData = pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER ) + ( 256 * sizeof( short ) ); + memset( pSalData->mpDitherDIB, 0, sizeof( BITMAPINFOHEADER ) ); + + BITMAPINFOHEADER* pBIH = reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB); + short* pColors = reinterpret_cast<short*>( pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER ) ); + + pBIH->biSize = sizeof( BITMAPINFOHEADER ); + pBIH->biWidth = 8; + pBIH->biHeight = 8; + pBIH->biPlanes = 1; + pBIH->biBitCount = 8; + + for( n = 0; n < nDitherPalCount; n++ ) + pColors[ n ] = static_cast<short>( n + DITHER_MAX_SYSCOLOR ); + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherDiff[ n ] = n % 51; + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherLow[ n ] = static_cast<BYTE>( n / 51 ); + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherHigh[ n ] = static_cast<BYTE>(std::min( pSalData->mpDitherLow[ n ] + 1, 5 )); + } + + // get system color entries + ImplUpdateSysColorEntries(); + } + + ReleaseDC( nullptr, hDC ); +} + +void ImplFreeSalGDI() +{ + SalData* pSalData = GetSalData(); + + if (pSalData->mbResourcesAlreadyFreed) + return; + + // destroy stock objects + int i; + for ( i = 0; i < pSalData->mnStockPenCount; i++ ) + DeletePen( pSalData->mhStockPenAry[i] ); + for ( i = 0; i < pSalData->mnStockBrushCount; i++ ) + DeleteBrush( pSalData->mhStockBrushAry[i] ); + + // delete 50% Brush + if ( pSalData->mh50Brush ) + { + DeleteBrush( pSalData->mh50Brush ); + pSalData->mh50Brush = nullptr; + } + + // delete 50% Bitmap + if ( pSalData->mh50Bmp ) + { + DeleteBitmap( pSalData->mh50Bmp ); + pSalData->mh50Bmp = nullptr; + } + + ImplClearHDCCache( pSalData ); + + // delete Ditherpalette, if existing + if ( pSalData->mhDitherPal ) + { + DeleteObject( pSalData->mhDitherPal ); + pSalData->mhDitherPal = nullptr; + } + + // delete buffers for dithering DIB patterns, if necessary + if ( pSalData->mhDitherDIB ) + { + GlobalUnlock( pSalData->mhDitherDIB ); + GlobalFree( pSalData->mhDitherDIB ); + pSalData->mhDitherDIB = nullptr; + pSalData->mpDitherDiff.reset(); + pSalData->mpDitherLow.reset(); + pSalData->mpDitherHigh.reset(); + } + + DeleteSysColorList(); + + // delete icon cache + SalIcon* pIcon = pSalData->mpFirstIcon; + pSalData->mpFirstIcon = nullptr; + while( pIcon ) + { + SalIcon* pTmp = pIcon->pNext; + DestroyIcon( pIcon->hIcon ); + DestroyIcon( pIcon->hSmallIcon ); + delete pIcon; + pIcon = pTmp; + } + + // delete temporary font list + ImplReleaseTempFonts(*pSalData, true); + + pSalData->mbResourcesAlreadyFreed = true; +} + +int ImplIsSysColorEntry( Color nColor ) +{ + SysColorEntry* pEntry = pFirstSysColor; + const DWORD nTestRGB = static_cast<DWORD>(RGB( nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue() )); + + while ( pEntry ) + { + if ( pEntry->nRGB == nTestRGB ) + return TRUE; + pEntry = pEntry->pNext; + } + + return FALSE; +} + +static int ImplIsPaletteEntry( BYTE nRed, BYTE nGreen, BYTE nBlue ) +{ + // dither color? + if ( !(nRed % DITHER_PAL_DELTA) && !(nGreen % DITHER_PAL_DELTA) && !(nBlue % DITHER_PAL_DELTA) ) + return TRUE; + + PALETTEENTRY* pPalEntry = aImplSalSysPalEntryAry; + + // standard palette color? + for ( sal_uInt16 i = 0; i < DITHER_MAX_SYSCOLOR; i++, pPalEntry++ ) + { + if( pPalEntry->peRed == nRed && pPalEntry->peGreen == nGreen && pPalEntry->peBlue == nBlue ) + return TRUE; + } + + // extra color? + if ( aImplExtraColor1.peRed == nRed && + aImplExtraColor1.peGreen == nGreen && + aImplExtraColor1.peBlue == nBlue ) + { + return TRUE; + } + + return FALSE; +} + +static void ImplInsertSysColorEntry( int nSysIndex ) +{ + const DWORD nRGB = GetSysColor( nSysIndex ); + + if ( !ImplIsPaletteEntry( GetRValue( nRGB ), GetGValue( nRGB ), GetBValue( nRGB ) ) ) + { + if ( !pFirstSysColor ) + { + pActSysColor = pFirstSysColor = new SysColorEntry; + pFirstSysColor->nRGB = nRGB; + pFirstSysColor->pNext = nullptr; + } + else + { + pActSysColor = pActSysColor->pNext = new SysColorEntry; + pActSysColor->nRGB = nRGB; + pActSysColor->pNext = nullptr; + } + } +} + +void ImplUpdateSysColorEntries() +{ + DeleteSysColorList(); + + // create new sys color list + ImplInsertSysColorEntry( COLOR_ACTIVEBORDER ); + ImplInsertSysColorEntry( COLOR_INACTIVEBORDER ); + ImplInsertSysColorEntry( COLOR_GRADIENTACTIVECAPTION ); + ImplInsertSysColorEntry( COLOR_GRADIENTINACTIVECAPTION ); + ImplInsertSysColorEntry( COLOR_3DFACE ); + ImplInsertSysColorEntry( COLOR_3DHILIGHT ); + ImplInsertSysColorEntry( COLOR_3DLIGHT ); + ImplInsertSysColorEntry( COLOR_3DSHADOW ); + ImplInsertSysColorEntry( COLOR_3DDKSHADOW ); + ImplInsertSysColorEntry( COLOR_INFOBK ); + ImplInsertSysColorEntry( COLOR_INFOTEXT ); + ImplInsertSysColorEntry( COLOR_BTNTEXT ); + ImplInsertSysColorEntry( COLOR_WINDOW ); + ImplInsertSysColorEntry( COLOR_WINDOWTEXT ); + ImplInsertSysColorEntry( COLOR_HIGHLIGHT ); + ImplInsertSysColorEntry( COLOR_HIGHLIGHTTEXT ); + ImplInsertSysColorEntry( COLOR_MENU ); + ImplInsertSysColorEntry( COLOR_MENUTEXT ); + ImplInsertSysColorEntry( COLOR_ACTIVECAPTION ); + ImplInsertSysColorEntry( COLOR_CAPTIONTEXT ); + ImplInsertSysColorEntry( COLOR_INACTIVECAPTION ); + ImplInsertSysColorEntry( COLOR_INACTIVECAPTIONTEXT ); +} + +void WinSalGraphics::InitGraphics() +{ + if (!getHDC()) + return; + + // calculate the minimal line width for the printer + if ( isPrinter() ) + { + int nDPIX = GetDeviceCaps( getHDC(), LOGPIXELSX ); + if ( nDPIX <= 300 ) + mnPenWidth = 0; + else + mnPenWidth = nDPIX/300; + } + + ::SetTextAlign( getHDC(), TA_BASELINE | TA_LEFT | TA_NOUPDATECP ); + ::SetBkMode( getHDC(), TRANSPARENT ); + ::SetROP2( getHDC(), R2_COPYPEN ); + + mpImpl->Init(); +} + +void WinSalGraphics::DeInitGraphics() +{ + if (!getHDC()) + return; + + // clear clip region + SelectClipRgn( getHDC(), nullptr ); + // select default objects + if ( mhDefPen ) + { + SelectPen( getHDC(), mhDefPen ); + mhDefPen = nullptr; + } + if ( mhDefBrush ) + { + SelectBrush( getHDC(), mhDefBrush ); + mhDefBrush = nullptr; + } + if ( mhDefFont ) + { + SelectFont( getHDC(), mhDefFont ); + mhDefFont = nullptr; + } + setPalette(nullptr); + + mpImpl->DeInit(); +} + +void WinSalGraphics::setHDC(HDC aNew) +{ + DeInitGraphics(); + mhLocalDC = aNew; + InitGraphics(); +} + +HDC ImplGetCachedDC( sal_uLong nID, HBITMAP hBmp ) +{ + SalData* pSalData = GetSalData(); + HDCCache* pC = &pSalData->maHDCCache[ nID ]; + + if( !pC->mhDC ) + { + HDC hDC = GetDC( nullptr ); + + // create new DC with DefaultBitmap + pC->mhDC = CreateCompatibleDC( hDC ); + + if( pSalData->mhDitherPal ) + { + pC->mhDefPal = SelectPalette( pC->mhDC, pSalData->mhDitherPal, TRUE ); + RealizePalette( pC->mhDC ); + } + + pC->mhSelBmp = CreateCompatibleBitmap( hDC, CACHED_HDC_DEFEXT, CACHED_HDC_DEFEXT ); + pC->mhDefBmp = static_cast<HBITMAP>(SelectObject( pC->mhDC, pC->mhSelBmp )); + + ReleaseDC( nullptr, hDC ); + } + + if ( hBmp ) + SelectObject( pC->mhDC, pC->mhActBmp = hBmp ); + else + pC->mhActBmp = nullptr; + + return pC->mhDC; +} + +void ImplReleaseCachedDC( sal_uLong nID ) +{ + SalData* pSalData = GetSalData(); + HDCCache* pC = &pSalData->maHDCCache[ nID ]; + + if ( pC->mhActBmp ) + SelectObject( pC->mhDC, pC->mhSelBmp ); +} + +void ImplClearHDCCache( SalData* pData ) +{ + for( sal_uLong i = 0; i < CACHESIZE_HDC; i++ ) + { + HDCCache* pC = &pData->maHDCCache[ i ]; + + if( pC->mhDC ) + { + SelectObject( pC->mhDC, pC->mhDefBmp ); + + if( pC->mhDefPal ) + SelectPalette( pC->mhDC, pC->mhDefPal, TRUE ); + + DeleteDC( pC->mhDC ); + DeleteObject( pC->mhSelBmp ); + } + } +} + +std::unique_ptr< CompatibleDC > CompatibleDC::create(SalGraphics &rGraphics, int x, int y, int width, int height) +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + return std::make_unique< SkiaCompatibleDC >( rGraphics, x, y, width, height ); +#endif + return std::unique_ptr< CompatibleDC >( new CompatibleDC( rGraphics, x, y, width, height )); +} + +CompatibleDC::CompatibleDC(SalGraphics &rGraphics, int x, int y, int width, int height, bool disable) + : mhBitmap(nullptr) + , mpData(nullptr) + , maRects(0, 0, width, height, x, y, width, height) + , mpImpl(nullptr) +{ + WinSalGraphics& rWinGraphics = static_cast<WinSalGraphics&>(rGraphics); + + if( disable ) + { + // we avoid the OpenGL drawing, instead we draw directly to the DC + mhCompatibleDC = rWinGraphics.getHDC(); + return; + } + + mpImpl = rWinGraphics.getWinSalGraphicsImplBase(); + mhCompatibleDC = CreateCompatibleDC(rWinGraphics.getHDC()); + + // move the origin so that we always paint at 0,0 - to keep the bitmap + // small + OffsetViewportOrgEx(mhCompatibleDC, -x, -y, nullptr); + + mhBitmap = WinSalVirtualDevice::ImplCreateVirDevBitmap(mhCompatibleDC, width, height, 32, reinterpret_cast<void **>(&mpData)); + + mhOrigBitmap = static_cast<HBITMAP>(SelectObject(mhCompatibleDC, mhBitmap)); +} + +CompatibleDC::~CompatibleDC() +{ + if (mpImpl) + { + SelectObject(mhCompatibleDC, mhOrigBitmap); + DeleteObject(mhBitmap); + DeleteDC(mhCompatibleDC); + } +} + +void CompatibleDC::fill(sal_uInt32 color) +{ + if (!mpData) + return; + + sal_uInt32 *p = mpData; + for (int i = maRects.mnSrcWidth * maRects.mnSrcHeight; i > 0; --i) + *p++ = color; +} + +WinSalGraphics::WinSalGraphics(WinSalGraphics::Type eType, bool bScreen, HWND hWnd, [[maybe_unused]] SalGeometryProvider *pProvider): + mhLocalDC(nullptr), + mbPrinter(eType == WinSalGraphics::PRINTER), + mbVirDev(eType == WinSalGraphics::VIRTUAL_DEVICE), + mbWindow(eType == WinSalGraphics::WINDOW), + mbScreen(bScreen), + mhWnd(hWnd), + mhRegion(nullptr), + mhDefPen(nullptr), + mhDefBrush(nullptr), + mhDefFont(nullptr), + mhDefPal(nullptr), + mpStdClipRgnData(nullptr), + mnPenWidth(GSL_PEN_WIDTH) +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled() && !mbPrinter) + { + auto const impl = new WinSkiaSalGraphicsImpl(*this, pProvider); + mpImpl.reset(impl); + mWinSalGraphicsImplBase = impl; + } + else +#endif + { + auto const impl = new WinSalGraphicsImpl(*this); + mpImpl.reset(impl); + mWinSalGraphicsImplBase = impl; + } +} + +WinSalGraphics::~WinSalGraphics() +{ + // free obsolete GDI objects + ReleaseFonts(); + + if ( mhRegion ) + { + DeleteRegion( mhRegion ); + mhRegion = nullptr; + } + + // delete cache data + delete [] reinterpret_cast<BYTE*>(mpStdClipRgnData); + + setHDC(nullptr); +} + +SalGraphicsImpl* WinSalGraphics::GetImpl() const +{ + return mpImpl.get(); +} + +bool WinSalGraphics::isPrinter() const +{ + return mbPrinter; +} + +bool WinSalGraphics::isVirtualDevice() const +{ + return mbVirDev; +} + +bool WinSalGraphics::isWindow() const +{ + return mbWindow; +} + +bool WinSalGraphics::isScreen() const +{ + return mbScreen; +} + +HWND WinSalGraphics::gethWnd() +{ + return mhWnd; +} + +void WinSalGraphics::setHWND(HWND hWnd) +{ + mhWnd = hWnd; +} + +HPALETTE WinSalGraphics::getDefPal() const +{ + assert(getHDC() || !mhDefPal); + return mhDefPal; +} + +UINT WinSalGraphics::setPalette(HPALETTE hNewPal, BOOL bForceBkgd) +{ + UINT res = GDI_ERROR; + + if (!getHDC()) + { + assert(!mhDefPal); + return res; + } + + if (hNewPal) + { + HPALETTE hOldPal = SelectPalette(getHDC(), hNewPal, bForceBkgd); + if (hOldPal) + { + if (!mhDefPal) + mhDefPal = hOldPal; + res = RealizePalette(getHDC()); + } + } + else + { + res = 0; + if (mhDefPal) + { + SelectPalette(getHDC(), mhDefPal, bForceBkgd); + mhDefPal = nullptr; + } + } + + return res; +} + +HRGN WinSalGraphics::getRegion() const +{ + return mhRegion; +} + +void WinSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) +{ + rDPIX = GetDeviceCaps( getHDC(), LOGPIXELSX ); + rDPIY = GetDeviceCaps( getHDC(), LOGPIXELSY ); + + // #111139# this fixes the symptom of div by zero on startup + // however, printing will fail most likely as communication with + // the printer seems not to work in this case + if( !rDPIX || !rDPIY ) + rDPIX = rDPIY = 600; +} + +void WinSalGraphics::getDWriteFactory(IDWriteFactory** pFactory, IDWriteGdiInterop** pInterop) +{ + if (!bDWriteDone) + { + HRESULT hr = S_OK; + hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown**>(&mxDWriteFactory)); + if (FAILED(hr)) + { + SAL_WARN("vcl.fonts", "HRESULT 0x" << OUString::number(hr, 16) << ": " + << WindowsErrorStringFromHRESULT(hr)); + abort(); + } + + hr = mxDWriteFactory->GetGdiInterop(&mxDWriteGdiInterop); + if (FAILED(hr)) + { + SAL_WARN("vcl.fonts", "HRESULT 0x" << OUString::number(hr, 16) << ": " + << WindowsErrorStringFromHRESULT(hr)); + abort(); + } + + bDWriteDone = true; + } + + if (pFactory) + *pFactory = mxDWriteFactory.get(); + if (pInterop) + *pInterop = mxDWriteGdiInterop.get(); +} + +sal_uInt16 WinSalGraphics::GetBitCount() const +{ + return mpImpl->GetBitCount(); +} + +tools::Long WinSalGraphics::GetGraphicsWidth() const +{ + return mpImpl->GetGraphicsWidth(); +} + +void WinSalGraphics::Flush() +{ + mWinSalGraphicsImplBase->Flush(); +} + +void WinSalGraphics::ResetClipRegion() +{ + mpImpl->ResetClipRegion(); +} + +void WinSalGraphics::setClipRegion( const vcl::Region& i_rClip ) +{ + mpImpl->setClipRegion( i_rClip ); +} + +void WinSalGraphics::SetLineColor() +{ + mpImpl->SetLineColor(); +} + +void WinSalGraphics::SetLineColor( Color nColor ) +{ + mpImpl->SetLineColor( nColor ); +} + +void WinSalGraphics::SetFillColor() +{ + mpImpl->SetFillColor(); +} + +void WinSalGraphics::SetFillColor( Color nColor ) +{ + mpImpl->SetFillColor( nColor ); +} + +void WinSalGraphics::SetXORMode( bool bSet, bool bInvertOnly ) +{ + mpImpl->SetXORMode( bSet, bInvertOnly ); +} + +void WinSalGraphics::SetROPLineColor( SalROPColor nROPColor ) +{ + mpImpl->SetROPLineColor( nROPColor ); +} + +void WinSalGraphics::SetROPFillColor( SalROPColor nROPColor ) +{ + mpImpl->SetROPFillColor( nROPColor ); +} + +void WinSalGraphics::drawPixel( tools::Long nX, tools::Long nY ) +{ + mpImpl->drawPixel( nX, nY ); +} + +void WinSalGraphics::drawPixel( tools::Long nX, tools::Long nY, Color nColor ) +{ + mpImpl->drawPixel( nX, nY, nColor ); +} + +void WinSalGraphics::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) +{ + mpImpl->drawLine( nX1, nY1, nX2, nY2 ); +} + +void WinSalGraphics::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + mpImpl->drawRect( nX, nY, nWidth, nHeight ); +} + +void WinSalGraphics::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) +{ + mpImpl->drawPolyLine( nPoints, pPtAry ); +} + +void WinSalGraphics::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) +{ + mpImpl->drawPolygon( nPoints, pPtAry ); +} + +void WinSalGraphics::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, + const Point** pPtAry ) +{ + mpImpl->drawPolyPolygon( nPoly, pPoints, pPtAry ); +} + +bool WinSalGraphics::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) +{ + return mpImpl->drawPolyLineBezier( nPoints, pPtAry, pFlgAry ); +} + +bool WinSalGraphics::drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry ) +{ + return mpImpl->drawPolygonBezier( nPoints, pPtAry, pFlgAry ); +} + +bool WinSalGraphics::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, + const Point* const* pPtAry, const PolyFlags* const* pFlgAry ) +{ + return mpImpl->drawPolyPolygonBezier( nPoly, pPoints, pPtAry, pFlgAry ); +} + +bool WinSalGraphics::drawGradient(const tools::PolyPolygon& rPoly, const Gradient& rGradient) +{ + return mpImpl->drawGradient(rPoly, rGradient); +} + +bool WinSalGraphics::implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient) +{ + return mpImpl->implDrawGradient(rPolyPolygon, rGradient); +} + +static BYTE* ImplSearchEntry( BYTE* pSource, BYTE const * pDest, sal_uLong nComp, sal_uLong nSize ) +{ + while ( nComp-- >= nSize ) + { + sal_uLong i; + for ( i = 0; i < nSize; i++ ) + { + if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) ) + break; + } + if ( i == nSize ) + return pSource; + pSource++; + } + return nullptr; +} + +static bool ImplGetBoundingBox( double* nNumb, BYTE* pSource, sal_uLong nSize ) +{ + bool bRetValue = false; + BYTE* pDest = ImplSearchEntry( pSource, reinterpret_cast<BYTE const *>("%%BoundingBox:"), nSize, 14 ); + if ( pDest ) + { + nNumb[0] = nNumb[1] = nNumb[2] = nNumb[3] = 0; + pDest += 14; + + int nSizeLeft = nSize - ( pDest - pSource ); + if ( nSizeLeft > 100 ) + nSizeLeft = 100; // only 100 bytes following the bounding box will be checked + + int i; + for ( i = 0; ( i < 4 ) && nSizeLeft; i++ ) + { + int nDivision = 1; + bool bDivision = false; + bool bNegative = false; + bool bValid = true; + + while ( ( --nSizeLeft ) && ( ( *pDest == ' ' ) || ( *pDest == 0x9 ) ) ) pDest++; + BYTE nByte = *pDest; + while ( nSizeLeft && ( nByte != ' ' ) && ( nByte != 0x9 ) && ( nByte != 0xd ) && ( nByte != 0xa ) ) + { + switch ( nByte ) + { + case '.' : + if ( bDivision ) + bValid = false; + else + bDivision = true; + break; + case '-' : + bNegative = true; + break; + default : + if ( ( nByte < '0' ) || ( nByte > '9' ) ) + nSizeLeft = 1; // error parsing the bounding box values + else if ( bValid ) + { + if ( bDivision ) + nDivision*=10; + nNumb[i] *= 10; + nNumb[i] += nByte - '0'; + } + break; + } + nSizeLeft--; + nByte = *(++pDest); + } + if ( bNegative ) + nNumb[i] = -nNumb[i]; + if ( bDivision && ( nDivision != 1 ) ) + nNumb[i] /= nDivision; + } + if ( i == 4 ) + bRetValue = true; + } + return bRetValue; +} + +#define POSTSCRIPT_BUFSIZE 0x4000 // MAXIMUM BUFSIZE EQ 0xFFFF + +bool WinSalGraphics::drawEPS( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, void* pPtr, sal_uInt32 nSize ) +{ + bool bRetValue = false; + + if ( mbPrinter ) + { + int nEscape = POSTSCRIPT_PASSTHROUGH; + + if ( Escape( getHDC(), QUERYESCSUPPORT, sizeof( int ), reinterpret_cast<LPSTR>(&nEscape), nullptr ) ) + { + double nBoundingBox[4]; + + if ( ImplGetBoundingBox( nBoundingBox, static_cast<BYTE*>(pPtr), nSize ) ) + { + OStringBuffer aBuf( POSTSCRIPT_BUFSIZE ); + + // reserve place for a sal_uInt16 + aBuf.append( "aa" ); + + // #107797# Write out EPS encapsulation header + + // directly taken from the PLRM 3.0, p. 726. Note: + // this will definitely cause problems when + // recursively creating and embedding PostScript files + // in OOo, since we use statically-named variables + // here (namely, b4_Inc_state_salWin, dict_count_salWin and + // op_count_salWin). Currently, I have no idea on how to + // work around that, except from scanning and + // interpreting the EPS for unused identifiers. + + // append the real text + aBuf.append( "\n\n/b4_Inc_state_salWin save def\n" + "/dict_count_salWin countdictstack def\n" + "/op_count_salWin count 1 sub def\n" + "userdict begin\n" + "/showpage {} def\n" + "0 setgray 0 setlinecap\n" + "1 setlinewidth 0 setlinejoin\n" + "10 setmiterlimit [] 0 setdash newpath\n" + "/languagelevel where\n" + "{\n" + " pop languagelevel\n" + " 1 ne\n" + " {\n" + " false setstrokeadjust false setoverprint\n" + " } if\n" + "} if\n\n" ); + + // #i10737# Apply clipping manually + + // Windows seems to ignore any clipping at the HDC, + // when followed by a POSTSCRIPT_PASSTHROUGH + + // Check whether we've got a clipping, consisting of + // exactly one rect (other cases should be, but aren't + // handled currently) + + // TODO: Handle more than one rectangle here (take + // care, the buffer can handle only POSTSCRIPT_BUFSIZE + // characters!) + if ( mhRegion != nullptr && + mpStdClipRgnData != nullptr && + mpClipRgnData == mpStdClipRgnData && + mpClipRgnData->rdh.nCount == 1 ) + { + RECT* pRect = &(mpClipRgnData->rdh.rcBound); + + aBuf.append( "\nnewpath\n" + + OString::number(pRect->left) + " " + OString::number(pRect->top) + + " moveto\n" + + OString::number(pRect->right) + " " + OString::number(pRect->top) + + " lineto\n" + + OString::number(pRect->right) + " " + + OString::number(pRect->bottom) + " lineto\n" + + OString::number(pRect->left) + " " + + OString::number(pRect->bottom) + " lineto\n" + "closepath\n" + "clip\n" + "newpath\n" ); + } + + // #107797# Write out buffer + + *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 ); + Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr ); + + // #107797# Write out EPS transformation code + + double dM11 = nWidth / ( nBoundingBox[2] - nBoundingBox[0] ); + double dM22 = nHeight / (nBoundingBox[1] - nBoundingBox[3] ); + // reserve a sal_uInt16 again + aBuf.setLength( 2 ); + aBuf.append( "\n\n[" + OString::number(dM11) + " 0 0 " + OString::number(dM22) + " " + + OString::number(nX - ( dM11 * nBoundingBox[0] )) + " " + + OString::number(nY - ( dM22 * nBoundingBox[3] )) + "] concat\n" + "%%BeginDocument:\n" ); + *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 ); + Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr ); + + // #107797# Write out actual EPS content + + sal_uLong nToDo = nSize; + sal_uLong nDoNow; + while ( nToDo ) + { + nDoNow = nToDo; + if ( nToDo > POSTSCRIPT_BUFSIZE - 2 ) + nDoNow = POSTSCRIPT_BUFSIZE - 2; + // the following is based on the string buffer allocation + // of size POSTSCRIPT_BUFSIZE at construction time of aBuf + *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>(nDoNow); + memcpy( const_cast<char *>(aBuf.getStr() + 2), static_cast<BYTE*>(pPtr) + nSize - nToDo, nDoNow ); + sal_uLong nResult = Escape ( getHDC(), nEscape, nDoNow + 2, aBuf.getStr(), nullptr ); + if (!nResult ) + break; + nToDo -= nResult; + } + + // #107797# Write out EPS encapsulation footer + + // reserve a sal_uInt16 again + aBuf.setLength( 2 ); + aBuf.append( "%%EndDocument\n" + "count op_count_salWin sub {pop} repeat\n" + "countdictstack dict_count_salWin sub {end} repeat\n" + "b4_Inc_state_salWin restore\n\n" ); + *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 ); + Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr ); + bRetValue = true; + } + } + } + + return bRetValue; +} + +SystemGraphicsData WinSalGraphics::GetGraphicsData() const +{ + SystemGraphicsData aRes; + aRes.nSize = sizeof(aRes); + aRes.hDC = getHDC(); + return aRes; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salgdi2.cxx b/vcl/win/gdi/salgdi2.cxx new file mode 100644 index 0000000000..409fcc74bd --- /dev/null +++ b/vcl/win/gdi/salgdi2.cxx @@ -0,0 +1,240 @@ +/* -*- 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 <memory> +#include <string.h> +#include <stdlib.h> + +#include <svsys.h> + +#include <win/wincomp.hxx> +#include <win/salbmp.h> +#include <win/saldata.hxx> +#include <win/salids.hrc> +#include <win/salgdi.h> +#include <win/salframe.h> + +#include <vcl/BitmapAccessMode.hxx> +#include <vcl/BitmapBuffer.hxx> +#include <vcl/BitmapPalette.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/Scanline.hxx> +#include <salgdiimpl.hxx> + +#include <config_features.h> +#if HAVE_FEATURE_SKIA +#include <skia/win/gdiimpl.hxx> +#include <skia/salbmp.hxx> +#endif + + +bool WinSalGraphics::supportsOperation( OutDevSupportType eType ) const +{ + return mpImpl->supportsOperation(eType); +} + +void WinSalGraphics::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + mpImpl->copyBits( rPosAry, pSrcGraphics ); +} + +void WinSalGraphics::copyArea( tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, + bool bWindowInvalidate ) +{ + mpImpl->copyArea( nDestX, nDestY, nSrcX, nSrcY, + nSrcWidth, nSrcHeight, bWindowInvalidate ); +} + +namespace +{ + +class ColorScanlineConverter +{ +public: + ScanlineFormat meSourceFormat; + + int mnComponentSize; + int mnComponentExchangeIndex; + + tools::Long mnScanlineSize; + + ColorScanlineConverter(ScanlineFormat eSourceFormat, int nComponentSize, tools::Long nScanlineSize) + : meSourceFormat(eSourceFormat) + , mnComponentSize(nComponentSize) + , mnComponentExchangeIndex(0) + , mnScanlineSize(nScanlineSize) + { + if (meSourceFormat == ScanlineFormat::N32BitTcAbgr || + meSourceFormat == ScanlineFormat::N32BitTcArgb) + { + mnComponentExchangeIndex = 1; + } + } + + void convertScanline(sal_uInt8* pSource, sal_uInt8* pDestination) + { + for (tools::Long x = 0; x < mnScanlineSize; x += mnComponentSize) + { + for (int i = 0; i < mnComponentSize; ++i) + { + pDestination[x + i] = pSource[x + i]; + } + pDestination[x + mnComponentExchangeIndex + 0] = pSource[x + mnComponentExchangeIndex + 2]; + pDestination[x + mnComponentExchangeIndex + 2] = pSource[x + mnComponentExchangeIndex + 0]; + } + } +}; + +void convertToWinSalBitmap(SalBitmap& rSalBitmap, WinSalBitmap& rWinSalBitmap) +{ + BitmapPalette aBitmapPalette; +#if HAVE_FEATURE_SKIA + if(SkiaSalBitmap* pSkiaSalBitmap = dynamic_cast<SkiaSalBitmap*>(&rSalBitmap)) + aBitmapPalette = pSkiaSalBitmap->Palette(); +#endif + + BitmapBuffer* pRead = rSalBitmap.AcquireBuffer(BitmapAccessMode::Read); + + rWinSalBitmap.Create(rSalBitmap.GetSize(), vcl::bitDepthToPixelFormat(rSalBitmap.GetBitCount()), aBitmapPalette); + BitmapBuffer* pWrite = rWinSalBitmap.AcquireBuffer(BitmapAccessMode::Write); + + sal_uInt8* pSource(pRead->mpBits); + sal_uInt8* pDestination(pWrite->mpBits); + tools::Long readRowChange = pRead->mnScanlineSize; + if(pRead->mnFormat & ScanlineFormat::TopDown) + { + pSource += pRead->mnScanlineSize * (pRead->mnHeight - 1); + readRowChange = -readRowChange; + } + + std::unique_ptr<ColorScanlineConverter> pConverter; + + if (RemoveScanline(pRead->mnFormat) == ScanlineFormat::N24BitTcRgb) + pConverter.reset(new ColorScanlineConverter(ScanlineFormat::N24BitTcRgb, + 3, pRead->mnScanlineSize)); + else if (RemoveScanline(pRead->mnFormat) == ScanlineFormat::N32BitTcRgba) + pConverter.reset(new ColorScanlineConverter(ScanlineFormat::N32BitTcRgba, + 4, pRead->mnScanlineSize)); + if (pConverter) + { + for (tools::Long y = 0; y < pRead->mnHeight; y++) + { + pConverter->convertScanline(pSource, pDestination); + pSource += readRowChange; + pDestination += pWrite->mnScanlineSize; + } + } + else + { + for (tools::Long y = 0; y < pRead->mnHeight; y++) + { + memcpy(pDestination, pSource, pRead->mnScanlineSize); + pSource += readRowChange; + pDestination += pWrite->mnScanlineSize; + } + } + rWinSalBitmap.ReleaseBuffer(pWrite, BitmapAccessMode::Write); + + rSalBitmap.ReleaseBuffer(pRead, BitmapAccessMode::Read); +} + +} // end anonymous namespace + +void WinSalGraphics::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) +{ + if (dynamic_cast<const WinSalBitmap*>(&rSalBitmap) == nullptr +#if HAVE_FEATURE_SKIA + && dynamic_cast<WinSkiaSalGraphicsImpl*>(mpImpl.get()) == nullptr +#endif + ) + { + std::unique_ptr<WinSalBitmap> pWinSalBitmap(new WinSalBitmap()); + SalBitmap& rConstBitmap = const_cast<SalBitmap&>(rSalBitmap); + convertToWinSalBitmap(rConstBitmap, *pWinSalBitmap); + mpImpl->drawBitmap(rPosAry, *pWinSalBitmap); + } + else + { + mpImpl->drawBitmap(rPosAry, rSalBitmap); + } +} + +void WinSalGraphics::drawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSSalBitmap, + const SalBitmap& rSTransparentBitmap ) +{ + if (dynamic_cast<const WinSalBitmap*>(&rSSalBitmap) == nullptr +#if HAVE_FEATURE_SKIA + && dynamic_cast<WinSkiaSalGraphicsImpl*>(mpImpl.get()) == nullptr +#endif + ) + { + std::unique_ptr<WinSalBitmap> pWinSalBitmap(new WinSalBitmap()); + SalBitmap& rConstBitmap = const_cast<SalBitmap&>(rSSalBitmap); + convertToWinSalBitmap(rConstBitmap, *pWinSalBitmap); + + + std::unique_ptr<WinSalBitmap> pWinTransparentSalBitmap(new WinSalBitmap()); + SalBitmap& rConstTransparentBitmap = const_cast<SalBitmap&>(rSTransparentBitmap); + convertToWinSalBitmap(rConstTransparentBitmap, *pWinTransparentSalBitmap); + + mpImpl->drawBitmap(rPosAry, *pWinSalBitmap, *pWinTransparentSalBitmap); + } + else + { + mpImpl->drawBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap); + } +} + +bool WinSalGraphics::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight, sal_uInt8 nTransparency ) +{ + return mpImpl->drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency ); +} + +void WinSalGraphics::drawMask( const SalTwoRect& rPosAry, + const SalBitmap& rSSalBitmap, + Color nMaskColor ) +{ + mpImpl->drawMask( rPosAry, rSSalBitmap, nMaskColor ); +} + +std::shared_ptr<SalBitmap> WinSalGraphics::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY ) +{ + return mpImpl->getBitmap( nX, nY, nDX, nDY ); +} + +Color WinSalGraphics::getPixel( tools::Long nX, tools::Long nY ) +{ + return mpImpl->getPixel( nX, nY ); +} + +void WinSalGraphics::invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags ) +{ + mpImpl->invert( nX, nY, nWidth, nHeight, nFlags ); +} + +void WinSalGraphics::invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags ) +{ + mpImpl->invert( nPoints, pPtAry, nSalFlags ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salgdi_gdiplus.cxx b/vcl/win/gdi/salgdi_gdiplus.cxx new file mode 100644 index 0000000000..198f755b23 --- /dev/null +++ b/vcl/win/gdi/salgdi_gdiplus.cxx @@ -0,0 +1,104 @@ +/* -*- 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 <string.h> +#include <svsys.h> +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salgdi.h> +#include <win/salbmp.h> + +#include "gdiimpl.hxx" + +void WinSalGraphics::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + mpImpl->drawPolyPolygon( + rObjectToDevice, + rPolyPolygon, + fTransparency); +} + +bool WinSalGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + return mpImpl->drawPolyLine( + rObjectToDevice, + rPolygon, + fTransparency, + fLineWidth, + pStroke, // MM01 + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline); +} + +bool WinSalGraphics::blendBitmap( + const SalTwoRect& rTR, + const SalBitmap& rBmp) +{ + return mpImpl->blendBitmap(rTR, rBmp); +} + +bool WinSalGraphics::blendAlphaBitmap( + const SalTwoRect& rTR, + const SalBitmap& rSrcBmp, + const SalBitmap& rMaskBmp, + const SalBitmap& rAlphaBmp) +{ + return mpImpl->blendAlphaBitmap(rTR, rSrcBmp, rMaskBmp, rAlphaBmp); +} + +bool WinSalGraphics::drawAlphaBitmap( + const SalTwoRect& rTR, + const SalBitmap& rSrcBitmap, + const SalBitmap& rAlphaBmp) +{ + return mpImpl->drawAlphaBitmap(rTR, rSrcBitmap, rAlphaBmp); +} + +bool WinSalGraphics::drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, + double fAlpha) +{ + return mpImpl->drawTransformedBitmap(rNull, rX, rY, + rSourceBitmap, pAlphaBitmap, fAlpha); +} + +bool WinSalGraphics::hasFastDrawTransformedBitmap() const +{ + return mpImpl->hasFastDrawTransformedBitmap(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salnativewidgets-luna.cxx b/vcl/win/gdi/salnativewidgets-luna.cxx new file mode 100644 index 0000000000..318009acad --- /dev/null +++ b/vcl/win/gdi/salnativewidgets-luna.cxx @@ -0,0 +1,1634 @@ +/* -*- 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 . + */ + +// General info: +// http://msdn.microsoft.com/en-us/library/windows/desktop/hh270423%28v=vs.85%29.aspx +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773178%28v=vs.85%29.aspx + +// Useful tool to explore the themes & their rendering: +// http://privat.rejbrand.se/UxExplore.exe +// (found at http://stackoverflow.com/questions/4009701/windows-visual-themes-gallery-of-parts-and-states/4009712#4009712) + +// Theme subclasses: +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773218%28v=vs.85%29.aspx + +// Drawing in non-client area (general DWM-related info): +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb688195%28v=vs.85%29.aspx + +#include <rtl/ustring.h> + +#include <osl/diagnose.h> +#include <osl/module.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <toolbarvalue.hxx> +#include <menubarvalue.hxx> + +#include <win/svsys.h> +#include <win/salgdi.h> +#include <win/saldata.hxx> +#include <win/salframe.h> +#include <win/scoped_gdi.hxx> +#include <win/wingdiimpl.hxx> + +#include <uxtheme.h> +#include <vssym32.h> + +#include <map> +#include <string> +#include <optional> +#include <ControlCacheKey.hxx> + +typedef std::map< std::wstring, HTHEME > ThemeMap; +static ThemeMap aThemeMap; + +/********************************************************* + * Initialize XP theming and local stuff + *********************************************************/ +void SalData::initNWF() +{ + ImplSVData* pSVData = ImplGetSVData(); + + // the menu bar and the top docking area should have a common background (gradient) + pSVData->maNWFData.mbMenuBarDockingAreaCommonBG = true; +} + +// ********************************************************* +// * Release theming handles +// ******************************************************** +void SalData::deInitNWF() +{ + for( auto& rEntry : aThemeMap ) + CloseThemeData(rEntry.second); + aThemeMap.clear(); +} + +static HTHEME getThemeHandle(HWND hWnd, LPCWSTR name, WinSalGraphicsImplBase* pGraphicsImpl) +{ + if( GetSalData()->mbThemeChanged ) + { + // throw away invalid theme handles + SalData::deInitNWF(); + // throw away native control cache + pGraphicsImpl->ClearNativeControlCache(); + GetSalData()->mbThemeChanged = false; + } + + ThemeMap::iterator iter; + if( (iter = aThemeMap.find( name )) != aThemeMap.end() ) + return iter->second; + // theme not found -> add it to map + HTHEME hTheme = OpenThemeData( hWnd, name ); + if( hTheme != nullptr ) + aThemeMap[name] = hTheme; + return hTheme; +} + +bool WinSalGraphics::isNativeControlSupported( ControlType nType, ControlPart nPart ) +{ + HTHEME hTheme = nullptr; + + switch( nType ) + { + case ControlType::Pushbutton: + case ControlType::Radiobutton: + case ControlType::Checkbox: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Button", mWinSalGraphicsImplBase); + break; + case ControlType::Scrollbar: + if( nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert ) + return false; // no background painting needed + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Scrollbar", mWinSalGraphicsImplBase); + break; + case ControlType::Combobox: + if( nPart == ControlPart::HasBackgroundTexture ) + return false; // we do not paint the inner part (ie the selection background/focus indication) + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase); + else if( nPart == ControlPart::ButtonDown ) + hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase); + break; + case ControlType::Spinbox: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase); + else if( nPart == ControlPart::AllButtons || + nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown || + nPart == ControlPart::ButtonLeft|| nPart == ControlPart::ButtonRight ) + hTheme = getThemeHandle(mhWnd, L"Spin", mWinSalGraphicsImplBase); + break; + case ControlType::SpinButtons: + if( nPart == ControlPart::Entire || nPart == ControlPart::AllButtons ) + hTheme = getThemeHandle(mhWnd, L"Spin", mWinSalGraphicsImplBase); + break; + case ControlType::Editbox: + case ControlType::MultilineEditbox: + if( nPart == ControlPart::HasBackgroundTexture ) + return false; // we do not paint the inner part (ie the selection background/focus indication) + //return TRUE; + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase); + break; + case ControlType::Listbox: + if( nPart == ControlPart::HasBackgroundTexture ) + return false; // we do not paint the inner part (ie the selection background/focus indication) + if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow ) + hTheme = getThemeHandle(mhWnd, L"Listview", mWinSalGraphicsImplBase); + else if( nPart == ControlPart::ButtonDown ) + hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase); + break; + case ControlType::TabPane: + case ControlType::TabBody: + case ControlType::TabItem: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Tab", mWinSalGraphicsImplBase); + break; + case ControlType::Toolbar: + if( nPart == ControlPart::Entire || nPart == ControlPart::Button ) + hTheme = getThemeHandle(mhWnd, L"Toolbar", mWinSalGraphicsImplBase); + else + // use rebar theme for grip and background + hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase); + break; + case ControlType::Menubar: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase); + else if( GetSalData()->mbThemeMenuSupport ) + { + if( nPart == ControlPart::MenuItem ) + hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase); + } + break; + case ControlType::MenuPopup: + if( GetSalData()->mbThemeMenuSupport ) + { + if( nPart == ControlPart::Entire || + nPart == ControlPart::MenuItem || + nPart == ControlPart::MenuItemCheckMark || + nPart == ControlPart::MenuItemRadioMark || + nPart == ControlPart::Separator ) + hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase); + } + break; + case ControlType::Progress: + case ControlType::LevelBar: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Progress", mWinSalGraphicsImplBase); + break; + case ControlType::Slider: + if( nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea ) + hTheme = getThemeHandle(mhWnd, L"Trackbar", mWinSalGraphicsImplBase); + break; + case ControlType::ListNode: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"TreeView", mWinSalGraphicsImplBase); + break; + default: + hTheme = nullptr; + break; + } + + return (hTheme != nullptr); +} + +bool WinSalGraphics::hitTestNativeControl( ControlType, + ControlPart, + const tools::Rectangle&, + const Point&, + bool& ) +{ + return false; +} + +static bool ImplDrawTheme( HTHEME hTheme, HDC hDC, int iPart, int iState, RECT rc, const OUString& aStr) +{ + HRESULT hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + + if( aStr.getLength() ) + { + RECT rcContent; + hr = GetThemeBackgroundContentRect( hTheme, hDC, iPart, iState, &rc, &rcContent); + hr = DrawThemeText( hTheme, hDC, iPart, iState, + o3tl::toW(aStr.getStr()), -1, + DT_CENTER | DT_VCENTER | DT_SINGLELINE, + 0, &rcContent); + } + return (hr == S_OK); +} + +// TS_TRUE returns optimal size +static std::optional<Size> ImplGetThemeSize(HTHEME hTheme, HDC hDC, int iPart, int iState, LPCRECT pRect, THEMESIZE eTS = TS_TRUE) +{ + if (SIZE aSz; SUCCEEDED(GetThemePartSize(hTheme, hDC, iPart, iState, pRect, eTS, &aSz))) + return Size(aSz.cx, aSz.cy); + return {}; +} + +static tools::Rectangle ImplGetThemeRect( HTHEME hTheme, HDC hDC, int iPart, int iState, const tools::Rectangle& /* aRect */, THEMESIZE eTS = TS_TRUE ) +{ + if (const std::optional<Size> oSz = ImplGetThemeSize(hTheme, hDC, iPart, iState, nullptr, eTS)) + return tools::Rectangle( 0, 0, oSz->Width(), oSz->Height() ); + else + return tools::Rectangle(); +} + +// Helper functions + +static void ImplConvertSpinbuttonValues( ControlPart nControlPart, const ControlState& rState, const tools::Rectangle& rRect, + int* pLunaPart, int *pLunaState, RECT *pRect ) +{ + if( nControlPart == ControlPart::ButtonDown ) + { + *pLunaPart = SPNP_DOWN; + if( rState & ControlState::PRESSED ) + *pLunaState = DNS_PRESSED; + else if( !(rState & ControlState::ENABLED) ) + *pLunaState = DNS_DISABLED; + else if( rState & ControlState::ROLLOVER ) + *pLunaState = DNS_HOT; + else + *pLunaState = DNS_NORMAL; + } + if( nControlPart == ControlPart::ButtonUp ) + { + *pLunaPart = SPNP_UP; + if( rState & ControlState::PRESSED ) + *pLunaState = UPS_PRESSED; + else if( !(rState & ControlState::ENABLED) ) + *pLunaState = UPS_DISABLED; + else if( rState & ControlState::ROLLOVER ) + *pLunaState = UPS_HOT; + else + *pLunaState = UPS_NORMAL; + } + if( nControlPart == ControlPart::ButtonRight ) + { + *pLunaPart = SPNP_UPHORZ; + if( rState & ControlState::PRESSED ) + *pLunaState = DNHZS_PRESSED; + else if( !(rState & ControlState::ENABLED) ) + *pLunaState = DNHZS_DISABLED; + else if( rState & ControlState::ROLLOVER ) + *pLunaState = DNHZS_HOT; + else + *pLunaState = DNHZS_NORMAL; + } + if( nControlPart == ControlPart::ButtonLeft ) + { + *pLunaPart = SPNP_DOWNHORZ; + if( rState & ControlState::PRESSED ) + *pLunaState = UPHZS_PRESSED; + else if( !(rState & ControlState::ENABLED) ) + *pLunaState = UPHZS_DISABLED; + else if( rState & ControlState::ROLLOVER ) + *pLunaState = UPHZS_HOT; + else + *pLunaState = UPHZS_NORMAL; + } + + pRect->left = rRect.Left(); + pRect->right = rRect.Right()+1; + pRect->top = rRect.Top(); + pRect->bottom = rRect.Bottom()+1; +} + +/// Draw an own toolbar style on Windows Vista or later, looks better there +static void impl_drawAeroToolbar( HDC hDC, RECT rc, bool bHorizontal ) +{ + if ( rc.top == 0 && bHorizontal ) + { + const int GRADIENT_HEIGHT = 32; + + LONG gradient_break = rc.top; + LONG gradient_bottom = rc.bottom - 1; + GRADIENT_RECT g_rect[1] = { { 0, 1 } }; + + // very slow gradient at the top (if we have space for that) + if ( gradient_bottom - rc.top > GRADIENT_HEIGHT ) + { + gradient_break = gradient_bottom - GRADIENT_HEIGHT; + + TRIVERTEX vert[2] = { + { rc.left, rc.top, 0xff00, 0xff00, 0xff00, 0xff00 }, + { rc.right, gradient_break, 0xfa00, 0xfa00, 0xfa00, 0xff00 }, + }; + GdiGradientFill( hDC, vert, 2, g_rect, 1, GRADIENT_FILL_RECT_V ); + } + + // gradient at the bottom + TRIVERTEX vert[2] = { + { rc.left, gradient_break, 0xfa00, 0xfa00, 0xfa00, 0xff00 }, + { rc.right, gradient_bottom, 0xf000, 0xf000, 0xf000, 0xff00 } + }; + GdiGradientFill( hDC, vert, 2, g_rect, 1, GRADIENT_FILL_RECT_V ); + + // and a darker horizontal line under that + ScopedSelectedHPEN hPen(hDC, CreatePen(PS_SOLID, 1, RGB( 0xb0, 0xb0, 0xb0))); + + MoveToEx( hDC, rc.left, gradient_bottom, nullptr ); + LineTo( hDC, rc.right, gradient_bottom ); + } + else + { + ScopedHBRUSH hbrush(CreateSolidBrush(RGB(0xf0, 0xf0, 0xf0))); + FillRect(hDC, &rc, hbrush.get()); + + // darker line to distinguish the toolbar and viewshell + // it is drawn only for the horizontal toolbars; it did not look well + // when done for the vertical ones too + if ( bHorizontal ) + { + LONG from_x, from_y, to_x, to_y; + + from_x = rc.left; + to_x = rc.right; + from_y = to_y = rc.top; + + ScopedSelectedHPEN hPen(hDC, CreatePen(PS_SOLID, 1, RGB( 0xb0, 0xb0, 0xb0))); + + MoveToEx( hDC, from_x, from_y, nullptr ); + LineTo( hDC, to_x, to_y ); + } + } +} + +static bool implDrawNativeMenuMark(HDC hDC, HTHEME hTheme, RECT rc, ControlPart nPart, + ControlState nState, OUString const& aCaption) +{ + int iState = (nState & ControlState::ENABLED) ? MCB_NORMAL : MCB_DISABLED; + ImplDrawTheme(hTheme, hDC, MENU_POPUPCHECKBACKGROUND, iState, rc, aCaption); + if (nPart == ControlPart::MenuItemCheckMark) + iState = (nState & ControlState::ENABLED) ? MC_CHECKMARKNORMAL : MC_CHECKMARKDISABLED; + else + iState = (nState & ControlState::ENABLED) ? MC_BULLETNORMAL : MC_BULLETDISABLED; + // tdf#133697: Get true size of mark, to avoid stretching + if (auto oSize = ImplGetThemeSize(hTheme, hDC, MENU_POPUPCHECK, iState, &rc)) + { + // center the mark inside the passed rectangle + if (const auto dx = (rc.right - rc.left - oSize->Width() + 1) / 2; dx > 0) + { + rc.left += dx; + rc.right = rc.left + oSize->Width(); + } + if (const auto dy = (rc.bottom - rc.top - oSize->Height() + 1) / 2; dy > 0) + { + rc.top += dy; + rc.bottom = rc.top + oSize->Height(); + } + } + return ImplDrawTheme(hTheme, hDC, MENU_POPUPCHECK, iState, rc, aCaption); +} + +bool UseDarkMode() +{ + static bool bOSSupportsDarkMode = OSSupportsDarkMode(); + if (!bOSSupportsDarkMode) + return false; + + bool bRet(false); + switch (MiscSettings::GetDarkMode()) + { + case 0: // auto + default: + { + HINSTANCE hUxthemeLib = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!hUxthemeLib) + return false; + + typedef bool(WINAPI* ShouldAppsUseDarkMode_t)(); + if (auto ShouldAppsUseDarkMode = reinterpret_cast<ShouldAppsUseDarkMode_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(132)))) + bRet = ShouldAppsUseDarkMode(); + + FreeLibrary(hUxthemeLib); + break; + } + case 1: // light + bRet = false; + break; + case 2: // dark + bRet = true; + break; + } + + return bRet; +} + +static bool ImplDrawNativeControl( HDC hDC, HTHEME hTheme, RECT rc, + ControlType nType, + ControlPart nPart, + ControlState nState, + const ImplControlValue& aValue, + OUString const & aCaption, + bool bUseDarkMode ) +{ + // a listbox dropdown is actually a combobox dropdown + if( nType == ControlType::Listbox ) + if( nPart == ControlPart::ButtonDown ) + nType = ControlType::Combobox; + + // draw entire combobox as a large edit box + if( nType == ControlType::Combobox ) + if( nPart == ControlPart::Entire ) + nType = ControlType::Editbox; + + // draw entire spinbox as a large edit box + if( nType == ControlType::Spinbox ) + if( nPart == ControlPart::Entire ) + nType = ControlType::Editbox; + + int iPart(0), iState(0); + if( nType == ControlType::Scrollbar ) + { + HRESULT hr; + if( nPart == ControlPart::ButtonUp ) + { + iPart = SBP_ARROWBTN; + if( nState & ControlState::PRESSED ) + iState = ABS_UPPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = ABS_UPDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = ABS_UPHOT; + else + iState = ABS_UPNORMAL; + hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + if( nPart == ControlPart::ButtonDown ) + { + iPart = SBP_ARROWBTN; + if( nState & ControlState::PRESSED ) + iState = ABS_DOWNPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = ABS_DOWNDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = ABS_DOWNHOT; + else + iState = ABS_DOWNNORMAL; + hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + if( nPart == ControlPart::ButtonLeft ) + { + iPart = SBP_ARROWBTN; + if( nState & ControlState::PRESSED ) + iState = ABS_LEFTPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = ABS_LEFTDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = ABS_LEFTHOT; + else + iState = ABS_LEFTNORMAL; + hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + if( nPart == ControlPart::ButtonRight ) + { + iPart = SBP_ARROWBTN; + if( nState & ControlState::PRESSED ) + iState = ABS_RIGHTPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = ABS_RIGHTDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = ABS_RIGHTHOT; + else + iState = ABS_RIGHTNORMAL; + hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert ) + { + iPart = (nPart == ControlPart::ThumbHorz) ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT; + if( nState & ControlState::PRESSED ) + iState = SCRBS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = SCRBS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = SCRBS_HOT; + else + iState = SCRBS_NORMAL; + + SIZE sz; + GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_MIN, &sz); + GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_TRUE, &sz); + GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_DRAW, &sz); + + hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + // paint gripper on thumb if enough space + if( ( (nPart == ControlPart::ThumbVert) && (rc.bottom-rc.top > 12) ) || + ( (nPart == ControlPart::ThumbHorz) && (rc.right-rc.left > 12) ) ) + { + iPart = (nPart == ControlPart::ThumbHorz) ? SBP_GRIPPERHORZ : SBP_GRIPPERVERT; + iState = 0; + DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + } + return (hr == S_OK); + } + if( nPart == ControlPart::TrackHorzLeft || nPart == ControlPart::TrackHorzRight || nPart == ControlPart::TrackVertUpper || nPart == ControlPart::TrackVertLower ) + { + switch( nPart ) + { + case ControlPart::TrackHorzLeft: iPart = SBP_UPPERTRACKHORZ; break; + case ControlPart::TrackHorzRight: iPart = SBP_LOWERTRACKHORZ; break; + case ControlPart::TrackVertUpper: iPart = SBP_UPPERTRACKVERT; break; + case ControlPart::TrackVertLower: iPart = SBP_LOWERTRACKVERT; break; + default: break; + } + + if( nState & ControlState::PRESSED ) + iState = SCRBS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = SCRBS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = SCRBS_HOT; + else + iState = SCRBS_NORMAL; + hr = DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + } + if( nType == ControlType::SpinButtons && nPart == ControlPart::AllButtons ) + { + if( aValue.getType() == ControlType::SpinButtons ) + { + const SpinbuttonValue* pValue = (aValue.getType() == ControlType::SpinButtons) ? static_cast<const SpinbuttonValue*>(&aValue) : nullptr; + + RECT rect; + ImplConvertSpinbuttonValues( pValue->mnUpperPart, pValue->mnUpperState, pValue->maUpperRect, &iPart, &iState, &rect ); + bool bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption); + + if( bOk ) + { + ImplConvertSpinbuttonValues( pValue->mnLowerPart, pValue->mnLowerState, pValue->maLowerRect, &iPart, &iState, &rect ); + bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption); + } + + return bOk; + } + } + if( nType == ControlType::Spinbox ) + { + if( nPart == ControlPart::AllButtons ) + { + if( aValue.getType() == ControlType::SpinButtons ) + { + const SpinbuttonValue *pValue = static_cast<const SpinbuttonValue*>(&aValue); + + RECT rect; + ImplConvertSpinbuttonValues( pValue->mnUpperPart, pValue->mnUpperState, pValue->maUpperRect, &iPart, &iState, &rect ); + bool bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption); + + if( bOk ) + { + ImplConvertSpinbuttonValues( pValue->mnLowerPart, pValue->mnLowerState, pValue->maLowerRect, &iPart, &iState, &rect ); + bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption); + } + + return bOk; + } + } + + if( nPart == ControlPart::ButtonDown ) + { + iPart = SPNP_DOWN; + if( nState & ControlState::PRESSED ) + iState = DNS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = DNS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = DNS_HOT; + else + iState = DNS_NORMAL; + } + if( nPart == ControlPart::ButtonUp ) + { + iPart = SPNP_UP; + if( nState & ControlState::PRESSED ) + iState = UPS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = UPS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = UPS_HOT; + else + iState = UPS_NORMAL; + } + if( nPart == ControlPart::ButtonRight ) + { + iPart = SPNP_DOWNHORZ; + if( nState & ControlState::PRESSED ) + iState = DNHZS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = DNHZS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = DNHZS_HOT; + else + iState = DNHZS_NORMAL; + } + if( nPart == ControlPart::ButtonLeft ) + { + iPart = SPNP_UPHORZ; + if( nState & ControlState::PRESSED ) + iState = UPHZS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = UPHZS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = UPHZS_HOT; + else + iState = UPHZS_NORMAL; + } + if( nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight || nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown ) + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + if( nType == ControlType::Combobox ) + { + if( nPart == ControlPart::ButtonDown ) + { + iPart = CP_DROPDOWNBUTTON; + if( nState & ControlState::PRESSED ) + iState = CBXS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = CBXS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = CBXS_HOT; + else + iState = CBXS_NORMAL; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + } + if( nType == ControlType::Pushbutton ) + { + iPart = BP_PUSHBUTTON; + if( nState & ControlState::PRESSED ) + iState = PBS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = PBS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = PBS_HOT; + else if( nState & ControlState::DEFAULT ) + iState = PBS_DEFAULTED; + //else if( nState & ControlState::FOCUSED ) + // iState = PBS_DEFAULTED; // may need to draw focus rect + else + iState = PBS_NORMAL; + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::Radiobutton ) + { + iPart = BP_RADIOBUTTON; + bool bChecked = ( aValue.getTristateVal() == ButtonValue::On ); + + if( nState & ControlState::PRESSED ) + iState = bChecked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = bChecked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = bChecked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT; + else + iState = bChecked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL; + + //if( nState & ControlState::FOCUSED ) + // iState |= PBS_DEFAULTED; // may need to draw focus rect + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::Checkbox ) + { + iPart = BP_CHECKBOX; + ButtonValue v = aValue.getTristateVal(); + + if( nState & ControlState::PRESSED ) + iState = (v == ButtonValue::On) ? CBS_CHECKEDPRESSED : + ( (v == ButtonValue::Off) ? CBS_UNCHECKEDPRESSED : CBS_MIXEDPRESSED ); + else if( !(nState & ControlState::ENABLED) ) + iState = (v == ButtonValue::On) ? CBS_CHECKEDDISABLED : + ( (v == ButtonValue::Off) ? CBS_UNCHECKEDDISABLED : CBS_MIXEDDISABLED ); + else if( nState & ControlState::ROLLOVER ) + iState = (v == ButtonValue::On) ? CBS_CHECKEDHOT : + ( (v == ButtonValue::Off) ? CBS_UNCHECKEDHOT : CBS_MIXEDHOT ); + else + iState = (v == ButtonValue::On) ? CBS_CHECKEDNORMAL : + ( (v == ButtonValue::Off) ? CBS_UNCHECKEDNORMAL : CBS_MIXEDNORMAL ); + + //if( nState & ControlState::FOCUSED ) + // iState |= PBS_DEFAULTED; // may need to draw focus rect + + //SIZE sz; + //THEMESIZE eSize = TS_DRAW; // TS_MIN, TS_TRUE, TS_DRAW + //GetThemePartSize( hTheme, hDC, iPart, iState, &rc, eSize, &sz); + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if (nType == ControlType::Editbox) + { + iPart = EP_EDITBORDER_NOSCROLL; + if( !(nState & ControlState::ENABLED) ) + iState = EPSN_DISABLED; + else if( nState & ControlState::FOCUSED ) + iState = EPSN_FOCUSED; + else if( nState & ControlState::ROLLOVER ) + iState = EPSN_HOT; + else + iState = EPSN_NORMAL; + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if (nType == ControlType::MultilineEditbox) + { + iPart = EP_EDITTEXT; + if( !(nState & ControlState::ENABLED) ) + iState = ETS_DISABLED; + else if( nState & ControlState::FOCUSED ) + iState = ETS_FOCUSED; + else if( nState & ControlState::ROLLOVER ) + iState = ETS_HOT; + else + iState = ETS_NORMAL; + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::Listbox ) + { + if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow ) + { + iPart = LVP_EMPTYTEXT; // ??? no idea which part to choose here + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + } + + if( nType == ControlType::TabPane ) + { + // tabpane in tabcontrols gets drawn in "darkmode" as if it was a + // a "light" theme, so bodge this by drawing a frame directly + if (bUseDarkMode) + { + Color aColor(Application::GetSettings().GetStyleSettings().GetDisableColor()); + ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(), + aColor.GetGreen(), + aColor.GetBlue()))); + FrameRect(hDC, &rc, hbrush.get()); + return true; + } + iPart = TABP_PANE; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::TabBody ) + { + // tabbody in main window gets drawn in white in "darkmode", so bodge this here + if (bUseDarkMode) + { + Color aColor(Application::GetSettings().GetStyleSettings().GetWindowColor()); + ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(), + aColor.GetGreen(), + aColor.GetBlue()))); + FillRect(hDC, &rc, hbrush.get()); + return true; + } + + iPart = TABP_BODY; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::TabItem ) + { + iPart = TABP_TABITEMLEFTEDGE; + rc.bottom--; + + OSL_ASSERT( aValue.getType() == ControlType::TabItem ); + + const TabitemValue& rValue = static_cast<const TabitemValue&>(aValue); + if (rValue.isBothAligned()) + { + iPart = TABP_TABITEMLEFTEDGE; + rc.right--; + } + else if (rValue.isLeftAligned()) + iPart = TABP_TABITEMLEFTEDGE; + else if (rValue.isRightAligned()) + iPart = TABP_TABITEMRIGHTEDGE; + else + iPart = TABP_TABITEM; + + if( !(nState & ControlState::ENABLED) ) + iState = TILES_DISABLED; + else if( nState & ControlState::SELECTED ) + { + iState = TILES_SELECTED; + // increase the selected tab + rc.left-=2; + if (rValue.isBothAligned()) + { + if (rValue.isLeftAligned() || rValue.isNotAligned()) + rc.right+=2; + if (rValue.isRightAligned()) + rc.right+=1; + } + rc.top-=2; + rc.bottom+=2; + } + else if( nState & ControlState::ROLLOVER ) + iState = TILES_HOT; + else if( nState & ControlState::FOCUSED ) + iState = TILES_FOCUSED; // may need to draw focus rect + else + iState = TILES_NORMAL; + + // tabitem in tabcontrols gets drawn in "darkmode" as if it was a + // a "light" theme, so bodge this by drawing with a button instead + if (bUseDarkMode) + { + Color aColor; + if (iState == TILES_SELECTED) + aColor = Application::GetSettings().GetStyleSettings().GetActiveTabColor(); + else + aColor = Application::GetSettings().GetStyleSettings().GetInactiveTabColor(); + ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(), + aColor.GetGreen(), + aColor.GetBlue()))); + FillRect(hDC, &rc, hbrush.get()); + + aColor = Application::GetSettings().GetStyleSettings().GetDisableColor(); + ScopedSelectedHPEN hPen(hDC, CreatePen(PS_SOLID, 1, RGB(aColor.GetRed(), + aColor.GetGreen(), + aColor.GetBlue()))); + POINT apt[4]; + apt[0].x = rc.left; + apt[0].y = rc.bottom - (iPart == TABP_TABITEMLEFTEDGE ? 1 : 2); + apt[1].x = rc.left; + apt[1].y = rc.top; + apt[2].x = rc.right; + apt[2].y = rc.top; + apt[3].x = rc.right; + apt[3].y = rc.bottom - 1; + Polyline(hDC, apt, SAL_N_ELEMENTS(apt)); + return true; + } + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::Toolbar ) + { + if( nPart == ControlPart::Button ) + { + iPart = TP_BUTTON; + bool bChecked = ( aValue.getTristateVal() == ButtonValue::On ); + if( !(nState & ControlState::ENABLED) ) + //iState = TS_DISABLED; + // disabled buttons are typically not painted at all but we need visual + // feedback when travelling by keyboard over disabled entries + iState = TS_HOT; + else if( nState & ControlState::PRESSED ) + iState = TS_PRESSED; + else if( nState & ControlState::ROLLOVER ) + iState = bChecked ? TS_HOTCHECKED : TS_HOT; + else + iState = bChecked ? TS_CHECKED : TS_NORMAL; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + else if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert ) + { + // the vertical gripper is not supported in most themes and it makes no + // sense to only support horizontal gripper + //iPart = (nPart == ControlPart::ThumbHorz) ? RP_GRIPPERVERT : RP_GRIPPER; + //return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + else if( nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert ) + { + if( aValue.getType() == ControlType::Toolbar ) + { + const ToolbarValue *pValue = static_cast<const ToolbarValue*>(&aValue); + if( pValue->mbIsTopDockingArea ) + rc.top = 0; // extend potential gradient to cover menu bar as well + } + + // toolbar in main window gets drawn in white in "darkmode", so bodge this here + if (bUseDarkMode) + { + Color aColor(Application::GetSettings().GetStyleSettings().GetWindowColor()); + ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(), + aColor.GetGreen(), + aColor.GetBlue()))); + FillRect(hDC, &rc, hbrush.get()); + return true; + } + + // make it more compatible with Aero + if (ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames && + !Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + impl_drawAeroToolbar( hDC, rc, nPart == ControlPart::DrawBackgroundHorz ); + return true; + } + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + } + + if( nType == ControlType::Menubar ) + { + if( nPart == ControlPart::Entire ) + { + if( aValue.getType() == ControlType::Menubar ) + { + const MenubarValue *pValue = static_cast<const MenubarValue*>(&aValue); + rc.bottom += pValue->maTopDockingAreaHeight; // extend potential gradient to cover docking area as well + + // menubar in main window gets drawn in white in "darkmode", so bodge this here + if (bUseDarkMode) + { + Color aColor(Application::GetSettings().GetStyleSettings().GetWindowColor()); + ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(), + aColor.GetGreen(), + aColor.GetBlue()))); + FillRect(hDC, &rc, hbrush.get()); + return true; + } + + // make it more compatible with Aero + if (ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames && + !Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + impl_drawAeroToolbar( hDC, rc, true ); + return true; + } + } + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + else if( nPart == ControlPart::MenuItem ) + { + if( nState & ControlState::ENABLED ) + { + if( nState & ControlState::SELECTED ) + iState = MBI_PUSHED; + else if( nState & ControlState::ROLLOVER ) + iState = MBI_HOT; + else + iState = MBI_NORMAL; + + if(GetSalData()->mbThemeMenuSupport && Application::GetSettings().GetStyleSettings().GetHighContrastMode() + && ( nState & (ControlState::SELECTED | nState & ControlState::ROLLOVER ))) + { + Color aColor(Application::GetSettings().GetStyleSettings().GetHighlightColor()); + ScopedHBRUSH hbrush(CreateSolidBrush(RGB(aColor.GetRed(), + aColor.GetGreen(), + aColor.GetBlue()))); + FillRect(hDC, &rc, hbrush.get()); + return true; + } + } + else + { + if( nState & ControlState::SELECTED ) + iState = MBI_DISABLEDPUSHED; + else if( nState & ControlState::ROLLOVER ) + iState = MBI_DISABLEDHOT; + else + iState = MBI_DISABLED; + } + return ImplDrawTheme( hTheme, hDC, MENU_BARITEM, iState, rc, aCaption ); + } + } + + if( nType == ControlType::Progress || nType == ControlType::LevelBar ) + { + if( nPart != ControlPart::Entire ) + return false; + + int nPartIdBackground = PP_BAR; + if( nType == ControlType::LevelBar ) + { + nPartIdBackground = PP_TRANSPARENTBAR; + iState = PBBS_PARTIAL; + } + + if( ! ImplDrawTheme( hTheme, hDC, nPartIdBackground, iState, rc, aCaption) ) + return false; + RECT aProgressRect = rc; + if( GetThemeBackgroundContentRect( hTheme, hDC, PP_BAR, iState, &rc, &aProgressRect) != S_OK ) + return false; + + tools::Long nProgressWidth = aValue.getNumericVal(); + nProgressWidth *= (aProgressRect.right - aProgressRect.left); + nProgressWidth /= (rc.right - rc.left); + if( AllSettings::GetLayoutRTL() ) + aProgressRect.left = aProgressRect.right - nProgressWidth; + else + aProgressRect.right = aProgressRect.left + nProgressWidth; + + if (nType == ControlType::LevelBar) + { + const auto nPercentage + = aValue.getNumericVal() * 100 / std::max(LONG{ 1 }, (rc.right - rc.left)); + + COLORREF aBrushColor{}; + if (nPercentage < 25) + aBrushColor = RGB(255, 0, 0); + else if (nPercentage < 50) + aBrushColor = RGB(255, 255, 0); + else if (nPercentage < 75) + aBrushColor = RGB(0, 0, 255); + else + aBrushColor = RGB(0, 255, 0); + + ScopedHBRUSH hBrush(CreateSolidBrush(aBrushColor)); + FillRect(hDC, &aProgressRect, hBrush.get()); + return true; + } + + return ImplDrawTheme( hTheme, hDC, PP_CHUNK, iState, aProgressRect, aCaption ); + } + + if( nType == ControlType::Slider ) + { + iPart = (nPart == ControlPart::TrackHorzArea) ? TKP_TRACK : TKP_TRACKVERT; + iState = (nPart == ControlPart::TrackHorzArea) ? static_cast<int>(TRS_NORMAL) : static_cast<int>(TRVS_NORMAL); + + tools::Rectangle aTrackRect = ImplGetThemeRect( hTheme, hDC, iPart, iState, tools::Rectangle() ); + RECT aTRect = rc; + if( nPart == ControlPart::TrackHorzArea ) + { + tools::Long nH = aTrackRect.GetHeight(); + aTRect.top += (rc.bottom - rc.top - nH)/2; + aTRect.bottom = aTRect.top + nH; + } + else + { + tools::Long nW = aTrackRect.GetWidth(); + aTRect.left += (rc.right - rc.left - nW)/2; + aTRect.right = aTRect.left + nW; + } + ImplDrawTheme( hTheme, hDC, iPart, iState, aTRect, aCaption ); + + RECT aThumbRect; + OSL_ASSERT( aValue.getType() == ControlType::Slider ); + const SliderValue* pVal = static_cast<const SliderValue*>(&aValue); + aThumbRect.left = pVal->maThumbRect.Left(); + aThumbRect.top = pVal->maThumbRect.Top(); + aThumbRect.right = pVal->maThumbRect.Right(); + aThumbRect.bottom = pVal->maThumbRect.Bottom(); + iPart = (nPart == ControlPart::TrackHorzArea) ? TKP_THUMB : TKP_THUMBVERT; + iState = (nState & ControlState::ENABLED) ? TUS_NORMAL : TUS_DISABLED; + return ImplDrawTheme( hTheme, hDC, iPart, iState, aThumbRect, aCaption ); + } + + if( nType == ControlType::ListNode ) + { + if( nPart != ControlPart::Entire ) + return false; + + ButtonValue aButtonValue = aValue.getTristateVal(); + iPart = TVP_GLYPH; + switch( aButtonValue ) + { + case ButtonValue::On: + iState = GLPS_OPENED; + break; + case ButtonValue::Off: + iState = GLPS_CLOSED; + break; + default: + return false; + } + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption ); + } + + if( GetSalData()->mbThemeMenuSupport ) + { + if( nType == ControlType::MenuPopup ) + { + if( nPart == ControlPart::Entire ) + { + RECT aGutterRC = rc; + if( AllSettings::GetLayoutRTL() ) + { + aGutterRC.right -= aValue.getNumericVal()+1; + aGutterRC.left = aGutterRC.right-3; + } + else + { + aGutterRC.left += aValue.getNumericVal(); + aGutterRC.right = aGutterRC.left+3; + } + return + ImplDrawTheme( hTheme, hDC, MENU_POPUPBACKGROUND, 0, rc, aCaption ) && + ImplDrawTheme( hTheme, hDC, MENU_POPUPGUTTER, 0, aGutterRC, aCaption ) + ; + } + else if( nPart == ControlPart::MenuItem ) + { + if( nState & ControlState::ENABLED ) + iState = (nState & ControlState::SELECTED) ? MPI_HOT : MPI_NORMAL; + else + iState = (nState & ControlState::SELECTED) ? MPI_DISABLEDHOT : MPI_DISABLED; + return ImplDrawTheme( hTheme, hDC, MENU_POPUPITEM, iState, rc, aCaption ); + } + else if( nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark ) + { + if (nState & ControlState::PRESSED) + return implDrawNativeMenuMark(hDC, hTheme, rc, nPart, nState, aCaption); + else + return true; // unchecked: do nothing + } + else if( nPart == ControlPart::Separator ) + { + // adjust for gutter position + if( AllSettings::GetLayoutRTL() ) + rc.right -= aValue.getNumericVal()+1; + else + rc.left += aValue.getNumericVal()+1; + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, + MENU_POPUPSEPARATOR, 0, tools::Rectangle( rc.left, rc.top, rc.right, rc.bottom ) ) ); + // center the separator inside the passed rectangle + auto const nDY = ((rc.bottom - rc.top + 1) - aRect.GetHeight()) / 2; + rc.top += nDY; + rc.bottom = rc.top+aRect.GetHeight()-1; + return ImplDrawTheme( hTheme, hDC, MENU_POPUPSEPARATOR, 0, rc, aCaption ); + } + } + } + + return false; +} + +bool WinSalGraphics::drawNativeControl( ControlType nType, + ControlPart nPart, + const tools::Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& aValue, + const OUString& aCaption, + const Color& /*rBackgroundColor*/ ) +{ + bool bOk = false; + HTHEME hTheme = nullptr; + + tools::Rectangle buttonRect = rControlRegion; + tools::Rectangle cacheRect = rControlRegion; + Size keySize = cacheRect.GetSize(); + + WinSalGraphicsImplBase* pImpl = mWinSalGraphicsImplBase; + if( !pImpl->UseRenderNativeControl()) + pImpl = nullptr; + + // tdf#95618 - A few controls render outside the region they're given. + if (pImpl && nType == ControlType::TabItem) + { + tools::Rectangle rNativeBoundingRegion; + tools::Rectangle rNativeContentRegion; + if (getNativeControlRegion(nType, nPart, rControlRegion, nState, aValue, aCaption, + rNativeBoundingRegion, rNativeContentRegion)) + { + cacheRect = rNativeBoundingRegion; + keySize = rNativeBoundingRegion.GetSize(); + } + } + + + ControlCacheKey aControlCacheKey(nType, nPart, nState, keySize); + if (pImpl != nullptr && pImpl->TryRenderCachedNativeControl(aControlCacheKey, buttonRect.Left(), buttonRect.Top())) + { + return true; + } + + const bool bUseDarkMode = UseDarkMode(); + if (bUseDarkMode) + SetWindowTheme(mhWnd, L"Explorer", nullptr); + + switch( nType ) + { + case ControlType::Pushbutton: + case ControlType::Radiobutton: + case ControlType::Checkbox: + hTheme = getThemeHandle(mhWnd, L"Button", mWinSalGraphicsImplBase); + break; + case ControlType::Scrollbar: + if (bUseDarkMode) + { + // tdf#153273 undo the earlier SetWindowTheme, and use an explicit Explorer::Scrollbar + // a) with "Scrollbar" and SetWindowTheme(... "Explorer" ...) then scrollbars in dialog + // and main windows are dark, but dropdowns are light + // b) with "Explorer::Scrollbar" and SetWindowTheme(... "Explorer" ...) then scrollbars + // in dropdowns are dark, but scrollbars in dialogs and main windows are sort of "extra + // dark" + // c) with "Explorer::Scrollbar" and no SetWindowTheme both cases are dark + SetWindowTheme(mhWnd, nullptr, nullptr); + hTheme = getThemeHandle(mhWnd, L"Explorer::Scrollbar", mWinSalGraphicsImplBase); + } + else + hTheme = getThemeHandle(mhWnd, L"Scrollbar", mWinSalGraphicsImplBase); + break; + case ControlType::Combobox: + if( nPart == ControlPart::Entire ) + { + if (bUseDarkMode && !(nState & ControlState::FOCUSED)) + SetWindowTheme(mhWnd, L"CFD", nullptr); + hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase); + } + else if( nPart == ControlPart::ButtonDown ) + { + if (bUseDarkMode) + SetWindowTheme(mhWnd, L"CFD", nullptr); + hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase); + } + break; + case ControlType::Spinbox: + if( nPart == ControlPart::Entire ) + { + if (bUseDarkMode && !(nState & ControlState::FOCUSED)) + SetWindowTheme(mhWnd, L"CFD", nullptr); + hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase); + } + else + hTheme = getThemeHandle(mhWnd, L"Spin", mWinSalGraphicsImplBase); + break; + case ControlType::SpinButtons: + hTheme = getThemeHandle(mhWnd, L"Spin", mWinSalGraphicsImplBase); + break; + case ControlType::Editbox: + if (bUseDarkMode && !(nState & ControlState::FOCUSED)) + SetWindowTheme(mhWnd, L"CFD", nullptr); + hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase); + break; + case ControlType::MultilineEditbox: + hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase); + break; + case ControlType::Listbox: + if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow ) + hTheme = getThemeHandle(mhWnd, L"Listview", mWinSalGraphicsImplBase); + else if( nPart == ControlPart::ButtonDown ) + { + if (bUseDarkMode) + SetWindowTheme(mhWnd, L"CFD", nullptr); + hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase); + } + break; + case ControlType::TabBody: + hTheme = getThemeHandle(mhWnd, L"Tab", mWinSalGraphicsImplBase); + break; + case ControlType::TabPane: + case ControlType::TabItem: + hTheme = getThemeHandle(mhWnd, L"Tab", mWinSalGraphicsImplBase); + break; + case ControlType::Toolbar: + if( nPart == ControlPart::Entire || nPart == ControlPart::Button ) + hTheme = getThemeHandle(mhWnd, L"Toolbar", mWinSalGraphicsImplBase); + else + // use rebar for grip and background + hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase); + break; + case ControlType::Menubar: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase); + else if( GetSalData()->mbThemeMenuSupport ) + { + if( nPart == ControlPart::MenuItem ) + hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase); + } + break; + case ControlType::Progress: + case ControlType::LevelBar: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"Progress", mWinSalGraphicsImplBase); + break; + case ControlType::ListNode: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle(mhWnd, L"TreeView", mWinSalGraphicsImplBase); + break; + case ControlType::Slider: + if( nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea ) + hTheme = getThemeHandle(mhWnd, L"Trackbar", mWinSalGraphicsImplBase); + break; + case ControlType::MenuPopup: + if( GetSalData()->mbThemeMenuSupport ) + { + if( nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || + nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark || + nPart == ControlPart::Separator + ) + hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase); + } + break; + default: + hTheme = nullptr; + break; + } + + if( !hTheme ) + { + if (bUseDarkMode) + SetWindowTheme(mhWnd, nullptr, nullptr); + return false; + } + + RECT rc; + rc.left = buttonRect.Left(); + rc.right = buttonRect.Right()+1; + rc.top = buttonRect.Top(); + rc.bottom = buttonRect.Bottom()+1; + + OUString aCaptionStr(aCaption.replace('~', '&')); // translate mnemonics + + if (pImpl == nullptr) + { + // set default text alignment + int ta = SetTextAlign(getHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP); + + bOk = ImplDrawNativeControl(getHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr, bUseDarkMode); + + // restore alignment + SetTextAlign(getHDC(), ta); + } + else + { + // We can do OpenGL/Skia + std::unique_ptr<CompatibleDC> aBlackDC(CompatibleDC::create(*this, cacheRect.Left(), cacheRect.Top(), cacheRect.GetWidth()+1, cacheRect.GetHeight()+1)); + SetTextAlign(aBlackDC->getCompatibleHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP); + aBlackDC->fill(RGB(0, 0, 0)); + + std::unique_ptr<CompatibleDC> aWhiteDC(CompatibleDC::create(*this, cacheRect.Left(), cacheRect.Top(), cacheRect.GetWidth()+1, cacheRect.GetHeight()+1)); + SetTextAlign(aWhiteDC->getCompatibleHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP); + aWhiteDC->fill(RGB(0xff, 0xff, 0xff)); + + if (ImplDrawNativeControl(aBlackDC->getCompatibleHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr, bUseDarkMode) && + ImplDrawNativeControl(aWhiteDC->getCompatibleHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr, bUseDarkMode)) + { + bOk = pImpl->RenderAndCacheNativeControl(*aWhiteDC, *aBlackDC, cacheRect.Left(), cacheRect.Top(), aControlCacheKey); + } + } + + if (bUseDarkMode) + SetWindowTheme(mhWnd, nullptr, nullptr); + return bOk; +} + +bool WinSalGraphics::getNativeControlRegion( ControlType nType, + ControlPart nPart, + const tools::Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& rControlValue, + const OUString&, + tools::Rectangle &rNativeBoundingRegion, + tools::Rectangle &rNativeContentRegion ) +{ + bool bRet = false; + + // FIXME: rNativeBoundingRegion has a different origin + // depending on which part is used; horrors. + + HDC hDC = GetDC( mhWnd ); + if( nType == ControlType::Toolbar ) + { + if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert ) + { + /* + // the vertical gripper is not supported in most themes and it makes no + // sense to only support horizontal gripper + + HTHEME hTheme = getThemeHandle(mhWnd, L"Rebar", mWinSalGraphicsImplBase); + if( hTheme ) + { + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, nPart == ControlPart::ThumbHorz ? RP_GRIPPERVERT : RP_GRIPPER, + 0, rControlRegion.GetBoundRect() ) ); + if( nPart == ControlPart::ThumbHorz && !aRect.IsEmpty() ) + { + tools::Rectangle aVertRect( 0, 0, aRect.getHeight(), aRect.getWidth() ); + rNativeContentRegion = aVertRect; + } + else + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + if( !rNativeContentRegion.IsEmpty() ) + bRet = TRUE; + } + */ + } + if( nPart == ControlPart::Button ) + { + HTHEME hTheme = getThemeHandle(mhWnd, L"Toolbar", mWinSalGraphicsImplBase); + if( hTheme ) + { + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, TP_SPLITBUTTONDROPDOWN, + TS_HOT, rControlRegion ) ); + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + if( !rNativeContentRegion.IsEmpty() ) + bRet = true; + } + } + } + if( nType == ControlType::Progress && nPart == ControlPart::Entire ) + { + HTHEME hTheme = getThemeHandle(mhWnd, L"Progress", mWinSalGraphicsImplBase); + if( hTheme ) + { + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, PP_BAR, + 0, rControlRegion ) ); + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + if( !rNativeContentRegion.IsEmpty() ) + bRet = true; + } + } + if( (nType == ControlType::Listbox || nType == ControlType::Combobox ) && nPart == ControlPart::Entire ) + { + HTHEME hTheme = getThemeHandle(mhWnd, L"Combobox", mWinSalGraphicsImplBase); + if( hTheme ) + { + tools::Rectangle aBoxRect( rControlRegion ); + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, CP_DROPDOWNBUTTON, + CBXS_NORMAL, aBoxRect ) ); + if( aRect.GetHeight() > aBoxRect.GetHeight() ) + aBoxRect.SetBottom( aBoxRect.Top() + aRect.GetHeight() ); + if( aRect.GetWidth() > aBoxRect.GetWidth() ) + aBoxRect.SetRight( aBoxRect.Left() + aRect.GetWidth() ); + rNativeContentRegion = aBoxRect; + rNativeBoundingRegion = rNativeContentRegion; + if( !aRect.IsEmpty() ) + bRet = true; + } + } + + if( (nType == ControlType::Editbox || nType == ControlType::Spinbox) && nPart == ControlPart::Entire ) + { + HTHEME hTheme = getThemeHandle(mhWnd, L"Edit", mWinSalGraphicsImplBase); + if( hTheme ) + { + // get border size + tools::Rectangle aBoxRect( rControlRegion ); + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, EP_BACKGROUNDWITHBORDER, + EBWBS_HOT, aBoxRect ) ); + // ad app font height + NONCLIENTMETRICSW aNonClientMetrics; + aNonClientMetrics.cbSize = sizeof( aNonClientMetrics ); + if ( SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, sizeof( aNonClientMetrics ), &aNonClientMetrics, 0 ) ) + { + LONG nFontHeight = aNonClientMetrics.lfMessageFont.lfHeight; + if( nFontHeight < 0 ) + nFontHeight = -nFontHeight; + + if( aRect.GetHeight() && nFontHeight ) + { + aRect.AdjustBottom(aRect.GetHeight()); + aRect.AdjustBottom(nFontHeight); + if( aRect.GetHeight() > aBoxRect.GetHeight() ) + aBoxRect.SetBottom( aBoxRect.Top() + aRect.GetHeight() ); + if( aRect.GetWidth() > aBoxRect.GetWidth() ) + aBoxRect.SetRight( aBoxRect.Left() + aRect.GetWidth() ); + rNativeContentRegion = aBoxRect; + rNativeBoundingRegion = rNativeContentRegion; + bRet = true; + } + } + } + } + + if( GetSalData()->mbThemeMenuSupport ) + { + if( nType == ControlType::MenuPopup ) + { + if( nPart == ControlPart::MenuItemCheckMark || + nPart == ControlPart::MenuItemRadioMark ) + { + HTHEME hTheme = getThemeHandle(mhWnd, L"Menu", mWinSalGraphicsImplBase); + tools::Rectangle aBoxRect( rControlRegion ); + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, + MENU_POPUPCHECK, + MC_CHECKMARKNORMAL, + aBoxRect ) ); + if (!aRect.IsEmpty()) + { + if (MARGINS mg; + SUCCEEDED(GetThemeMargins(hTheme, hDC, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, + TMT_CONTENTMARGINS, nullptr, &mg))) + { + aRect.AdjustLeft(-mg.cxLeftWidth); + aRect.AdjustRight(mg.cxRightWidth); + aRect.AdjustTop(-mg.cyTopHeight); + aRect.AdjustBottom(mg.cyBottomHeight); + } + rNativeContentRegion = rNativeBoundingRegion = aRect; + bRet = true; + } + } + } + } + + if( nType == ControlType::Slider && ( (nPart == ControlPart::ThumbHorz) || (nPart == ControlPart::ThumbVert) ) ) + { + HTHEME hTheme = getThemeHandle(mhWnd, L"Trackbar", mWinSalGraphicsImplBase); + if( hTheme ) + { + int iPart = (nPart == ControlPart::ThumbHorz) ? TKP_THUMB : TKP_THUMBVERT; + int iState = (nPart == ControlPart::ThumbHorz) ? static_cast<int>(TUS_NORMAL) : static_cast<int>(TUVS_NORMAL); + tools::Rectangle aThumbRect = ImplGetThemeRect( hTheme, hDC, iPart, iState, tools::Rectangle() ); + if( nPart == ControlPart::ThumbHorz ) + { + tools::Long nW = aThumbRect.GetWidth(); + tools::Rectangle aRect( rControlRegion ); + aRect.SetRight( aRect.Left() + nW - 1 ); + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + } + else + { + tools::Long nH = aThumbRect.GetHeight(); + tools::Rectangle aRect( rControlRegion ); + aRect.SetBottom( aRect.Top() + nH - 1 ); + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + } + bRet = true; + } + } + + if ( ( nType == ControlType::TabItem ) && ( nPart == ControlPart::Entire ) ) + { + tools::Rectangle aControlRect( rControlRegion ); + rNativeContentRegion = aControlRect; + + aControlRect.AdjustBottom(-1); + + if( rControlValue.getType() == ControlType::TabItem ) + { + const TabitemValue& rValue = static_cast<const TabitemValue&>(rControlValue); + if (rValue.isBothAligned()) + aControlRect.AdjustRight(-1); + + if ( nState & ControlState::SELECTED ) + { + aControlRect.AdjustLeft(-2); + if (!rValue.isBothAligned()) + { + if (rValue.isLeftAligned() || rValue.isNotAligned()) + aControlRect.AdjustRight(2); + if (rValue.isRightAligned()) + aControlRect.AdjustRight(1); + } + aControlRect.AdjustTop(-2); + aControlRect.AdjustBottom(2); + } + } + rNativeBoundingRegion = aControlRect; + bRet = true; + } + + ReleaseDC( mhWnd, hDC ); + return bRet; +} + +void WinSalGraphics::updateSettingsNative( AllSettings& rSettings ) +{ + if ( !IsThemeActive() ) + return; + + StyleSettings aStyleSettings = rSettings.GetStyleSettings(); + ImplSVData* pSVData = ImplGetSVData(); + + // don't draw frame around each and every toolbar + pSVData->maNWFData.mbDockingAreaAvoidTBFrames = true; + + // FIXME get the color directly from the theme, not from the settings + Color aMenuBarTextColor = aStyleSettings.GetPersonaMenuBarTextColor().value_or( aStyleSettings.GetMenuTextColor() ); + // in aero menuitem highlight text is drawn in the same color as normal + // high contrast highlight color is not related to persona and not apply blur or transparency + if( !aStyleSettings.GetHighContrastMode() ) + { + aStyleSettings.SetMenuHighlightTextColor( aStyleSettings.GetMenuTextColor() ); + aStyleSettings.SetMenuBarRolloverTextColor( aMenuBarTextColor ); + aStyleSettings.SetMenuBarHighlightTextColor( aMenuBarTextColor ); + } + + pSVData->maNWFData.mnMenuFormatBorderX = 2; + pSVData->maNWFData.mnMenuFormatBorderY = 2; + pSVData->maNWFData.maMenuBarHighlightTextColor = aMenuBarTextColor; + GetSalData()->mbThemeMenuSupport = true; + + rSettings.SetStyleSettings( aStyleSettings ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salprn.cxx b/vcl/win/gdi/salprn.cxx new file mode 100644 index 0000000000..3302efa2d9 --- /dev/null +++ b/vcl/win/gdi/salprn.cxx @@ -0,0 +1,1604 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <memory> +#include <vector> +#include <string.h> + +#include <svsys.h> + +#include <osl/module.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <tools/urlobj.hxx> + +#include <vcl/weld.hxx> +#include <vcl/QueueInfo.hxx> + +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salgdi.h> +#include <win/salframe.h> +#include <win/salprn.h> + +#include <salptype.hxx> +#include <print.h> +#include <jobset.h> + +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/FilePicker.hpp> +#include <com/sun/star/ui/dialogs/XFilterManager.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/windowsdebugoutput.hxx> + +#include <vcl/threadex.hxx> + +#include <malloc.h> + +#include <winspool.h> +#if defined GetDefaultPrinter +# undef GetDefaultPrinter +#endif +#if defined SetPrinterData +# undef SetPrinterData +#endif + +#define CATCH_DRIVER_EX_BEGIN \ + __try \ + { +#define CATCH_DRIVER_EX_END(mes, p) \ + } \ + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))\ + { \ + OSL_FAIL( mes ); \ + p->markInvalid(); \ + } +#define CATCH_DRIVER_EX_END_2(mes) \ + } \ + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))\ + { \ + OSL_FAIL( mes ); \ + } + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::ui::dialogs; + +static DEVMODEW const * SAL_DEVMODE_W( const ImplJobSetup* pSetupData ) +{ + DEVMODEW const * pRet = nullptr; + SalDriverData const * pDrv = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData()); + if( pSetupData->GetDriverDataLen() >= sizeof(DEVMODEW)+sizeof(SalDriverData)-1 ) + pRet = reinterpret_cast<DEVMODEW const *>((pSetupData->GetDriverData()) + (pDrv->mnDriverOffset)); + return pRet; +} + +static PrintQueueFlags ImplWinQueueStatusToSal( DWORD nWinStatus ) +{ + PrintQueueFlags nStatus = PrintQueueFlags::NONE; + if ( nWinStatus & PRINTER_STATUS_PAUSED ) + nStatus |= PrintQueueFlags::Paused; + if ( nWinStatus & PRINTER_STATUS_ERROR ) + nStatus |= PrintQueueFlags::Error; + if ( nWinStatus & PRINTER_STATUS_PENDING_DELETION ) + nStatus |= PrintQueueFlags::PendingDeletion; + if ( nWinStatus & PRINTER_STATUS_PAPER_JAM ) + nStatus |= PrintQueueFlags::PaperJam; + if ( nWinStatus & PRINTER_STATUS_PAPER_OUT ) + nStatus |= PrintQueueFlags::PaperOut; + if ( nWinStatus & PRINTER_STATUS_MANUAL_FEED ) + nStatus |= PrintQueueFlags::ManualFeed; + if ( nWinStatus & PRINTER_STATUS_PAPER_PROBLEM ) + nStatus |= PrintQueueFlags::PaperProblem; + if ( nWinStatus & PRINTER_STATUS_OFFLINE ) + nStatus |= PrintQueueFlags::Offline; + if ( nWinStatus & PRINTER_STATUS_IO_ACTIVE ) + nStatus |= PrintQueueFlags::IOActive; + if ( nWinStatus & PRINTER_STATUS_BUSY ) + nStatus |= PrintQueueFlags::Busy; + if ( nWinStatus & PRINTER_STATUS_PRINTING ) + nStatus |= PrintQueueFlags::Printing; + if ( nWinStatus & PRINTER_STATUS_OUTPUT_BIN_FULL ) + nStatus |= PrintQueueFlags::OutputBinFull; + if ( nWinStatus & PRINTER_STATUS_WAITING ) + nStatus |= PrintQueueFlags::Waiting; + if ( nWinStatus & PRINTER_STATUS_PROCESSING ) + nStatus |= PrintQueueFlags::Processing; + if ( nWinStatus & PRINTER_STATUS_INITIALIZING ) + nStatus |= PrintQueueFlags::Initializing; + if ( nWinStatus & PRINTER_STATUS_WARMING_UP ) + nStatus |= PrintQueueFlags::WarmingUp; + if ( nWinStatus & PRINTER_STATUS_TONER_LOW ) + nStatus |= PrintQueueFlags::TonerLow; + if ( nWinStatus & PRINTER_STATUS_NO_TONER ) + nStatus |= PrintQueueFlags::NoToner; + if ( nWinStatus & PRINTER_STATUS_PAGE_PUNT ) + nStatus |= PrintQueueFlags::PagePunt; + if ( nWinStatus & PRINTER_STATUS_USER_INTERVENTION ) + nStatus |= PrintQueueFlags::UserIntervention; + if ( nWinStatus & PRINTER_STATUS_OUT_OF_MEMORY ) + nStatus |= PrintQueueFlags::OutOfMemory; + if ( nWinStatus & PRINTER_STATUS_DOOR_OPEN ) + nStatus |= PrintQueueFlags::DoorOpen; + if ( nWinStatus & PRINTER_STATUS_SERVER_UNKNOWN ) + nStatus |= PrintQueueFlags::StatusUnknown; + if ( nWinStatus & PRINTER_STATUS_POWER_SAVE ) + nStatus |= PrintQueueFlags::PowerSave; + if ( nStatus == PrintQueueFlags::NONE && !(nWinStatus & PRINTER_STATUS_NOT_AVAILABLE) ) + nStatus |= PrintQueueFlags::Ready; + return nStatus; +} + + +void WinSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList ) +{ + DWORD i; + DWORD nBytes = 0; + DWORD nInfoPrn4 = 0; + EnumPrintersW( PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr, 4, nullptr, 0, &nBytes, &nInfoPrn4 ); + if ( nBytes ) + { + PRINTER_INFO_4W* pWinInfo4 = static_cast<PRINTER_INFO_4W*>(std::malloc( nBytes )); + if ( EnumPrintersW( PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr, 4, reinterpret_cast<LPBYTE>(pWinInfo4), nBytes, &nBytes, &nInfoPrn4 ) ) + { + for ( i = 0; i < nInfoPrn4; i++ ) + { + std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo); + pInfo->maPrinterName = o3tl::toU(pWinInfo4[i].pPrinterName); + pInfo->mnStatus = PrintQueueFlags::NONE; + pInfo->mnJobs = 0; + pList->Add( std::move(pInfo) ); + } + } + std::free( pWinInfo4 ); + } +} + +void WinSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* pInfo ) +{ + HANDLE hPrinter = nullptr; + LPWSTR pPrnName = const_cast<LPWSTR>(o3tl::toW(pInfo->maPrinterName.getStr())); + if( OpenPrinterW( pPrnName, &hPrinter, nullptr ) ) + { + DWORD nBytes = 0; + GetPrinterW( hPrinter, 2, nullptr, 0, &nBytes ); + if( nBytes ) + { + PRINTER_INFO_2W* pWinInfo2 = static_cast<PRINTER_INFO_2W*>(std::malloc(nBytes)); + if( GetPrinterW( hPrinter, 2, reinterpret_cast<LPBYTE>(pWinInfo2), nBytes, &nBytes ) ) + { + if( pWinInfo2->pDriverName ) + pInfo->maDriver = o3tl::toU(pWinInfo2->pDriverName); + OUString aPortName; + if ( pWinInfo2->pPortName ) + aPortName = o3tl::toU(pWinInfo2->pPortName); + // pLocation can be 0 (the Windows docu doesn't describe this) + if ( pWinInfo2->pLocation && *pWinInfo2->pLocation ) + pInfo->maLocation = o3tl::toU(pWinInfo2->pLocation); + else + pInfo->maLocation = aPortName; + // pComment can be 0 (the Windows docu doesn't describe this) + if ( pWinInfo2->pComment ) + pInfo->maComment = o3tl::toU(pWinInfo2->pComment); + pInfo->mnStatus = ImplWinQueueStatusToSal( pWinInfo2->Status ); + pInfo->mnJobs = pWinInfo2->cJobs; + if( ! pInfo->moPortName ) + pInfo->moPortName = aPortName; + } + std::free(pWinInfo2); + } + ClosePrinter( hPrinter ); + } +} + +OUString WinSalInstance::GetDefaultPrinter() +{ + DWORD nChars = 0; + GetDefaultPrinterW( nullptr, &nChars ); + if( nChars ) + { + std::vector<WCHAR> pStr(nChars); + if (GetDefaultPrinterW(pStr.data(), &nChars)) + return OUString(o3tl::toU(pStr.data())); + } + return OUString(); +} + +static DWORD ImplDeviceCaps( WinSalInfoPrinter const * pPrinter, WORD nCaps, + BYTE* pOutput, const ImplJobSetup* pSetupData ) +{ + DEVMODEW const * pDevMode; + if ( !pSetupData || !pSetupData->GetDriverData() ) + pDevMode = nullptr; + else + pDevMode = SAL_DEVMODE_W( pSetupData ); + + return DeviceCapabilitiesW( o3tl::toW(pPrinter->maDeviceName.getStr()), + o3tl::toW(pPrinter->maPortName.getStr()), + nCaps, reinterpret_cast<LPWSTR>(pOutput), pDevMode ); +} + +static bool ImplTestSalJobSetup( WinSalInfoPrinter const * pPrinter, + ImplJobSetup* pSetupData, bool bDelete ) +{ + if ( pSetupData && pSetupData->GetDriverData() ) + { + // signature and size must fit to avoid using + // JobSetups from a wrong system + + // initialize versions from jobsetup + // those will be overwritten with driver's version + DEVMODEW const * pDevModeW = nullptr; + LONG dmSpecVersion = -1; + LONG dmDriverVersion = -1; + SalDriverData const * pSalDriverData = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData()); + BYTE const * pDriverData = reinterpret_cast<BYTE const *>(pSalDriverData) + pSalDriverData->mnDriverOffset; + pDevModeW = reinterpret_cast<DEVMODEW const *>(pDriverData); + + LONG nSysJobSize = -1; + if( pPrinter && pDevModeW ) + { + // just too many driver crashes in that area -> check the dmSpecVersion and dmDriverVersion fields always !!! + // this prevents using the jobsetup between different Windows versions (eg from XP to 9x) but we + // can avoid potential driver crashes as their jobsetups are often not compatible + // #110800#, #111151#, #112381#, #i16580#, #i14173# and perhaps #112375# + HANDLE hPrn; + LPWSTR pPrinterNameW = const_cast<LPWSTR>(o3tl::toW(pPrinter->maDeviceName.getStr())); + if ( !OpenPrinterW( pPrinterNameW, &hPrn, nullptr ) ) + return false; + + // #131642# hPrn==HGDI_ERROR even though OpenPrinter() succeeded! + if( hPrn == HGDI_ERROR ) + return false; + + nSysJobSize = DocumentPropertiesW( nullptr, hPrn, + pPrinterNameW, + nullptr, nullptr, 0 ); + + if( nSysJobSize < 0 ) + { + ClosePrinter( hPrn ); + return false; + } + DEVMODEW *pBuffer = static_cast<DEVMODEW*>(_alloca( nSysJobSize )); + LONG nRet = DocumentPropertiesW( nullptr, hPrn, + pPrinterNameW, + pBuffer, nullptr, DM_OUT_BUFFER ); + if( nRet < 0 ) + { + ClosePrinter( hPrn ); + return false; + } + + // the spec version differs between the windows platforms, ie 98,NT,2000/XP + // this allows us to throw away printer settings from other platforms that might crash a buggy driver + // we check the driver version as well + dmSpecVersion = pBuffer->dmSpecVersion; + dmDriverVersion = pBuffer->dmDriverVersion; + + ClosePrinter( hPrn ); + } + SalDriverData const * pSetupDriverData = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData()); + if ( (pSetupData->GetSystem() == JOBSETUP_SYSTEM_WINDOWS) && + (pPrinter->maDriverName == pSetupData->GetDriver()) && + (pSetupData->GetDriverDataLen() > sizeof( SalDriverData )) && + static_cast<tools::Long>(pSetupData->GetDriverDataLen() - pSetupDriverData->mnDriverOffset) == nSysJobSize && + pSetupDriverData->mnSysSignature == SAL_DRIVERDATA_SYSSIGN ) + { + if( pDevModeW && + (dmSpecVersion == pDevModeW->dmSpecVersion) && + (dmDriverVersion == pDevModeW->dmDriverVersion) ) + return true; + } + if ( bDelete ) + { + pSetupData->SetDriverData( nullptr, 0 ); + } + } + + return false; +} + +static bool ImplUpdateSalJobSetup( WinSalInfoPrinter const * pPrinter, ImplJobSetup* pSetupData, + bool bIn, weld::Window* pVisibleDlgParent ) +{ + HANDLE hPrn; + LPWSTR pPrinterNameW = const_cast<LPWSTR>(o3tl::toW(pPrinter->maDeviceName.getStr())); + if ( !OpenPrinterW( pPrinterNameW, &hPrn, nullptr ) ) + return false; + // #131642# hPrn==HGDI_ERROR even though OpenPrinter() succeeded! + if( hPrn == HGDI_ERROR ) + return false; + + LONG nRet; + HWND hWnd = nullptr; + DWORD nMode = DM_OUT_BUFFER; + std::unique_ptr<sal_uInt8[]> pDriverData; + SalDriverData* pOutBuffer = nullptr; + BYTE const * pInBuffer = nullptr; + + LONG nSysJobSize = DocumentPropertiesW( hWnd, hPrn, + pPrinterNameW, + nullptr, nullptr, 0 ); + if ( nSysJobSize < 0 ) + { + ClosePrinter( hPrn ); + return false; + } + + // make Outputbuffer + const std::size_t nDriverDataLen = sizeof(SalDriverData) + nSysJobSize-1; + pDriverData = std::make_unique<sal_uInt8[]>( nDriverDataLen ); + memset(pDriverData.get(), 0, nDriverDataLen); + pOutBuffer = reinterpret_cast<SalDriverData*>(pDriverData.get()); + pOutBuffer->mnSysSignature = SAL_DRIVERDATA_SYSSIGN; + // calculate driver data offset including structure padding + pOutBuffer->mnDriverOffset = sal::static_int_cast<sal_uInt16>( + reinterpret_cast<char*>(pOutBuffer->maDriverData) - + reinterpret_cast<char*>(pOutBuffer) ); + + // check if we have a suitable input buffer + if ( bIn && ImplTestSalJobSetup( pPrinter, pSetupData, false ) ) + { + pInBuffer = pSetupData->GetDriverData() + reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData())->mnDriverOffset; + nMode |= DM_IN_BUFFER; + } + + // check if the dialog should be shown + if ( pVisibleDlgParent ) + { + hWnd = pVisibleDlgParent->get_system_data().hWnd; + nMode |= DM_IN_PROMPT; + } + + // Release mutex, in the other case we don't get paints and so on + sal_uInt32 nMutexCount = 0; + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst && pVisibleDlgParent ) + nMutexCount = pInst->ReleaseYieldMutexAll(); + + BYTE* pOutDevMode = reinterpret_cast<BYTE*>(pOutBuffer) + pOutBuffer->mnDriverOffset; + nRet = DocumentPropertiesW( hWnd, hPrn, + pPrinterNameW, + reinterpret_cast<LPDEVMODEW>(pOutDevMode), reinterpret_cast<LPDEVMODEW>(const_cast<BYTE *>(pInBuffer)), nMode ); + if ( pInst && pVisibleDlgParent ) + pInst->AcquireYieldMutex( nMutexCount ); + ClosePrinter( hPrn ); + + if( (nRet < 0) || (pVisibleDlgParent && (nRet == IDCANCEL)) ) + return false; + + // fill up string buffers with 0 so they do not influence a JobSetup's memcmp + if( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmSize >= 64 ) + { + sal_Int32 nLen = rtl_ustr_getLength( o3tl::toU(reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName) ); + if ( sal::static_int_cast<size_t>(nLen) < SAL_N_ELEMENTS( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName ) ) + memset( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName+nLen, 0, sizeof( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName )-(nLen*sizeof(sal_Unicode)) ); + } + if( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmSize >= 166 ) + { + sal_Int32 nLen = rtl_ustr_getLength( o3tl::toU(reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName) ); + if ( sal::static_int_cast<size_t>(nLen) < SAL_N_ELEMENTS( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName ) ) + memset( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName+nLen, 0, sizeof( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName )-(nLen*sizeof(sal_Unicode)) ); + } + + // update data + pSetupData->SetDriverData(std::move(pDriverData), nDriverDataLen); + pSetupData->SetSystem( JOBSETUP_SYSTEM_WINDOWS ); + + return true; +} + +static void ImplDevModeToJobSetup( WinSalInfoPrinter const * pPrinter, ImplJobSetup* pSetupData, JobSetFlags nFlags ) +{ + if ( !pSetupData || !pSetupData->GetDriverData() ) + return; + + DEVMODEW const * pDevModeW = SAL_DEVMODE_W(pSetupData); + if( pDevModeW == nullptr ) + return; + + // Orientation + if ( nFlags & JobSetFlags::ORIENTATION ) + { + if ( pDevModeW->dmOrientation == DMORIENT_PORTRAIT ) + pSetupData->SetOrientation( Orientation::Portrait ); + else if ( pDevModeW->dmOrientation == DMORIENT_LANDSCAPE ) + pSetupData->SetOrientation( Orientation::Landscape ); + } + + // PaperBin + if ( nFlags & JobSetFlags::PAPERBIN ) + { + const DWORD nCount = ImplDeviceCaps( pPrinter, DC_BINS, nullptr, pSetupData ); + + if ( nCount && (nCount != GDI_ERROR) ) + { + WORD* pBins = static_cast<WORD*>(rtl_allocateZeroMemory( nCount*sizeof(WORD) )); + ImplDeviceCaps( pPrinter, DC_BINS, reinterpret_cast<BYTE*>(pBins), pSetupData ); + pSetupData->SetPaperBin( 0 ); + + // search the right bin and assign index to mnPaperBin + for( DWORD i = 0; i < nCount; ++i ) + { + if( pDevModeW->dmDefaultSource == pBins[ i ] ) + { + pSetupData->SetPaperBin( static_cast<sal_uInt16>(i) ); + break; + } + } + + std::free( pBins ); + } + } + + // PaperSize + if ( nFlags & JobSetFlags::PAPERSIZE ) + { + if( (pDevModeW->dmFields & (DM_PAPERWIDTH|DM_PAPERLENGTH)) == (DM_PAPERWIDTH|DM_PAPERLENGTH) ) + { + pSetupData->SetPaperWidth( pDevModeW->dmPaperWidth*10 ); + pSetupData->SetPaperHeight( pDevModeW->dmPaperLength*10 ); + } + else + { + const DWORD nPaperCount = ImplDeviceCaps( pPrinter, DC_PAPERS, nullptr, pSetupData ); + WORD* pPapers = nullptr; + const DWORD nPaperSizeCount = ImplDeviceCaps( pPrinter, DC_PAPERSIZE, nullptr, pSetupData ); + POINT* pPaperSizes = nullptr; + if ( nPaperCount && (nPaperCount != GDI_ERROR) ) + { + pPapers = static_cast<WORD*>(rtl_allocateZeroMemory(nPaperCount*sizeof(WORD))); + ImplDeviceCaps( pPrinter, DC_PAPERS, reinterpret_cast<BYTE*>(pPapers), pSetupData ); + } + if ( nPaperSizeCount && (nPaperSizeCount != GDI_ERROR) ) + { + pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nPaperSizeCount*sizeof(POINT))); + ImplDeviceCaps( pPrinter, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData ); + } + if( nPaperSizeCount == nPaperCount && pPaperSizes && pPapers ) + { + for( DWORD i = 0; i < nPaperCount; ++i ) + { + if( pPapers[ i ] == pDevModeW->dmPaperSize ) + { + pSetupData->SetPaperWidth( pPaperSizes[ i ].x*10 ); + pSetupData->SetPaperHeight( pPaperSizes[ i ].y*10 ); + break; + } + } + } + if( pPapers ) + std::free( pPapers ); + if( pPaperSizes ) + std::free( pPaperSizes ); + } + switch( pDevModeW->dmPaperSize ) + { + case DMPAPER_LETTER: + pSetupData->SetPaperFormat( PAPER_LETTER ); + break; + case DMPAPER_TABLOID: + pSetupData->SetPaperFormat( PAPER_TABLOID ); + break; + case DMPAPER_LEDGER: + pSetupData->SetPaperFormat( PAPER_LEDGER ); + break; + case DMPAPER_LEGAL: + pSetupData->SetPaperFormat( PAPER_LEGAL ); + break; + case DMPAPER_STATEMENT: + pSetupData->SetPaperFormat( PAPER_STATEMENT ); + break; + case DMPAPER_EXECUTIVE: + pSetupData->SetPaperFormat( PAPER_EXECUTIVE ); + break; + case DMPAPER_A3: + pSetupData->SetPaperFormat( PAPER_A3 ); + break; + case DMPAPER_A4: + pSetupData->SetPaperFormat( PAPER_A4 ); + break; + case DMPAPER_A5: + pSetupData->SetPaperFormat( PAPER_A5 ); + break; + //See http://wiki.openoffice.org/wiki/DefaultPaperSize + //i.e. + //http://msdn.microsoft.com/en-us/library/dd319099(VS.85).aspx + //DMPAPER_B4 12 B4 (JIS) 257 x 364 mm + //http://partners.adobe.com/public/developer/en/ps/5003.PPD_Spec_v4.3.pdf + //also says that the MS DMPAPER_B4 is JIS, which makes most sense. And + //matches our Excel filter's belief about the matching XlPaperSize + //enumeration. + + //http://msdn.microsoft.com/en-us/library/ms776398(VS.85).aspx said + ////"DMPAPER_B4 12 B4 (JIS) 250 x 354" + //which is bogus as it's either JIS 257 x 364 or ISO 250 x 353 + //(cmc) + case DMPAPER_B4: + pSetupData->SetPaperFormat( PAPER_B4_JIS ); + break; + case DMPAPER_B5: + pSetupData->SetPaperFormat( PAPER_B5_JIS ); + break; + case DMPAPER_QUARTO: + pSetupData->SetPaperFormat( PAPER_QUARTO ); + break; + case DMPAPER_10X14: + pSetupData->SetPaperFormat( PAPER_10x14 ); + break; + case DMPAPER_NOTE: + pSetupData->SetPaperFormat( PAPER_LETTER ); + break; + case DMPAPER_ENV_9: + pSetupData->SetPaperFormat( PAPER_ENV_9 ); + break; + case DMPAPER_ENV_10: + pSetupData->SetPaperFormat( PAPER_ENV_10 ); + break; + case DMPAPER_ENV_11: + pSetupData->SetPaperFormat( PAPER_ENV_11 ); + break; + case DMPAPER_ENV_12: + pSetupData->SetPaperFormat( PAPER_ENV_12 ); + break; + case DMPAPER_ENV_14: + pSetupData->SetPaperFormat( PAPER_ENV_14 ); + break; + case DMPAPER_CSHEET: + pSetupData->SetPaperFormat( PAPER_C ); + break; + case DMPAPER_DSHEET: + pSetupData->SetPaperFormat( PAPER_D ); + break; + case DMPAPER_ESHEET: + pSetupData->SetPaperFormat( PAPER_E ); + break; + case DMPAPER_ENV_DL: + pSetupData->SetPaperFormat( PAPER_ENV_DL ); + break; + case DMPAPER_ENV_C5: + pSetupData->SetPaperFormat( PAPER_ENV_C5 ); + break; + case DMPAPER_ENV_C3: + pSetupData->SetPaperFormat( PAPER_ENV_C3 ); + break; + case DMPAPER_ENV_C4: + pSetupData->SetPaperFormat( PAPER_ENV_C4 ); + break; + case DMPAPER_ENV_C6: + pSetupData->SetPaperFormat( PAPER_ENV_C6 ); + break; + case DMPAPER_ENV_C65: + pSetupData->SetPaperFormat( PAPER_ENV_C65 ); + break; + case DMPAPER_ENV_ITALY: + pSetupData->SetPaperFormat( PAPER_ENV_ITALY ); + break; + case DMPAPER_ENV_MONARCH: + pSetupData->SetPaperFormat( PAPER_ENV_MONARCH ); + break; + case DMPAPER_ENV_PERSONAL: + pSetupData->SetPaperFormat( PAPER_ENV_PERSONAL ); + break; + case DMPAPER_FANFOLD_US: + pSetupData->SetPaperFormat( PAPER_FANFOLD_US ); + break; + case DMPAPER_FANFOLD_STD_GERMAN: + pSetupData->SetPaperFormat( PAPER_FANFOLD_DE ); + break; + case DMPAPER_FANFOLD_LGL_GERMAN: + pSetupData->SetPaperFormat( PAPER_FANFOLD_LEGAL_DE ); + break; + case DMPAPER_ISO_B4: + pSetupData->SetPaperFormat( PAPER_B4_ISO ); + break; + case DMPAPER_JAPANESE_POSTCARD: + pSetupData->SetPaperFormat( PAPER_POSTCARD_JP ); + break; + case DMPAPER_9X11: + pSetupData->SetPaperFormat( PAPER_9x11 ); + break; + case DMPAPER_10X11: + pSetupData->SetPaperFormat( PAPER_10x11 ); + break; + case DMPAPER_15X11: + pSetupData->SetPaperFormat( PAPER_15x11 ); + break; + case DMPAPER_ENV_INVITE: + pSetupData->SetPaperFormat( PAPER_ENV_INVITE ); + break; + case DMPAPER_A_PLUS: + pSetupData->SetPaperFormat( PAPER_A_PLUS ); + break; + case DMPAPER_B_PLUS: + pSetupData->SetPaperFormat( PAPER_B_PLUS ); + break; + case DMPAPER_LETTER_PLUS: + pSetupData->SetPaperFormat( PAPER_LETTER_PLUS ); + break; + case DMPAPER_A4_PLUS: + pSetupData->SetPaperFormat( PAPER_A4_PLUS ); + break; + case DMPAPER_A2: + pSetupData->SetPaperFormat( PAPER_A2 ); + break; + case DMPAPER_DBL_JAPANESE_POSTCARD: + pSetupData->SetPaperFormat( PAPER_DOUBLEPOSTCARD_JP ); + break; + case DMPAPER_A6: + pSetupData->SetPaperFormat( PAPER_A6 ); + break; + case DMPAPER_B6_JIS: + pSetupData->SetPaperFormat( PAPER_B6_JIS ); + break; + case DMPAPER_12X11: + pSetupData->SetPaperFormat( PAPER_12x11 ); + break; + default: + pSetupData->SetPaperFormat( PAPER_USER ); + break; + } + } + + if( nFlags & JobSetFlags::DUPLEXMODE ) + { + DuplexMode eDuplex = DuplexMode::Unknown; + if( pDevModeW->dmFields & DM_DUPLEX ) + { + if( pDevModeW->dmDuplex == DMDUP_SIMPLEX ) + eDuplex = DuplexMode::Off; + else if( pDevModeW->dmDuplex == DMDUP_VERTICAL ) + eDuplex = DuplexMode::LongEdge; + else if( pDevModeW->dmDuplex == DMDUP_HORIZONTAL ) + eDuplex = DuplexMode::ShortEdge; + } + pSetupData->SetDuplexMode( eDuplex ); + } +} + +static void ImplJobSetupToDevMode( WinSalInfoPrinter const * pPrinter, const ImplJobSetup* pSetupData, JobSetFlags nFlags ) +{ + if ( !pSetupData || !pSetupData->GetDriverData() ) + return; + + DEVMODEW* pDevModeW = const_cast<DEVMODEW *>(SAL_DEVMODE_W(pSetupData)); + if( pDevModeW == nullptr ) + return; + + // Orientation + if ( nFlags & JobSetFlags::ORIENTATION ) + { + pDevModeW->dmFields |= DM_ORIENTATION; + if ( pSetupData->GetOrientation() == Orientation::Portrait ) + pDevModeW->dmOrientation = DMORIENT_PORTRAIT; + else + pDevModeW->dmOrientation = DMORIENT_LANDSCAPE; + } + + // PaperBin + if ( nFlags & JobSetFlags::PAPERBIN ) + { + const DWORD nCount = ImplDeviceCaps( pPrinter, DC_BINS, nullptr, pSetupData ); + + if ( nCount && (nCount != GDI_ERROR) ) + { + WORD* pBins = static_cast<WORD*>(rtl_allocateZeroMemory(nCount*sizeof(WORD))); + ImplDeviceCaps( pPrinter, DC_BINS, reinterpret_cast<BYTE*>(pBins), pSetupData ); + pDevModeW->dmFields |= DM_DEFAULTSOURCE; + pDevModeW->dmDefaultSource = pBins[ pSetupData->GetPaperBin() ]; + std::free( pBins ); + } + } + + // PaperSize + if ( nFlags & JobSetFlags::PAPERSIZE ) + { + pDevModeW->dmFields |= DM_PAPERSIZE; + pDevModeW->dmPaperWidth = 0; + pDevModeW->dmPaperLength = 0; + + switch( pSetupData->GetPaperFormat() ) + { + case PAPER_A2: + pDevModeW->dmPaperSize = DMPAPER_A2; + break; + case PAPER_A3: + pDevModeW->dmPaperSize = DMPAPER_A3; + break; + case PAPER_A4: + pDevModeW->dmPaperSize = DMPAPER_A4; + break; + case PAPER_A5: + pDevModeW->dmPaperSize = DMPAPER_A5; + break; + case PAPER_B4_ISO: + pDevModeW->dmPaperSize = DMPAPER_ISO_B4; + break; + case PAPER_LETTER: + pDevModeW->dmPaperSize = DMPAPER_LETTER; + break; + case PAPER_LEGAL: + pDevModeW->dmPaperSize = DMPAPER_LEGAL; + break; + case PAPER_TABLOID: + pDevModeW->dmPaperSize = DMPAPER_TABLOID; + break; + + // http://msdn.microsoft.com/en-us/library/ms776398(VS.85).aspx + // DMPAPER_ENV_B6 is documented as: + // "DMPAPER_ENV_B6 35 Envelope B6 176 x 125 mm" + // which is the wrong way around, it is surely 125 x 176, i.e. + // compare DMPAPER_ENV_B4 and DMPAPER_ENV_B4 as + // DMPAPER_ENV_B4 33 Envelope B4 250 x 353 mm + // DMPAPER_ENV_B5 34 Envelope B5 176 x 250 mm + + case PAPER_ENV_C4: + pDevModeW->dmPaperSize = DMPAPER_ENV_C4; + break; + case PAPER_ENV_C5: + pDevModeW->dmPaperSize = DMPAPER_ENV_C5; + break; + case PAPER_ENV_C6: + pDevModeW->dmPaperSize = DMPAPER_ENV_C6; + break; + case PAPER_ENV_C65: + pDevModeW->dmPaperSize = DMPAPER_ENV_C65; + break; + case PAPER_ENV_DL: + pDevModeW->dmPaperSize = DMPAPER_ENV_DL; + break; + case PAPER_C: + pDevModeW->dmPaperSize = DMPAPER_CSHEET; + break; + case PAPER_D: + pDevModeW->dmPaperSize = DMPAPER_DSHEET; + break; + case PAPER_E: + pDevModeW->dmPaperSize = DMPAPER_ESHEET; + break; + case PAPER_EXECUTIVE: + pDevModeW->dmPaperSize = DMPAPER_EXECUTIVE; + break; + case PAPER_FANFOLD_LEGAL_DE: + pDevModeW->dmPaperSize = DMPAPER_FANFOLD_LGL_GERMAN; + break; + case PAPER_ENV_MONARCH: + pDevModeW->dmPaperSize = DMPAPER_ENV_MONARCH; + break; + case PAPER_ENV_PERSONAL: + pDevModeW->dmPaperSize = DMPAPER_ENV_PERSONAL; + break; + case PAPER_ENV_9: + pDevModeW->dmPaperSize = DMPAPER_ENV_9; + break; + case PAPER_ENV_10: + pDevModeW->dmPaperSize = DMPAPER_ENV_10; + break; + case PAPER_ENV_11: + pDevModeW->dmPaperSize = DMPAPER_ENV_11; + break; + case PAPER_ENV_12: + pDevModeW->dmPaperSize = DMPAPER_ENV_12; + break; + //See the comments on DMPAPER_B4 above + case PAPER_B4_JIS: + pDevModeW->dmPaperSize = DMPAPER_B4; + break; + case PAPER_B5_JIS: + pDevModeW->dmPaperSize = DMPAPER_B5; + break; + case PAPER_B6_JIS: + pDevModeW->dmPaperSize = DMPAPER_B6_JIS; + break; + case PAPER_LEDGER: + pDevModeW->dmPaperSize = DMPAPER_LEDGER; + break; + case PAPER_STATEMENT: + pDevModeW->dmPaperSize = DMPAPER_STATEMENT; + break; + case PAPER_10x14: + pDevModeW->dmPaperSize = DMPAPER_10X14; + break; + case PAPER_ENV_14: + pDevModeW->dmPaperSize = DMPAPER_ENV_14; + break; + case PAPER_ENV_C3: + pDevModeW->dmPaperSize = DMPAPER_ENV_C3; + break; + case PAPER_ENV_ITALY: + pDevModeW->dmPaperSize = DMPAPER_ENV_ITALY; + break; + case PAPER_FANFOLD_US: + pDevModeW->dmPaperSize = DMPAPER_FANFOLD_US; + break; + case PAPER_FANFOLD_DE: + pDevModeW->dmPaperSize = DMPAPER_FANFOLD_STD_GERMAN; + break; + case PAPER_POSTCARD_JP: + pDevModeW->dmPaperSize = DMPAPER_JAPANESE_POSTCARD; + break; + case PAPER_9x11: + pDevModeW->dmPaperSize = DMPAPER_9X11; + break; + case PAPER_10x11: + pDevModeW->dmPaperSize = DMPAPER_10X11; + break; + case PAPER_15x11: + pDevModeW->dmPaperSize = DMPAPER_15X11; + break; + case PAPER_ENV_INVITE: + pDevModeW->dmPaperSize = DMPAPER_ENV_INVITE; + break; + case PAPER_A_PLUS: + pDevModeW->dmPaperSize = DMPAPER_A_PLUS; + break; + case PAPER_B_PLUS: + pDevModeW->dmPaperSize = DMPAPER_B_PLUS; + break; + case PAPER_LETTER_PLUS: + pDevModeW->dmPaperSize = DMPAPER_LETTER_PLUS; + break; + case PAPER_A4_PLUS: + pDevModeW->dmPaperSize = DMPAPER_A4_PLUS; + break; + case PAPER_DOUBLEPOSTCARD_JP: + pDevModeW->dmPaperSize = DMPAPER_DBL_JAPANESE_POSTCARD; + break; + case PAPER_A6: + pDevModeW->dmPaperSize = DMPAPER_A6; + break; + case PAPER_12x11: + pDevModeW->dmPaperSize = DMPAPER_12X11; + break; + default: + { + short nPaper = 0; + const DWORD nPaperCount = ImplDeviceCaps( pPrinter, DC_PAPERS, nullptr, pSetupData ); + WORD* pPapers = nullptr; + const DWORD nPaperSizeCount = ImplDeviceCaps( pPrinter, DC_PAPERSIZE, nullptr, pSetupData ); + POINT* pPaperSizes = nullptr; + DWORD nLandscapeAngle = ImplDeviceCaps( pPrinter, DC_ORIENTATION, nullptr, pSetupData ); + if ( nPaperCount && (nPaperCount != GDI_ERROR) ) + { + pPapers = static_cast<WORD*>(rtl_allocateZeroMemory(nPaperCount*sizeof(WORD))); + ImplDeviceCaps( pPrinter, DC_PAPERS, reinterpret_cast<BYTE*>(pPapers), pSetupData ); + } + if ( nPaperSizeCount && (nPaperSizeCount != GDI_ERROR) ) + { + pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nPaperSizeCount*sizeof(POINT))); + ImplDeviceCaps( pPrinter, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData ); + } + if ( (nPaperSizeCount == nPaperCount) && pPapers && pPaperSizes ) + { + PaperInfo aInfo(pSetupData->GetPaperWidth(), pSetupData->GetPaperHeight()); + // compare paper formats and select a good match + for ( DWORD i = 0; i < nPaperCount; ++i ) + { + if ( aInfo.sloppyEqual(PaperInfo(pPaperSizes[i].x*10, pPaperSizes[i].y*10))) + { + nPaper = pPapers[i]; + break; + } + } + + // If the printer supports landscape orientation, check paper sizes again + // with landscape orientation. This is necessary as a printer driver provides + // all paper sizes with portrait orientation only!! + if ( !nPaper && nLandscapeAngle != 0 ) + { + PaperInfo aRotatedInfo(pSetupData->GetPaperHeight(), pSetupData->GetPaperWidth()); + for ( DWORD i = 0; i < nPaperCount; ++i ) + { + if ( aRotatedInfo.sloppyEqual(PaperInfo(pPaperSizes[i].x*10, pPaperSizes[i].y*10)) ) + { + nPaper = pPapers[i]; + break; + } + } + } + + if ( nPaper ) + pDevModeW->dmPaperSize = nPaper; + } + + if ( !nPaper ) + { + pDevModeW->dmFields |= DM_PAPERLENGTH | DM_PAPERWIDTH; + pDevModeW->dmPaperSize = DMPAPER_USER; + pDevModeW->dmPaperWidth = static_cast<short>(pSetupData->GetPaperWidth()/10); + pDevModeW->dmPaperLength = static_cast<short>(pSetupData->GetPaperHeight()/10); + } + + if ( pPapers ) + std::free(pPapers); + if ( pPaperSizes ) + std::free(pPaperSizes); + + break; + } + } + } + if( nFlags & JobSetFlags::DUPLEXMODE ) + { + switch( pSetupData->GetDuplexMode() ) + { + case DuplexMode::Off: + pDevModeW->dmFields |= DM_DUPLEX; + pDevModeW->dmDuplex = DMDUP_SIMPLEX; + break; + case DuplexMode::ShortEdge: + pDevModeW->dmFields |= DM_DUPLEX; + pDevModeW->dmDuplex = DMDUP_HORIZONTAL; + break; + case DuplexMode::LongEdge: + pDevModeW->dmFields |= DM_DUPLEX; + pDevModeW->dmDuplex = DMDUP_VERTICAL; + break; + case DuplexMode::Unknown: + break; + } + } +} + +static HDC ImplCreateICW_WithCatch( LPWSTR pDriver, + LPCWSTR pDevice, + DEVMODEW const * pDevMode ) +{ + HDC hDC = nullptr; + CATCH_DRIVER_EX_BEGIN; + hDC = CreateICW( pDriver, pDevice, nullptr, pDevMode ); + CATCH_DRIVER_EX_END_2( "exception in CreateICW" ); + return hDC; +} + +static HDC ImplCreateSalPrnIC( WinSalInfoPrinter const * pPrinter, const ImplJobSetup* pSetupData ) +{ + HDC hDC = nullptr; + DEVMODEW const * pDevMode; + if ( pSetupData && pSetupData->GetDriverData() ) + pDevMode = SAL_DEVMODE_W( pSetupData ); + else + pDevMode = nullptr; + // #95347 some buggy drivers (eg, OKI) write to those buffers in CreateIC, although declared const - so provide some space + // pl: does this hold true for Unicode functions ? + if( pPrinter->maDriverName.getLength() > 2048 || pPrinter->maDeviceName.getLength() > 2048 ) + return nullptr; + sal_Unicode pDriverName[ 4096 ]; + sal_Unicode pDeviceName[ 4096 ]; + memcpy( pDriverName, pPrinter->maDriverName.getStr(), pPrinter->maDriverName.getLength()*sizeof(sal_Unicode)); + memset( pDriverName+pPrinter->maDriverName.getLength(), 0, 32 ); + memcpy( pDeviceName, pPrinter->maDeviceName.getStr(), pPrinter->maDeviceName.getLength()*sizeof(sal_Unicode)); + memset( pDeviceName+pPrinter->maDeviceName.getLength(), 0, 32 ); + hDC = ImplCreateICW_WithCatch( o3tl::toW(pDriverName), + o3tl::toW(pDeviceName), + pDevMode ); + return hDC; +} + +static WinSalGraphics* ImplCreateSalPrnGraphics( HDC hDC ) +{ + WinSalGraphics* pGraphics = new WinSalGraphics(WinSalGraphics::PRINTER, false, nullptr, /* CHECKME */ nullptr); + pGraphics->SetLayout( SalLayoutFlags::NONE ); + pGraphics->setHDC(hDC); + return pGraphics; +} + +static bool ImplUpdateSalPrnIC( WinSalInfoPrinter* pPrinter, const ImplJobSetup* pSetupData ) +{ + HDC hNewDC = ImplCreateSalPrnIC( pPrinter, pSetupData ); + if ( !hNewDC ) + return false; + + pPrinter->setHDC(hNewDC); + return true; +} + + +SalInfoPrinter* WinSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo, + ImplJobSetup* pSetupData ) +{ + WinSalInfoPrinter* pPrinter = new WinSalInfoPrinter; + if( ! pQueueInfo->moPortName ) + GetPrinterQueueState( pQueueInfo ); + pPrinter->maDriverName = pQueueInfo->maDriver; + pPrinter->maDeviceName = pQueueInfo->maPrinterName; + pPrinter->maPortName = pQueueInfo->moPortName ? *pQueueInfo->moPortName : OUString(); + + // check if the provided setup data match the actual printer + ImplTestSalJobSetup( pPrinter, pSetupData, true ); + + HDC hDC = ImplCreateSalPrnIC( pPrinter, pSetupData ); + if ( !hDC ) + { + delete pPrinter; + return nullptr; + } + + pPrinter->setHDC(hDC); + if ( !pSetupData->GetDriverData() ) + ImplUpdateSalJobSetup( pPrinter, pSetupData, false, nullptr ); + ImplDevModeToJobSetup( pPrinter, pSetupData, JobSetFlags::ALL ); + pSetupData->SetSystem( JOBSETUP_SYSTEM_WINDOWS ); + + return pPrinter; +} + +void WinSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter ) +{ + delete pPrinter; +} + + +WinSalInfoPrinter::WinSalInfoPrinter() : + m_hDC(nullptr), + m_pGraphics(nullptr), + m_bGraphics(false) +{ + m_bPapersInit = false; +} + +WinSalInfoPrinter::~WinSalInfoPrinter() +{ + setHDC(nullptr); +} + +void WinSalInfoPrinter::setHDC(HDC hNewDC) +{ + assert(!m_bGraphics); + + if (m_hDC) + { + assert(!m_pGraphics || m_hDC == m_pGraphics->getHDC()); + delete m_pGraphics; + m_pGraphics = nullptr; + DeleteDC(m_hDC); + } + + m_hDC = hNewDC; +} + +void WinSalInfoPrinter::InitPaperFormats( const ImplJobSetup* pSetupData ) +{ + m_aPaperFormats.clear(); + + DWORD nCount = ImplDeviceCaps( this, DC_PAPERSIZE, nullptr, pSetupData ); + if( nCount == GDI_ERROR ) + nCount = 0; + + if( nCount ) + { + POINT* pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nCount*sizeof(POINT))); + ImplDeviceCaps( this, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData ); + + sal_Unicode* pNamesBuffer = static_cast<sal_Unicode*>(std::malloc(nCount*64*sizeof(sal_Unicode))); + ImplDeviceCaps( this, DC_PAPERNAMES, reinterpret_cast<BYTE*>(pNamesBuffer), pSetupData ); + + SAL_INFO("vcl.print", "DC_PAPERSIZE sizes (mm) from printer: " << DC_PAPERSIZE_array_to_string(pPaperSizes, nCount)); + + for( DWORD i = 0; i < nCount; ++i ) + { + PaperInfo aInfo(pPaperSizes[i].x * 10, pPaperSizes[i].y * 10); + m_aPaperFormats.push_back( aInfo ); + } + std::free( pNamesBuffer ); + std::free( pPaperSizes ); + } + + m_bPapersInit = true; +} + +int WinSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* pSetupData ) +{ + const DWORD nRet = ImplDeviceCaps( this, DC_ORIENTATION, nullptr, pSetupData ); + + if( nRet != GDI_ERROR ) + return static_cast<int>(nRet) * 10; + return 900; // guess +} + +SalGraphics* WinSalInfoPrinter::AcquireGraphics() +{ + assert(m_hDC); + if (m_bGraphics) + return nullptr; + + if (!m_pGraphics) + m_pGraphics = ImplCreateSalPrnGraphics(m_hDC); + if (m_pGraphics) + m_bGraphics = true; + + return m_pGraphics; +} + +void WinSalInfoPrinter::ReleaseGraphics( SalGraphics* ) +{ + m_bGraphics = false; +} + +bool WinSalInfoPrinter::Setup(weld::Window* pFrame, ImplJobSetup* pSetupData) +{ + if ( ImplUpdateSalJobSetup(this, pSetupData, true, pFrame)) + { + ImplDevModeToJobSetup( this, pSetupData, JobSetFlags::ALL ); + return ImplUpdateSalPrnIC( this, pSetupData ); + } + + return false; +} + +bool WinSalInfoPrinter::SetPrinterData( ImplJobSetup* pSetupData ) +{ + if ( !ImplTestSalJobSetup( this, pSetupData, false ) ) + return false; + return ImplUpdateSalPrnIC( this, pSetupData ); +} + +bool WinSalInfoPrinter::SetData( JobSetFlags nFlags, ImplJobSetup* pSetupData ) +{ + ImplJobSetupToDevMode( this, pSetupData, nFlags ); + if ( ImplUpdateSalJobSetup( this, pSetupData, true, nullptr ) ) + { + ImplDevModeToJobSetup( this, pSetupData, nFlags ); + return ImplUpdateSalPrnIC( this, pSetupData ); + } + + return false; +} + +sal_uInt16 WinSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pSetupData ) +{ + DWORD nRet = ImplDeviceCaps( this, DC_BINS, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return nRet; + else + return 0; +} + +OUString WinSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pSetupData, sal_uInt16 nPaperBin ) +{ + OUString aPaperBinName; + + DWORD nBins = ImplDeviceCaps( this, DC_BINNAMES, nullptr, pSetupData ); + if ( (nPaperBin < nBins) && (nBins != GDI_ERROR) ) + { + auto pBuffer = std::make_unique<sal_Unicode[]>(nBins*24); + DWORD nRet = ImplDeviceCaps( this, DC_BINNAMES, reinterpret_cast<BYTE*>(pBuffer.get()), pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + aPaperBinName = OUString( pBuffer.get() + (nPaperBin*24) ); + } + + return aPaperBinName; +} + +sal_uInt32 WinSalInfoPrinter::GetCapabilities( const ImplJobSetup* pSetupData, PrinterCapType nType ) +{ + DWORD nRet; + + switch ( nType ) + { + case PrinterCapType::SupportDialog: + return TRUE; + case PrinterCapType::Copies: + nRet = ImplDeviceCaps( this, DC_COPIES, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return nRet; + return 0; + case PrinterCapType::CollateCopies: + nRet = ImplDeviceCaps( this, DC_COLLATE, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + { + nRet = ImplDeviceCaps( this, DC_COPIES, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return nRet; + } + return 0; + + case PrinterCapType::SetOrientation: + nRet = ImplDeviceCaps( this, DC_ORIENTATION, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return TRUE; + return FALSE; + + case PrinterCapType::SetPaperSize: + case PrinterCapType::SetPaper: + nRet = ImplDeviceCaps( this, DC_PAPERS, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return TRUE; + return FALSE; + + default: + break; + } + + return 0; +} + +void WinSalInfoPrinter::GetPageInfo( const ImplJobSetup*, + tools::Long& rOutWidth, tools::Long& rOutHeight, + Point& rPageOffset, + Size& rPaperSize ) +{ + HDC hDC = m_hDC; + + rOutWidth = GetDeviceCaps( hDC, HORZRES ); + rOutHeight = GetDeviceCaps( hDC, VERTRES ); + + rPageOffset.setX( GetDeviceCaps( hDC, PHYSICALOFFSETX ) ); + rPageOffset.setY( GetDeviceCaps( hDC, PHYSICALOFFSETY ) ); + rPaperSize.setWidth( GetDeviceCaps( hDC, PHYSICALWIDTH ) ); + rPaperSize.setHeight( GetDeviceCaps( hDC, PHYSICALHEIGHT ) ); +} + + +std::unique_ptr<SalPrinter> WinSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter ) +{ + WinSalPrinter* pPrinter = new WinSalPrinter; + pPrinter->mpInfoPrinter = static_cast<WinSalInfoPrinter*>(pInfoPrinter); + return std::unique_ptr<SalPrinter>(pPrinter); +} + +static BOOL CALLBACK SalPrintAbortProc( HDC hPrnDC, int /* nError */ ) +{ + SalData* pSalData = GetSalData(); + WinSalPrinter* pPrinter; + int i = 0; + bool bWhile = true; + + // Ensure we handle the mutex which will be released in WinSalInstance::DoYield + SolarMutexGuard aSolarMutexGuard; + do + { + // process messages + bWhile = Application::Reschedule( true ); + if (i > 15) + bWhile = false; + else + ++i; + + pPrinter = pSalData->mpFirstPrinter; + while ( pPrinter ) + { + if( pPrinter->mhDC == hPrnDC ) + break; + + pPrinter = pPrinter->mpNextPrinter; + } + + if ( !pPrinter || pPrinter->mbAbort ) + return FALSE; + } + while ( bWhile ); + + return TRUE; +} + +static DEVMODEW const * ImplSalSetCopies( DEVMODEW const * pDevMode, sal_uInt32 nCopies, bool bCollate ) +{ + if ( pDevMode && (nCopies > 1) ) + { + if ( nCopies > 32765 ) + nCopies = 32765; + sal_uLong nDevSize = pDevMode->dmSize+pDevMode->dmDriverExtra; + LPDEVMODEW pNewDevMode = static_cast<LPDEVMODEW>(std::malloc( nDevSize )); + assert(pNewDevMode); // Don't handle OOM conditions + memcpy( pNewDevMode, pDevMode, nDevSize ); + pNewDevMode->dmFields |= DM_COPIES; + pNewDevMode->dmCopies = static_cast<short>(static_cast<sal_uInt16>(nCopies)); + pNewDevMode->dmFields |= DM_COLLATE; + if ( bCollate ) + pNewDevMode->dmCollate = DMCOLLATE_TRUE; + else + pNewDevMode->dmCollate = DMCOLLATE_FALSE; + return pNewDevMode; + } + else + { + return pDevMode; + } +} + + +WinSalPrinter::WinSalPrinter() : + mpInfoPrinter( nullptr ), + mpNextPrinter( nullptr ), + mhDC( nullptr ), + mnError( SalPrinterError::NONE ), + mnCopies( 0 ), + mbCollate( false ), + mbAbort( false ), + mbValid( true ) +{ + SalData* pSalData = GetSalData(); + // insert printer in printerlist + mpNextPrinter = pSalData->mpFirstPrinter; + pSalData->mpFirstPrinter = this; +} + +WinSalPrinter::~WinSalPrinter() +{ + SalData* pSalData = GetSalData(); + + // release DC if there is one still around because of AbortJob + HDC hDC = mhDC; + if ( hDC ) + { + // explicitly reset(), so the mxGraphics's borrowed HDC defaults are + // restored and WinSalGraphics's destructor won't work on a deleted HDC. + mxGraphics.reset(); + DeleteDC( hDC ); + } + + // remove printer from printerlist + if ( this == pSalData->mpFirstPrinter ) + pSalData->mpFirstPrinter = mpNextPrinter; + else + { + WinSalPrinter* pTempPrinter = pSalData->mpFirstPrinter; + + while( pTempPrinter->mpNextPrinter != this ) + pTempPrinter = pTempPrinter->mpNextPrinter; + + pTempPrinter->mpNextPrinter = mpNextPrinter; + } +} + +void WinSalPrinter::markInvalid() +{ + mbValid = false; +} + +// need wrappers for StarTocW/A to use structured exception handling +// since SEH does not mix with standard exception handling's cleanup +static int lcl_StartDocW1( HDC hDC, DOCINFOW const * pInfo, WinSalPrinter* pPrt ) +{ + int nRet = 0; + CATCH_DRIVER_EX_BEGIN; + nRet = ::StartDocW( hDC, pInfo ); + CATCH_DRIVER_EX_END( "exception in StartDocW", pPrt ); + return nRet; +} + +static int lcl_StartDocW( HDC hDC, DOCINFOW const * pInfo, WinSalPrinter* pPrt ) +{ + //tdf#127547 - Freeze/crash in Microsoft Print to PDF dialog, if we try to paste while + // executing the StartDocW method, Windows will call back into us on a separate thread, + // where we will attempt to take the SolarMutex. + SolarMutexReleaser aReleaser; + + return lcl_StartDocW1(hDC, pInfo, pPrt); +} + +bool WinSalPrinter::StartJob( const OUString* pFileName, + const OUString& rJobName, + const OUString&, + sal_uInt32 nCopies, + bool bCollate, + bool /*bDirect*/, + ImplJobSetup* pSetupData ) +{ + mnError = SalPrinterError::NONE; + mbAbort = false; + mnCopies = nCopies; + mbCollate = bCollate; + + DEVMODEW const * pOrgDevModeW = nullptr; + DEVMODEW const * pDevModeW = nullptr; + HDC hDC = nullptr; + if ( pSetupData && pSetupData->GetDriverData() ) + { + pOrgDevModeW = SAL_DEVMODE_W( pSetupData ); + pDevModeW = ImplSalSetCopies( pOrgDevModeW, nCopies, bCollate ); + } + + // #95347 some buggy drivers (eg, OKI) write to those buffers in CreateDC, although declared const - so provide some space + sal_Unicode aDrvBuf[4096]; + sal_Unicode aDevBuf[4096]; + memcpy( aDrvBuf, mpInfoPrinter->maDriverName.getStr(), (mpInfoPrinter->maDriverName.getLength()+1)*sizeof(sal_Unicode)); + memcpy( aDevBuf, mpInfoPrinter->maDeviceName.getStr(), (mpInfoPrinter->maDeviceName.getLength()+1)*sizeof(sal_Unicode)); + hDC = CreateDCW( o3tl::toW(aDrvBuf), + o3tl::toW(aDevBuf), + nullptr, + pDevModeW ); + + if ( pDevModeW != pOrgDevModeW ) + std::free( const_cast<DEVMODEW *>(pDevModeW) ); + + if ( !hDC ) + { + mnError = SalPrinterError::General; + return false; + } + + // make sure mhDC is set before the printer driver may call our abortproc + mhDC = hDC; + if ( SetAbortProc( hDC, SalPrintAbortProc ) <= 0 ) + { + mnError = SalPrinterError::General; + return false; + } + + mnError = SalPrinterError::NONE; + mbAbort = false; + + // As the Telecom Balloon Fax driver tends to send messages repeatedly + // we try to process first all, and then insert a dummy message + for (int i = 0; Application::Reschedule( true ) && i <= 15; ++i); + bool const ret = PostMessageW(GetSalData()->mpInstance->mhComWnd, SAL_MSG_DUMMY, 0, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + + // bring up a file chooser if printing to file port but no file name given + OUString aOutFileName; + if( mpInfoPrinter->maPortName.equalsIgnoreAsciiCase( "FILE:" ) && (!pFileName || pFileName->isEmpty()) ) + { + + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference< XFilePicker3 > xFilePicker = FilePicker::createWithMode(xContext, TemplateDescription::FILESAVE_SIMPLE); + + if( xFilePicker->execute() == ExecutableDialogResults::OK ) + { + Sequence< OUString > aPathSeq( xFilePicker->getSelectedFiles() ); + INetURLObject aObj( aPathSeq[0] ); + aOutFileName = aObj.PathToFileName(); + } + else + { + mnError = SalPrinterError::Abort; + return false; + } + } + + DOCINFOW aInfo = {}; + aInfo.cbSize = sizeof( aInfo ); + aInfo.lpszDocName = o3tl::toW(rJobName.getStr()); + if ( pFileName || aOutFileName.getLength() ) + { + if ( (pFileName && !pFileName->isEmpty()) || aOutFileName.getLength() ) + { + aInfo.lpszOutput = o3tl::toW((pFileName && !pFileName->isEmpty()) ? pFileName->getStr() : aOutFileName.getStr()); + } + else + aInfo.lpszOutput = L"FILE:"; + } + else + aInfo.lpszOutput = nullptr; + + // start Job, in the main thread + int nRet = vcl::solarthread::syncExecute([hDC, this, &aInfo]() -> int { return lcl_StartDocW(hDC, &aInfo, this); }); + + if ( nRet <= 0 ) + { + DWORD nError = GetLastError(); + if ( (nRet == SP_USERABORT) || (nRet == SP_APPABORT) || (nError == ERROR_PRINT_CANCELLED) || (nError == ERROR_CANCELLED) ) + mnError = SalPrinterError::Abort; + else + mnError = SalPrinterError::General; + return false; + } + + return true; +} + +void WinSalPrinter::DoEndDoc(HDC hDC) +{ + CATCH_DRIVER_EX_BEGIN; + if( ::EndDoc( hDC ) <= 0 ) + GetLastError(); + CATCH_DRIVER_EX_END( "exception in EndDoc", this ); +} + +bool WinSalPrinter::EndJob() +{ + HDC hDC = mhDC; + if (isValid()) + { + mxGraphics.reset(); + + // #i54419# Windows fax printer brings up a dialog in EndDoc + // which text previously copied in soffice process can be + // pasted to -> deadlock due to mutex not released. + // it should be safe to release the yield mutex over the EndDoc + // call, however the real solution is supposed to be the threading + // framework yet to come. + { + SolarMutexReleaser aReleaser; + DoEndDoc( hDC ); + } + DeleteDC( hDC ); + mhDC = nullptr; + } + + return true; +} + +SalGraphics* WinSalPrinter::StartPage( ImplJobSetup* pSetupData, bool bNewJobData ) +{ + if (!isValid()) + return nullptr; + + HDC hDC = mhDC; + if ( pSetupData && pSetupData->GetDriverData() && bNewJobData ) + { + DEVMODEW const * pOrgDevModeW; + DEVMODEW const * pDevModeW; + pOrgDevModeW = SAL_DEVMODE_W( pSetupData ); + pDevModeW = ImplSalSetCopies( pOrgDevModeW, mnCopies, mbCollate ); + ResetDCW( hDC, pDevModeW ); + if ( pDevModeW != pOrgDevModeW ) + std::free( const_cast<DEVMODEW *>(pDevModeW) ); + } + volatile int nRet = 0; + CATCH_DRIVER_EX_BEGIN; + nRet = ::StartPage( hDC ); + CATCH_DRIVER_EX_END( "exception in StartPage", this ); + + if ( nRet <= 0 ) + { + GetLastError(); + mnError = SalPrinterError::General; + return nullptr; + } + + // Hack to work around old PostScript printer drivers optimizing away empty pages + // TODO: move into ImplCreateSalPrnGraphics()? + HPEN hTempPen = SelectPen( hDC, GetStockPen( NULL_PEN ) ); + HBRUSH hTempBrush = SelectBrush( hDC, GetStockBrush( NULL_BRUSH ) ); + Rectangle( hDC, -8000, -8000, -7999, -7999 ); + SelectPen( hDC, hTempPen ); + SelectBrush( hDC, hTempBrush ); + + mxGraphics.reset(ImplCreateSalPrnGraphics( hDC )); + return mxGraphics.get(); +} + +void WinSalPrinter::EndPage() +{ + mxGraphics.reset(); + + if (!isValid()) + return; + + HDC hDC = mhDC; + volatile int nRet = 0; + CATCH_DRIVER_EX_BEGIN; + nRet = ::EndPage( hDC ); + CATCH_DRIVER_EX_END( "exception in EndPage", this ); + + if ( nRet <= 0 ) + { + GetLastError(); + mnError = SalPrinterError::General; + } +} + +SalPrinterError WinSalPrinter::GetErrorCode() +{ + return mnError; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salvd.cxx b/vcl/win/gdi/salvd.cxx new file mode 100644 index 0000000000..7b3e7e11fc --- /dev/null +++ b/vcl/win/gdi/salvd.cxx @@ -0,0 +1,223 @@ +/* -*- 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 <svsys.h> + +#include <comphelper/windowserrorstring.hxx> + +#include <vcl/sysdata.hxx> + +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salgdi.h> +#include <win/salvd.h> +#include <sal/log.hxx> +#include <o3tl/temporary.hxx> + +HBITMAP WinSalVirtualDevice::ImplCreateVirDevBitmap(HDC hDC, tools::Long nDX, tools::Long nDY, sal_uInt16 nBitCount, void **ppData) +{ + HBITMAP hBitmap; + + if ( nBitCount == 1 ) + { + hBitmap = CreateBitmap( static_cast<int>(nDX), static_cast<int>(nDY), 1, 1, nullptr ); + SAL_WARN_IF( !hBitmap, "vcl", "CreateBitmap failed: " << WindowsErrorString( GetLastError() ) ); + ppData = nullptr; + } + else + { + if (nBitCount == 0) + nBitCount = static_cast<WORD>(GetDeviceCaps(hDC, BITSPIXEL)); + + // #146839# Don't use CreateCompatibleBitmap() - there seem to + // be built-in limits for those HBITMAPs, at least this fails + // rather often on large displays/multi-monitor setups. + BITMAPINFO aBitmapInfo; + aBitmapInfo.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); + aBitmapInfo.bmiHeader.biWidth = nDX; + aBitmapInfo.bmiHeader.biHeight = nDY; + aBitmapInfo.bmiHeader.biPlanes = 1; + aBitmapInfo.bmiHeader.biBitCount = nBitCount; + aBitmapInfo.bmiHeader.biCompression = BI_RGB; + aBitmapInfo.bmiHeader.biSizeImage = 0; + aBitmapInfo.bmiHeader.biXPelsPerMeter = 0; + aBitmapInfo.bmiHeader.biYPelsPerMeter = 0; + aBitmapInfo.bmiHeader.biClrUsed = 0; + aBitmapInfo.bmiHeader.biClrImportant = 0; + + hBitmap = CreateDIBSection( hDC, &aBitmapInfo, + DIB_RGB_COLORS, ppData, nullptr, + 0 ); + SAL_WARN_IF( !hBitmap, "vcl", "CreateDIBSection failed: " << WindowsErrorString( GetLastError() ) ); + } + + return hBitmap; +} + +std::unique_ptr<SalVirtualDevice> WinSalInstance::CreateVirtualDevice( SalGraphics& rSGraphics, + tools::Long &nDX, tools::Long &nDY, + DeviceFormat /*eFormat*/, + const SystemGraphicsData* pData ) +{ + WinSalGraphics& rGraphics = static_cast<WinSalGraphics&>(rSGraphics); + HDC hDC = nullptr; + + if( pData ) + { + hDC = (pData->hDC) ? pData->hDC : GetDC(pData->hWnd); + if (hDC) + { + nDX = GetDeviceCaps( hDC, HORZRES ); + nDY = GetDeviceCaps( hDC, VERTRES ); + } + else + { + nDX = 0; + nDY = 0; + } + } + else + { + hDC = CreateCompatibleDC( rGraphics.getHDC() ); + SAL_WARN_IF( !hDC, "vcl", "CreateCompatibleDC failed: " << WindowsErrorString( GetLastError() ) ); + } + + if (!hDC) + return nullptr; + + sal_uInt16 nBitCount = 0; + HBITMAP hBmp = nullptr; + if (!pData) + { + // #124826# continue even if hBmp could not be created + // if we would return a failure in this case, the process + // would terminate which is not required + hBmp = WinSalVirtualDevice::ImplCreateVirDevBitmap(rGraphics.getHDC(), + nDX, nDY, nBitCount, + &o3tl::temporary<void*>(nullptr)); + } + + const bool bForeignDC = pData != nullptr && pData->hDC != nullptr; + const SalData* pSalData = GetSalData(); + + WinSalVirtualDevice* pVDev = new WinSalVirtualDevice(hDC, hBmp, nBitCount, + bForeignDC, nDX, nDY); + + WinSalGraphics* pVirGraphics = new WinSalGraphics(WinSalGraphics::VIRTUAL_DEVICE, + rGraphics.isScreen(), nullptr, pVDev); + + // by default no! mirroring for VirtualDevices, can be enabled with EnableRTL() + pVirGraphics->SetLayout( SalLayoutFlags::NONE ); + pVirGraphics->setHDC(hDC); + + if ( pSalData->mhDitherPal && pVirGraphics->isScreen() ) + { + pVirGraphics->setPalette(pSalData->mhDitherPal); + RealizePalette( hDC ); + } + + pVDev->setGraphics(pVirGraphics); + + return std::unique_ptr<SalVirtualDevice>(pVDev); +} + +WinSalVirtualDevice::WinSalVirtualDevice(HDC hDC, HBITMAP hBMP, sal_uInt16 nBitCount, bool bForeignDC, tools::Long nWidth, tools::Long nHeight) + : mhLocalDC(hDC), // HDC or 0 for Cache Device + mhBmp(hBMP), // Memory Bitmap + mnBitCount(nBitCount), // BitCount (0 or 1) + mbGraphics(false), // is Graphics used + mbForeignDC(bForeignDC), // uses a foreign DC instead of a bitmap + mnWidth(nWidth), + mnHeight(nHeight) +{ + // Default Bitmap + if (hBMP) + mhDefBmp = SelectBitmap(hDC, hBMP); + else + mhDefBmp = nullptr; + + // insert VirDev into list of virtual devices + SalData* pSalData = GetSalData(); + mpNext = pSalData->mpFirstVD; + pSalData->mpFirstVD = this; +} + +WinSalVirtualDevice::~WinSalVirtualDevice() +{ + // remove VirDev from list of virtual devices + SalData* pSalData = GetSalData(); + WinSalVirtualDevice** ppVirDev = &pSalData->mpFirstVD; + for(; (*ppVirDev != this) && *ppVirDev; ppVirDev = &(*ppVirDev)->mpNext ); + if( *ppVirDev ) + *ppVirDev = mpNext; + + HDC hDC = mpGraphics->getHDC(); + // restore the mpGraphics' original HDC values, so the HDC can be deleted in the !mbForeignDC case + mpGraphics->setHDC(nullptr); + + if( mhDefBmp ) + SelectBitmap(hDC, mhDefBmp); + if( !mbForeignDC ) + DeleteDC(hDC); +} + +SalGraphics* WinSalVirtualDevice::AcquireGraphics() +{ + if ( mbGraphics ) + return nullptr; + + if ( mpGraphics ) + mbGraphics = true; + + return mpGraphics.get(); +} + +void WinSalVirtualDevice::ReleaseGraphics( SalGraphics* ) +{ + mbGraphics = false; +} + +bool WinSalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY ) +{ + if( mbForeignDC || !mhBmp ) + return true; // ??? + + HBITMAP hNewBmp = ImplCreateVirDevBitmap(getHDC(), nDX, nDY, mnBitCount, + &o3tl::temporary<void*>(nullptr)); + if (!hNewBmp) + { + mnWidth = 0; + mnHeight = 0; + return false; + } + + mnWidth = nDX; + mnHeight = nDY; + + SelectBitmap(getHDC(), hNewBmp); + mhBmp.reset(hNewBmp); + + if (mpGraphics) + mpGraphics->GetImpl()->Init(); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/winlayout.cxx b/vcl/win/gdi/winlayout.cxx new file mode 100644 index 0000000000..eb5c740580 --- /dev/null +++ b/vcl/win/gdi/winlayout.cxx @@ -0,0 +1,235 @@ +/* -*- 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 <sal/config.h> +#include <config_features.h> + +#include <memory> + +#include <o3tl/safeint.hxx> +#include <osl/module.h> +#include <osl/file.h> +#include <sal/log.hxx> + +#include <comphelper/windowserrorstring.hxx> +#include <comphelper/scopeguard.hxx> + +#include <win/salgdi.h> +#include <win/saldata.hxx> +#include <win/wingdiimpl.hxx> +#include <ImplOutDevData.hxx> + +#include <win/DWriteTextRenderer.hxx> +#include <win/scoped_gdi.hxx> + +#include <sallayout.hxx> + +#include <cstdio> +#include <cstdlib> + +#include <rtl/character.hxx> + +#include <algorithm> + +#include <shlwapi.h> +#include <winver.h> + +TextOutRenderer& TextOutRenderer::get(bool bUseDWrite, bool bRenderingModeNatural) +{ + SalData* const pSalData = GetSalData(); + + if (!pSalData) + { // don't call this after DeInitVCL() + fprintf(stderr, "TextOutRenderer fatal error: no SalData"); + abort(); + } + + if (bUseDWrite) + { + if (!pSalData->m_pD2DWriteTextOutRenderer + || static_cast<D2DWriteTextOutRenderer*>(pSalData->m_pD2DWriteTextOutRenderer.get()) + ->GetRenderingModeNatural() + != bRenderingModeNatural) + { + pSalData->m_pD2DWriteTextOutRenderer.reset( + new D2DWriteTextOutRenderer(bRenderingModeNatural)); + } + return *pSalData->m_pD2DWriteTextOutRenderer; + } + if (!pSalData->m_pExTextOutRenderer) + { + pSalData->m_pExTextOutRenderer.reset(new ExTextOutRenderer); + } + return *pSalData->m_pExTextOutRenderer; +} + +bool ExTextOutRenderer::operator()(GenericSalLayout const& rLayout, SalGraphics& /*rGraphics*/, + HDC hDC, bool /*bRenderingModeNatural*/) +{ + int nStart = 0; + basegfx::B2DPoint aPos; + const GlyphItem* pGlyph; + const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont()); + UINT nTextAlign = GetTextAlign(hDC); + UINT nCurTextAlign = nTextAlign; + sal_Int32 nGlyphOffset = -pWinFont->GetTmDescent(); + + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + wchar_t glyphWStr = pGlyph->glyphId(); + UINT32 nNewTextAlign = nCurTextAlign; + sal_Int32 nYOffset = 0; + + if (pWinFont->IsCJKVerticalFont() && pGlyph->IsVertical()) + { + nNewTextAlign = VTA_CENTER | TA_BOTTOM; + nYOffset = nGlyphOffset; + } + else + nNewTextAlign = nTextAlign; + + if (nCurTextAlign != nNewTextAlign) + SetTextAlign(hDC, nNewTextAlign); + + ExtTextOutW(hDC, aPos.getX(), aPos.getY() + nYOffset, ETO_GLYPH_INDEX, nullptr, &glyphWStr, + 1, nullptr); + + nCurTextAlign = nNewTextAlign; + } + + if (nCurTextAlign != nTextAlign) + SetTextAlign(hDC, nTextAlign); + + return true; +} + +std::unique_ptr<GenericSalLayout> WinSalGraphics::GetTextLayout(int nFallbackLevel) +{ + assert(mpWinFontEntry[nFallbackLevel]); + if (!mpWinFontEntry[nFallbackLevel]) + return nullptr; + + assert(mpWinFontEntry[nFallbackLevel]->GetFontFace()); + + mpWinFontEntry[nFallbackLevel]->SetGraphics(this); + return std::make_unique<GenericSalLayout>(*mpWinFontEntry[nFallbackLevel]); +} + +WinFontInstance::WinFontInstance(const WinFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP) + : LogicalFontInstance(rPFF, rFSP) + , m_pGraphics(nullptr) + , m_hFont(nullptr) + , m_bIsCJKVerticalFont(false) + , m_nTmDescent(0) +{ +} + +WinFontInstance::~WinFontInstance() +{ + if (m_hFont) + ::DeleteFont(m_hFont); +} + +bool WinFontInstance::hasHScale() const +{ + const vcl::font::FontSelectPattern& rPattern = GetFontSelectPattern(); + int nHeight(rPattern.mnHeight); + int nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight); + return nWidth != nHeight; +} + +float WinFontInstance::getHScale() const +{ + const vcl::font::FontSelectPattern& rPattern = GetFontSelectPattern(); + int nHeight(rPattern.mnHeight); + if (!nHeight) + return 1.0; + float nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight); + return nWidth / nHeight; +} + +void WinFontInstance::ImplInitHbFont(hb_font_t* /*pHbFont*/) +{ + assert(m_pGraphics); + // Calculate the AverageWidthFactor, see LogicalFontInstance::GetScale(). + if (GetFontSelectPattern().mnWidth) + { + double nUPEM = GetFontFace()->UnitsPerEm(); + + LOGFONTW aLogFont; + GetObjectW(m_hFont, sizeof(LOGFONTW), &aLogFont); + + // Set the height (font size) to EM to minimize rounding errors. + aLogFont.lfHeight = -nUPEM; + // Set width to the default to get the original value in the metrics. + aLogFont.lfWidth = 0; + + TEXTMETRICW aFontMetric; + { + // Get the font metrics. + HDC hDC = m_pGraphics->getHDC(); + ScopedSelectedHFONT hFont(hDC, CreateFontIndirectW(&aLogFont)); + GetTextMetricsW(hDC, &aFontMetric); + } + + SetAverageWidthFactor(nUPEM / aFontMetric.tmAveCharWidth); + } +} + +void WinFontInstance::SetGraphics(WinSalGraphics* pGraphics) +{ + m_pGraphics = pGraphics; + if (m_hFont) + return; + HFONT hOrigFont; + HDC hDC = m_pGraphics->getHDC(); + std::tie(m_hFont, m_bIsCJKVerticalFont, m_nTmDescent) + = m_pGraphics->ImplDoSetFont(hDC, GetFontSelectPattern(), GetFontFace(), hOrigFont); + SelectObject(hDC, hOrigFont); +} + +void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout, HDC hDC, bool bUseDWrite, + bool bRenderingModeNatural) +{ + TextOutRenderer& render = TextOutRenderer::get(bUseDWrite, bRenderingModeNatural); + render(rLayout, *this, hDC, bRenderingModeNatural); +} + +void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout) +{ + if (!mbPrinter && mWinSalGraphicsImplBase->DrawTextLayout(rLayout)) + return; // handled by mWinSalGraphicsImplBase + + HDC hDC = getHDC(); + const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont()); + const HFONT hLayoutFont = pWinFont->GetHFONT(); + + const HFONT hOrigFont = ::SelectFont(hDC, hLayoutFont); + + // DWrite text renderer performs vertical writing better except printing. + const bool bVerticalScreenText + = !mbPrinter && rLayout.GetFont().GetFontSelectPattern().mbVertical; + const bool bRenderingModeNatural = rLayout.GetSubpixelPositioning(); + const bool bUseDWrite = bVerticalScreenText || bRenderingModeNatural; + DrawTextLayout(rLayout, hDC, bUseDWrite, bRenderingModeNatural); + + ::SelectFont(hDC, hOrigFont); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/src/50.bmp b/vcl/win/src/50.bmp Binary files differnew file mode 100644 index 0000000000..b9d56fcd14 --- /dev/null +++ b/vcl/win/src/50.bmp diff --git a/vcl/win/src/50.png b/vcl/win/src/50.png Binary files differnew file mode 100644 index 0000000000..8517d965f0 --- /dev/null +++ b/vcl/win/src/50.png diff --git a/vcl/win/src/ase.cur b/vcl/win/src/ase.cur Binary files differnew file mode 100644 index 0000000000..7634a7d34a --- /dev/null +++ b/vcl/win/src/ase.cur diff --git a/vcl/win/src/asn.cur b/vcl/win/src/asn.cur Binary files differnew file mode 100644 index 0000000000..e444e42bf3 --- /dev/null +++ b/vcl/win/src/asn.cur diff --git a/vcl/win/src/asne.cur b/vcl/win/src/asne.cur Binary files differnew file mode 100644 index 0000000000..e92cc65e7e --- /dev/null +++ b/vcl/win/src/asne.cur diff --git a/vcl/win/src/asns.cur b/vcl/win/src/asns.cur Binary files differnew file mode 100644 index 0000000000..04d0b09c35 --- /dev/null +++ b/vcl/win/src/asns.cur diff --git a/vcl/win/src/asnswe.cur b/vcl/win/src/asnswe.cur Binary files differnew file mode 100644 index 0000000000..a0e25b16de --- /dev/null +++ b/vcl/win/src/asnswe.cur diff --git a/vcl/win/src/asnw.cur b/vcl/win/src/asnw.cur Binary files differnew file mode 100644 index 0000000000..20322bc97b --- /dev/null +++ b/vcl/win/src/asnw.cur diff --git a/vcl/win/src/ass.cur b/vcl/win/src/ass.cur Binary files differnew file mode 100644 index 0000000000..7166636a1a --- /dev/null +++ b/vcl/win/src/ass.cur diff --git a/vcl/win/src/asse.cur b/vcl/win/src/asse.cur Binary files differnew file mode 100644 index 0000000000..8cb71234b0 --- /dev/null +++ b/vcl/win/src/asse.cur diff --git a/vcl/win/src/assw.cur b/vcl/win/src/assw.cur Binary files differnew file mode 100644 index 0000000000..46ee06d168 --- /dev/null +++ b/vcl/win/src/assw.cur diff --git a/vcl/win/src/asw.cur b/vcl/win/src/asw.cur Binary files differnew file mode 100644 index 0000000000..0ccac50f45 --- /dev/null +++ b/vcl/win/src/asw.cur diff --git a/vcl/win/src/aswe.cur b/vcl/win/src/aswe.cur Binary files differnew file mode 100644 index 0000000000..c238b7e10a --- /dev/null +++ b/vcl/win/src/aswe.cur diff --git a/vcl/win/src/chain.cur b/vcl/win/src/chain.cur Binary files differnew file mode 100644 index 0000000000..02abb7ab71 --- /dev/null +++ b/vcl/win/src/chain.cur diff --git a/vcl/win/src/chainnot.cur b/vcl/win/src/chainnot.cur Binary files differnew file mode 100644 index 0000000000..938ece03f3 --- /dev/null +++ b/vcl/win/src/chainnot.cur diff --git a/vcl/win/src/chart.cur b/vcl/win/src/chart.cur Binary files differnew file mode 100644 index 0000000000..25fe85b760 --- /dev/null +++ b/vcl/win/src/chart.cur diff --git a/vcl/win/src/copydata.cur b/vcl/win/src/copydata.cur Binary files differnew file mode 100644 index 0000000000..d3c4bc93af --- /dev/null +++ b/vcl/win/src/copydata.cur diff --git a/vcl/win/src/copydlnk.cur b/vcl/win/src/copydlnk.cur Binary files differnew file mode 100644 index 0000000000..495fd5e177 --- /dev/null +++ b/vcl/win/src/copydlnk.cur diff --git a/vcl/win/src/copyf.cur b/vcl/win/src/copyf.cur Binary files differnew file mode 100644 index 0000000000..450c09443a --- /dev/null +++ b/vcl/win/src/copyf.cur diff --git a/vcl/win/src/copyf2.cur b/vcl/win/src/copyf2.cur Binary files differnew file mode 100644 index 0000000000..ac8de5da6b --- /dev/null +++ b/vcl/win/src/copyf2.cur diff --git a/vcl/win/src/copyflnk.cur b/vcl/win/src/copyflnk.cur Binary files differnew file mode 100644 index 0000000000..e67f0539fa --- /dev/null +++ b/vcl/win/src/copyflnk.cur diff --git a/vcl/win/src/crook.cur b/vcl/win/src/crook.cur Binary files differnew file mode 100644 index 0000000000..c40cf591e2 --- /dev/null +++ b/vcl/win/src/crook.cur diff --git a/vcl/win/src/crop.cur b/vcl/win/src/crop.cur Binary files differnew file mode 100644 index 0000000000..327fb06976 --- /dev/null +++ b/vcl/win/src/crop.cur diff --git a/vcl/win/src/darc.cur b/vcl/win/src/darc.cur Binary files differnew file mode 100644 index 0000000000..38504fa23c --- /dev/null +++ b/vcl/win/src/darc.cur diff --git a/vcl/win/src/dbezier.cur b/vcl/win/src/dbezier.cur Binary files differnew file mode 100644 index 0000000000..f630b837dd --- /dev/null +++ b/vcl/win/src/dbezier.cur diff --git a/vcl/win/src/dcapt.cur b/vcl/win/src/dcapt.cur Binary files differnew file mode 100644 index 0000000000..10dd5ba0d6 --- /dev/null +++ b/vcl/win/src/dcapt.cur diff --git a/vcl/win/src/dcirccut.cur b/vcl/win/src/dcirccut.cur Binary files differnew file mode 100644 index 0000000000..b19d3f8257 --- /dev/null +++ b/vcl/win/src/dcirccut.cur diff --git a/vcl/win/src/dconnect.cur b/vcl/win/src/dconnect.cur Binary files differnew file mode 100644 index 0000000000..5318d8f22d --- /dev/null +++ b/vcl/win/src/dconnect.cur diff --git a/vcl/win/src/dellipse.cur b/vcl/win/src/dellipse.cur Binary files differnew file mode 100644 index 0000000000..c489a64033 --- /dev/null +++ b/vcl/win/src/dellipse.cur diff --git a/vcl/win/src/detectiv.cur b/vcl/win/src/detectiv.cur Binary files differnew file mode 100644 index 0000000000..30e5685b64 --- /dev/null +++ b/vcl/win/src/detectiv.cur diff --git a/vcl/win/src/dfree.cur b/vcl/win/src/dfree.cur Binary files differnew file mode 100644 index 0000000000..3ff56d0076 --- /dev/null +++ b/vcl/win/src/dfree.cur diff --git a/vcl/win/src/dline.cur b/vcl/win/src/dline.cur Binary files differnew file mode 100644 index 0000000000..623c33ac23 --- /dev/null +++ b/vcl/win/src/dline.cur diff --git a/vcl/win/src/dpie.cur b/vcl/win/src/dpie.cur Binary files differnew file mode 100644 index 0000000000..3b911cd01e --- /dev/null +++ b/vcl/win/src/dpie.cur diff --git a/vcl/win/src/dpolygon.cur b/vcl/win/src/dpolygon.cur Binary files differnew file mode 100644 index 0000000000..9467f1e286 --- /dev/null +++ b/vcl/win/src/dpolygon.cur diff --git a/vcl/win/src/drect.cur b/vcl/win/src/drect.cur Binary files differnew file mode 100644 index 0000000000..60a5242c20 --- /dev/null +++ b/vcl/win/src/drect.cur diff --git a/vcl/win/src/dtext.cur b/vcl/win/src/dtext.cur Binary files differnew file mode 100644 index 0000000000..01e7d31eae --- /dev/null +++ b/vcl/win/src/dtext.cur diff --git a/vcl/win/src/fatcross.cur b/vcl/win/src/fatcross.cur Binary files differnew file mode 100644 index 0000000000..9f92d04029 --- /dev/null +++ b/vcl/win/src/fatcross.cur diff --git a/vcl/win/src/fill.cur b/vcl/win/src/fill.cur Binary files differnew file mode 100644 index 0000000000..78f5fad87a --- /dev/null +++ b/vcl/win/src/fill.cur diff --git a/vcl/win/src/hshear.cur b/vcl/win/src/hshear.cur Binary files differnew file mode 100644 index 0000000000..5cf2211458 --- /dev/null +++ b/vcl/win/src/hshear.cur diff --git a/vcl/win/src/linkdata.cur b/vcl/win/src/linkdata.cur Binary files differnew file mode 100644 index 0000000000..e47c1dea2c --- /dev/null +++ b/vcl/win/src/linkdata.cur diff --git a/vcl/win/src/linkf.cur b/vcl/win/src/linkf.cur Binary files differnew file mode 100644 index 0000000000..6cc498a026 --- /dev/null +++ b/vcl/win/src/linkf.cur diff --git a/vcl/win/src/magnify.cur b/vcl/win/src/magnify.cur Binary files differnew file mode 100644 index 0000000000..1e32b92351 --- /dev/null +++ b/vcl/win/src/magnify.cur diff --git a/vcl/win/src/mirror.cur b/vcl/win/src/mirror.cur Binary files differnew file mode 100644 index 0000000000..e05eb836eb --- /dev/null +++ b/vcl/win/src/mirror.cur diff --git a/vcl/win/src/movebw.cur b/vcl/win/src/movebw.cur Binary files differnew file mode 100644 index 0000000000..d079eb9fe2 --- /dev/null +++ b/vcl/win/src/movebw.cur diff --git a/vcl/win/src/movedata.cur b/vcl/win/src/movedata.cur Binary files differnew file mode 100644 index 0000000000..4d67cbe471 --- /dev/null +++ b/vcl/win/src/movedata.cur diff --git a/vcl/win/src/movedlnk.cur b/vcl/win/src/movedlnk.cur Binary files differnew file mode 100644 index 0000000000..1bb7b03064 --- /dev/null +++ b/vcl/win/src/movedlnk.cur diff --git a/vcl/win/src/movef.cur b/vcl/win/src/movef.cur Binary files differnew file mode 100644 index 0000000000..6abee2381d --- /dev/null +++ b/vcl/win/src/movef.cur diff --git a/vcl/win/src/movef2.cur b/vcl/win/src/movef2.cur Binary files differnew file mode 100644 index 0000000000..d044981a3f --- /dev/null +++ b/vcl/win/src/movef2.cur diff --git a/vcl/win/src/moveflnk.cur b/vcl/win/src/moveflnk.cur Binary files differnew file mode 100644 index 0000000000..630fa1bc3e --- /dev/null +++ b/vcl/win/src/moveflnk.cur diff --git a/vcl/win/src/movept.cur b/vcl/win/src/movept.cur Binary files differnew file mode 100644 index 0000000000..81d3af5a05 --- /dev/null +++ b/vcl/win/src/movept.cur diff --git a/vcl/win/src/nullptr.cur b/vcl/win/src/nullptr.cur Binary files differnew file mode 100644 index 0000000000..28dbb2a903 --- /dev/null +++ b/vcl/win/src/nullptr.cur diff --git a/vcl/win/src/pivotcol.cur b/vcl/win/src/pivotcol.cur Binary files differnew file mode 100644 index 0000000000..061b3ba926 --- /dev/null +++ b/vcl/win/src/pivotcol.cur diff --git a/vcl/win/src/pivotdel.cur b/vcl/win/src/pivotdel.cur Binary files differnew file mode 100644 index 0000000000..4497dacd99 --- /dev/null +++ b/vcl/win/src/pivotdel.cur diff --git a/vcl/win/src/pivotfld.cur b/vcl/win/src/pivotfld.cur Binary files differnew file mode 100644 index 0000000000..efbbead893 --- /dev/null +++ b/vcl/win/src/pivotfld.cur diff --git a/vcl/win/src/pivotrow.cur b/vcl/win/src/pivotrow.cur Binary files differnew file mode 100644 index 0000000000..649444e9e1 --- /dev/null +++ b/vcl/win/src/pivotrow.cur diff --git a/vcl/win/src/rotate.cur b/vcl/win/src/rotate.cur Binary files differnew file mode 100644 index 0000000000..43c2a54a10 --- /dev/null +++ b/vcl/win/src/rotate.cur diff --git a/vcl/win/src/salsrc.rc b/vcl/win/src/salsrc.rc new file mode 100644 index 0000000000..b23ac149a7 --- /dev/null +++ b/vcl/win/src/salsrc.rc @@ -0,0 +1,90 @@ +/* -*- Mode: Fundamental; 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 <win/salids.hrc> + +SAL_RESID_POINTER_NULL CURSOR nullptr.cur +SAL_RESID_POINTER_MAGNIFY CURSOR magnify.cur +SAL_RESID_POINTER_FILL CURSOR fill.cur +SAL_RESID_POINTER_ROTATE CURSOR rotate.cur +SAL_RESID_POINTER_HSHEAR CURSOR hshear.cur +SAL_RESID_POINTER_VSHEAR CURSOR vshear.cur +SAL_RESID_POINTER_MIRROR CURSOR mirror.cur +SAL_RESID_POINTER_CROOK CURSOR crook.cur +SAL_RESID_POINTER_CROP CURSOR crop.cur +SAL_RESID_POINTER_MOVEPOINT CURSOR movept.cur +SAL_RESID_POINTER_MOVEBEZIERWEIGHT CURSOR movebw.cur +SAL_RESID_POINTER_MOVEDATA CURSOR movedata.cur +SAL_RESID_POINTER_COPYDATA CURSOR copydata.cur +SAL_RESID_POINTER_LINKDATA CURSOR linkdata.cur +SAL_RESID_POINTER_MOVEDATALINK CURSOR movedlnk.cur +SAL_RESID_POINTER_COPYDATALINK CURSOR copydlnk.cur +SAL_RESID_POINTER_MOVEFILE CURSOR movef.cur +SAL_RESID_POINTER_COPYFILE CURSOR copyf.cur +SAL_RESID_POINTER_LINKFILE CURSOR linkf.cur +SAL_RESID_POINTER_MOVEFILELINK CURSOR moveflnk.cur +SAL_RESID_POINTER_COPYFILELINK CURSOR copyflnk.cur +SAL_RESID_POINTER_MOVEFILES CURSOR movef2.cur +SAL_RESID_POINTER_COPYFILES CURSOR copyf2.cur +SAL_RESID_POINTER_DRAW_LINE CURSOR dline.cur +SAL_RESID_POINTER_DRAW_RECT CURSOR drect.cur +SAL_RESID_POINTER_DRAW_POLYGON CURSOR dpolygon.cur +SAL_RESID_POINTER_DRAW_BEZIER CURSOR dbezier.cur +SAL_RESID_POINTER_DRAW_ARC CURSOR darc.cur +SAL_RESID_POINTER_DRAW_PIE CURSOR dpie.cur +SAL_RESID_POINTER_DRAW_CIRCLECUT CURSOR dcirccut.cur +SAL_RESID_POINTER_DRAW_ELLIPSE CURSOR dellipse.cur +SAL_RESID_POINTER_DRAW_FREEHAND CURSOR dfree.cur +SAL_RESID_POINTER_DRAW_CONNECT CURSOR dconnect.cur +SAL_RESID_POINTER_DRAW_TEXT CURSOR dtext.cur +SAL_RESID_POINTER_DRAW_CAPTION CURSOR dcapt.cur +SAL_RESID_POINTER_CHART CURSOR chart.cur +SAL_RESID_POINTER_DETECTIVE CURSOR detectiv.cur +SAL_RESID_POINTER_PIVOT_COL CURSOR pivotcol.cur +SAL_RESID_POINTER_PIVOT_ROW CURSOR pivotrow.cur +SAL_RESID_POINTER_PIVOT_FIELD CURSOR pivotfld.cur +SAL_RESID_POINTER_PIVOT_DELETE CURSOR pivotdel.cur +SAL_RESID_POINTER_CHAIN CURSOR chain.cur +SAL_RESID_POINTER_CHAIN_NOTALLOWED CURSOR chainnot.cur +SAL_RESID_POINTER_AUTOSCROLL_N CURSOR asn.cur +SAL_RESID_POINTER_AUTOSCROLL_S CURSOR ass.cur +SAL_RESID_POINTER_AUTOSCROLL_W CURSOR asw.cur +SAL_RESID_POINTER_AUTOSCROLL_E CURSOR ase.cur +SAL_RESID_POINTER_AUTOSCROLL_NW CURSOR asnw.cur +SAL_RESID_POINTER_AUTOSCROLL_NE CURSOR asne.cur +SAL_RESID_POINTER_AUTOSCROLL_SW CURSOR assw.cur +SAL_RESID_POINTER_AUTOSCROLL_SE CURSOR asse.cur +SAL_RESID_POINTER_AUTOSCROLL_NS CURSOR asns.cur +SAL_RESID_POINTER_AUTOSCROLL_WE CURSOR aswe.cur +SAL_RESID_POINTER_AUTOSCROLL_NSWE CURSOR asnswe.cur +SAL_RESID_POINTER_TEXT_VERTICAL CURSOR vtext.cur +SAL_RESID_POINTER_TAB_SELECT_S CURSOR tblsels.cur +SAL_RESID_POINTER_TAB_SELECT_E CURSOR tblsele.cur +SAL_RESID_POINTER_TAB_SELECT_SE CURSOR tblselse.cur +SAL_RESID_POINTER_TAB_SELECT_W CURSOR tblselw.cur +SAL_RESID_POINTER_TAB_SELECT_SW CURSOR tblselsw.cur +SAL_RESID_POINTER_HIDEWHITESPACE CURSOR wshide.cur +SAL_RESID_POINTER_SHOWWHITESPACE CURSOR wsshow.cur +SAL_RESID_POINTER_FATCROSS CURSOR fatcross.cur + +SAL_RESID_BITMAP_50 BITMAP "50.bmp" + +SAL_RESID_ICON_DEFAULT ICON sd.ico + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/src/sd.ico b/vcl/win/src/sd.ico Binary files differnew file mode 100644 index 0000000000..b2a0a07a67 --- /dev/null +++ b/vcl/win/src/sd.ico diff --git a/vcl/win/src/tblsele.cur b/vcl/win/src/tblsele.cur Binary files differnew file mode 100644 index 0000000000..3683e20df1 --- /dev/null +++ b/vcl/win/src/tblsele.cur diff --git a/vcl/win/src/tblsels.cur b/vcl/win/src/tblsels.cur Binary files differnew file mode 100644 index 0000000000..007182d734 --- /dev/null +++ b/vcl/win/src/tblsels.cur diff --git a/vcl/win/src/tblselse.cur b/vcl/win/src/tblselse.cur Binary files differnew file mode 100644 index 0000000000..986f013950 --- /dev/null +++ b/vcl/win/src/tblselse.cur diff --git a/vcl/win/src/tblselsw.cur b/vcl/win/src/tblselsw.cur Binary files differnew file mode 100644 index 0000000000..adabba1a2a --- /dev/null +++ b/vcl/win/src/tblselsw.cur diff --git a/vcl/win/src/tblselw.cur b/vcl/win/src/tblselw.cur Binary files differnew file mode 100644 index 0000000000..a95eb85af4 --- /dev/null +++ b/vcl/win/src/tblselw.cur diff --git a/vcl/win/src/vshear.cur b/vcl/win/src/vshear.cur Binary files differnew file mode 100644 index 0000000000..a4bbf7e8eb --- /dev/null +++ b/vcl/win/src/vshear.cur diff --git a/vcl/win/src/vtext.cur b/vcl/win/src/vtext.cur Binary files differnew file mode 100644 index 0000000000..776177901e --- /dev/null +++ b/vcl/win/src/vtext.cur diff --git a/vcl/win/src/wshide.cur b/vcl/win/src/wshide.cur Binary files differnew file mode 100644 index 0000000000..bfa8fdfdba --- /dev/null +++ b/vcl/win/src/wshide.cur diff --git a/vcl/win/src/wsshow.cur b/vcl/win/src/wsshow.cur Binary files differnew file mode 100644 index 0000000000..e0c2106034 --- /dev/null +++ b/vcl/win/src/wsshow.cur diff --git a/vcl/win/window/keynames.cxx b/vcl/win/window/keynames.cxx new file mode 100644 index 0000000000..e30f7284ca --- /dev/null +++ b/vcl/win/window/keynames.cxx @@ -0,0 +1,225 @@ +/* -*- 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 <string.h> +#include <o3tl/string_view.hxx> +#include <rtl/ustring.hxx> +#include <sal/macros.h> + +#include <win/salframe.h> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +// Use unique ;) names to avoid clashes with the KEY_* (especially +// KEY_SHIFT) from <vcl/vclenum.hxx> + +#define PAPUGA_KEY_ESC 0x10000 +#define PAPUGA_KEY_BACK 0xE0000 +#define PAPUGA_KEY_ENTER 0x1C0000 +#define PAPUGA_KEY_SPACEBAR 0x390000 +#define PAPUGA_KEY_HOME 0x1470000 +#define PAPUGA_KEY_UP 0x1480000 +#define PAPUGA_KEY_PAGEUP 0x1490000 +#define PAPUGA_KEY_LEFT 0x14B0000 +#define PAPUGA_KEY_RIGHT 0x14D0000 +#define PAPUGA_KEY_END 0x14F0000 +#define PAPUGA_KEY_DOWN 0x1500000 +#define PAPUGA_KEY_PAGEDOWN 0x1510000 +#define PAPUGA_KEY_INSERT 0x1520000 +#define PAPUGA_KEY_DELETE 0x1530000 +#define PAPUGA_KEY_CONTROL 0x21D0000 +#define PAPUGA_KEY_SHIFT 0x22A0000 +#define PAPUGA_KEY_ALT 0x2380000 + +namespace vcl_sal { + + namespace { + + struct KeysNameReplacement + { + LONG aSymbol; + const char* pName; + }; + + struct KeyboardReplacements + { + const char* pLangName; + const KeysNameReplacement* pReplacements; + int nReplacements; + }; + + } + + // CAUTION CAUTION CAUTION + // Every string value in the replacements tables must be in UTF-8 + // but with the UTF-8 bytes encoded, not as such! Be careful! + + const struct KeysNameReplacement aImplReplacements_Asturian[] = + { + { PAPUGA_KEY_BACK, "Retrocesu" }, + { PAPUGA_KEY_ENTER, "Intro" }, + { PAPUGA_KEY_SPACEBAR, "Espaciu" }, + { PAPUGA_KEY_HOME, "Aniciu" }, + { PAPUGA_KEY_UP, "Arriba" }, + { PAPUGA_KEY_PAGEUP, "Re P\xc3\xa1" "x" }, + { PAPUGA_KEY_LEFT, "Izquierda" }, + { PAPUGA_KEY_RIGHT, "Drecha" }, + { PAPUGA_KEY_END, "Fin" }, + { PAPUGA_KEY_DOWN, "Abaxo" }, + { PAPUGA_KEY_PAGEDOWN, "Av P\xc3\xa1" "x" }, + { PAPUGA_KEY_INSERT, "Ins" }, + { PAPUGA_KEY_DELETE, "Supr" }, + { PAPUGA_KEY_SHIFT, "May\xc3\xba" "s" }, + }; + + const struct KeysNameReplacement aImplReplacements_Catalan[] = + { + { PAPUGA_KEY_BACK, "Retroc\xc3\xa9" "s" }, + { PAPUGA_KEY_ENTER, "Retorn" }, + { PAPUGA_KEY_SPACEBAR, "Espai" }, + { PAPUGA_KEY_HOME, "Inici" }, + { PAPUGA_KEY_UP, "Amunt" }, + { PAPUGA_KEY_PAGEUP, "Re P\xc3\xa0" "g" }, + { PAPUGA_KEY_LEFT, "Esquerra" }, + { PAPUGA_KEY_RIGHT, "Dreta" }, + { PAPUGA_KEY_END, "Fi" }, + { PAPUGA_KEY_DOWN, "Avall" }, + { PAPUGA_KEY_PAGEDOWN, "Av P\xc3\xa0" "g" }, + { PAPUGA_KEY_INSERT, "Ins" }, + { PAPUGA_KEY_DELETE, "Supr" }, + { PAPUGA_KEY_SHIFT, "Maj" }, + }; + + const struct KeysNameReplacement aImplReplacements_Estonian[] = + { + { PAPUGA_KEY_RIGHT, "Nool paremale" }, + { PAPUGA_KEY_LEFT, "Nool vasakule" }, + { PAPUGA_KEY_UP, "Nool \xc3\xbc" "les" }, + { PAPUGA_KEY_DOWN, "Nool alla" }, + { PAPUGA_KEY_BACK, "Tagasil\xc3\xbc" "ke" }, + { PAPUGA_KEY_ENTER, "Enter" }, + { PAPUGA_KEY_SPACEBAR, "T\xc3\xbc" "hik" }, + }; + + const struct KeysNameReplacement aImplReplacements_Lithuanian[] = + { + { PAPUGA_KEY_ESC, "Gr" }, + { PAPUGA_KEY_BACK, "Naikinti" }, + { PAPUGA_KEY_ENTER, "\xc4\xae" "vesti" }, + { PAPUGA_KEY_SPACEBAR, "Tarpas" }, + { PAPUGA_KEY_HOME, "Prad" }, + { PAPUGA_KEY_UP, "Auk\xc5\xa1" "tyn" }, + { PAPUGA_KEY_PAGEUP, "Psl\xe2\x86\x91" }, + { PAPUGA_KEY_LEFT, "Kair\xc4\x97" "n" }, + { PAPUGA_KEY_RIGHT, "De\xc5\xa1" "in\xc4\x97" "n" }, + { PAPUGA_KEY_END, "Pab" }, + { PAPUGA_KEY_DOWN, "\xc5\xbd" "emyn" }, + { PAPUGA_KEY_PAGEDOWN, "Psl\xe2\x86\x93" }, + { PAPUGA_KEY_INSERT, "\xc4\xae" "terpti" }, + { PAPUGA_KEY_DELETE, "\xc5\xa0" "al" }, + { PAPUGA_KEY_CONTROL, "Vald" }, + { PAPUGA_KEY_SHIFT, "Lyg2" }, + { PAPUGA_KEY_ALT, "Alt" }, + }; + + const struct KeysNameReplacement aImplReplacements_Slovenian[] = + { + { PAPUGA_KEY_ESC, "Ube\xc5\xbe" "nica" }, + { PAPUGA_KEY_BACK, "Vra\xc4\x8d" "alka" }, + { PAPUGA_KEY_ENTER, "Vna\xc5\xa1" "alka" }, + { PAPUGA_KEY_SPACEBAR, "Preslednica" }, + { PAPUGA_KEY_HOME, "Za\xc4\x8d" "etek" }, + { PAPUGA_KEY_UP, "Navzgor" }, + { PAPUGA_KEY_PAGEUP, "Prej\xc5\xa1" "nja stran" }, + { PAPUGA_KEY_LEFT, "Levo" }, + { PAPUGA_KEY_RIGHT, "Desno" }, + { PAPUGA_KEY_END, "Konec" }, + { PAPUGA_KEY_DOWN, "Navzdol" }, + { PAPUGA_KEY_PAGEDOWN, "Naslednja stran" }, + { PAPUGA_KEY_INSERT, "Vrivalka" }, + { PAPUGA_KEY_DELETE, "Brisalka" }, + { PAPUGA_KEY_CONTROL, "Krmilka" }, + { PAPUGA_KEY_SHIFT, "Dvigalka" }, + { PAPUGA_KEY_ALT, "Izmenjalka" }, + }; + + const struct KeysNameReplacement aImplReplacements_Spanish[] = + { + { PAPUGA_KEY_BACK, "Retroceso" }, + { PAPUGA_KEY_ENTER, "Intro" }, + { PAPUGA_KEY_SPACEBAR, "Espacio" }, + { PAPUGA_KEY_HOME, "Inicio" }, + { PAPUGA_KEY_UP, "Arriba" }, + { PAPUGA_KEY_PAGEUP, "Re P\xc3\xa1" "g" }, + { PAPUGA_KEY_LEFT, "Izquierda" }, + { PAPUGA_KEY_RIGHT, "Derecha" }, + { PAPUGA_KEY_END, "Fin" }, + { PAPUGA_KEY_DOWN, "Abajo" }, + { PAPUGA_KEY_PAGEDOWN, "Av P\xc3\xa1" "g" }, + { PAPUGA_KEY_INSERT, "Ins" }, + { PAPUGA_KEY_DELETE, "Supr" }, + { PAPUGA_KEY_SHIFT, "May\xc3\xba" "s" }, + }; + + const struct KeysNameReplacement aImplReplacements_Hungarian[] = + { + { PAPUGA_KEY_RIGHT, "Jobbra" }, + { PAPUGA_KEY_LEFT, "Balra" }, + { PAPUGA_KEY_UP, "Fel" }, + { PAPUGA_KEY_DOWN, "Le" }, + { PAPUGA_KEY_ENTER, "Enter" }, + { PAPUGA_KEY_SPACEBAR, "Sz\xc3\xb3" "k\xc3\xb6" "z" }, + }; + + const struct KeyboardReplacements aKeyboards[] = + { + { "ast",aImplReplacements_Asturian, std::size(aImplReplacements_Asturian) }, + { "ca", aImplReplacements_Catalan, std::size(aImplReplacements_Catalan) }, + { "et", aImplReplacements_Estonian, std::size(aImplReplacements_Estonian) }, + { "hu", aImplReplacements_Hungarian, std::size(aImplReplacements_Hungarian) }, + { "lt", aImplReplacements_Lithuanian, std::size(aImplReplacements_Lithuanian) }, + { "sl", aImplReplacements_Slovenian, std::size(aImplReplacements_Slovenian) }, + { "es", aImplReplacements_Spanish, std::size(aImplReplacements_Spanish) }, + }; + + // translate keycodes, used within the displayed menu shortcuts + OUString getKeysReplacementName( std::u16string_view pLang, LONG nSymbol ) + { + for( unsigned int n = 0; n < SAL_N_ELEMENTS(aKeyboards); n++ ) + { + if( o3tl::equalsAscii( pLang, aKeyboards[n].pLangName ) ) + { + const struct KeysNameReplacement* pRepl = aKeyboards[n].pReplacements; + for( int m = aKeyboards[n].nReplacements ; m ; ) + { + if( nSymbol == pRepl[--m].aSymbol ) + return OUString( pRepl[m].pName, strlen(pRepl[m].pName), RTL_TEXTENCODING_UTF8 ); + } + } + } + + return OUString(); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/window/salframe.cxx b/vcl/win/window/salframe.cxx new file mode 100644 index 0000000000..cf2c8c6f8b --- /dev/null +++ b/vcl/win/window/salframe.cxx @@ -0,0 +1,6099 @@ +/* -*- 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 <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/Rectangle.hpp> + +#include <officecfg/Office/Common.hxx> + +#include <memory> +#include <string.h> +#include <limits.h> + +#include <svsys.h> + +#include <comphelper/windowserrorstring.hxx> + +#include <fstream> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/ini_parser.hpp> +#include <osl/file.hxx> +#include <osl/process.h> + +#include <rtl/character.hxx> +#include <rtl/string.h> +#include <rtl/ustring.h> +#include <sal/log.hxx> + +#include <osl/module.h> +#include <comphelper/scopeguard.hxx> +#include <tools/debug.hxx> +#include <o3tl/enumarray.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <vcl/event.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/timer.hxx> +#include <vcl/settings.hxx> +#include <vcl/keycodes.hxx> +#include <vcl/window.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/svapp.hxx> +#include <vcl/ptrstyle.hxx> + +#include <win/wincomp.hxx> +#include <win/salids.hrc> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salbmp.h> +#include <win/salgdi.h> +#include <win/salsys.h> +#include <win/salframe.h> +#include <win/salvd.h> +#include <win/salmenu.h> +#include <win/salobj.h> +#include <win/saltimer.h> + +#include <helpwin.hxx> +#include <window.h> +#include <sallayout.hxx> + +#include <vector> + +#include <com/sun/star/uno/Exception.hpp> + +#include <oleacc.h> +#include <com/sun/star/accessibility/XMSAAService.hpp> + +#include <time.h> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <dwmapi.h> +#include <shobjidl.h> +#include <propkey.h> +#include <propvarutil.h> +#include <shellapi.h> +#include <uxtheme.h> +#include <Vssym32.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; + +#ifndef IDC_PEN +# define IDC_PEN MAKEINTRESOURCE(32631) +#endif + +const unsigned int WM_USER_SYSTEM_WINDOW_ACTIVATED = RegisterWindowMessageW(L"SYSTEM_WINDOW_ACTIVATED"); + +bool WinSalFrame::mbInReparent = false; + +// Macros for support of WM_UNICHAR & Keyman 6.0 +#define Uni_SupplementaryPlanesStart 0x10000 + +static void UpdateFrameGeometry(WinSalFrame* pFrame); +static void SetMaximizedFrameGeometry( HWND hWnd, WinSalFrame* pFrame, RECT* pParentRect = nullptr ); + +static void SetGeometrySize(vcl::WindowPosSize& rWinPosSize, const Size& rSize) +{ + rWinPosSize.setWidth(rSize.Width() < 0 ? 0 : rSize.Width()); + rWinPosSize.setHeight(rSize.Height() < 0 ? 0 : rSize.Height()); +} + +// If called with UpdateFrameGeometry, it must be called after it, as UpdateFrameGeometry +// updates the geometry depending on the old state! +void WinSalFrame::UpdateFrameState() +{ + // don't overwrite restore state in fullscreen mode + if (isFullScreen()) + return; + + const bool bVisible = (GetWindowStyle(mhWnd) & WS_VISIBLE); + if (IsIconic(mhWnd)) + { + m_eState &= ~vcl::WindowState(vcl::WindowState::Normal | vcl::WindowState::Maximized); + m_eState |= vcl::WindowState::Minimized; + if (bVisible) + mnShowState = SW_SHOWMINIMIZED; + } + else if (IsZoomed(mhWnd)) + { + m_eState &= ~vcl::WindowState(vcl::WindowState::Minimized | vcl::WindowState::Normal); + m_eState |= vcl::WindowState::Maximized; + if (bVisible) + mnShowState = SW_SHOWMAXIMIZED; + mbRestoreMaximize = true; + } + else + { + m_eState &= ~vcl::WindowState(vcl::WindowState::Minimized | vcl::WindowState::Maximized); + m_eState |= vcl::WindowState::Normal; + if (bVisible) + mnShowState = SW_SHOWNORMAL; + mbRestoreMaximize = false; + } +} + +// if pParentRect is set, the workarea of the monitor that contains pParentRect is returned +void ImplSalGetWorkArea( HWND hWnd, RECT *pRect, const RECT *pParentRect ) +{ + if (Application::IsHeadlessModeEnabled()) { + pRect->left = 0; + pRect->top = 0; + pRect->right = VIRTUAL_DESKTOP_WIDTH; + pRect->bottom = VIRTUAL_DESKTOP_HEIGHT; + return; + } + // check if we or our parent is fullscreen, then the taskbar should be ignored + bool bIgnoreTaskbar = false; + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if( pFrame ) + { + vcl::Window *pWin = pFrame->GetWindow(); + while( pWin ) + { + WorkWindow *pWorkWin = (pWin->GetType() == WindowType::WORKWINDOW) ? static_cast<WorkWindow *>(pWin) : nullptr; + if( pWorkWin && pWorkWin->ImplGetWindowImpl()->mbReallyVisible && pWorkWin->IsFullScreenMode() ) + { + bIgnoreTaskbar = true; + break; + } + else + pWin = pWin->ImplGetWindowImpl()->mpParent; + } + } + + // calculates the work area taking multiple monitors into account + static int nMonitors = GetSystemMetrics( SM_CMONITORS ); + if( nMonitors == 1 ) + { + if( bIgnoreTaskbar ) + { + pRect->left = pRect->top = 0; + pRect->right = GetSystemMetrics( SM_CXSCREEN ); + pRect->bottom = GetSystemMetrics( SM_CYSCREEN ); + } + else + SystemParametersInfoW( SPI_GETWORKAREA, 0, pRect, 0 ); + } + else + { + if( pParentRect != nullptr ) + { + // return the size of the monitor where pParentRect lives + HMONITOR hMonitor; + MONITORINFO mi; + + // get the nearest monitor to the passed rect. + hMonitor = MonitorFromRect(pParentRect, MONITOR_DEFAULTTONEAREST); + + // get the work area or entire monitor rect. + mi.cbSize = sizeof(mi); + GetMonitorInfo(hMonitor, &mi); + if( !bIgnoreTaskbar ) + *pRect = mi.rcWork; + else + *pRect = mi.rcMonitor; + } + else + { + // return the union of all monitors + pRect->left = GetSystemMetrics( SM_XVIRTUALSCREEN ); + pRect->top = GetSystemMetrics( SM_YVIRTUALSCREEN ); + pRect->right = pRect->left + GetSystemMetrics( SM_CXVIRTUALSCREEN ); + pRect->bottom = pRect->top + GetSystemMetrics( SM_CYVIRTUALSCREEN ); + + // virtualscreen does not take taskbar into account, so use the corresponding + // diffs between screen and workarea from the default screen + // however, this is still not perfect: the taskbar might not be on the primary screen + if( !bIgnoreTaskbar ) + { + RECT wRect, scrRect; + SystemParametersInfoW( SPI_GETWORKAREA, 0, &wRect, 0 ); + scrRect.left = 0; + scrRect.top = 0; + scrRect.right = GetSystemMetrics( SM_CXSCREEN ); + scrRect.bottom = GetSystemMetrics( SM_CYSCREEN ); + + pRect->left += wRect.left; + pRect->top += wRect.top; + pRect->right -= scrRect.right - wRect.right; + pRect->bottom -= scrRect.bottom - wRect.bottom; + } + } + } +} + +namespace { + +enum PreferredAppMode +{ + AllowDark = 1, + ForceDark = 2, + ForceLight = 3 +}; + +} + +static void UpdateDarkMode(HWND hWnd) +{ + static bool bOSSupportsDarkMode = OSSupportsDarkMode(); + if (!bOSSupportsDarkMode) + return; + + HINSTANCE hUxthemeLib = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!hUxthemeLib) + return; + + typedef PreferredAppMode(WINAPI* SetPreferredAppMode_t)(PreferredAppMode); + auto SetPreferredAppMode = reinterpret_cast<SetPreferredAppMode_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(135))); + if (SetPreferredAppMode) + { + switch (MiscSettings::GetDarkMode()) + { + case 0: + SetPreferredAppMode(AllowDark); + break; + case 1: + SetPreferredAppMode(ForceLight); + break; + case 2: + SetPreferredAppMode(ForceDark); + break; + } + } + + BOOL bDarkMode = UseDarkMode(); + + typedef void(WINAPI* AllowDarkModeForWindow_t)(HWND, BOOL); + auto AllowDarkModeForWindow = reinterpret_cast<AllowDarkModeForWindow_t>(GetProcAddress(hUxthemeLib, MAKEINTRESOURCEA(133))); + if (AllowDarkModeForWindow) + AllowDarkModeForWindow(hWnd, bDarkMode); + + FreeLibrary(hUxthemeLib); + + if (!AllowDarkModeForWindow) + return; + + DwmSetWindowAttribute(hWnd, 20, &bDarkMode, sizeof(bDarkMode)); +} + +SalFrame* ImplSalCreateFrame( WinSalInstance* pInst, + HWND hWndParent, SalFrameStyleFlags nSalFrameStyle ) +{ + WinSalFrame* pFrame = new WinSalFrame; + HWND hWnd; + DWORD nSysStyle = 0; + DWORD nExSysStyle = 0; + bool bSubFrame = false; + + static const char* pEnvSynchronize = getenv("SAL_SYNCHRONIZE"); + if ( pEnvSynchronize ) // no buffering of drawing commands + GdiSetBatchLimit( 1 ); + + static const char* pEnvTransparentFloats = getenv("SAL_TRANSPARENT_FLOATS" ); + + // determine creation data + if ( nSalFrameStyle & (SalFrameStyleFlags::PLUG | SalFrameStyleFlags::SYSTEMCHILD) ) + { + nSysStyle |= WS_CHILD; + if( nSalFrameStyle & SalFrameStyleFlags::SYSTEMCHILD ) + nSysStyle |= WS_CLIPSIBLINGS; + } + else + { + // #i87402# commenting out WS_CLIPCHILDREN + // this breaks SalFrameStyleFlags::SYSTEMCHILD handling, which is not + // used currently. Probably SalFrameStyleFlags::SYSTEMCHILD should be + // removed again. + + // nSysStyle |= WS_CLIPCHILDREN; + if ( hWndParent ) + { + nSysStyle |= WS_POPUP; + bSubFrame = true; + pFrame->mbNoIcon = true; + } + else + { + // Only with WS_OVERLAPPED we get a useful default position/size + if ( (nSalFrameStyle & (SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::MOVEABLE)) == + (SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::MOVEABLE) ) + nSysStyle |= WS_OVERLAPPED; + else + { + nSysStyle |= WS_POPUP; + if ( !(nSalFrameStyle & SalFrameStyleFlags::MOVEABLE) ) + nExSysStyle |= WS_EX_TOOLWINDOW; // avoid taskbar appearance, for eg splash screen + } + } + + if ( nSalFrameStyle & SalFrameStyleFlags::MOVEABLE ) + { + pFrame->mbCaption = true; + nSysStyle |= WS_SYSMENU | WS_CAPTION; + if ( !hWndParent ) + nSysStyle |= WS_SYSMENU | WS_MINIMIZEBOX; + else + nExSysStyle |= WS_EX_DLGMODALFRAME; + + if ( nSalFrameStyle & SalFrameStyleFlags::SIZEABLE ) + { + pFrame->mbSizeBorder = true; + nSysStyle |= WS_THICKFRAME; + if ( !hWndParent ) + nSysStyle |= WS_MAXIMIZEBOX; + } + else + pFrame->mbFixBorder = true; + + if ( nSalFrameStyle & SalFrameStyleFlags::DEFAULT ) + nExSysStyle |= WS_EX_APPWINDOW; + } + if( nSalFrameStyle & SalFrameStyleFlags::TOOLWINDOW + // #100656# toolwindows lead to bad alt-tab behaviour, if they have the focus + // you must press it twice to leave the application + // so toolwindows are only used for non sizeable windows + // which are typically small, so a small caption makes sense + + // #103578# looked too bad - above changes reverted + /* && !(nSalFrameStyle & SalFrameStyleFlags::SIZEABLE) */ ) + { + pFrame->mbNoIcon = true; + nExSysStyle |= WS_EX_TOOLWINDOW; + if ( pEnvTransparentFloats /*&& !(nSalFrameStyle & SalFrameStyleFlags::MOVEABLE) */) + nExSysStyle |= WS_EX_LAYERED; + } + } + if ( nSalFrameStyle & SalFrameStyleFlags::FLOAT ) + { + nExSysStyle |= WS_EX_TOOLWINDOW; + pFrame->mbFloatWin = true; + + if (pEnvTransparentFloats) + nExSysStyle |= WS_EX_LAYERED; + + } + if (nSalFrameStyle & SalFrameStyleFlags::TOOLTIP) + nExSysStyle |= WS_EX_TOPMOST; + + // init frame data + pFrame->mnStyle = nSalFrameStyle; + + // determine show style + pFrame->mnShowState = SW_SHOWNORMAL; + if ( (nSysStyle & (WS_POPUP | WS_MAXIMIZEBOX | WS_THICKFRAME)) == (WS_MAXIMIZEBOX | WS_THICKFRAME) ) + { + if ( GetSystemMetrics( SM_CXSCREEN ) <= 1024 ) + pFrame->mnShowState = SW_SHOWMAXIMIZED; + else + { + if ( nSalFrameStyle & SalFrameStyleFlags::DEFAULT ) + { + SalData* pSalData = GetSalData(); + pFrame->mnShowState = pSalData->mnCmdShow; + if ( (pFrame->mnShowState != SW_SHOWMINIMIZED) && + (pFrame->mnShowState != SW_MINIMIZE) && + (pFrame->mnShowState != SW_SHOWMINNOACTIVE) ) + { + if ( (pFrame->mnShowState == SW_SHOWMAXIMIZED) || + (pFrame->mnShowState == SW_MAXIMIZE) ) + pFrame->mbOverwriteState = false; + pFrame->mnShowState = SW_SHOWMAXIMIZED; + } + else + pFrame->mbOverwriteState = false; + } + else + { + // Document Windows are also maximized, if the current Document Window + // is also maximized + HWND hWnd2 = GetForegroundWindow(); + if ( hWnd2 && IsMaximized( hWnd2 ) && + (GetWindowInstance( hWnd2 ) == pInst->mhInst) && + ((GetWindowStyle( hWnd2 ) & (WS_POPUP | WS_MAXIMIZEBOX | WS_THICKFRAME)) == (WS_MAXIMIZEBOX | WS_THICKFRAME)) ) + pFrame->mnShowState = SW_SHOWMAXIMIZED; + } + } + } + + // create frame + LPCWSTR pClassName; + if ( bSubFrame ) + { + if ( nSalFrameStyle & (SalFrameStyleFlags::MOVEABLE|SalFrameStyleFlags::NOSHADOW) ) // check if shadow not wanted + pClassName = SAL_SUBFRAME_CLASSNAMEW; + else + pClassName = SAL_TMPSUBFRAME_CLASSNAMEW; // undecorated floaters will get shadow on XP + } + else + { + if ( nSalFrameStyle & SalFrameStyleFlags::MOVEABLE ) + pClassName = SAL_FRAME_CLASSNAMEW; + else + pClassName = SAL_TMPSUBFRAME_CLASSNAMEW; + } + hWnd = CreateWindowExW( nExSysStyle, pClassName, L"", nSysStyle, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, + hWndParent, nullptr, pInst->mhInst, pFrame ); + SAL_WARN_IF(!hWnd, "vcl", "CreateWindowExW failed: " << WindowsErrorString(GetLastError())); + +#if OSL_DEBUG_LEVEL > 1 + // set transparency value + if( GetWindowExStyle( hWnd ) & WS_EX_LAYERED ) + SetLayeredWindowAttributes( hWnd, 0, 230, 0x00000002 /*LWA_ALPHA*/ ); +#endif + if ( !hWnd ) + { + delete pFrame; + return nullptr; + } + + // If we have a Window with a Caption Bar and without + // a MaximizeBox, we change the SystemMenu + if ( (nSysStyle & (WS_CAPTION | WS_MAXIMIZEBOX)) == (WS_CAPTION) ) + { + HMENU hSysMenu = GetSystemMenu( hWnd, FALSE ); + if ( hSysMenu ) + { + if ( !(nSysStyle & (WS_MINIMIZEBOX | WS_MAXIMIZEBOX)) ) + DeleteMenu( hSysMenu, SC_RESTORE, MF_BYCOMMAND ); + else + EnableMenuItem( hSysMenu, SC_RESTORE, MF_BYCOMMAND | MF_GRAYED | MF_DISABLED ); + if ( !(nSysStyle & WS_MINIMIZEBOX) ) + DeleteMenu( hSysMenu, SC_MINIMIZE, MF_BYCOMMAND ); + if ( !(nSysStyle & WS_MAXIMIZEBOX) ) + DeleteMenu( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND ); + if ( !(nSysStyle & WS_THICKFRAME) ) + DeleteMenu( hSysMenu, SC_SIZE, MF_BYCOMMAND ); + } + } + if ( (nSysStyle & WS_SYSMENU) && !(nSalFrameStyle & SalFrameStyleFlags::CLOSEABLE) ) + { + HMENU hSysMenu = GetSystemMenu( hWnd, FALSE ); + if ( hSysMenu ) + EnableMenuItem( hSysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED | MF_DISABLED ); + } + + // reset input context + pFrame->mhDefIMEContext = ImmAssociateContext( hWnd, nullptr ); + + // determine output size and state + RECT aRect; + GetClientRect( hWnd, &aRect ); + pFrame->mbDefPos = true; + + UpdateFrameGeometry(pFrame); + pFrame->UpdateFrameState(); + + if( pFrame->mnShowState == SW_SHOWMAXIMIZED ) + { + // #96084 set a useful internal window size because + // the window will not be maximized (and the size updated) before show() + + SetMaximizedFrameGeometry( hWnd, pFrame ); + } + + return pFrame; +} + +// helper that only creates the HWND +// to allow for easy reparenting of system windows, (i.e. destroy and create new) +HWND ImplSalReCreateHWND( HWND hWndParent, HWND oldhWnd, bool bAsChild ) +{ + HINSTANCE hInstance = GetSalData()->mhInst; + sal_uLong nSysStyle = GetWindowLongW( oldhWnd, GWL_STYLE ); + sal_uLong nExSysStyle = GetWindowLongW( oldhWnd, GWL_EXSTYLE ); + + if( bAsChild ) + { + nSysStyle = WS_CHILD; + nExSysStyle = 0; + } + + LPCWSTR pClassName = SAL_SUBFRAME_CLASSNAMEW; + return CreateWindowExW( nExSysStyle, pClassName, L"", nSysStyle, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, + hWndParent, nullptr, hInstance, GetWindowPtr( oldhWnd ) ); +} + +// translation table from System keycodes into StartView keycodes +#define KEY_TAB_SIZE 168 + +const sal_uInt16 aImplTranslateKeyTab[KEY_TAB_SIZE] = +{ + // StarView-Code System-Code Index + 0, // 0 + 0, // VK_LBUTTON 1 + 0, // VK_RBUTTON 2 + 0, // VK_CANCEL 3 + 0, // VK_MBUTTON 4 + 0, // 5 + 0, // 6 + 0, // 7 + KEY_BACKSPACE, // VK_BACK 8 + KEY_TAB, // VK_TAB 9 + 0, // 10 + 0, // 11 + 0, // VK_CLEAR 12 + KEY_RETURN, // VK_RETURN 13 + 0, // 14 + 0, // 15 + 0, // VK_SHIFT 16 + 0, // VK_CONTROL 17 + 0, // VK_MENU 18 + 0, // VK_PAUSE 19 + 0, // VK_CAPITAL 20 + 0, // VK_HANGUL 21 + 0, // 22 + 0, // 23 + 0, // 24 + KEY_HANGUL_HANJA, // VK_HANJA 25 + 0, // 26 + KEY_ESCAPE, // VK_ESCAPE 27 + 0, // 28 + 0, // 29 + 0, // 30 + 0, // 31 + KEY_SPACE, // VK_SPACE 32 + KEY_PAGEUP, // VK_PRIOR 33 + KEY_PAGEDOWN, // VK_NEXT 34 + KEY_END, // VK_END 35 + KEY_HOME, // VK_HOME 36 + KEY_LEFT, // VK_LEFT 37 + KEY_UP, // VK_UP 38 + KEY_RIGHT, // VK_RIGHT 39 + KEY_DOWN, // VK_DOWN 40 + 0, // VK_SELECT 41 + 0, // VK_PRINT 42 + 0, // VK_EXECUTE 43 + 0, // VK_SNAPSHOT 44 + KEY_INSERT, // VK_INSERT 45 + KEY_DELETE, // VK_DELETE 46 + KEY_HELP, // VK_HELP 47 + KEY_0, // 48 + KEY_1, // 49 + KEY_2, // 50 + KEY_3, // 51 + KEY_4, // 52 + KEY_5, // 53 + KEY_6, // 54 + KEY_7, // 55 + KEY_8, // 56 + KEY_9, // 57 + 0, // 58 + 0, // 59 + 0, // 60 + 0, // 61 + 0, // 62 + 0, // 63 + 0, // 64 + KEY_A, // 65 + KEY_B, // 66 + KEY_C, // 67 + KEY_D, // 68 + KEY_E, // 69 + KEY_F, // 70 + KEY_G, // 71 + KEY_H, // 72 + KEY_I, // 73 + KEY_J, // 74 + KEY_K, // 75 + KEY_L, // 76 + KEY_M, // 77 + KEY_N, // 78 + KEY_O, // 79 + KEY_P, // 80 + KEY_Q, // 81 + KEY_R, // 82 + KEY_S, // 83 + KEY_T, // 84 + KEY_U, // 85 + KEY_V, // 86 + KEY_W, // 87 + KEY_X, // 88 + KEY_Y, // 89 + KEY_Z, // 90 + 0, // VK_LWIN 91 + 0, // VK_RWIN 92 + KEY_CONTEXTMENU, // VK_APPS 93 + 0, // 94 + 0, // 95 + KEY_0, // VK_NUMPAD0 96 + KEY_1, // VK_NUMPAD1 97 + KEY_2, // VK_NUMPAD2 98 + KEY_3, // VK_NUMPAD3 99 + KEY_4, // VK_NUMPAD4 100 + KEY_5, // VK_NUMPAD5 101 + KEY_6, // VK_NUMPAD6 102 + KEY_7, // VK_NUMPAD7 103 + KEY_8, // VK_NUMPAD8 104 + KEY_9, // VK_NUMPAD9 105 + KEY_MULTIPLY, // VK_MULTIPLY 106 + KEY_ADD, // VK_ADD 107 + KEY_DECIMAL, // VK_SEPARATOR 108 + KEY_SUBTRACT, // VK_SUBTRACT 109 + KEY_DECIMAL, // VK_DECIMAL 110 + KEY_DIVIDE, // VK_DIVIDE 111 + KEY_F1, // VK_F1 112 + KEY_F2, // VK_F2 113 + KEY_F3, // VK_F3 114 + KEY_F4, // VK_F4 115 + KEY_F5, // VK_F5 116 + KEY_F6, // VK_F6 117 + KEY_F7, // VK_F7 118 + KEY_F8, // VK_F8 119 + KEY_F9, // VK_F9 120 + KEY_F10, // VK_F10 121 + KEY_F11, // VK_F11 122 + KEY_F12, // VK_F12 123 + KEY_F13, // VK_F13 124 + KEY_F14, // VK_F14 125 + KEY_F15, // VK_F15 126 + KEY_F16, // VK_F16 127 + KEY_F17, // VK_F17 128 + KEY_F18, // VK_F18 129 + KEY_F19, // VK_F19 130 + KEY_F20, // VK_F20 131 + KEY_F21, // VK_F21 132 + KEY_F22, // VK_F22 133 + KEY_F23, // VK_F23 134 + KEY_F24, // VK_F24 135 + 0, // 136 + 0, // 137 + 0, // 138 + 0, // 139 + 0, // 140 + 0, // 141 + 0, // 142 + 0, // 143 + 0, // NUMLOCK 144 + 0, // SCROLLLOCK 145 + 0, // 146 + 0, // 147 + 0, // 148 + 0, // 149 + 0, // 150 + 0, // 151 + 0, // 152 + 0, // 153 + 0, // 154 + 0, // 155 + 0, // 156 + 0, // 157 + 0, // 158 + 0, // 159 + 0, // 160 + 0, // 161 + 0, // 162 + 0, // 163 + 0, // 164 + 0, // 165 + KEY_XF86BACK, // VK_BROWSER_BACK 166 + KEY_XF86FORWARD // VK_BROWSER_FORWARD 167 +}; + +static UINT ImplSalGetWheelScrollLines() +{ + UINT nScrLines = 0; + HWND hWndMsWheel = FindWindowW( MSH_WHEELMODULE_CLASS, MSH_WHEELMODULE_TITLE ); + if ( hWndMsWheel ) + { + UINT nGetScrollLinesMsgId = RegisterWindowMessageW( MSH_SCROLL_LINES ); + nScrLines = static_cast<UINT>(SendMessageW( hWndMsWheel, nGetScrollLinesMsgId, 0, 0 )); + } + + if ( !nScrLines ) + if( !SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &nScrLines, 0 ) ) + nScrLines = 0 ; + + if ( !nScrLines ) + nScrLines = 3; + + return nScrLines; +} + +static UINT ImplSalGetWheelScrollChars() +{ + UINT nScrChars = 0; + if( !SystemParametersInfoW( SPI_GETWHEELSCROLLCHARS, 0, &nScrChars, 0 ) ) + { + return 3; + } + + // system settings successfully read + return nScrChars; +} + +static void ImplSalAddBorder( const WinSalFrame* pFrame, int& width, int& height ) +{ + // transform client size into window size + RECT aWinRect; + aWinRect.left = 0; + aWinRect.right = width-1; + aWinRect.top = 0; + aWinRect.bottom = height-1; + AdjustWindowRectEx( &aWinRect, GetWindowStyle( pFrame->mhWnd ), + FALSE, GetWindowExStyle( pFrame->mhWnd ) ); + width = aWinRect.right - aWinRect.left + 1; + height = aWinRect.bottom - aWinRect.top + 1; +} + +static void ImplSalCalcFullScreenSize( const WinSalFrame* pFrame, + int& rX, int& rY, int& rDX, int& rDY ) +{ + // set window to screen size + int nFrameX; + int nFrameY; + int nCaptionY; + int nScreenX = 0; + int nScreenY = 0; + int nScreenDX = 0; + int nScreenDY = 0; + + if ( pFrame->mbSizeBorder ) + { + nFrameX = GetSystemMetrics( SM_CXSIZEFRAME ); + nFrameY = GetSystemMetrics( SM_CYSIZEFRAME ); + } + else if ( pFrame->mbFixBorder ) + { + nFrameX = GetSystemMetrics( SM_CXFIXEDFRAME ); + nFrameY = GetSystemMetrics( SM_CYFIXEDFRAME ); + } + else if ( pFrame->mbBorder ) + { + nFrameX = GetSystemMetrics( SM_CXBORDER ); + nFrameY = GetSystemMetrics( SM_CYBORDER ); + } + else + { + nFrameX = 0; + nFrameY = 0; + } + if ( pFrame->mbCaption ) + nCaptionY = GetSystemMetrics( SM_CYCAPTION ); + else + nCaptionY = 0; + + try + { + AbsoluteScreenPixelRectangle aRect; + sal_Int32 nMonitors = Application::GetScreenCount(); + if( (pFrame->mnDisplay >= 0) && (pFrame->mnDisplay < nMonitors) ) + { + aRect = Application::GetScreenPosSizePixel(pFrame->mnDisplay); + } + else + { + for (sal_Int32 i = 0; i < nMonitors; i++) + aRect.Union(Application::GetScreenPosSizePixel(i)); + } + nScreenX = aRect.Left(); + nScreenY = aRect.Top(); + nScreenDX = aRect.GetWidth(); + nScreenDY = aRect.GetHeight(); + } + catch( Exception& ) + { + } + + if( !nScreenDX || !nScreenDY ) + { + nScreenDX = GetSystemMetrics( SM_CXSCREEN ); + nScreenDY = GetSystemMetrics( SM_CYSCREEN ); + } + + rX = nScreenX -nFrameX; + rY = nScreenY -(nFrameY+nCaptionY); + rDX = nScreenDX+(nFrameX*2); + rDY = nScreenDY+(nFrameY*2)+nCaptionY; +} + +static void ImplSalFrameFullScreenPos( WinSalFrame* pFrame, bool bAlways = false ) +{ + if ( bAlways || !IsIconic( pFrame->mhWnd ) ) + { + // set window to screen size + int nX; + int nY; + int nWidth; + int nHeight; + ImplSalCalcFullScreenSize( pFrame, nX, nY, nWidth, nHeight ); + SetWindowPos( pFrame->mhWnd, nullptr, + nX, nY, nWidth, nHeight, + SWP_NOZORDER | SWP_NOACTIVATE ); + } +} + +namespace { + +void SetForegroundWindow_Impl(HWND hwnd) +{ + if (!Application::IsHeadlessModeEnabled()) + SetForegroundWindow(hwnd); +} + +} + +WinSalFrame::WinSalFrame() +{ + SalData* pSalData = GetSalData(); + + mhWnd = nullptr; + mhCursor = LoadCursor( nullptr, IDC_ARROW ); + mhDefIMEContext = nullptr; + mpLocalGraphics = nullptr; + mpThreadGraphics = nullptr; + m_eState = vcl::WindowState::Normal; + mnShowState = SW_SHOWNORMAL; + mnMinWidth = 0; + mnMinHeight = 0; + mnMaxWidth = SHRT_MAX; + mnMaxHeight = SHRT_MAX; + mnInputLang = 0; + mnInputCodePage = 0; + mbGraphics = false; + mbCaption = false; + mbBorder = false; + mbFixBorder = false; + mbSizeBorder = false; + mbFullScreenCaption = false; + mbPresentation = false; + mbInShow = false; + mbRestoreMaximize = false; + mbInMoveMsg = false; + mbInSizeMsg = false; + mbFullScreenToolWin = false; + mbDefPos = true; + mbOverwriteState = true; + mbIME = false; + mbHandleIME = false; + mbSpezIME = false; + mbAtCursorIME = false; + mbCandidateMode = false; + mbFloatWin = false; + mbNoIcon = false; + mSelectedhMenu = nullptr; + mLastActivatedhMenu = nullptr; + mpClipRgnData = nullptr; + mbFirstClipRect = true; + mpNextClipRect = nullptr; + mnDisplay = 0; + mbPropertiesStored = false; + + // get data, when making 1st frame + if ( !pSalData->mpFirstFrame ) + { + if ( !aSalShlData.mnWheelScrollLines ) + aSalShlData.mnWheelScrollLines = ImplSalGetWheelScrollLines(); + if ( !aSalShlData.mnWheelScrollChars ) + aSalShlData.mnWheelScrollChars = ImplSalGetWheelScrollChars(); + } + + // insert frame in framelist + mpNextFrame = pSalData->mpFirstFrame; + pSalData->mpFirstFrame = this; +} + +void WinSalFrame::updateScreenNumber() +{ + if( mnDisplay == -1 ) // spans all monitors + return; + WinSalSystem* pSys = static_cast<WinSalSystem*>(ImplGetSalSystem()); + if( pSys ) + { + const std::vector<WinSalSystem::DisplayMonitor>& rMonitors = + pSys->getMonitors(); + AbsoluteScreenPixelPoint aPoint(maGeometry.pos()); + size_t nMon = rMonitors.size(); + for( size_t i = 0; i < nMon; i++ ) + { + if( rMonitors[i].m_aArea.Contains( aPoint ) ) + { + mnDisplay = static_cast<sal_Int32>(i); + maGeometry.setScreen(static_cast<unsigned int>(i)); + } + } + } +} + +bool WinSalFrame::ReleaseFrameGraphicsDC( WinSalGraphics* pGraphics ) +{ + assert( pGraphics ); + SalData* pSalData = GetSalData(); + HDC hDC = pGraphics->getHDC(); + if ( !hDC ) + return false; + pGraphics->setHDC(nullptr); + SendMessageW( pSalData->mpInstance->mhComWnd, SAL_MSG_RELEASEDC, + reinterpret_cast<WPARAM>(mhWnd), reinterpret_cast<LPARAM>(hDC) ); + if ( pGraphics == mpThreadGraphics ) + pSalData->mnCacheDCInUse--; + return true; +} + +WinSalFrame::~WinSalFrame() +{ + SalData* pSalData = GetSalData(); + + if( mpClipRgnData ) + delete [] reinterpret_cast<BYTE*>(mpClipRgnData); + + // remove frame from framelist + WinSalFrame** ppFrame = &pSalData->mpFirstFrame; + for(; (*ppFrame != this) && *ppFrame; ppFrame = &(*ppFrame)->mpNextFrame ); + if( *ppFrame ) + *ppFrame = mpNextFrame; + mpNextFrame = nullptr; + + // destroy the thread SalGraphics + if ( mpThreadGraphics ) + { + ReleaseFrameGraphicsDC( mpThreadGraphics ); + delete mpThreadGraphics; + mpThreadGraphics = nullptr; + } + + // destroy the local SalGraphics + if ( mpLocalGraphics ) + { + ReleaseFrameGraphicsDC( mpLocalGraphics ); + delete mpLocalGraphics; + mpLocalGraphics = nullptr; + } + + if ( mhWnd ) + { + // reset mouse leave data + if ( pSalData->mhWantLeaveMsg == mhWnd ) + { + pSalData->mhWantLeaveMsg = nullptr; + } + + // remove windows properties + if ( mbPropertiesStored ) + SetApplicationID( OUString() ); + + // destroy system frame + if ( !DestroyWindow( mhWnd ) ) + SetWindowPtr( mhWnd, nullptr ); + + mhWnd = nullptr; + } +} + +bool WinSalFrame::InitFrameGraphicsDC( WinSalGraphics *pGraphics, HDC hDC, HWND hWnd ) +{ + SalData* pSalData = GetSalData(); + assert( pGraphics ); + pGraphics->setHWND( hWnd ); + + HDC hCurrentDC = pGraphics->getHDC(); + assert( !hCurrentDC || (hCurrentDC == hDC) ); + if ( hCurrentDC ) + return true; + pGraphics->setHDC( hDC ); + + if ( !hDC ) + return false; + + if ( pSalData->mhDitherPal ) + pGraphics->setPalette(pSalData->mhDitherPal); + + if ( pGraphics == mpThreadGraphics ) + pSalData->mnCacheDCInUse++; + return true; +} + +SalGraphics* WinSalFrame::AcquireGraphics() +{ + if ( mbGraphics || !mhWnd ) + return nullptr; + + SalData* pSalData = GetSalData(); + WinSalGraphics *pGraphics = nullptr; + HDC hDC = nullptr; + + // Other threads get an own DC, because Windows modify in the + // other case our DC (changing clip region), when they send a + // WM_ERASEBACKGROUND message + if ( !pSalData->mpInstance->IsMainThread() ) + { + // We use only three CacheDC's for all threads, because W9x is limited + // to max. 5 Cache DC's per thread + if ( pSalData->mnCacheDCInUse >= 3 ) + return nullptr; + + if ( !mpThreadGraphics ) + mpThreadGraphics = new WinSalGraphics(WinSalGraphics::WINDOW, true, mhWnd, this); + pGraphics = mpThreadGraphics; + assert( !pGraphics->getHDC() ); + hDC = reinterpret_cast<HDC>(static_cast<sal_IntPtr>(SendMessageW( pSalData->mpInstance->mhComWnd, + SAL_MSG_GETCACHEDDC, reinterpret_cast<WPARAM>(mhWnd), 0 ))); + } + else + { + if ( !mpLocalGraphics ) + mpLocalGraphics = new WinSalGraphics(WinSalGraphics::WINDOW, true, mhWnd, this); + pGraphics = mpLocalGraphics; + hDC = pGraphics->getHDC(); + if ( !hDC ) + hDC = GetDC( mhWnd ); + } + + mbGraphics = InitFrameGraphicsDC( pGraphics, hDC, mhWnd ); + return mbGraphics ? pGraphics : nullptr; +} + +void WinSalFrame::ReleaseGraphics( SalGraphics* pGraphics ) +{ + if ( mpThreadGraphics == pGraphics ) + ReleaseFrameGraphicsDC( mpThreadGraphics ); + mbGraphics = false; +} + +bool WinSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData) +{ + bool const ret = PostMessageW(mhWnd, SAL_MSG_USEREVENT, 0, reinterpret_cast<LPARAM>(pData.get())); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + if (ret) + pData.release(); + return ret; +} + +void WinSalFrame::SetTitle( const OUString& rTitle ) +{ + static_assert( sizeof( WCHAR ) == sizeof( sal_Unicode ), "must be the same size" ); + + SetWindowTextW( mhWnd, o3tl::toW(rTitle.getStr()) ); +} + +void WinSalFrame::SetIcon( sal_uInt16 nIcon ) +{ + // If we have a window without an Icon (for example a dialog), ignore this call + if ( mbNoIcon ) + return; + + // 0 means default (class) icon + HICON hIcon = nullptr, hSmIcon = nullptr; + if ( !nIcon ) + nIcon = 1; + + ImplLoadSalIcon( nIcon, hIcon, hSmIcon ); + + SAL_WARN_IF( !hIcon , "vcl", "WinSalFrame::SetIcon(): Could not load large icon !" ); + SAL_WARN_IF( !hSmIcon , "vcl", "WinSalFrame::SetIcon(): Could not load small icon !" ); + + SendMessageW( mhWnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) ); + SendMessageW( mhWnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hSmIcon) ); +} + +void WinSalFrame::SetMenu( SalMenu* pSalMenu ) +{ + WinSalMenu* pWMenu = static_cast<WinSalMenu*>(pSalMenu); + if( pSalMenu && pWMenu->mbMenuBar ) + ::SetMenu( mhWnd, pWMenu->mhMenu ); +} + +static HWND ImplGetParentHwnd( HWND hWnd ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if( !pFrame || !pFrame->GetWindow()) + return ::GetParent( hWnd ); + vcl::Window *pRealParent = pFrame->GetWindow()->ImplGetWindowImpl()->mpRealParent; + if( pRealParent ) + return static_cast<WinSalFrame*>(pRealParent->ImplGetWindowImpl()->mpFrame)->mhWnd; + else + return ::GetParent( hWnd ); + +} + +SalFrame* WinSalFrame::GetParent() const +{ + return GetWindowPtr( ImplGetParentHwnd( mhWnd ) ); +} + +static void ImplSalShow( HWND hWnd, bool bVisible, bool bNoActivate ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return; + + if ( bVisible ) + { + pFrame->mbDefPos = false; + pFrame->mbOverwriteState = true; + pFrame->mbInShow = true; + + // #i4715, save position + RECT aRectPreMatrox, aRectPostMatrox; + GetWindowRect( hWnd, &aRectPreMatrox ); + + vcl::DeletionListener aDogTag( pFrame ); + if( bNoActivate ) + ShowWindow( hWnd, SW_SHOWNOACTIVATE ); + else + ShowWindow( hWnd, pFrame->mnShowState ); + if( aDogTag.isDeleted() ) + return; + + if (pFrame->mbFloatWin && !(pFrame->mnStyle & SalFrameStyleFlags::NOSHADOW)) + { + // erase the window immediately to improve XP shadow effect + // otherwise the shadow may appears long time before the rest of the window + // especially when accessibility is on + HDC dc = GetDC( hWnd ); + RECT aRect; + GetClientRect( hWnd, &aRect ); + FillRect( dc, &aRect, reinterpret_cast<HBRUSH>(COLOR_MENU+1) ); // choose the menucolor, because its mostly noticeable for menus + ReleaseDC( hWnd, dc ); + } + + // #i4715, matrox centerpopup might have changed our position + // reposition popups without caption (menus, dropdowns, tooltips) + GetWindowRect( hWnd, &aRectPostMatrox ); + if( (GetWindowStyle( hWnd ) & WS_POPUP) && + !pFrame->mbCaption && + (aRectPreMatrox.left != aRectPostMatrox.left || aRectPreMatrox.top != aRectPostMatrox.top) ) + SetWindowPos( hWnd, nullptr, aRectPreMatrox.left, aRectPreMatrox.top, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE ); + + if( aDogTag.isDeleted() ) + return; + vcl::Window *pClientWin = pFrame->GetWindow()->ImplGetClientWindow(); + if ( pFrame->mbFloatWin || ( pClientWin && (pClientWin->GetStyle() & WB_SYSTEMFLOATWIN) ) ) + pFrame->mnShowState = SW_SHOWNOACTIVATE; + else + pFrame->mnShowState = SW_SHOW; + // hide toolbar for W98 + if ( pFrame->mbPresentation ) + { + HWND hWndParent = ::GetParent( hWnd ); + if ( hWndParent ) + SetForegroundWindow_Impl( hWndParent ); + SetForegroundWindow_Impl( hWnd ); + } + + pFrame->mbInShow = false; + pFrame->updateScreenNumber(); + + // Direct Paint only, if we get the SolarMutex + if ( ImplSalYieldMutexTryToAcquire() ) + { + UpdateWindow( hWnd ); + ImplSalYieldMutexRelease(); + } + } + else + { + ShowWindow( hWnd, SW_HIDE ); + } +} + +void WinSalFrame::SetExtendedFrameStyle( SalExtStyle ) +{ +} + +void WinSalFrame::Show( bool bVisible, bool bNoActivate ) +{ + // Post this Message to the window, because this only works + // in the thread of the window, which has create this window. + // We post this message to avoid deadlocks + if ( GetSalData()->mnAppThreadId != GetCurrentThreadId() ) + { + bool const ret = PostMessageW(mhWnd, SAL_MSG_SHOW, WPARAM(bVisible), LPARAM(bNoActivate)); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + else + ImplSalShow( mhWnd, bVisible, bNoActivate ); +} + +void WinSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) +{ + mnMinWidth = nWidth; + mnMinHeight = nHeight; +} + +void WinSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) +{ + mnMaxWidth = nWidth; + mnMaxHeight = nHeight; +} + +void WinSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, + sal_uInt16 nFlags ) +{ + bool bVisible = (GetWindowStyle( mhWnd ) & WS_VISIBLE) != 0; + if ( !bVisible ) + { + vcl::Window *pClientWin = GetWindow()->ImplGetClientWindow(); + if ( mbFloatWin || ( pClientWin && (pClientWin->GetStyle() & WB_SYSTEMFLOATWIN) ) ) + mnShowState = SW_SHOWNOACTIVATE; + else + mnShowState = SW_SHOWNORMAL; + } + else + { + if ( IsIconic( mhWnd ) || IsZoomed( mhWnd ) ) + ShowWindow( mhWnd, SW_RESTORE ); + } + + SalEvent nEvent = SalEvent::NONE; + UINT nPosSize = 0; + RECT aClientRect, aWindowRect; + GetClientRect( mhWnd, &aClientRect ); // x,y always 0,0, but width and height without border + GetWindowRect( mhWnd, &aWindowRect ); // x,y in screen coordinates, width and height with border + + if ( !(nFlags & (SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y)) ) + nPosSize |= SWP_NOMOVE; + else + { + //SAL_WARN_IF( !nX || !nY, "vcl", " Windowposition of (0,0) requested!" ); + nEvent = SalEvent::Move; + } + if ( !(nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT)) ) + nPosSize |= SWP_NOSIZE; + else + nEvent = (nEvent == SalEvent::Move) ? SalEvent::MoveResize : SalEvent::Resize; + + if ( !(nFlags & SAL_FRAME_POSSIZE_X) ) + nX = aWindowRect.left; + if ( !(nFlags & SAL_FRAME_POSSIZE_Y) ) + nY = aWindowRect.top; + if ( !(nFlags & SAL_FRAME_POSSIZE_WIDTH) ) + nWidth = aClientRect.right-aClientRect.left; + if ( !(nFlags & SAL_FRAME_POSSIZE_HEIGHT) ) + nHeight = aClientRect.bottom-aClientRect.top; + + // Calculate window size including the border + RECT aWinRect; + aWinRect.left = 0; + aWinRect.right = static_cast<int>(nWidth)-1; + aWinRect.top = 0; + aWinRect.bottom = static_cast<int>(nHeight)-1; + AdjustWindowRectEx( &aWinRect, GetWindowStyle( mhWnd ), + FALSE, GetWindowExStyle( mhWnd ) ); + nWidth = aWinRect.right - aWinRect.left + 1; + nHeight = aWinRect.bottom - aWinRect.top + 1; + + HWND hWndParent = ImplGetParentHwnd(mhWnd); + // For dialogs (WS_POPUP && WS_DLGFRAME), we need to find the "real" parent, + // in case multiple dialogs are stacked on each other + // (we don't want to position the second dialog relative to the first one, but relative to the main window) + if ( (GetWindowStyle( mhWnd ) & WS_POPUP) && (GetWindowStyle( mhWnd ) & WS_DLGFRAME) ) // mhWnd is a dialog + { + while ( hWndParent && (GetWindowStyle( hWndParent ) & WS_POPUP) && (GetWindowStyle( hWndParent ) & WS_DLGFRAME) ) + { + hWndParent = ::ImplGetParentHwnd( hWndParent ); + } + } + + if ( !(nPosSize & SWP_NOMOVE) && hWndParent ) + { + RECT aParentRect; + GetClientRect( hWndParent, &aParentRect ); + if( AllSettings::GetLayoutRTL() ) + nX = (aParentRect.right - aParentRect.left) - nWidth-1 - nX; + + //#110386#, do not transform coordinates for system child windows + if( !(GetWindowStyle( mhWnd ) & WS_CHILD) ) + { + POINT aPt; + aPt.x = nX; + aPt.y = nY; + + WinSalFrame* pParentFrame = GetWindowPtr( hWndParent ); + if ( pParentFrame && pParentFrame->mnShowState == SW_SHOWMAXIMIZED ) + { + // #i42485#: parent will be shown maximized in which case + // a ClientToScreen uses the wrong coordinates (i.e. those from the restore pos) + // so use the (already updated) frame geometry for the transformation + aPt.x += pParentFrame->maGeometry.x(); + aPt.y += pParentFrame->maGeometry.y(); + } + else + ClientToScreen( hWndParent, &aPt ); + + nX = aPt.x; + nY = aPt.y; + + // the position is set + mbDefPos = false; + } + } + + // #i3338# to be conformant to UNIX we must position the client window, ie without the decoration + // #i43250# if the position was read from the system (GetWindowRect(), see above), it must not be modified + if ( nFlags & SAL_FRAME_POSSIZE_X ) + nX += aWinRect.left; + if ( nFlags & SAL_FRAME_POSSIZE_Y ) + nY += aWinRect.top; + + int nScreenX; + int nScreenY; + int nScreenWidth; + int nScreenHeight; + + RECT aRect; + ImplSalGetWorkArea( mhWnd, &aRect, nullptr ); + nScreenX = aRect.left; + nScreenY = aRect.top; + nScreenWidth = aRect.right-aRect.left; + nScreenHeight = aRect.bottom-aRect.top; + + if ( mbDefPos && (nPosSize & SWP_NOMOVE)) // we got no positioning request, so choose default position + { + // center window + + HWND hWndParent2 = ::GetParent( mhWnd ); + // Search for TopLevel Frame + while ( hWndParent2 && (GetWindowStyle( hWndParent2 ) & WS_CHILD) ) + hWndParent2 = ::GetParent( hWndParent2 ); + // if the Window has a Parent, then center the window to + // the parent, in the other case to the screen + if ( hWndParent2 && !IsIconic( hWndParent2 ) && + (GetWindowStyle( hWndParent2 ) & WS_VISIBLE) ) + { + RECT aParentRect; + GetWindowRect( hWndParent2, &aParentRect ); + int nParentWidth = aParentRect.right-aParentRect.left; + int nParentHeight = aParentRect.bottom-aParentRect.top; + + // We don't center, when Parent is smaller than our window + if ( (nParentWidth-GetSystemMetrics( SM_CXFIXEDFRAME ) <= nWidth) && + (nParentHeight-GetSystemMetrics( SM_CYFIXEDFRAME ) <= nHeight) ) + { + int nOff = GetSystemMetrics( SM_CYSIZEFRAME ) + GetSystemMetrics( SM_CYCAPTION ); + nX = aParentRect.left+nOff; + nY = aParentRect.top+nOff; + } + else + { + nX = (nParentWidth-nWidth)/2 + aParentRect.left; + nY = (nParentHeight-nHeight)/2 + aParentRect.top; + } + } + else + { + POINT pt; + GetCursorPos( &pt ); + RECT aRect2; + aRect2.left = pt.x; + aRect2.top = pt.y; + aRect2.right = pt.x+2; + aRect2.bottom = pt.y+2; + + // dualmonitor support: + // Get screensize of the monitor with the mouse pointer + ImplSalGetWorkArea( mhWnd, &aRect2, &aRect2 ); + + nX = ((aRect2.right-aRect2.left)-nWidth)/2 + aRect2.left; + nY = ((aRect2.bottom-aRect2.top)-nHeight)/2 + aRect2.top; + } + + //if ( bVisible ) + // mbDefPos = FALSE; + + mbDefPos = false; // center only once + nPosSize &= ~SWP_NOMOVE; // activate positioning + nEvent = SalEvent::MoveResize; + } + + // Adjust Window in the screen + bool bCheckOffScreen = true; + + // but don't do this for floaters or ownerdraw windows that are currently moved interactively + if( (mnStyle & SalFrameStyleFlags::FLOAT) && !(mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) ) + bCheckOffScreen = false; + + if( mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION ) + { + // may be the window is currently being moved (mouse is captured), then no check is required + if( mhWnd == ::GetCapture() ) + bCheckOffScreen = false; + else + bCheckOffScreen = true; + } + + if( bCheckOffScreen ) + { + if ( nX+nWidth > nScreenX+nScreenWidth ) + nX = (nScreenX+nScreenWidth) - nWidth; + if ( nY+nHeight > nScreenY+nScreenHeight ) + nY = (nScreenY+nScreenHeight) - nHeight; + if ( nX < nScreenX ) + nX = nScreenX; + if ( nY < nScreenY ) + nY = nScreenY; + } + + UINT nPosFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | nPosSize; + // bring floating windows always to top + if( !(mnStyle & SalFrameStyleFlags::FLOAT) ) + nPosFlags |= SWP_NOZORDER; // do not change z-order + + SetWindowPos( mhWnd, HWND_TOP, nX, nY, static_cast<int>(nWidth), static_cast<int>(nHeight), nPosFlags ); + + UpdateFrameGeometry(this); + + // Notification -- really ??? + if( nEvent != SalEvent::NONE ) + CallCallback( nEvent, nullptr ); +} + +void WinSalFrame::ImplSetParentFrame( HWND hNewParentWnd, bool bAsChild ) +{ + // save hwnd, will be overwritten in WM_CREATE during createwindow + HWND hWndOld = mhWnd; + HWND hWndOldParent = ::GetParent( hWndOld ); + SalData* pSalData = GetSalData(); + + if( hNewParentWnd == hWndOldParent ) + return; + + ::std::vector< WinSalFrame* > children; + ::std::vector< WinSalObject* > systemChildren; + + // search child windows + WinSalFrame *pFrame = pSalData->mpFirstFrame; + while( pFrame ) + { + HWND hWndParent = ::GetParent( pFrame->mhWnd ); + if( mhWnd == hWndParent ) + children.push_back( pFrame ); + pFrame = pFrame->mpNextFrame; + } + + // search system child windows (plugins etc.) + WinSalObject *pObject = pSalData->mpFirstObject; + while( pObject ) + { + HWND hWndParent = ::GetParent( pObject->mhWnd ); + if( mhWnd == hWndParent ) + systemChildren.push_back( pObject ); + pObject = pObject->mpNextObject; + } + + // to recreate the DCs, if they were destroyed + bool bHadLocalGraphics = false, bHadThreadGraphics = false; + + HFONT hFont = nullptr; + HPEN hPen = nullptr; + HBRUSH hBrush = nullptr; + + int oldCount = pSalData->mnCacheDCInUse; + + // release the thread DC + if ( mpThreadGraphics ) + { + // save current gdi objects before hdc is gone + HDC hDC = mpThreadGraphics->getHDC(); + if ( hDC ) + { + hFont = static_cast<HFONT>(GetCurrentObject( hDC, OBJ_FONT )); + hPen = static_cast<HPEN>(GetCurrentObject( hDC, OBJ_PEN )); + hBrush = static_cast<HBRUSH>(GetCurrentObject( hDC, OBJ_BRUSH )); + } + + bHadThreadGraphics = ReleaseFrameGraphicsDC( mpThreadGraphics ); + assert( (bHadThreadGraphics && hDC) || (!bHadThreadGraphics && !hDC) ); + } + + // release the local DC + if ( mpLocalGraphics ) + bHadLocalGraphics = ReleaseFrameGraphicsDC( mpLocalGraphics ); + + // create a new hwnd with the same styles + HWND hWndParent = hNewParentWnd; + // forward to main thread + HWND hWnd = reinterpret_cast<HWND>(static_cast<sal_IntPtr>(SendMessageW( pSalData->mpInstance->mhComWnd, + bAsChild ? SAL_MSG_RECREATECHILDHWND : SAL_MSG_RECREATEHWND, + reinterpret_cast<WPARAM>(hWndParent), reinterpret_cast<LPARAM>(mhWnd) ))); + + // succeeded ? + SAL_WARN_IF( !IsWindow( hWnd ), "vcl", "WinSalFrame::SetParent not successful"); + + // re-create thread DC + if( bHadThreadGraphics ) + { + HDC hDC = reinterpret_cast<HDC>(static_cast<sal_IntPtr>( + SendMessageW( pSalData->mpInstance->mhComWnd, + SAL_MSG_GETCACHEDDC, reinterpret_cast<WPARAM>(hWnd), 0 ))); + InitFrameGraphicsDC( mpThreadGraphics, hDC, hWnd ); + if ( hDC ) + { + // re-select saved gdi objects + if( hFont ) + SelectObject( hDC, hFont ); + if( hPen ) + SelectObject( hDC, hPen ); + if( hBrush ) + SelectObject( hDC, hBrush ); + + SAL_WARN_IF( oldCount != pSalData->mnCacheDCInUse, "vcl", "WinSalFrame::SetParent() hDC count corrupted"); + } + } + + // re-create local DC + if( bHadLocalGraphics ) + InitFrameGraphicsDC( mpLocalGraphics, GetDC( hWnd ), hWnd ); + + // TODO: add SetParent() call for SalObjects + SAL_WARN_IF( !systemChildren.empty(), "vcl", "WinSalFrame::SetParent() parent of living system child window will be destroyed!"); + + // reparent children before old parent is destroyed + for (auto & child : children) + child->ImplSetParentFrame( hWnd, false ); + + children.clear(); + systemChildren.clear(); + + // Now destroy original HWND in the thread where it was created. + SendMessageW( GetSalData()->mpInstance->mhComWnd, + SAL_MSG_DESTROYHWND, WPARAM(0), reinterpret_cast<LPARAM>(hWndOld)); +} + +void WinSalFrame::SetParent( SalFrame* pNewParent ) +{ + WinSalFrame::mbInReparent = true; + ImplSetParentFrame( static_cast<WinSalFrame*>(pNewParent)->mhWnd, false ); + WinSalFrame::mbInReparent = false; +} + +void WinSalFrame::SetPluginParent( SystemParentData* pNewParent ) +{ + if ( pNewParent->hWnd == nullptr ) + { + pNewParent->hWnd = GetDesktopWindow(); + } + + WinSalFrame::mbInReparent = true; + ImplSetParentFrame( pNewParent->hWnd, true ); + WinSalFrame::mbInReparent = false; +} + +void WinSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle &rRect ) +{ + RECT aRect; + + // pass cursor's position to ImplSalGetWorkArea to determine work area on + // multi monitor setups correctly. + POINT aPoint; + GetCursorPos(&aPoint); + RECT aRect2{ aPoint.x, aPoint.y, aPoint.x + 2, aPoint.y + 2 }; + + ImplSalGetWorkArea( mhWnd, &aRect, &aRect2 ); + rRect.SetLeft( aRect.left ); + rRect.SetRight( aRect.right-1 ); + rRect.SetTop( aRect.top ); + rRect.SetBottom( aRect.bottom-1 ); +} + +void WinSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) +{ + rWidth = maGeometry.width(); + rHeight = maGeometry.height(); +} + +void WinSalFrame::SetWindowState(const vcl::WindowData* pState) +{ + // Check if the window fits into the screen, in case the screen + // resolution changed + int nX; + int nY; + int nWidth; + int nHeight; + int nScreenX; + int nScreenY; + int nScreenWidth; + int nScreenHeight; + + RECT aRect; + ImplSalGetWorkArea( mhWnd, &aRect, nullptr ); + // #102500# allow some overlap, the window could have been made a little larger than the physical screen + nScreenX = aRect.left-10; + nScreenY = aRect.top-10; + nScreenWidth = aRect.right-aRect.left+20; + nScreenHeight = aRect.bottom-aRect.top+20; + + UINT nPosSize = 0; + RECT aWinRect; + GetWindowRect( mhWnd, &aWinRect ); + + // to be consistent with Unix, the frame state is without(!) decoration + // ->add the decoration + RECT aRect2 = aWinRect; + AdjustWindowRectEx( &aRect2, GetWindowStyle( mhWnd ), + FALSE, GetWindowExStyle( mhWnd ) ); + tools::Long nTopDeco = abs( aWinRect.top - aRect2.top ); + tools::Long nLeftDeco = abs( aWinRect.left - aRect2.left ); + tools::Long nBottomDeco = abs( aWinRect.bottom - aRect2.bottom ); + tools::Long nRightDeco = abs( aWinRect.right - aRect2.right ); + + // adjust window position/size to fit the screen + if ( !(pState->mask() & vcl::WindowDataMask::Pos) ) + nPosSize |= SWP_NOMOVE; + if ( !(pState->mask() & vcl::WindowDataMask::Size) ) + nPosSize |= SWP_NOSIZE; + if ( pState->mask() & vcl::WindowDataMask::X ) + nX = static_cast<int>(pState->x()) - nLeftDeco; + else + nX = aWinRect.left; + if ( pState->mask() & vcl::WindowDataMask::Y ) + nY = static_cast<int>(pState->y()) - nTopDeco; + else + nY = aWinRect.top; + if ( pState->mask() & vcl::WindowDataMask::Width ) + nWidth = static_cast<int>(pState->width()) + nLeftDeco + nRightDeco; + else + nWidth = aWinRect.right-aWinRect.left; + if ( pState->mask() & vcl::WindowDataMask::Height ) + nHeight = static_cast<int>(pState->height()) + nTopDeco + nBottomDeco; + else + nHeight = aWinRect.bottom-aWinRect.top; + + // Adjust Window in the screen: + // if it does not fit into the screen do nothing, ie default pos/size will be used + // if there is an overlap with the screen border move the window while keeping its size + + if( nWidth > nScreenWidth || nHeight > nScreenHeight ) + nPosSize |= (SWP_NOMOVE | SWP_NOSIZE); + + if ( nX+nWidth > nScreenX+nScreenWidth ) + nX = (nScreenX+nScreenWidth) - nWidth; + if ( nY+nHeight > nScreenY+nScreenHeight ) + nY = (nScreenY+nScreenHeight) - nHeight; + if ( nX < nScreenX ) + nX = nScreenX; + if ( nY < nScreenY ) + nY = nScreenY; + + // set Restore-Position + WINDOWPLACEMENT aPlacement; + aPlacement.length = sizeof( aPlacement ); + GetWindowPlacement( mhWnd, &aPlacement ); + + // set State + const bool bIsMinimized = IsIconic(mhWnd); + const bool bIsMaximized = IsZoomed(mhWnd); + const bool bVisible = (GetWindowStyle(mhWnd) & WS_VISIBLE); + bool bUpdateHiddenFramePos = false; + if ( !bVisible ) + { + aPlacement.showCmd = SW_HIDE; + + if (mbOverwriteState && (pState->mask() & vcl::WindowDataMask::State)) + { + if (pState->state() & vcl::WindowState::Minimized) + mnShowState = SW_SHOWMINIMIZED; + else if (pState->state() & vcl::WindowState::Maximized) + { + mnShowState = SW_SHOWMAXIMIZED; + bUpdateHiddenFramePos = true; + } + else if (pState->state() & vcl::WindowState::Normal) + mnShowState = SW_SHOWNORMAL; + } + } + else + { + if ( pState->mask() & vcl::WindowDataMask::State ) + { + if ( pState->state() & vcl::WindowState::Minimized ) + { + if ( pState->state() & vcl::WindowState::Maximized ) + aPlacement.flags |= WPF_RESTORETOMAXIMIZED; + aPlacement.showCmd = SW_SHOWMINIMIZED; + } + else if ( pState->state() & vcl::WindowState::Maximized ) + aPlacement.showCmd = SW_SHOWMAXIMIZED; + else if ( pState->state() & vcl::WindowState::Normal ) + aPlacement.showCmd = SW_RESTORE; + } + } + + // if a window is neither minimized nor maximized or need not be + // positioned visibly (that is in visible state), do not use + // SetWindowPlacement since it calculates including the TaskBar + if (!bIsMinimized && !bIsMaximized && (!bVisible || (aPlacement.showCmd == SW_RESTORE))) + { + if( bUpdateHiddenFramePos ) + { + RECT aStateRect; + aStateRect.left = nX; + aStateRect.top = nY; + aStateRect.right = nX+nWidth; + aStateRect.bottom = nY+nHeight; + // #96084 set a useful internal window size because + // the window will not be maximized (and the size updated) before show() + SetMaximizedFrameGeometry( mhWnd, this, &aStateRect ); + SetWindowPos( mhWnd, nullptr, + maGeometry.x(), maGeometry.y(), maGeometry.width(), maGeometry.height(), + SWP_NOZORDER | SWP_NOACTIVATE | nPosSize ); + } + else + SetWindowPos( mhWnd, nullptr, + nX, nY, nWidth, nHeight, + SWP_NOZORDER | SWP_NOACTIVATE | nPosSize ); + } + else + { + if( !(nPosSize & (SWP_NOMOVE|SWP_NOSIZE)) ) + { + aPlacement.rcNormalPosition.left = nX-nScreenX; + aPlacement.rcNormalPosition.top = nY-nScreenY; + aPlacement.rcNormalPosition.right = nX+nWidth-nScreenX; + aPlacement.rcNormalPosition.bottom = nY+nHeight-nScreenY; + } + SetWindowPlacement( mhWnd, &aPlacement ); + } + + if( !(nPosSize & SWP_NOMOVE) ) + mbDefPos = false; // window was positioned +} + +bool WinSalFrame::GetWindowState(vcl::WindowData* pState) +{ + pState->setPosSize(maGeometry.posSize()); + pState->setState(m_eState); + pState->setMask(vcl::WindowDataMask::PosSizeState); + return true; +} + +void WinSalFrame::SetScreenNumber( unsigned int nNewScreen ) +{ + WinSalSystem* pSys = static_cast<WinSalSystem*>(ImplGetSalSystem()); + if( pSys ) + { + const std::vector<WinSalSystem::DisplayMonitor>& rMonitors = + pSys->getMonitors(); + size_t nMon = rMonitors.size(); + if( nNewScreen < nMon ) + { + AbsoluteScreenPixelPoint aOldMonPos, aNewMonPos( rMonitors[nNewScreen].m_aArea.TopLeft() ); + AbsoluteScreenPixelPoint aCurPos(maGeometry.pos()); + for( size_t i = 0; i < nMon; i++ ) + { + if( rMonitors[i].m_aArea.Contains( aCurPos ) ) + { + aOldMonPos = rMonitors[i].m_aArea.TopLeft(); + break; + } + } + mnDisplay = nNewScreen; + maGeometry.setScreen(nNewScreen); + SetPosSize( aNewMonPos.X() + (maGeometry.x() - aOldMonPos.X()), + aNewMonPos.Y() + (maGeometry.y() - aOldMonPos.Y()), + 0, 0, + SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ); + } + } +} + +void WinSalFrame::SetApplicationID( const OUString &rApplicationID ) +{ + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd378430(v=vs.85).aspx + // A window's properties must be removed before the window is closed. + + IPropertyStore *pps; + HRESULT hr = SHGetPropertyStoreForWindow(mhWnd, IID_PPV_ARGS(&pps)); + if (SUCCEEDED(hr)) + { + PROPVARIANT pv; + if (!rApplicationID.isEmpty()) + { + hr = InitPropVariantFromString(o3tl::toW(rApplicationID.getStr()), &pv); + mbPropertiesStored = true; + } + else + // if rApplicationID we remove the property from the window, if present + PropVariantInit(&pv); + + if (SUCCEEDED(hr)) + { + hr = pps->SetValue(PKEY_AppUserModel_ID, pv); + PropVariantClear(&pv); + } + pps->Release(); + } +} + +void WinSalFrame::ShowFullScreen(const bool bFullScreen, const sal_Int32 nDisplay) +{ + if ((isFullScreen() == bFullScreen) && (!bFullScreen || (mnDisplay == nDisplay))) + return; + + mnDisplay = nDisplay; + + if ( bFullScreen ) + { + m_eState |= vcl::WindowState::FullScreen; + // to hide the Windows taskbar + DWORD nExStyle = GetWindowExStyle( mhWnd ); + if ( nExStyle & WS_EX_TOOLWINDOW ) + { + mbFullScreenToolWin = true; + nExStyle &= ~WS_EX_TOOLWINDOW; + SetWindowExStyle( mhWnd, nExStyle ); + } + // save old position + GetWindowRect( mhWnd, &maFullScreenRect ); + + // save show state + mnFullScreenShowState = mnShowState; + if ( !(GetWindowStyle( mhWnd ) & WS_VISIBLE) ) + mnShowState = SW_SHOW; + + // Save caption state. + mbFullScreenCaption = mbCaption; + if (mbCaption) + { + DWORD nStyle = GetWindowStyle(mhWnd); + SetWindowStyle(mhWnd, nStyle & ~WS_CAPTION); + mbCaption = false; + } + + // set window to screen size + ImplSalFrameFullScreenPos( this, true ); + } + else + { + m_eState &= ~vcl::WindowState::FullScreen; + // when the ShowState has to be reset, hide the window first to + // reduce flicker + bool bVisible = (GetWindowStyle( mhWnd ) & WS_VISIBLE) != 0; + if ( bVisible && (mnShowState != mnFullScreenShowState) ) + ShowWindow( mhWnd, SW_HIDE ); + + if ( mbFullScreenToolWin ) + SetWindowExStyle( mhWnd, GetWindowExStyle( mhWnd ) | WS_EX_TOOLWINDOW ); + mbFullScreenToolWin = false; + + // Restore caption state. + if (mbFullScreenCaption) + { + DWORD nStyle = GetWindowStyle(mhWnd); + SetWindowStyle(mhWnd, nStyle | WS_CAPTION); + } + mbCaption = mbFullScreenCaption; + + SetWindowPos( mhWnd, nullptr, + maFullScreenRect.left, + maFullScreenRect.top, + maFullScreenRect.right-maFullScreenRect.left, + maFullScreenRect.bottom-maFullScreenRect.top, + SWP_NOZORDER | SWP_NOACTIVATE ); + + // restore show state + if ( mnShowState != mnFullScreenShowState ) + { + mnShowState = mnFullScreenShowState; + if ( bVisible ) + { + mbInShow = true; + ShowWindow( mhWnd, mnShowState ); + mbInShow = false; + UpdateWindow( mhWnd ); + } + } + } +} + +void WinSalFrame::StartPresentation( bool bStart ) +{ + if ( mbPresentation == bStart ) + return; + + mbPresentation = bStart; + + if ( bStart ) + { + // turn off screen-saver / power saving when in Presentation mode + SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); + } + else + { + // turn on screen-saver / power saving back + SetThreadExecutionState(ES_CONTINUOUS); + } +} + +void WinSalFrame::SetAlwaysOnTop( bool bOnTop ) +{ + HWND hWnd; + if ( bOnTop ) + hWnd = HWND_TOPMOST; + else + hWnd = HWND_NOTOPMOST; + SetWindowPos( mhWnd, hWnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); +} + +static bool EnableAttachThreadInputHack() +{ + OUString aBootstrapUri; + if (osl_getProcessWorkingDir(&aBootstrapUri.pData) != osl_Process_E_None) + return false; + aBootstrapUri += "/bootstrap.ini"; + + OUString aSystemFileName; + if (osl::FileBase::getSystemPathFromFileURL(aBootstrapUri, aSystemFileName) != osl::FileBase::E_None) + return false; + if (aSystemFileName.getLength() > MAX_PATH) + return false; + + // this uses the Boost ini parser, instead of tools::Config, as we already use it to read other + // values from bootstrap.ini in desktop/win32/source/loader.cxx, because that watchdog process + // can't access LO libs. This way the handling is consistent. + try + { + boost::property_tree::ptree pt; + std::ifstream aFile(o3tl::toW(aSystemFileName.getStr())); + boost::property_tree::ini_parser::read_ini(aFile, pt); + const bool bEnabled = pt.get("Win32.EnableAttachThreadInputHack", false); + SAL_WARN_IF(bEnabled, "vcl", "AttachThreadInput hack is enabled. Watch out for deadlocks!"); + return bEnabled; + } + catch (...) + { + return false; + } +} + +static void ImplSalToTop( HWND hWnd, SalFrameToTop nFlags ) +{ + static const bool bEnableAttachThreadInputHack = EnableAttachThreadInputHack(); + + WinSalFrame* pToTopFrame = GetWindowPtr( hWnd ); + if( pToTopFrame && (pToTopFrame->mnStyle & SalFrameStyleFlags::SYSTEMCHILD) ) + BringWindowToTop( hWnd ); + + if ( nFlags & SalFrameToTop::ForegroundTask ) + { + // LO used to always call AttachThreadInput here, which resulted in deadlocks + // in some installations for unknown reasons! + if (bEnableAttachThreadInputHack) + { + // This magic code is necessary to connect the input focus of the + // current window thread and the thread which owns the window that + // should be the new foreground window. + HWND hCurrWnd = GetForegroundWindow(); + DWORD myThreadID = GetCurrentThreadId(); + DWORD currThreadID = GetWindowThreadProcessId(hCurrWnd,nullptr); + AttachThreadInput(myThreadID, currThreadID, TRUE); + SetForegroundWindow_Impl(hWnd); + AttachThreadInput(myThreadID, currThreadID, FALSE); + } + else + SetForegroundWindow_Impl(hWnd); + } + + if ( nFlags & SalFrameToTop::RestoreWhenMin ) + { + HWND hIconicWnd = hWnd; + while ( hIconicWnd ) + { + if ( IsIconic( hIconicWnd ) ) + { + WinSalFrame* pFrame = GetWindowPtr( hIconicWnd ); + if ( pFrame ) + { + if ( GetWindowPtr( hWnd )->mbRestoreMaximize ) + ShowWindow( hIconicWnd, SW_MAXIMIZE ); + else + ShowWindow( hIconicWnd, SW_RESTORE ); + } + else + ShowWindow( hIconicWnd, SW_RESTORE ); + } + + hIconicWnd = ::GetParent( hIconicWnd ); + } + } + + if ( !IsIconic( hWnd ) && IsWindowVisible( hWnd ) ) + { + SetFocus( hWnd ); + + // Windows sometimes incorrectly reports to have the focus; + // thus make sure to really get the focus + if ( ::GetFocus() == hWnd ) + SetForegroundWindow_Impl( hWnd ); + } +} + +void WinSalFrame::ToTop( SalFrameToTop nFlags ) +{ + nFlags &= ~SalFrameToTop::GrabFocus; // this flag is not needed on win32 + // Post this Message to the window, because this only works + // in the thread of the window, which has create this window. + // We post this message to avoid deadlocks + if ( GetSalData()->mnAppThreadId != GetCurrentThreadId() ) + { + bool const ret = PostMessageW( mhWnd, SAL_MSG_TOTOP, static_cast<WPARAM>(nFlags), 0 ); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + else + ImplSalToTop( mhWnd, nFlags ); +} + +void WinSalFrame::SetPointer( PointerStyle ePointerStyle ) +{ + struct ImplPtrData + { + HCURSOR mhCursor; + LPCTSTR mnSysId; + UINT mnOwnId; + }; + + static o3tl::enumarray<PointerStyle, ImplPtrData> aImplPtrTab = + { + // [-loplugin:redundantfcast]: + ImplPtrData{ nullptr, IDC_ARROW, 0 }, // POINTER_ARROW + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_NULL }, // POINTER_NULL + ImplPtrData{ nullptr, IDC_WAIT, 0 }, // POINTER_WAIT + ImplPtrData{ nullptr, IDC_IBEAM, 0 }, // POINTER_TEXT + ImplPtrData{ nullptr, IDC_HELP, 0 }, // POINTER_HELP + ImplPtrData{ nullptr, IDC_CROSS, 0 }, // POINTER_CROSS + ImplPtrData{ nullptr, IDC_SIZEALL, 0 }, // POINTER_MOVE + ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_NSIZE + ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_SSIZE + ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_WSIZE + ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_ESIZE + ImplPtrData{ nullptr, IDC_SIZENWSE, 0 }, // POINTER_NWSIZE + ImplPtrData{ nullptr, IDC_SIZENESW, 0 }, // POINTER_NESIZE + ImplPtrData{ nullptr, IDC_SIZENESW, 0 }, // POINTER_SWSIZE + ImplPtrData{ nullptr, IDC_SIZENWSE, 0 }, // POINTER_SESIZE + ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_WINDOW_NSIZE + ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_WINDOW_SSIZE + ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_WINDOW_WSIZE + ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_WINDOW_ESIZE + ImplPtrData{ nullptr, IDC_SIZENWSE, 0 }, // POINTER_WINDOW_NWSIZE + ImplPtrData{ nullptr, IDC_SIZENESW, 0 }, // POINTER_WINDOW_NESIZE + ImplPtrData{ nullptr, IDC_SIZENESW, 0 }, // POINTER_WINDOW_SWSIZE + ImplPtrData{ nullptr, IDC_SIZENWSE, 0 }, // POINTER_WINDOW_SESIZE + ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_HSPLIT + ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_VSPLIT + ImplPtrData{ nullptr, IDC_SIZEWE, 0 }, // POINTER_HSIZEBAR + ImplPtrData{ nullptr, IDC_SIZENS, 0 }, // POINTER_VSIZEBAR + ImplPtrData{ nullptr, IDC_HAND, 0 }, // POINTER_HAND + ImplPtrData{ nullptr, IDC_HAND, 0 }, // POINTER_REFHAND + ImplPtrData{ nullptr, IDC_PEN, 0 }, // POINTER_PEN + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MAGNIFY }, // POINTER_MAGNIFY + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_FILL }, // POINTER_FILL + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_ROTATE }, // POINTER_ROTATE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_HSHEAR }, // POINTER_HSHEAR + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_VSHEAR }, // POINTER_VSHEAR + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MIRROR }, // POINTER_MIRROR + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CROOK }, // POINTER_CROOK + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CROP }, // POINTER_CROP + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEPOINT }, // POINTER_MOVEPOINT + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEBEZIERWEIGHT }, // POINTER_MOVEBEZIERWEIGHT + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEDATA }, // POINTER_MOVEDATA + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYDATA }, // POINTER_COPYDATA + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_LINKDATA }, // POINTER_LINKDATA + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEDATALINK }, // POINTER_MOVEDATALINK + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYDATALINK }, // POINTER_COPYDATALINK + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEFILE }, // POINTER_MOVEFILE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYFILE }, // POINTER_COPYFILE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_LINKFILE }, // POINTER_LINKFILE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEFILELINK }, // POINTER_MOVEFILELINK + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYFILELINK }, // POINTER_COPYFILELINK + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_MOVEFILES }, // POINTER_MOVEFILES + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_COPYFILES }, // POINTER_COPYFILES + ImplPtrData{ nullptr, IDC_NO, 0 }, // POINTER_NOTALLOWED + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_LINE }, // POINTER_DRAW_LINE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_RECT }, // POINTER_DRAW_RECT + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_POLYGON }, // POINTER_DRAW_POLYGON + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_BEZIER }, // POINTER_DRAW_BEZIER + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_ARC }, // POINTER_DRAW_ARC + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_PIE }, // POINTER_DRAW_PIE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_CIRCLECUT }, // POINTER_DRAW_CIRCLECUT + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_ELLIPSE }, // POINTER_DRAW_ELLIPSE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_FREEHAND }, // POINTER_DRAW_FREEHAND + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_CONNECT }, // POINTER_DRAW_CONNECT + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_TEXT }, // POINTER_DRAW_TEXT + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DRAW_CAPTION }, // POINTER_DRAW_CAPTION + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CHART }, // POINTER_CHART + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_DETECTIVE }, // POINTER_DETECTIVE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_PIVOT_COL }, // POINTER_PIVOT_COL + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_PIVOT_ROW }, // POINTER_PIVOT_ROW + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_PIVOT_FIELD }, // POINTER_PIVOT_FIELD + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CHAIN }, // POINTER_CHAIN + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_CHAIN_NOTALLOWED }, // POINTER_CHAIN_NOTALLOWED + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_N }, // POINTER_AUTOSCROLL_N + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_S }, // POINTER_AUTOSCROLL_S + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_W }, // POINTER_AUTOSCROLL_W + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_E }, // POINTER_AUTOSCROLL_E + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NW }, // POINTER_AUTOSCROLL_NW + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NE }, // POINTER_AUTOSCROLL_NE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_SW }, // POINTER_AUTOSCROLL_SW + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_SE }, // POINTER_AUTOSCROLL_SE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NS }, // POINTER_AUTOSCROLL_NS + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_WE }, // POINTER_AUTOSCROLL_WE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_AUTOSCROLL_NSWE }, // POINTER_AUTOSCROLL_NSWE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TEXT_VERTICAL }, // POINTER_TEXT_VERTICAL + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_PIVOT_DELETE }, // POINTER_PIVOT_DELETE + + // #i32329# + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_S }, // POINTER_TAB_SELECT_S + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_E }, // POINTER_TAB_SELECT_E + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_SE }, // POINTER_TAB_SELECT_SE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_W }, // POINTER_TAB_SELECT_W + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_TAB_SELECT_SW }, // POINTER_TAB_SELECT_SW + + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_HIDEWHITESPACE }, // POINTER_HIDEWHITESPACE + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_SHOWWHITESPACE }, // POINTER_UNHIDEWHITESPACE + + ImplPtrData{ nullptr, nullptr, SAL_RESID_POINTER_FATCROSS } // POINTER_FATCROSS + }; + + // Mousepointer loaded ? + if ( !aImplPtrTab[ePointerStyle].mhCursor ) + { + if ( aImplPtrTab[ePointerStyle].mnOwnId ) + aImplPtrTab[ePointerStyle].mhCursor = ImplLoadSalCursor( aImplPtrTab[ePointerStyle].mnOwnId ); + else + aImplPtrTab[ePointerStyle].mhCursor = LoadCursor( nullptr, aImplPtrTab[ePointerStyle].mnSysId ); + } + + // change the mouse pointer if different + if ( mhCursor != aImplPtrTab[ePointerStyle].mhCursor ) + { + mhCursor = aImplPtrTab[ePointerStyle].mhCursor; + SetCursor( mhCursor ); + } +} + +void WinSalFrame::CaptureMouse( bool bCapture ) +{ + // Send this Message to the window, because CaptureMouse() only work + // in the thread of the window, which has create this window + int nMsg; + if ( bCapture ) + nMsg = SAL_MSG_CAPTUREMOUSE; + else + nMsg = SAL_MSG_RELEASEMOUSE; + SendMessageW( mhWnd, nMsg, 0, 0 ); +} + +void WinSalFrame::SetPointerPos( tools::Long nX, tools::Long nY ) +{ + POINT aPt; + aPt.x = static_cast<int>(nX); + aPt.y = static_cast<int>(nY); + ClientToScreen( mhWnd, &aPt ); + SetCursorPos( aPt.x, aPt.y ); +} + +void WinSalFrame::Flush() +{ + if(mpLocalGraphics) + mpLocalGraphics->Flush(); + if(mpThreadGraphics) + mpThreadGraphics->Flush(); + GdiFlush(); +} + +static void ImplSalFrameSetInputContext( HWND hWnd, const SalInputContext* pContext ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + bool bIME(pContext->mnOptions & InputContextFlags::Text); + if ( bIME ) + { + if ( !pFrame->mbIME ) + { + pFrame->mbIME = true; + + if ( pFrame->mhDefIMEContext ) + { + ImmAssociateContext( pFrame->mhWnd, pFrame->mhDefIMEContext ); + UINT nImeProps = ImmGetProperty( GetKeyboardLayout( 0 ), IGP_PROPERTY ); + pFrame->mbSpezIME = (nImeProps & IME_PROP_SPECIAL_UI) != 0; + pFrame->mbAtCursorIME = (nImeProps & IME_PROP_AT_CARET) != 0; + pFrame->mbHandleIME = !pFrame->mbSpezIME; + } + } + + // When the application can't handle IME messages, then the + // System should handle the IME handling + if ( !(pContext->mnOptions & InputContextFlags::ExtText) ) + pFrame->mbHandleIME = false; + + // Set the Font for IME Handling + if ( pContext->mpFont ) + { + HIMC hIMC = ImmGetContext( pFrame->mhWnd ); + if ( hIMC ) + { + LOGFONTW aLogFont; + ImplGetLogFontFromFontSelect(pContext->mpFont->GetFontSelectPattern(), + nullptr, aLogFont); + ImmSetCompositionFontW( hIMC, &aLogFont ); + ImmReleaseContext( pFrame->mhWnd, hIMC ); + } + } + } + else + { + if ( pFrame->mbIME ) + { + pFrame->mbIME = false; + pFrame->mbHandleIME = false; + ImmAssociateContext( pFrame->mhWnd, nullptr ); + } + } +} + +void WinSalFrame::SetInputContext( SalInputContext* pContext ) +{ + // Must be called in the main thread! + SendMessageW( mhWnd, SAL_MSG_SETINPUTCONTEXT, 0, reinterpret_cast<LPARAM>(pContext) ); +} + +static void ImplSalFrameEndExtTextInput( HWND hWnd, EndExtTextInputFlags nFlags ) +{ + HIMC hIMC = ImmGetContext( hWnd ); + if ( hIMC ) + { + DWORD nIndex; + if ( nFlags & EndExtTextInputFlags::Complete ) + nIndex = CPS_COMPLETE; + else + nIndex = CPS_CANCEL; + + ImmNotifyIME( hIMC, NI_COMPOSITIONSTR, nIndex, 0 ); + ImmReleaseContext( hWnd, hIMC ); + } +} + +void WinSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags ) +{ + // Must be called in the main thread! + SendMessageW( mhWnd, SAL_MSG_ENDEXTTEXTINPUT, static_cast<WPARAM>(nFlags), 0 ); +} + +static void ImplGetKeyNameText( LONG lParam, sal_Unicode* pBuf, + UINT& rCount, UINT nMaxSize, + const char* pReplace ) +{ + static_assert( sizeof( WCHAR ) == sizeof( sal_Unicode ), "must be the same size" ); + + static const int nMaxKeyLen = 350; + WCHAR aKeyBuf[ nMaxKeyLen ]; + int nKeyLen = 0; + if ( lParam ) + { + OUString aLang = Application::GetSettings().GetUILanguageTag().getLanguage(); + OUString aRet; + + aRet = ::vcl_sal::getKeysReplacementName( aLang, lParam ); + if( aRet.isEmpty() ) + { + nKeyLen = GetKeyNameTextW( lParam, aKeyBuf, nMaxKeyLen ); + SAL_WARN_IF( nKeyLen > nMaxKeyLen, "vcl", "Invalid key name length!" ); + if( nKeyLen > nMaxKeyLen ) + nKeyLen = 0; + else if( nKeyLen > 0 ) + { + // Capitalize just the first letter of key names + CharLowerBuffW( aKeyBuf, nKeyLen ); + + bool bUpper = true; + for( WCHAR *pW=aKeyBuf, *pE=pW+nKeyLen; pW < pE; ++pW ) + { + if( bUpper ) + CharUpperBuffW( pW, 1 ); + bUpper = (*pW=='+') || (*pW=='-') || (*pW==' ') || (*pW=='.'); + } + } + } + else + { + nKeyLen = aRet.getLength(); + wcscpy( aKeyBuf, o3tl::toW( aRet.getStr() )); + } + } + + if ( (nKeyLen > 0) || pReplace ) + { + if( (rCount > 0) && (rCount < nMaxSize) ) + { + pBuf[rCount] = '+'; + rCount++; + } + + if( nKeyLen > 0 ) + { + WCHAR *pW = aKeyBuf, *pE = aKeyBuf + nKeyLen; + while ((pW < pE) && *pW && (rCount < nMaxSize)) + pBuf[rCount++] = *pW++; + } + else // fall back to provided default name + { + while( *pReplace && (rCount < nMaxSize) ) + { + pBuf[rCount] = *pReplace; + rCount++; + pReplace++; + } + } + } + else + rCount = 0; +} + +OUString WinSalFrame::GetKeyName( sal_uInt16 nKeyCode ) +{ + static const UINT nMaxKeyLen = 350; + sal_Unicode aKeyBuf[ nMaxKeyLen ]; + UINT nKeyBufLen = 0; + UINT nSysCode = 0; + + if ( nKeyCode & KEY_MOD1 ) + { + nSysCode = MapVirtualKeyW( VK_CONTROL, 0 ); + nSysCode = (nSysCode << 16) | ((sal_uLong(1)) << 25); + ImplGetKeyNameText( nSysCode, aKeyBuf, nKeyBufLen, nMaxKeyLen, "Ctrl" ); + } + + if ( nKeyCode & KEY_MOD2 ) + { + nSysCode = MapVirtualKeyW( VK_MENU, 0 ); + nSysCode = (nSysCode << 16) | ((sal_uLong(1)) << 25); + ImplGetKeyNameText( nSysCode, aKeyBuf, nKeyBufLen, nMaxKeyLen, "Alt" ); + } + + if ( nKeyCode & KEY_SHIFT ) + { + nSysCode = MapVirtualKeyW( VK_SHIFT, 0 ); + nSysCode = (nSysCode << 16) | ((sal_uLong(1)) << 25); + ImplGetKeyNameText( nSysCode, aKeyBuf, nKeyBufLen, nMaxKeyLen, "Shift" ); + } + + sal_uInt16 nCode = nKeyCode & 0x0FFF; + sal_uLong nSysCode2 = 0; + const char* pReplace = nullptr; + sal_Unicode cSVCode = 0; + char aFBuf[4]; + nSysCode = 0; + + if ( (nCode >= KEY_0) && (nCode <= KEY_9) ) + cSVCode = '0' + (nCode - KEY_0); + else if ( (nCode >= KEY_A) && (nCode <= KEY_Z) ) + cSVCode = 'A' + (nCode - KEY_A); + else if ( (nCode >= KEY_F1) && (nCode <= KEY_F26) ) + { + nSysCode = VK_F1 + (nCode - KEY_F1); + aFBuf[0] = 'F'; + if (nCode <= KEY_F9) + { + aFBuf[1] = sal::static_int_cast<char>('1' + (nCode - KEY_F1)); + aFBuf[2] = 0; + } + else if (nCode <= KEY_F19) + { + aFBuf[1] = '1'; + aFBuf[2] = sal::static_int_cast<char>('0' + (nCode - KEY_F10)); + aFBuf[3] = 0; + } + else + { + aFBuf[1] = '2'; + aFBuf[2] = sal::static_int_cast<char>('0' + (nCode - KEY_F20)); + aFBuf[3] = 0; + } + pReplace = aFBuf; + } + else + { + switch ( nCode ) + { + case KEY_DOWN: + nSysCode = VK_DOWN; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Down"; + break; + case KEY_UP: + nSysCode = VK_UP; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Up"; + break; + case KEY_LEFT: + nSysCode = VK_LEFT; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Left"; + break; + case KEY_RIGHT: + nSysCode = VK_RIGHT; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Right"; + break; + case KEY_HOME: + nSysCode = VK_HOME; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Home"; + break; + case KEY_END: + nSysCode = VK_END; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "End"; + break; + case KEY_PAGEUP: + nSysCode = VK_PRIOR; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Page Up"; + break; + case KEY_PAGEDOWN: + nSysCode = VK_NEXT; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Page Down"; + break; + case KEY_RETURN: + nSysCode = VK_RETURN; + pReplace = "Enter"; + break; + case KEY_ESCAPE: + nSysCode = VK_ESCAPE; + pReplace = "Escape"; + break; + case KEY_TAB: + nSysCode = VK_TAB; + pReplace = "Tab"; + break; + case KEY_BACKSPACE: + nSysCode = VK_BACK; + pReplace = "Backspace"; + break; + case KEY_SPACE: + nSysCode = VK_SPACE; + pReplace = "Space"; + break; + case KEY_INSERT: + nSysCode = VK_INSERT; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Insert"; + break; + case KEY_DELETE: + nSysCode = VK_DELETE; + nSysCode2 = ((sal_uLong(1)) << 24); + pReplace = "Delete"; + break; + + case KEY_ADD: + cSVCode = '+'; + break; + case KEY_SUBTRACT: + cSVCode = '-'; + break; + case KEY_MULTIPLY: + cSVCode = '*'; + break; + case KEY_DIVIDE: + cSVCode = '/'; + break; + case KEY_POINT: + cSVCode = '.'; + break; + case KEY_COMMA: + cSVCode = ','; + break; + case KEY_LESS: + cSVCode = '<'; + break; + case KEY_GREATER: + cSVCode = '>'; + break; + case KEY_EQUAL: + cSVCode = '='; + break; + case KEY_NUMBERSIGN: + cSVCode = '#'; + break; + case KEY_XF86FORWARD: + cSVCode = VK_BROWSER_FORWARD; + break; + case KEY_XF86BACK: + cSVCode = VK_BROWSER_BACK; + break; + case KEY_COLON: + cSVCode = ':'; + break; + case KEY_SEMICOLON: + cSVCode = ';'; + break; + case KEY_QUOTERIGHT: + cSVCode = '\''; + break; + case KEY_BRACKETLEFT: + cSVCode = '['; + break; + case KEY_BRACKETRIGHT: + cSVCode = ']'; + break; + case KEY_QUOTELEFT: + cSVCode = '`'; + break; + case KEY_RIGHTCURLYBRACKET: + cSVCode = '}'; + break; + } + } + + if ( nSysCode ) + { + nSysCode = MapVirtualKeyW( nSysCode, 0 ); + if ( nSysCode ) + nSysCode = (nSysCode << 16) | nSysCode2; + ImplGetKeyNameText( nSysCode, aKeyBuf, nKeyBufLen, nMaxKeyLen, pReplace ); + } + else + { + if ( cSVCode ) + { + if ( nKeyBufLen > 0 ) + aKeyBuf[ nKeyBufLen++ ] = '+'; + if( nKeyBufLen < nMaxKeyLen ) + aKeyBuf[ nKeyBufLen++ ] = cSVCode; + } + } + + if( !nKeyBufLen ) + return OUString(); + + return OUString( aKeyBuf, sal::static_int_cast< sal_uInt16 >(nKeyBufLen) ); +} + +static Color ImplWinColorToSal( COLORREF nColor ) +{ + return Color( GetRValue( nColor ), GetGValue( nColor ), GetBValue( nColor ) ); +} + +static void ImplSalUpdateStyleFontW( HDC hDC, const LOGFONTW& rLogFont, vcl::Font& rFont ) +{ + ImplSalLogFontToFontW( hDC, rLogFont, rFont ); + + // On Windows 9x, Windows NT we get sometimes very small sizes + // (for example for the small Caption height). + // So if it is MS Sans Serif, a none scalable font we use + // 8 Point as the minimum control height, in all other cases + // 6 Point is the smallest one + if ( rFont.GetFontHeight() < 8 ) + { + if ( rtl_ustr_compareIgnoreAsciiCase( o3tl::toU(rLogFont.lfFaceName), o3tl::toU(L"MS Sans Serif") ) == 0 ) + rFont.SetFontHeight( 8 ); + else if ( rFont.GetFontHeight() < 6 ) + rFont.SetFontHeight( 6 ); + } +} + +static tools::Long ImplW2I( const wchar_t* pStr ) +{ + tools::Long n = 0; + int nSign = 1; + + if ( *pStr == '-' ) + { + nSign = -1; + pStr++; + } + + while( (*pStr >= 48) && (*pStr <= 57) ) + { + n *= 10; + n += ((*pStr) - 48); + pStr++; + } + + n *= nSign; + + return n; +} + +void WinSalFrame::UpdateSettings( AllSettings& rSettings ) +{ + MouseSettings aMouseSettings = rSettings.GetMouseSettings(); + aMouseSettings.SetDoubleClickTime( GetDoubleClickTime() ); + aMouseSettings.SetDoubleClickWidth( GetSystemMetrics( SM_CXDOUBLECLK ) ); + aMouseSettings.SetDoubleClickHeight( GetSystemMetrics( SM_CYDOUBLECLK ) ); + tools::Long nDragWidth = GetSystemMetrics( SM_CXDRAG ); + tools::Long nDragHeight = GetSystemMetrics( SM_CYDRAG ); + if ( nDragWidth ) + aMouseSettings.SetStartDragWidth( nDragWidth ); + if ( nDragHeight ) + aMouseSettings.SetStartDragHeight( nDragHeight ); + HKEY hRegKey; + if ( RegOpenKeyW( HKEY_CURRENT_USER, + L"Control Panel\\Desktop", + &hRegKey ) == ERROR_SUCCESS ) + { + wchar_t aValueBuf[10]; + DWORD nValueSize = sizeof( aValueBuf ); + DWORD nType; + if ( RegQueryValueExW( hRegKey, L"MenuShowDelay", nullptr, + &nType, reinterpret_cast<LPBYTE>(aValueBuf), &nValueSize ) == ERROR_SUCCESS ) + { + if ( nType == REG_SZ ) + aMouseSettings.SetMenuDelay( static_cast<sal_uLong>(ImplW2I( aValueBuf )) ); + } + + RegCloseKey( hRegKey ); + } + + StyleSettings aStyleSettings = rSettings.GetStyleSettings(); + + // High contrast + HIGHCONTRAST hc; + hc.cbSize = sizeof( HIGHCONTRAST ); + if( SystemParametersInfoW( SPI_GETHIGHCONTRAST, hc.cbSize, &hc, 0 ) + && (hc.dwFlags & HCF_HIGHCONTRASTON) ) + aStyleSettings.SetHighContrastMode( true ); + else + aStyleSettings.SetHighContrastMode( false ); + + aStyleSettings.SetScrollBarSize( GetSystemMetrics( SM_CXVSCROLL ) ); + aStyleSettings.SetSpinSize( GetSystemMetrics( SM_CXVSCROLL ) ); + UINT blinkTime = GetCaretBlinkTime(); + aStyleSettings.SetCursorBlinkTime( + blinkTime == 0 || blinkTime == INFINITE // 0 indicates error + ? STYLE_CURSOR_NOBLINKTIME : blinkTime ); + aStyleSettings.SetFloatTitleHeight( GetSystemMetrics( SM_CYSMCAPTION ) ); + aStyleSettings.SetTitleHeight( GetSystemMetrics( SM_CYCAPTION ) ); + aStyleSettings.SetActiveBorderColor( ImplWinColorToSal( GetSysColor( COLOR_ACTIVEBORDER ) ) ); + aStyleSettings.SetDeactiveBorderColor( ImplWinColorToSal( GetSysColor( COLOR_INACTIVEBORDER ) ) ); + aStyleSettings.SetDeactiveColor( ImplWinColorToSal( GetSysColor( COLOR_GRADIENTINACTIVECAPTION ) ) ); + + Color aControlTextColor; + Color aMenuBarTextColor; + Color aMenuBarRolloverTextColor; + Color aHighlightTextColor = ImplWinColorToSal(GetSysColor(COLOR_HIGHLIGHTTEXT)); + + BOOL bFlatMenus = FALSE; + SystemParametersInfoW( SPI_GETFLATMENU, 0, &bFlatMenus, 0); + if( bFlatMenus ) + { + aStyleSettings.SetUseFlatMenus( true ); + // flat borders for our controls etc. as well in this mode (ie, no 3d borders) + // this is not active in the classic style appearance + aStyleSettings.SetUseFlatBorders( true ); + } + else + { + aStyleSettings.SetUseFlatMenus( false ); + aStyleSettings.SetUseFlatBorders( false ); + } + + if( bFlatMenus ) + { + aStyleSettings.SetMenuHighlightColor( ImplWinColorToSal( GetSysColor( COLOR_MENUHILIGHT ) ) ); + aStyleSettings.SetMenuBarRolloverColor( ImplWinColorToSal( GetSysColor( COLOR_MENUHILIGHT ) ) ); + aStyleSettings.SetMenuBorderColor( ImplWinColorToSal( GetSysColor( COLOR_3DSHADOW ) ) ); + } + else + { + aStyleSettings.SetMenuHighlightColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) ); + aStyleSettings.SetMenuBarRolloverColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) ); + aStyleSettings.SetMenuBorderColor( ImplWinColorToSal( GetSysColor( COLOR_3DLIGHT ) ) ); + } + + const bool bUseDarkMode(UseDarkMode()); + + OUString sThemeName(!bUseDarkMode ? u"colibre" : u"colibre_dark"); + aStyleSettings.SetPreferredIconTheme(sThemeName, bUseDarkMode); + + if (bUseDarkMode) + { + SetWindowTheme(mhWnd, L"Explorer", nullptr); + + HTHEME hTheme = OpenThemeData(nullptr, L"ItemsView"); + COLORREF color; + GetThemeColor(hTheme, 0, 0, TMT_FILLCOLOR, &color); + aStyleSettings.SetFaceColor( ImplWinColorToSal( color ) ); + aStyleSettings.SetWindowColor( ImplWinColorToSal( color ) ); + + // tdf#156040 in the absence of a better idea, do like + // StyleSettings::Set3DColors does + Color aLightColor(ImplWinColorToSal(color)); + aLightColor.DecreaseLuminance(64); + aStyleSettings.SetLightColor(aLightColor); + + GetThemeColor(hTheme, 0, 0, TMT_TEXTCOLOR, &color); + aStyleSettings.SetWindowTextColor( ImplWinColorToSal( color ) ); + aStyleSettings.SetToolTextColor( ImplWinColorToSal( color ) ); + GetThemeColor(hTheme, 0, 0, TMT_SHADOWCOLOR, &color); + aStyleSettings.SetShadowColor( ImplWinColorToSal( color ) ); + GetThemeColor(hTheme, 0, 0, TMT_DKSHADOW3D, &color); + aStyleSettings.SetDarkShadowColor( ImplWinColorToSal( color ) ); + CloseThemeData(hTheme); + + hTheme = OpenThemeData(mhWnd, L"Button"); + GetThemeColor(hTheme, BP_PUSHBUTTON, PBS_NORMAL, TMT_TEXTCOLOR, &color); + aControlTextColor = ImplWinColorToSal(color); + GetThemeColor(hTheme, BP_CHECKBOX, CBS_CHECKEDNORMAL, TMT_TEXTCOLOR, &color); + aStyleSettings.SetRadioCheckTextColor( ImplWinColorToSal( color ) ); + CloseThemeData(hTheme); + + SetWindowTheme(mhWnd, nullptr, nullptr); + + hTheme = OpenThemeData(mhWnd, L"Menu"); + GetThemeColor(hTheme, MENU_POPUPITEM, MBI_NORMAL, TMT_TEXTCOLOR, &color); + aStyleSettings.SetMenuTextColor( ImplWinColorToSal( color ) ); + aMenuBarTextColor = ImplWinColorToSal( color ); + aMenuBarRolloverTextColor = ImplWinColorToSal( color ); + CloseThemeData(hTheme); + + aStyleSettings.SetActiveTabColor( aStyleSettings.GetWindowColor() ); + hTheme = OpenThemeData(mhWnd, L"Toolbar"); + GetThemeColor(hTheme, 0, 0, TMT_FILLCOLOR, &color); + aStyleSettings.SetInactiveTabColor( ImplWinColorToSal( color ) ); + // see ImplDrawNativeControl for dark mode + aStyleSettings.SetMenuBarColor( aStyleSettings.GetWindowColor() ); + CloseThemeData(hTheme); + + hTheme = OpenThemeData(mhWnd, L"Textstyle"); + if (hTheme) + { + GetThemeColor(hTheme, TEXT_HYPERLINKTEXT, TS_HYPERLINK_NORMAL, TMT_TEXTCOLOR, &color); + aStyleSettings.SetLinkColor(ImplWinColorToSal(color)); + CloseThemeData(hTheme); + } + + // tdf#148448 pick a warning color more likely to be readable as a + // background in a dark theme + aStyleSettings.SetWarningColor(Color(0xf5, 0x79, 0x00)); + } + else + { + aStyleSettings.SetFaceColor( ImplWinColorToSal( GetSysColor( COLOR_3DFACE ) ) ); + aStyleSettings.SetWindowColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOW ) ) ); + aStyleSettings.SetWindowTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) ); + aStyleSettings.SetToolTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) ); + aStyleSettings.SetLightColor( ImplWinColorToSal( GetSysColor( COLOR_3DHILIGHT ) ) ); + aStyleSettings.SetShadowColor( ImplWinColorToSal( GetSysColor( COLOR_3DSHADOW ) ) ); + aStyleSettings.SetDarkShadowColor( ImplWinColorToSal( GetSysColor( COLOR_3DDKSHADOW ) ) ); + aControlTextColor = ImplWinColorToSal(GetSysColor(COLOR_BTNTEXT)); + aStyleSettings.SetRadioCheckTextColor( ImplWinColorToSal( GetSysColor( COLOR_WINDOWTEXT ) ) ); + aStyleSettings.SetMenuTextColor( ImplWinColorToSal( GetSysColor( COLOR_MENUTEXT ) ) ); + aMenuBarTextColor = ImplWinColorToSal( GetSysColor( COLOR_MENUTEXT ) ); + aMenuBarRolloverTextColor = aHighlightTextColor; + if( bFlatMenus ) + aStyleSettings.SetMenuBarColor( ImplWinColorToSal( GetSysColor( COLOR_MENUBAR ) ) ); + else + aStyleSettings.SetMenuBarColor( ImplWinColorToSal( GetSysColor( COLOR_MENU ) ) ); + aStyleSettings.SetActiveTabColor( aStyleSettings.GetWindowColor() ); + aStyleSettings.SetInactiveTabColor( aStyleSettings.GetFaceColor() ); + } + + if ( std::optional<Color> aColor = aStyleSettings.GetPersonaMenuBarTextColor() ) + { + aMenuBarTextColor = *aColor; + if (!aStyleSettings.GetHighContrastMode()) + aMenuBarRolloverTextColor = *aColor; + } + + aStyleSettings.SetMenuBarTextColor( aMenuBarTextColor ); + aStyleSettings.SetMenuBarRolloverTextColor( aMenuBarRolloverTextColor ); + + aStyleSettings.SetLightBorderColor( ImplWinColorToSal( GetSysColor( COLOR_3DLIGHT ) ) ); + aStyleSettings.SetHelpColor( ImplWinColorToSal( GetSysColor( COLOR_INFOBK ) ) ); + aStyleSettings.SetHelpTextColor( ImplWinColorToSal( GetSysColor( COLOR_INFOTEXT ) ) ); + + aStyleSettings.SetWorkspaceColor(aStyleSettings.GetFaceColor()); + aStyleSettings.SetDialogColor(aStyleSettings.GetFaceColor()); + aStyleSettings.SetDialogTextColor(aControlTextColor); + + Color aHighlightButtonTextColor = aStyleSettings.GetHighContrastMode() ? + aHighlightTextColor : aControlTextColor; + + if (aStyleSettings.GetHighContrastMode()) + { + Color aLinkColor(ImplWinColorToSal(GetSysColor(COLOR_HOTLIGHT))); + aStyleSettings.SetLinkColor(aLinkColor); + aStyleSettings.SetVisitedLinkColor(aLinkColor); + } + + aStyleSettings.SetDefaultButtonTextColor(aHighlightButtonTextColor); + aStyleSettings.SetButtonTextColor(aControlTextColor); + aStyleSettings.SetDefaultActionButtonTextColor(aHighlightButtonTextColor); + aStyleSettings.SetActionButtonTextColor(aControlTextColor); + aStyleSettings.SetFlatButtonTextColor(aControlTextColor); + aStyleSettings.SetDefaultButtonRolloverTextColor(aHighlightButtonTextColor); + aStyleSettings.SetButtonRolloverTextColor(aHighlightButtonTextColor); + aStyleSettings.SetDefaultActionButtonRolloverTextColor(aHighlightButtonTextColor); + aStyleSettings.SetActionButtonRolloverTextColor(aHighlightButtonTextColor); + aStyleSettings.SetFlatButtonRolloverTextColor(aHighlightButtonTextColor); + aStyleSettings.SetDefaultButtonPressedRolloverTextColor(aControlTextColor); + aStyleSettings.SetButtonPressedRolloverTextColor(aControlTextColor); + aStyleSettings.SetDefaultActionButtonPressedRolloverTextColor(aControlTextColor); + aStyleSettings.SetActionButtonPressedRolloverTextColor(aControlTextColor); + aStyleSettings.SetFlatButtonPressedRolloverTextColor(aControlTextColor); + + aStyleSettings.SetTabTextColor(aControlTextColor); + aStyleSettings.SetTabRolloverTextColor(aControlTextColor); + aStyleSettings.SetTabHighlightTextColor(aControlTextColor); + + aStyleSettings.SetGroupTextColor( aStyleSettings.GetRadioCheckTextColor() ); + aStyleSettings.SetLabelTextColor( aStyleSettings.GetRadioCheckTextColor() ); + aStyleSettings.SetFieldColor( aStyleSettings.GetWindowColor() ); + aStyleSettings.SetListBoxWindowBackgroundColor( aStyleSettings.GetWindowColor() ); + aStyleSettings.SetFieldTextColor( aStyleSettings.GetWindowTextColor() ); + aStyleSettings.SetFieldRolloverTextColor( aStyleSettings.GetFieldTextColor() ); + aStyleSettings.SetListBoxWindowTextColor( aStyleSettings.GetFieldTextColor() ); + + aStyleSettings.SetAccentColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) ); + // https://devblogs.microsoft.com/oldnewthing/20170405-00/?p=95905 + + aStyleSettings.SetHighlightColor( ImplWinColorToSal( GetSysColor( COLOR_HIGHLIGHT ) ) ); + aStyleSettings.SetHighlightTextColor(aHighlightTextColor); + aStyleSettings.SetListBoxWindowHighlightColor( aStyleSettings.GetHighlightColor() ); + aStyleSettings.SetListBoxWindowHighlightTextColor( aStyleSettings.GetHighlightTextColor() ); + aStyleSettings.SetMenuHighlightTextColor( aStyleSettings.GetHighlightTextColor() ); + + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maNWFData.mnMenuFormatBorderX = 0; + pSVData->maNWFData.mnMenuFormatBorderY = 0; + pSVData->maNWFData.maMenuBarHighlightTextColor = COL_TRANSPARENT; + GetSalData()->mbThemeMenuSupport = false; + aStyleSettings.SetMenuColor( ImplWinColorToSal( GetSysColor( COLOR_MENU ) ) ); + aStyleSettings.SetMenuBarHighlightTextColor(aStyleSettings.GetMenuHighlightTextColor()); + aStyleSettings.SetActiveColor( ImplWinColorToSal( GetSysColor( COLOR_ACTIVECAPTION ) ) ); + aStyleSettings.SetActiveTextColor( ImplWinColorToSal( GetSysColor( COLOR_CAPTIONTEXT ) ) ); + aStyleSettings.SetDeactiveColor( ImplWinColorToSal( GetSysColor( COLOR_INACTIVECAPTION ) ) ); + aStyleSettings.SetDeactiveTextColor( ImplWinColorToSal( GetSysColor( COLOR_INACTIVECAPTIONTEXT ) ) ); + + aStyleSettings.SetCheckedColorSpecialCase( ); + + // caret width + DWORD nCaretWidth = 2; + if( SystemParametersInfoW( SPI_GETCARETWIDTH, 0, &nCaretWidth, 0 ) ) + aStyleSettings.SetCursorSize( nCaretWidth ); + + // Query Fonts + vcl::Font aMenuFont = aStyleSettings.GetMenuFont(); + vcl::Font aTitleFont = aStyleSettings.GetTitleFont(); + vcl::Font aFloatTitleFont = aStyleSettings.GetFloatTitleFont(); + vcl::Font aHelpFont = aStyleSettings.GetHelpFont(); + vcl::Font aAppFont = aStyleSettings.GetAppFont(); + vcl::Font aIconFont = aStyleSettings.GetIconFont(); + HDC hDC = GetDC( nullptr ); + NONCLIENTMETRICSW aNonClientMetrics; + aNonClientMetrics.cbSize = sizeof( aNonClientMetrics ); + if ( SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, sizeof( aNonClientMetrics ), &aNonClientMetrics, 0 ) ) + { + ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfMenuFont, aMenuFont ); + ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfCaptionFont, aTitleFont ); + ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfSmCaptionFont, aFloatTitleFont ); + ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfStatusFont, aHelpFont ); + ImplSalUpdateStyleFontW( hDC, aNonClientMetrics.lfMessageFont, aAppFont ); + + LOGFONTW aLogFont; + if ( SystemParametersInfoW( SPI_GETICONTITLELOGFONT, 0, &aLogFont, 0 ) ) + ImplSalUpdateStyleFontW( hDC, aLogFont, aIconFont ); + } + + ReleaseDC( nullptr, hDC ); + + aStyleSettings.SetToolbarIconSize(ToolbarIconSize::Large); + + aStyleSettings.BatchSetFonts( aAppFont, aAppFont ); + + aStyleSettings.SetMenuFont( aMenuFont ); + aStyleSettings.SetTitleFont( aTitleFont ); + aStyleSettings.SetFloatTitleFont( aFloatTitleFont ); + aStyleSettings.SetHelpFont( aHelpFont ); + aStyleSettings.SetIconFont( aIconFont ); + + if ( aAppFont.GetWeight() > WEIGHT_NORMAL ) + aAppFont.SetWeight( WEIGHT_NORMAL ); + aStyleSettings.SetToolFont( aAppFont ); + aStyleSettings.SetTabFont( aAppFont ); + + BOOL bDragFull; + if ( SystemParametersInfoW( SPI_GETDRAGFULLWINDOWS, 0, &bDragFull, 0 ) ) + { + DragFullOptions nDragFullOptions = aStyleSettings.GetDragFullOptions(); + if ( bDragFull ) + nDragFullOptions |= DragFullOptions::WindowMove | DragFullOptions::WindowSize | DragFullOptions::Docking | DragFullOptions::Split; + else + nDragFullOptions &= ~DragFullOptions(DragFullOptions::WindowMove | DragFullOptions::WindowSize | DragFullOptions::Docking | DragFullOptions::Split); + aStyleSettings.SetDragFullOptions( nDragFullOptions ); + } + + if ( RegOpenKeyW( HKEY_CURRENT_USER, + L"Control Panel\\International\\Calendars\\TwoDigitYearMax", + &hRegKey ) == ERROR_SUCCESS ) + { + wchar_t aValueBuf[10]; + DWORD nValue; + DWORD nValueSize = sizeof( aValueBuf ); + DWORD nType; + if ( RegQueryValueExW( hRegKey, L"1", nullptr, + &nType, reinterpret_cast<LPBYTE>(aValueBuf), &nValueSize ) == ERROR_SUCCESS ) + { + if ( nType == REG_SZ ) + { + nValue = static_cast<sal_uLong>(ImplW2I( aValueBuf )); + if ( (nValue > 1000) && (nValue < 10000) ) + { + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::DateFormat::TwoDigitYear::set(static_cast<sal_Int32>(nValue-99), batch); + batch->commit(); + } + } + } + + RegCloseKey( hRegKey ); + } + + rSettings.SetMouseSettings( aMouseSettings ); + rSettings.SetStyleSettings( aStyleSettings ); + + // now apply the values from theming, if available + WinSalGraphics::updateSettingsNative( rSettings ); +} + +const SystemEnvData* WinSalFrame::GetSystemData() const +{ + return &maSysData; +} + +void WinSalFrame::Beep() +{ + // a simple beep + MessageBeep( 0 ); +} + +SalFrame::SalPointerState WinSalFrame::GetPointerState() +{ + SalPointerState aState; + aState.mnState = 0; + + if ( GetKeyState( VK_LBUTTON ) & 0x8000 ) + aState.mnState |= MOUSE_LEFT; + if ( GetKeyState( VK_MBUTTON ) & 0x8000 ) + aState.mnState |= MOUSE_MIDDLE; + if ( GetKeyState( VK_RBUTTON ) & 0x8000 ) + aState.mnState |= MOUSE_RIGHT; + if ( GetKeyState( VK_SHIFT ) & 0x8000 ) + aState.mnState |= KEY_SHIFT; + if ( GetKeyState( VK_CONTROL ) & 0x8000 ) + aState.mnState |= KEY_MOD1; + if ( GetKeyState( VK_MENU ) & 0x8000 ) + aState.mnState |= KEY_MOD2; + + POINT pt; + GetCursorPos( &pt ); + + aState.maPos = Point(pt.x - maGeometry.x(), pt.y - maGeometry.y()); + return aState; +} + +KeyIndicatorState WinSalFrame::GetIndicatorState() +{ + KeyIndicatorState aState = KeyIndicatorState::NONE; + if (::GetKeyState(VK_CAPITAL)) + aState |= KeyIndicatorState::CAPSLOCK; + + if (::GetKeyState(VK_NUMLOCK)) + aState |= KeyIndicatorState::NUMLOCK; + + if (::GetKeyState(VK_SCROLL)) + aState |= KeyIndicatorState::SCROLLLOCK; + + return aState; +} + +void WinSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode ) +{ + BYTE nVKey = 0; + switch (nKeyCode) + { + case KEY_CAPSLOCK: + nVKey = VK_CAPITAL; + break; + } + + if (nVKey > 0 && nVKey < 255) + { + ::keybd_event(nVKey, 0x45, KEYEVENTF_EXTENDEDKEY, 0); + ::keybd_event(nVKey, 0x45, KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP, 0); + } +} + +void WinSalFrame::ResetClipRegion() +{ + SetWindowRgn( mhWnd, nullptr, TRUE ); +} + +void WinSalFrame::BeginSetClipRegion( sal_uInt32 nRects ) +{ + if( mpClipRgnData ) + delete [] reinterpret_cast<BYTE*>(mpClipRgnData); + sal_uLong nRectBufSize = sizeof(RECT)*nRects; + mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]); + mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER ); + mpClipRgnData->rdh.iType = RDH_RECTANGLES; + mpClipRgnData->rdh.nCount = nRects; + mpClipRgnData->rdh.nRgnSize = nRectBufSize; + SetRectEmpty( &(mpClipRgnData->rdh.rcBound) ); + mpNextClipRect = reinterpret_cast<RECT*>(&(mpClipRgnData->Buffer)); + mbFirstClipRect = true; +} + +void WinSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + if( ! mpClipRgnData ) + return; + + RECT* pRect = mpNextClipRect; + RECT* pBoundRect = &(mpClipRgnData->rdh.rcBound); + tools::Long nRight = nX + nWidth; + tools::Long nBottom = nY + nHeight; + + if ( mbFirstClipRect ) + { + pBoundRect->left = nX; + pBoundRect->top = nY; + pBoundRect->right = nRight; + pBoundRect->bottom = nBottom; + mbFirstClipRect = false; + } + else + { + if ( nX < pBoundRect->left ) + pBoundRect->left = static_cast<int>(nX); + + if ( nY < pBoundRect->top ) + pBoundRect->top = static_cast<int>(nY); + + if ( nRight > pBoundRect->right ) + pBoundRect->right = static_cast<int>(nRight); + + if ( nBottom > pBoundRect->bottom ) + pBoundRect->bottom = static_cast<int>(nBottom); + } + + pRect->left = static_cast<int>(nX); + pRect->top = static_cast<int>(nY); + pRect->right = static_cast<int>(nRight); + pRect->bottom = static_cast<int>(nBottom); + if( (mpNextClipRect - reinterpret_cast<RECT*>(&mpClipRgnData->Buffer)) < static_cast<int>(mpClipRgnData->rdh.nCount) ) + mpNextClipRect++; +} + +void WinSalFrame::EndSetClipRegion() +{ + if( ! mpClipRgnData ) + return; + + HRGN hRegion; + + // create region from accumulated rectangles + if ( mpClipRgnData->rdh.nCount == 1 ) + { + RECT* pRect = &(mpClipRgnData->rdh.rcBound); + hRegion = CreateRectRgn( pRect->left, pRect->top, + pRect->right, pRect->bottom ); + } + else + { + sal_uLong nSize = mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER); + hRegion = ExtCreateRegion( nullptr, nSize, mpClipRgnData ); + } + delete [] reinterpret_cast<BYTE*>(mpClipRgnData); + mpClipRgnData = nullptr; + + SAL_WARN_IF( !hRegion, "vcl", "WinSalFrame::EndSetClipRegion() - Can't create ClipRegion" ); + if( hRegion ) + { + RECT aWindowRect; + GetWindowRect( mhWnd, &aWindowRect ); + POINT aPt; + aPt.x=0; + aPt.y=0; + ClientToScreen( mhWnd, &aPt ); + OffsetRgn( hRegion, aPt.x - aWindowRect.left, aPt.y - aWindowRect.top ); + + if( SetWindowRgn( mhWnd, hRegion, TRUE ) == 0 ) + DeleteObject( hRegion ); + } +} + +void WinSalFrame::UpdateDarkMode() +{ + ::UpdateDarkMode(mhWnd); +} + +bool WinSalFrame::GetUseDarkMode() const +{ + return UseDarkMode(); +} + +bool WinSalFrame::GetUseReducedAnimation() const +{ + BOOL bEnableAnimation = FALSE; + SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &bEnableAnimation, 0); + return !bEnableAnimation; +} + +static bool ImplHandleMouseMsg( HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + if( nMsg == WM_LBUTTONDOWN || nMsg == WM_MBUTTONDOWN || nMsg == WM_RBUTTONDOWN ) + { + // #103168# post again if async focus has not arrived yet + // hopefully we will not receive the corresponding button up before this + // button down arrives again + vcl::Window *pWin = pFrame->GetWindow(); + if( pWin && pWin->ImplGetWindowImpl()->mpFrameData->mnFocusId ) + { + bool const ret = PostMessageW( hWnd, nMsg, wParam, lParam ); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + return true; + } + } + SalMouseEvent aMouseEvt; + bool nRet; + SalEvent nEvent = SalEvent::NONE; + bool bCall = true; + + aMouseEvt.mnX = static_cast<short>(LOWORD( lParam )); + aMouseEvt.mnY = static_cast<short>(HIWORD( lParam )); + aMouseEvt.mnCode = 0; + aMouseEvt.mnTime = GetMessageTime(); + + // Use GetKeyState(), as some Logitech mouse drivers do not check + // KeyState when simulating double-click with center mouse button + + if ( GetKeyState( VK_LBUTTON ) & 0x8000 ) + aMouseEvt.mnCode |= MOUSE_LEFT; + if ( GetKeyState( VK_MBUTTON ) & 0x8000 ) + aMouseEvt.mnCode |= MOUSE_MIDDLE; + if ( GetKeyState( VK_RBUTTON ) & 0x8000 ) + aMouseEvt.mnCode |= MOUSE_RIGHT; + if ( GetKeyState( VK_SHIFT ) & 0x8000 ) + aMouseEvt.mnCode |= KEY_SHIFT; + if ( GetKeyState( VK_CONTROL ) & 0x8000 ) + aMouseEvt.mnCode |= KEY_MOD1; + if ( GetKeyState( VK_MENU ) & 0x8000 ) + aMouseEvt.mnCode |= KEY_MOD2; + + switch ( nMsg ) + { + case WM_MOUSEMOVE: + { + // As the mouse events are not collected correctly when + // pressing modifier keys (as interrupted by KeyEvents) + // we do this here ourselves + if ( aMouseEvt.mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2) ) + { + MSG aTempMsg; + if ( PeekMessageW( &aTempMsg, hWnd, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE | PM_NOYIELD ) ) + { + if ( (aTempMsg.message == WM_MOUSEMOVE) && + (aTempMsg.wParam == wParam) ) + return true; + } + } + + SalData* pSalData = GetSalData(); + // Test for MouseLeave + if ( pSalData->mhWantLeaveMsg && (pSalData->mhWantLeaveMsg != hWnd) ) + SendMessageW( pSalData->mhWantLeaveMsg, SAL_MSG_MOUSELEAVE, 0, GetMessagePos() ); + + pSalData->mhWantLeaveMsg = hWnd; + aMouseEvt.mnButton = 0; + nEvent = SalEvent::MouseMove; + } + break; + + case WM_NCMOUSEMOVE: + case SAL_MSG_MOUSELEAVE: + { + SalData* pSalData = GetSalData(); + if ( pSalData->mhWantLeaveMsg == hWnd ) + { + // Mouse-Coordinates are relative to the screen + POINT aPt; + aPt.x = static_cast<short>(LOWORD(lParam)); + aPt.y = static_cast<short>(HIWORD(lParam)); + ScreenToClient(hWnd, &aPt); + if (const auto& pHelpWin = ImplGetSVHelpData().mpHelpWin) + { + const tools::Rectangle& rHelpRect = pHelpWin->GetHelpArea(); + if (rHelpRect.Contains(Point(aPt.x, aPt.y))) + { + // We have entered a tooltip (help window). Don't call the handler here; it + // would launch the sequence "Mouse leaves the Control->Control redraws-> + // Help window gets destroyed->Mouse enters the Control->Control redraws", + // which takes CPU and may flicker. Just destroy the help window and pretend + // we are still over the original window. + ImplDestroyHelpWindow(true); + bCall = false; + break; + } + } + pSalData->mhWantLeaveMsg = nullptr; + aMouseEvt.mnX = aPt.x; + aMouseEvt.mnY = aPt.y; + aMouseEvt.mnButton = 0; + nEvent = SalEvent::MouseLeave; + } + else + bCall = false; + } + break; + + case WM_LBUTTONDOWN: + aMouseEvt.mnButton = MOUSE_LEFT; + nEvent = SalEvent::MouseButtonDown; + break; + + case WM_MBUTTONDOWN: + aMouseEvt.mnButton = MOUSE_MIDDLE; + nEvent = SalEvent::MouseButtonDown; + break; + + case WM_RBUTTONDOWN: + aMouseEvt.mnButton = MOUSE_RIGHT; + nEvent = SalEvent::MouseButtonDown; + break; + + case WM_LBUTTONUP: + aMouseEvt.mnButton = MOUSE_LEFT; + nEvent = SalEvent::MouseButtonUp; + break; + + case WM_MBUTTONUP: + aMouseEvt.mnButton = MOUSE_MIDDLE; + nEvent = SalEvent::MouseButtonUp; + break; + + case WM_RBUTTONUP: + aMouseEvt.mnButton = MOUSE_RIGHT; + nEvent = SalEvent::MouseButtonUp; + break; + } + + // check if this window was destroyed - this might happen if we are the help window + // and sent a mouse leave message to the application which killed the help window, ie ourselves + if( !IsWindow( hWnd ) ) + return false; + + if ( bCall ) + { + if ( nEvent == SalEvent::MouseButtonDown ) + UpdateWindow( hWnd ); + + if( AllSettings::GetLayoutRTL() ) + aMouseEvt.mnX = pFrame->maGeometry.width() - 1 - aMouseEvt.mnX; + + nRet = pFrame->CallCallback( nEvent, &aMouseEvt ); + if ( nMsg == WM_MOUSEMOVE ) + SetCursor( pFrame->mhCursor ); + } + else + nRet = false; + + return nRet; +} + +static bool ImplHandleMouseActivateMsg( HWND hWnd ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + if ( pFrame->mbFloatWin ) + return true; + + return pFrame->CallCallback( SalEvent::MouseActivate, nullptr ); +} + +static bool ImplHandleWheelMsg( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam ) +{ + DBG_ASSERT( nMsg == WM_MOUSEWHEEL || + nMsg == WM_MOUSEHWHEEL, + "ImplHandleWheelMsg() called with no wheel mouse event" ); + + ImplSalYieldMutexAcquireWithWait(); + + bool nRet = false; + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame ) + { + WORD nWinModCode = LOWORD( wParam ); + POINT aWinPt; + aWinPt.x = static_cast<short>(LOWORD( lParam )); + aWinPt.y = static_cast<short>(HIWORD( lParam )); + ScreenToClient( hWnd, &aWinPt ); + + SalWheelMouseEvent aWheelEvt; + aWheelEvt.mnTime = GetMessageTime(); + aWheelEvt.mnX = aWinPt.x; + aWheelEvt.mnY = aWinPt.y; + aWheelEvt.mnCode = 0; + aWheelEvt.mnDelta = static_cast<short>(HIWORD( wParam )); + aWheelEvt.mnNotchDelta = aWheelEvt.mnDelta/WHEEL_DELTA; + if( aWheelEvt.mnNotchDelta == 0 ) + { + if( aWheelEvt.mnDelta > 0 ) + aWheelEvt.mnNotchDelta = 1; + else if( aWheelEvt.mnDelta < 0 ) + aWheelEvt.mnNotchDelta = -1; + } + + if( nMsg == WM_MOUSEWHEEL ) + { + if ( aSalShlData.mnWheelScrollLines == WHEEL_PAGESCROLL ) + aWheelEvt.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL; + else + aWheelEvt.mnScrollLines = aSalShlData.mnWheelScrollLines; + aWheelEvt.mbHorz = false; + } + else + { + aWheelEvt.mnScrollLines = aSalShlData.mnWheelScrollChars; + aWheelEvt.mbHorz = true; + + // fdo#36380 - seems horiz scrolling has swapped direction + aWheelEvt.mnDelta *= -1; + aWheelEvt.mnNotchDelta *= -1; + } + + if ( nWinModCode & MK_SHIFT ) + aWheelEvt.mnCode |= KEY_SHIFT; + if ( nWinModCode & MK_CONTROL ) + aWheelEvt.mnCode |= KEY_MOD1; + if ( GetKeyState( VK_MENU ) & 0x8000 ) + aWheelEvt.mnCode |= KEY_MOD2; + + if( AllSettings::GetLayoutRTL() ) + aWheelEvt.mnX = pFrame->maGeometry.width() - 1 - aWheelEvt.mnX; + + nRet = pFrame->CallCallback( SalEvent::WheelMouse, &aWheelEvt ); + } + + ImplSalYieldMutexRelease(); + + return nRet; +} + +static sal_uInt16 ImplSalGetKeyCode( WPARAM wParam ) +{ + sal_uInt16 nKeyCode; + + // convert KeyCode + if ( wParam < KEY_TAB_SIZE ) + nKeyCode = aImplTranslateKeyTab[wParam]; + else + { + SalData* pSalData = GetSalData(); + std::map< UINT, sal_uInt16 >::const_iterator it = pSalData->maVKMap.find( static_cast<UINT>(wParam) ); + if( it != pSalData->maVKMap.end() ) + nKeyCode = it->second; + else + nKeyCode = 0; + } + + return nKeyCode; +} + +static void ImplUpdateInputLang( WinSalFrame* pFrame ) +{ + UINT nLang = LOWORD( GetKeyboardLayout( 0 ) ); + if ( nLang && nLang != pFrame->mnInputLang ) + { + // keep input lang up-to-date + pFrame->mnInputLang = nLang; + } + + // We are on Windows NT so we use Unicode FrameProcs and get + // Unicode charcodes directly from Windows no need to set up a + // code page + return; +} + +static sal_Unicode ImplGetCharCode( WinSalFrame* pFrame, WPARAM nCharCode ) +{ + ImplUpdateInputLang( pFrame ); + + // We are on Windows NT so we use Unicode FrameProcs and we + // get Unicode charcodes directly from Windows + return static_cast<sal_Unicode>(nCharCode); +} + +LanguageType WinSalFrame::GetInputLanguage() +{ + if( !mnInputLang ) + ImplUpdateInputLang( this ); + + if( !mnInputLang ) + return LANGUAGE_DONTKNOW; + else + return LanguageType(mnInputLang); +} + +bool WinSalFrame::MapUnicodeToKeyCode( sal_Unicode aUnicode, LanguageType aLangType, vcl::KeyCode& rKeyCode ) +{ + bool bRet = false; + sal_IntPtr nLangType = static_cast<sal_uInt16>(aLangType); + // just use the passed language identifier, do not try to load additional keyboard support + HKL hkl = reinterpret_cast<HKL>(nLangType); + + if( hkl ) + { + SHORT scan = VkKeyScanExW( aUnicode, hkl ); + if( LOWORD(scan) == 0xFFFF ) + // keyboard not loaded or key cannot be mapped + bRet = false; + else + { + BYTE vkeycode = LOBYTE(scan); + BYTE shiftstate = HIBYTE(scan); + + // Last argument is set to false, because there's no decision made + // yet which key should be assigned to MOD3 modifier on Windows. + // Windows key - user's can be confused, because it should display + // Windows menu (applies to both left/right key) + // Menu key - this key is used to display context menu + // AltGr key - probably it has no sense + rKeyCode = vcl::KeyCode( ImplSalGetKeyCode( vkeycode ), + (shiftstate & 0x01) != 0, // shift + (shiftstate & 0x02) != 0, // ctrl + (shiftstate & 0x04) != 0, // alt + false ); + bRet = true; + } + } + + return bRet; +} + +static void UnsetAltIfAltGr(SalKeyEvent& rKeyEvt, sal_uInt16 nModCode) +{ + if ((nModCode & (KEY_MOD1 | KEY_MOD2)) == (KEY_MOD1 | KEY_MOD2) && + rKeyEvt.mnCharCode) + { + // this is actually AltGr and should not be handled as Alt + rKeyEvt.mnCode &= ~(KEY_MOD1 | KEY_MOD2); + } +} + +// tdf#152404 Commit uncommitted text before dispatching key shortcuts. In +// certain cases such as pressing Control-Alt-C in a Writer document while +// there is uncommitted text will call WinSalFrame::EndExtTextInput() which +// will dispatch a SalEvent::EndExtTextInput event. Writer's handler for that +// event will delete the uncommitted text and then insert the committed text +// but LibreOffice will crash when deleting the uncommitted text because +// deletion of the text also removes and deletes the newly inserted comment. +static void FlushIMBeforeShortCut(WinSalFrame* pFrame, SalEvent nEvent, sal_uInt16 nModCode) +{ + if (pFrame->mbCandidateMode && nEvent == SalEvent::KeyInput + && (nModCode & (KEY_MOD1 | KEY_MOD2))) + { + pFrame->EndExtTextInput(EndExtTextInputFlags::Complete); + } +} + +static bool ImplHandleKeyMsg( HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, LRESULT& rResult ) +{ + static bool bIgnoreCharMsg = false; + static WPARAM nDeadChar = 0; + static WPARAM nLastVKChar = 0; + static sal_uInt16 nLastChar = 0; + static ModKeyFlags nLastModKeyCode = ModKeyFlags::NONE; + static bool bWaitForModKeyRelease = false; + sal_uInt16 nRepeat = LOWORD( lParam )-1; + sal_uInt16 nModCode = 0; + + // this key might have been relayed by SysChild and thus + // may not be processed twice + GetSalData()->mnSalObjWantKeyEvt = 0; + + if ( nMsg == WM_DEADCHAR ) + { + nDeadChar = wParam; + return false; + } + + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + // reset the background mode for each text input, + // as some tools such as RichWin may have changed it + if ( pFrame->mpLocalGraphics && + pFrame->mpLocalGraphics->getHDC() ) + SetBkMode( pFrame->mpLocalGraphics->getHDC(), TRANSPARENT ); + + // determine modifiers + if ( GetKeyState( VK_SHIFT ) & 0x8000 ) + nModCode |= KEY_SHIFT; + if ( GetKeyState( VK_CONTROL ) & 0x8000 ) + nModCode |= KEY_MOD1; + if (GetKeyState(VK_MENU) & 0x8000) + nModCode |= KEY_MOD2; + + if ( (nMsg == WM_CHAR) || (nMsg == WM_SYSCHAR) ) + { + nDeadChar = 0; + + if ( bIgnoreCharMsg ) + { + bIgnoreCharMsg = false; + // #101635# if zero is returned here for WM_SYSCHAR (ALT+<key>) Windows will beep + // because this 'hotkey' was not processed -> better return 1 + // except for Alt-SPACE which should always open the sysmenu (#104616#) + + // also return zero if a system menubar is available that might process this hotkey + // this also applies to the OLE inplace embedding where we are a child window + if( (GetWindowStyle( hWnd ) & WS_CHILD) || GetMenu( hWnd ) || (wParam == 0x20) ) + return false; + else + return true; + } + + // ignore backspace as a single key, so that + // we do not get problems for combinations w/ a DeadKey + if ( wParam == 0x08 ) // BACKSPACE + return false; + + // only "free flying" WM_CHAR messages arrive here, that are + // created by typing an ALT-NUMPAD combination + SalKeyEvent aKeyEvt; + + if ( (wParam >= '0') && (wParam <= '9') ) + aKeyEvt.mnCode = sal::static_int_cast<sal_uInt16>(KEYGROUP_NUM + wParam - '0'); + else if ( (wParam >= 'A') && (wParam <= 'Z') ) + aKeyEvt.mnCode = sal::static_int_cast<sal_uInt16>(KEYGROUP_ALPHA + wParam - 'A'); + else if ( (wParam >= 'a') && (wParam <= 'z') ) + aKeyEvt.mnCode = sal::static_int_cast<sal_uInt16>(KEYGROUP_ALPHA + wParam - 'a'); + else if ( wParam == 0x0D ) // RETURN + aKeyEvt.mnCode = KEY_RETURN; + else if ( wParam == 0x1B ) // ESCAPE + aKeyEvt.mnCode = KEY_ESCAPE; + else if ( wParam == 0x09 ) // TAB + aKeyEvt.mnCode = KEY_TAB; + else if ( wParam == 0x20 ) // SPACE + aKeyEvt.mnCode = KEY_SPACE; + else + aKeyEvt.mnCode = 0; + + aKeyEvt.mnCode |= nModCode; + aKeyEvt.mnCharCode = ImplGetCharCode( pFrame, wParam ); + aKeyEvt.mnRepeat = nRepeat; + + UnsetAltIfAltGr(aKeyEvt, nModCode); + FlushIMBeforeShortCut(pFrame, SalEvent::KeyInput, nModCode); + + nLastChar = 0; + nLastVKChar = 0; + + bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt ); + pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt ); + return nRet; + } + // #i11583#, MCD, 2003-01-13, Support for WM_UNICHAR & Keyman 6.0; addition begins + else if( nMsg == WM_UNICHAR ) + { + // If Windows is asking if we accept WM_UNICHAR, return TRUE + if(wParam == UNICODE_NOCHAR) + { + rResult = TRUE; // ssa: this will actually return TRUE to windows + return true; // ...but this will only avoid calling the defwindowproc + } + + if (!rtl::isUnicodeCodePoint(wParam)) + return false; + + SalKeyEvent aKeyEvt; + aKeyEvt.mnCode = nModCode; // Or should it be 0? - as this is always a character returned + aKeyEvt.mnRepeat = 0; + + if( wParam >= Uni_SupplementaryPlanesStart ) + { + // character is supplementary char in UTF-32 format - must be converted to UTF-16 supplementary pair + aKeyEvt.mnCharCode = rtl::getHighSurrogate(wParam); + nLastChar = 0; + nLastVKChar = 0; + pFrame->CallCallback(SalEvent::KeyInput, &aKeyEvt); + pFrame->CallCallback(SalEvent::KeyUp, &aKeyEvt); + wParam = rtl::getLowSurrogate(wParam); + } + + aKeyEvt.mnCharCode = static_cast<sal_Unicode>(wParam); + + nLastChar = 0; + nLastVKChar = 0; + bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt ); + pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt ); + + return nRet; + } + // MCD, 2003-01-13, Support for WM_UNICHAR & Keyman 6.0; addition ends + else + { + // for shift, control and menu we issue a KeyModChange event + if ( (wParam == VK_SHIFT) || (wParam == VK_CONTROL) || (wParam == VK_MENU) ) + { + SalKeyModEvent aModEvt; + aModEvt.mbDown = false; // auto-accelerator feature not supported here. + aModEvt.mnCode = nModCode; + aModEvt.mnModKeyCode = ModKeyFlags::NONE; // no command events will be sent if this member is 0 + + ModKeyFlags tmpCode = ModKeyFlags::NONE; + if( GetKeyState( VK_LSHIFT ) & 0x8000 ) + tmpCode |= ModKeyFlags::LeftShift; + if( GetKeyState( VK_RSHIFT ) & 0x8000 ) + tmpCode |= ModKeyFlags::RightShift; + if( GetKeyState( VK_LCONTROL ) & 0x8000 ) + tmpCode |= ModKeyFlags::LeftMod1; + if( GetKeyState( VK_RCONTROL ) & 0x8000 ) + tmpCode |= ModKeyFlags::RightMod1; + if( GetKeyState( VK_LMENU ) & 0x8000 ) + tmpCode |= ModKeyFlags::LeftMod2; + if( GetKeyState( VK_RMENU ) & 0x8000 ) + tmpCode |= ModKeyFlags::RightMod2; + + if( tmpCode < nLastModKeyCode ) + { + aModEvt.mnModKeyCode = nLastModKeyCode; + nLastModKeyCode = ModKeyFlags::NONE; + bWaitForModKeyRelease = true; + } + else + { + if( !bWaitForModKeyRelease ) + nLastModKeyCode = tmpCode; + } + + if( tmpCode == ModKeyFlags::NONE ) + bWaitForModKeyRelease = false; + + return pFrame->CallCallback( SalEvent::KeyModChange, &aModEvt ); + } + else + { + SalKeyEvent aKeyEvt; + SalEvent nEvent; + MSG aCharMsg; + bool bCharPeek = false; + UINT nCharMsg = WM_CHAR; + bool bKeyUp = (nMsg == WM_KEYUP) || (nMsg == WM_SYSKEYUP); + + nLastModKeyCode = ModKeyFlags::NONE; // make sure no modkey messages are sent if they belong to a hotkey (see above) + aKeyEvt.mnCharCode = 0; + aKeyEvt.mnCode = ImplSalGetKeyCode( wParam ); + if ( !bKeyUp ) + { + // check for charcode + // Get the related WM_CHAR message using PeekMessage, if available. + // The WM_CHAR message is always at the beginning of the + // message queue. Also it is made certain that there is always only + // one WM_CHAR message in the queue. + bCharPeek = PeekMessageW( &aCharMsg, hWnd, + WM_CHAR, WM_CHAR, PM_NOREMOVE | PM_NOYIELD ); + if ( bCharPeek && (nDeadChar == aCharMsg.wParam) ) + { + bCharPeek = false; + nDeadChar = 0; + + if ( wParam == VK_BACK ) + { + PeekMessageW( &aCharMsg, hWnd, + nCharMsg, nCharMsg, PM_REMOVE | PM_NOYIELD ); + return false; + } + } + else + { + if ( !bCharPeek ) + { + bCharPeek = PeekMessageW( &aCharMsg, hWnd, + WM_SYSCHAR, WM_SYSCHAR, PM_NOREMOVE | PM_NOYIELD ); + nCharMsg = WM_SYSCHAR; + } + } + if ( bCharPeek ) + aKeyEvt.mnCharCode = ImplGetCharCode( pFrame, aCharMsg.wParam ); + else + aKeyEvt.mnCharCode = 0; + + nLastChar = aKeyEvt.mnCharCode; + nLastVKChar = wParam; + } + else + { + if ( wParam == nLastVKChar ) + { + aKeyEvt.mnCharCode = nLastChar; + nLastChar = 0; + nLastVKChar = 0; + } + } + + if ( aKeyEvt.mnCode || aKeyEvt.mnCharCode ) + { + if ( bKeyUp ) + nEvent = SalEvent::KeyUp; + else + nEvent = SalEvent::KeyInput; + + aKeyEvt.mnCode |= nModCode; + aKeyEvt.mnRepeat = nRepeat; + + UnsetAltIfAltGr(aKeyEvt, nModCode); + FlushIMBeforeShortCut(pFrame, nEvent, nModCode); + + bIgnoreCharMsg = bCharPeek; + bool nRet = pFrame->CallCallback( nEvent, &aKeyEvt ); + // independent part only reacts on keyup but Windows does not send + // keyup for VK_HANJA + if( aKeyEvt.mnCode == KEY_HANGUL_HANJA ) + nRet = pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt ); + + bIgnoreCharMsg = false; + + // char-message, then remove or ignore + if ( bCharPeek ) + { + nDeadChar = 0; + if ( nRet ) + { + PeekMessageW( &aCharMsg, hWnd, + nCharMsg, nCharMsg, PM_REMOVE | PM_NOYIELD ); + } + else + bIgnoreCharMsg = true; + } + + return nRet; + } + else + return false; + } + } +} + +bool ImplHandleSalObjKeyMsg( HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam ) +{ + if ( (nMsg == WM_KEYDOWN) || (nMsg == WM_KEYUP) ) + { + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + sal_uInt16 nRepeat = LOWORD( lParam )-1; + sal_uInt16 nModCode = 0; + + // determine modifiers + if ( GetKeyState( VK_SHIFT ) & 0x8000 ) + nModCode |= KEY_SHIFT; + if ( GetKeyState( VK_CONTROL ) & 0x8000 ) + nModCode |= KEY_MOD1; + if ( GetKeyState( VK_MENU ) & 0x8000 ) + nModCode |= KEY_MOD2; + + if ( (wParam != VK_SHIFT) && (wParam != VK_CONTROL) && (wParam != VK_MENU) ) + { + SalKeyEvent aKeyEvt; + SalEvent nEvent; + + // convert KeyCode + aKeyEvt.mnCode = ImplSalGetKeyCode( wParam ); + aKeyEvt.mnCharCode = 0; + + if ( aKeyEvt.mnCode ) + { + if (nMsg == WM_KEYUP) + nEvent = SalEvent::KeyUp; + else + nEvent = SalEvent::KeyInput; + + aKeyEvt.mnCode |= nModCode; + aKeyEvt.mnRepeat = nRepeat; + bool nRet = pFrame->CallCallback( nEvent, &aKeyEvt ); + return nRet; + } + else + return false; + } + } + + return false; +} + +bool ImplHandleSalObjSysCharMsg( HWND hWnd, WPARAM wParam, LPARAM lParam ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + sal_uInt16 nRepeat = LOWORD( lParam )-1; + sal_uInt16 nModCode = 0; + sal_uInt16 cKeyCode = static_cast<sal_uInt16>(wParam); + + // determine modifiers + if ( GetKeyState( VK_SHIFT ) & 0x8000 ) + nModCode |= KEY_SHIFT; + if ( GetKeyState( VK_CONTROL ) & 0x8000 ) + nModCode |= KEY_MOD1; + nModCode |= KEY_MOD2; + + // assemble KeyEvent + SalKeyEvent aKeyEvt; + if ( (cKeyCode >= 48) && (cKeyCode <= 57) ) + aKeyEvt.mnCode = KEY_0+(cKeyCode-48); + else if ( (cKeyCode >= 65) && (cKeyCode <= 90) ) + aKeyEvt.mnCode = KEY_A+(cKeyCode-65); + else if ( (cKeyCode >= 97) && (cKeyCode <= 122) ) + aKeyEvt.mnCode = KEY_A+(cKeyCode-97); + else + aKeyEvt.mnCode = 0; + aKeyEvt.mnCode |= nModCode; + aKeyEvt.mnCharCode = ImplGetCharCode( pFrame, cKeyCode ); + aKeyEvt.mnRepeat = nRepeat; + bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt ); + pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt ); + return nRet; +} + +namespace { + +enum class DeferPolicy +{ + Blocked, + Allowed +}; + +} + +// Remember to release the solar mutex on success! +static WinSalFrame* ProcessOrDeferMessage( HWND hWnd, INT nMsg, WPARAM pWParam = 0, + DeferPolicy eCanDefer = DeferPolicy::Allowed ) +{ + bool bFailedCondition = false, bGotMutex = false; + WinSalFrame* pFrame = nullptr; + + if ( DeferPolicy::Blocked == eCanDefer ) + assert( (DeferPolicy::Blocked == eCanDefer) && (nMsg == 0) && (pWParam == 0) ); + else + assert( (DeferPolicy::Allowed == eCanDefer) && (nMsg != 0) ); + + if ( DeferPolicy::Blocked == eCanDefer ) + { + ImplSalYieldMutexAcquireWithWait(); + bGotMutex = true; + } + else if ( !(bGotMutex = ImplSalYieldMutexTryToAcquire()) ) + bFailedCondition = true; + + if ( !bFailedCondition ) + { + pFrame = GetWindowPtr( hWnd ); + bFailedCondition = pFrame == nullptr; + } + + if ( bFailedCondition ) + { + if ( bGotMutex ) + ImplSalYieldMutexRelease(); + if ( DeferPolicy::Allowed == eCanDefer ) + { + bool const ret = PostMessageW(hWnd, nMsg, pWParam, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + } + + return pFrame; +} + +namespace { + +enum class PostedState +{ + IsPosted, + IsInitial +}; + +} + +static bool ImplHandlePostPaintMsg( HWND hWnd, RECT* pRect, + PostedState eProcessed = PostedState::IsPosted ) +{ + RECT* pMsgRect; + if ( PostedState::IsInitial == eProcessed ) + { + pMsgRect = new RECT; + CopyRect( pMsgRect, pRect ); + } + else + pMsgRect = pRect; + + WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_POSTPAINT, + reinterpret_cast<WPARAM>(pMsgRect) ); + if ( pFrame ) + { + SalPaintEvent aPEvt( pRect->left, pRect->top, pRect->right-pRect->left, pRect->bottom-pRect->top ); + pFrame->CallCallback( SalEvent::Paint, &aPEvt ); + ImplSalYieldMutexRelease(); + if ( PostedState::IsPosted == eProcessed ) + delete pRect; + } + + return (pFrame != nullptr); +} + +static bool ImplHandlePaintMsg( HWND hWnd ) +{ + bool bPaintSuccessful = false; + + // even without the Yield mutex, we can still change the clip region, + // because other threads don't use the Yield mutex + // --> see AcquireGraphics() + + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame ) + { + // clip region must be set, as we don't get a proper + // bounding rectangle otherwise + WinSalGraphics *pGraphics = pFrame->mpLocalGraphics; + bool bHasClipRegion = pGraphics && + pGraphics->getHDC() && pGraphics->getRegion(); + if ( bHasClipRegion ) + SelectClipRgn( pGraphics->getHDC(), nullptr ); + + // according to Windows documentation one shall check first if + // there really is a paint-region + RECT aUpdateRect; + PAINTSTRUCT aPs; + bool bHasPaintRegion = GetUpdateRect( hWnd, nullptr, FALSE ); + if ( bHasPaintRegion ) + { + // call BeginPaint/EndPaint to query the paint rect and use + // this information in the (deferred) paint + BeginPaint( hWnd, &aPs ); + CopyRect( &aUpdateRect, &aPs.rcPaint ); + } + + // reset clip region + if ( bHasClipRegion ) + SelectClipRgn( pGraphics->getHDC(), pGraphics->getRegion() ); + + // try painting + if ( bHasPaintRegion ) + { + bPaintSuccessful = ImplHandlePostPaintMsg( + hWnd, &aUpdateRect, PostedState::IsInitial ); + EndPaint( hWnd, &aPs ); + } + else // if there is nothing to paint, the paint is successful + bPaintSuccessful = true; + } + + return bPaintSuccessful; +} + +static void SetMaximizedFrameGeometry( HWND hWnd, WinSalFrame* pFrame, RECT* pParentRect ) +{ + // calculate and set frame geometry of a maximized window - useful if the window is still hidden + + // dualmonitor support: + // Get screensize of the monitor with the mouse pointer + + RECT aRectMouse; + if( ! pParentRect ) + { + POINT pt; + GetCursorPos( &pt ); + aRectMouse.left = pt.x; + aRectMouse.top = pt.y; + aRectMouse.right = pt.x+2; + aRectMouse.bottom = pt.y+2; + pParentRect = &aRectMouse; + } + + RECT aRect; + ImplSalGetWorkArea( hWnd, &aRect, pParentRect ); + + // a maximized window has no other borders than the caption + pFrame->maGeometry.setDecorations(0, pFrame->mbCaption ? GetSystemMetrics(SM_CYCAPTION) : 0, 0, 0); + + aRect.top += pFrame->maGeometry.topDecoration(); + pFrame->maGeometry.setPos({ aRect.left, aRect.top }); + SetGeometrySize(pFrame->maGeometry, { aRect.right - aRect.left, aRect.bottom - aRect.top }); +} + +static void UpdateFrameGeometry(WinSalFrame* pFrame) +{ + if( !pFrame ) + return; + const HWND hWnd = pFrame->mhWnd; + + RECT aRect; + GetWindowRect( hWnd, &aRect ); + pFrame->maGeometry.setPosSize({ 0, 0 }, { 0, 0 }); + pFrame->maGeometry.setDecorations(0, 0, 0, 0); + pFrame->maGeometry.setScreen(0); + + if ( IsIconic( hWnd ) ) + return; + + POINT aPt; + aPt.x=0; + aPt.y=0; + ClientToScreen(hWnd, &aPt); + int cx = aPt.x - aRect.left; + + pFrame->maGeometry.setDecorations(cx, aPt.y - aRect.top, cx, 0); + pFrame->maGeometry.setPos({ aPt.x, aPt.y }); + + RECT aInnerRect; + GetClientRect( hWnd, &aInnerRect ); + if( aInnerRect.right ) + { + // improve right decoration + aPt.x=aInnerRect.right; + aPt.y=aInnerRect.top; + ClientToScreen(hWnd, &aPt); + pFrame->maGeometry.setRightDecoration(aRect.right - aPt.x); + } + if( aInnerRect.bottom ) // may be zero if window was not shown yet + pFrame->maGeometry.setBottomDecoration(aRect.bottom - aPt.y - aInnerRect.bottom); + else + // bottom border is typically the same as left/right + pFrame->maGeometry.setBottomDecoration(pFrame->maGeometry.leftDecoration()); + + int nWidth = aRect.right - aRect.left + - pFrame->maGeometry.rightDecoration() - pFrame->maGeometry.leftDecoration(); + int nHeight = aRect.bottom - aRect.top + - pFrame->maGeometry.bottomDecoration() - pFrame->maGeometry.topDecoration(); + SetGeometrySize(pFrame->maGeometry, { nWidth, nHeight }); + pFrame->updateScreenNumber(); +} + +static void ImplCallClosePopupsHdl( HWND hWnd ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame ) + { + pFrame->CallCallback( SalEvent::ClosePopups, nullptr ); + } +} + +static void ImplCallMoveHdl(HWND hWnd) +{ + WinSalFrame* pFrame = ProcessOrDeferMessage(hWnd, SAL_MSG_POSTMOVE); + if (!pFrame) + return; + + pFrame->CallCallback(SalEvent::Move, nullptr); + + ImplSalYieldMutexRelease(); +} + +static void ImplHandleMoveMsg(HWND hWnd, LPARAM lParam) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if (!pFrame) + return; + + UpdateFrameGeometry(pFrame); + +#ifdef NDEBUG + (void) lParam; +#endif + assert(IsIconic(hWnd) || (pFrame->maGeometry.x() == static_cast<sal_Int16>(LOWORD(lParam)))); + assert(IsIconic(hWnd) || (pFrame->maGeometry.y() == static_cast<sal_Int16>(HIWORD(lParam)))); + + if (GetWindowStyle(hWnd) & WS_VISIBLE) + pFrame->mbDefPos = false; + + // protect against recursion + if (!pFrame->mbInMoveMsg) + { + // adjust window again for FullScreenMode + pFrame->mbInMoveMsg = true; + if (pFrame->isFullScreen()) + ImplSalFrameFullScreenPos(pFrame); + pFrame->mbInMoveMsg = false; + } + + pFrame->UpdateFrameState(); + + ImplCallMoveHdl(hWnd); +} + +static void ImplCallSizeHdl( HWND hWnd ) +{ + WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_POSTCALLSIZE ); + if (!pFrame) + return; + + pFrame->CallCallback(SalEvent::Resize, nullptr); + // to avoid double Paints by VCL and SAL + if (IsWindowVisible(hWnd) && !pFrame->mbInShow) + UpdateWindow(hWnd); + + ImplSalYieldMutexRelease(); +} + +static void ImplHandleSizeMsg(HWND hWnd, WPARAM wParam, LPARAM lParam) +{ + if ((wParam == SIZE_MAXSHOW) || (wParam == SIZE_MAXHIDE)) + return; + + WinSalFrame* pFrame = GetWindowPtr(hWnd); + if (!pFrame) + return; + + UpdateFrameGeometry(pFrame); + +#ifdef NDEBUG + (void) lParam; +#endif + assert(pFrame->maGeometry.width() == static_cast<sal_Int16>(LOWORD(lParam))); + assert(pFrame->maGeometry.height() == static_cast<sal_Int16>(HIWORD(lParam))); + + pFrame->UpdateFrameState(); + + ImplCallSizeHdl(hWnd); + + WinSalTimer* pTimer = static_cast<WinSalTimer*>(ImplGetSVData()->maSchedCtx.mpSalTimer); + if (pTimer) + pTimer->SetForceRealTimer(true); +} + +static void ImplHandleFocusMsg( HWND hWnd ) +{ + WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, SAL_MSG_POSTFOCUS ); + if (!pFrame) + return; + const ::comphelper::ScopeGuard aScopeGuard([](){ ImplSalYieldMutexRelease(); }); + + if (WinSalFrame::mbInReparent) + return; + + const bool bGotFocus = ::GetFocus() == hWnd; + if (bGotFocus) + { + if (IsWindowVisible(hWnd) && !pFrame->mbInShow) + UpdateWindow(hWnd); + + // do we support IME? + if (pFrame->mbIME && pFrame->mhDefIMEContext) + { + UINT nImeProps = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY); + pFrame->mbSpezIME = (nImeProps & IME_PROP_SPECIAL_UI) != 0; + pFrame->mbAtCursorIME = (nImeProps & IME_PROP_AT_CARET) != 0; + pFrame->mbHandleIME = !pFrame->mbSpezIME; + } + } + + pFrame->CallCallback(bGotFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr); +} + +static void ImplHandleCloseMsg( HWND hWnd ) +{ + WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, WM_CLOSE ); + if ( pFrame ) + { + pFrame->CallCallback( SalEvent::Close, nullptr ); + ImplSalYieldMutexRelease(); + } +} + +static bool ImplHandleShutDownMsg( HWND hWnd ) +{ + bool nRet = false; + WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, 0, 0, DeferPolicy::Blocked ); + if ( pFrame ) + { + nRet = pFrame->CallCallback( SalEvent::Shutdown, nullptr ); + ImplSalYieldMutexRelease(); + } + return nRet; +} + +static void ImplHandleSettingsChangeMsg( HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam ) +{ + SalEvent nSalEvent = SalEvent::SettingsChanged; + + if ( nMsg == WM_DEVMODECHANGE ) + nSalEvent = SalEvent::PrinterChanged; + else if ( nMsg == WM_DISPLAYCHANGE ) + nSalEvent = SalEvent::DisplayChanged; + else if ( nMsg == WM_FONTCHANGE ) + nSalEvent = SalEvent::FontChanged; + else if ( nMsg == WM_WININICHANGE ) + { + if ( lParam ) + { + if ( ImplSalWICompareAscii( reinterpret_cast<const wchar_t*>(lParam), "devices" ) == 0 ) + nSalEvent = SalEvent::PrinterChanged; + } + } + + if ( nMsg == WM_SETTINGCHANGE ) + { + if ( wParam == SPI_SETWHEELSCROLLLINES ) + aSalShlData.mnWheelScrollLines = ImplSalGetWheelScrollLines(); + else if( wParam == SPI_SETWHEELSCROLLCHARS ) + aSalShlData.mnWheelScrollChars = ImplSalGetWheelScrollChars(); + UpdateDarkMode(hWnd); + GetSalData()->mbThemeChanged = true; + } + + if ( WM_SYSCOLORCHANGE == nMsg && GetSalData()->mhDitherPal ) + ImplUpdateSysColorEntries(); + + WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, 0, 0, DeferPolicy::Blocked ); + if (!pFrame) + return; + + if (((nMsg == WM_DISPLAYCHANGE) || (nMsg == WM_WININICHANGE)) && pFrame->isFullScreen()) + ImplSalFrameFullScreenPos(pFrame); + + pFrame->CallCallback(nSalEvent, nullptr); + + ImplSalYieldMutexRelease(); +} + +static void ImplHandleUserEvent( HWND hWnd, LPARAM lParam ) +{ + WinSalFrame* pFrame = ProcessOrDeferMessage( hWnd, 0, 0, DeferPolicy::Blocked ); + if ( pFrame ) + { + pFrame->CallCallback( SalEvent::UserEvent, reinterpret_cast<void*>(lParam) ); + ImplSalYieldMutexRelease(); + } +} + +static void ImplHandleForcePalette( HWND hWnd ) +{ + SalData* pSalData = GetSalData(); + HPALETTE hPal = pSalData->mhDitherPal; + if (!hPal) + return; + + WinSalFrame* pFrame = ProcessOrDeferMessage(hWnd, SAL_MSG_FORCEPALETTE); + if (!pFrame) + return; + const ::comphelper::ScopeGuard aScopeGuard([](){ ImplSalYieldMutexRelease(); }); + + WinSalGraphics* pGraphics = pFrame->mpLocalGraphics; + if (!pGraphics || !pGraphics->getHDC() || !pGraphics->getDefPal() + || (pGraphics->setPalette(hPal, FALSE) == GDI_ERROR)) + return; + + InvalidateRect(hWnd, nullptr, FALSE); + UpdateWindow(hWnd); + pFrame->CallCallback(SalEvent::DisplayChanged, nullptr); +} + +static LRESULT ImplHandlePalette( bool bFrame, HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, bool& rDef ) +{ + SalData* pSalData = GetSalData(); + HPALETTE hPal = pSalData->mhDitherPal; + if ( !hPal ) + return 0; + + rDef = false; + if ( pSalData->mbInPalChange ) + return 0; + + if ( (nMsg == WM_PALETTECHANGED) || (nMsg == SAL_MSG_POSTPALCHANGED) ) + { + if ( reinterpret_cast<HWND>(wParam) == hWnd ) + return 0; + } + + bool bReleaseMutex = false; + if ( (nMsg == WM_QUERYNEWPALETTE) || (nMsg == WM_PALETTECHANGED) ) + { + // as Windows can send these messages also, we have to use + // the Solar semaphore + if ( ImplSalYieldMutexTryToAcquire() ) + bReleaseMutex = true; + else if ( nMsg == WM_QUERYNEWPALETTE ) + { + bool const ret = PostMessageW(hWnd, SAL_MSG_POSTQUERYNEWPAL, wParam, lParam); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + else /* ( nMsg == WM_PALETTECHANGED ) */ + { + bool const ret = PostMessageW(hWnd, SAL_MSG_POSTPALCHANGED, wParam, lParam); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + } + + WinSalVirtualDevice*pTempVD; + WinSalFrame* pTempFrame; + WinSalGraphics* pGraphics; + HDC hDC; + HPALETTE hOldPal = nullptr; + UINT nCols = GDI_ERROR; + bool bUpdate; + + pSalData->mbInPalChange = true; + + // reset all palettes in VirDevs and Frames + pTempVD = pSalData->mpFirstVD; + while ( pTempVD ) + { + pGraphics = pTempVD->getGraphics(); + pGraphics->setPalette(nullptr); + pTempVD = pTempVD->getNext(); + } + pTempFrame = pSalData->mpFirstFrame; + while ( pTempFrame ) + { + pGraphics = pTempFrame->mpLocalGraphics; + pGraphics->setPalette(nullptr); + pTempFrame = pTempFrame->mpNextFrame; + } + + // re-initialize palette + WinSalFrame* pFrame = nullptr; + if ( bFrame ) + pFrame = GetWindowPtr( hWnd ); + + UnrealizeObject(hPal); + const bool bStdDC = pFrame && pFrame->mpLocalGraphics && pFrame->mpLocalGraphics->getHDC(); + if (!bStdDC) + { + hDC = GetDC(hWnd); + hOldPal = SelectPalette(hDC, hPal, TRUE); + if (hOldPal) + nCols = RealizePalette(hDC); + } + else + { + hDC = pFrame->mpLocalGraphics->getHDC(); + nCols = pFrame->mpLocalGraphics->setPalette(hPal); + } + + bUpdate = nCols != 0 && nCols != GDI_ERROR; + + if ( !bStdDC ) + { + if (hOldPal) + SelectPalette(hDC, hOldPal, TRUE); + ReleaseDC( hWnd, hDC ); + } + + // reset all palettes in VirDevs and Frames + pTempVD = pSalData->mpFirstVD; + while ( pTempVD ) + { + pGraphics = pTempVD->getGraphics(); + if ( pGraphics->getDefPal() ) + pGraphics->setPalette(hPal); + pTempVD = pTempVD->getNext(); + } + + pTempFrame = pSalData->mpFirstFrame; + while ( pTempFrame ) + { + if ( pTempFrame != pFrame ) + { + pGraphics = pTempFrame->mpLocalGraphics; + if (pGraphics && pGraphics->getDefPal()) + { + UINT nRes = pGraphics->setPalette(hPal); + if (nRes != 0 && nRes != GDI_ERROR) + bUpdate = true; + } + } + pTempFrame = pTempFrame->mpNextFrame; + } + + // if colors changed, update the window + if ( bUpdate ) + { + pTempFrame = pSalData->mpFirstFrame; + while ( pTempFrame ) + { + pGraphics = pTempFrame->mpLocalGraphics; + if (pGraphics && pGraphics->getDefPal()) + { + InvalidateRect( pTempFrame->mhWnd, nullptr, FALSE ); + UpdateWindow( pTempFrame->mhWnd ); + pTempFrame->CallCallback( SalEvent::DisplayChanged, nullptr ); + } + pTempFrame = pTempFrame->mpNextFrame; + } + } + + pSalData->mbInPalChange = false; + + if ( bReleaseMutex ) + ImplSalYieldMutexRelease(); + + if ( nMsg == WM_PALETTECHANGED ) + return 0; + else + return nCols; +} + +static bool ImplHandleMinMax( HWND hWnd, LPARAM lParam ) +{ + bool bRet = false; + + if ( ImplSalYieldMutexTryToAcquire() ) + { + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame ) + { + MINMAXINFO* pMinMax = reinterpret_cast<MINMAXINFO*>(lParam); + + if (pFrame->isFullScreen()) + { + int nX; + int nY; + int nDX; + int nDY; + ImplSalCalcFullScreenSize( pFrame, nX, nY, nDX, nDY ); + + if ( pMinMax->ptMaxPosition.x > nX ) + pMinMax->ptMaxPosition.x = nX; + if ( pMinMax->ptMaxPosition.y > nY ) + pMinMax->ptMaxPosition.y = nY; + + if ( pMinMax->ptMaxSize.x < nDX ) + pMinMax->ptMaxSize.x = nDX; + if ( pMinMax->ptMaxSize.y < nDY ) + pMinMax->ptMaxSize.y = nDY; + if ( pMinMax->ptMaxTrackSize.x < nDX ) + pMinMax->ptMaxTrackSize.x = nDX; + if ( pMinMax->ptMaxTrackSize.y < nDY ) + pMinMax->ptMaxTrackSize.y = nDY; + + pMinMax->ptMinTrackSize.x = nDX; + pMinMax->ptMinTrackSize.y = nDY; + + bRet = true; + } + + if ( pFrame->mnMinWidth || pFrame->mnMinHeight ) + { + int nWidth = pFrame->mnMinWidth; + int nHeight = pFrame->mnMinHeight; + + ImplSalAddBorder( pFrame, nWidth, nHeight ); + + if ( pMinMax->ptMinTrackSize.x < nWidth ) + pMinMax->ptMinTrackSize.x = nWidth; + if ( pMinMax->ptMinTrackSize.y < nHeight ) + pMinMax->ptMinTrackSize.y = nHeight; + } + + if ( pFrame->mnMaxWidth || pFrame->mnMaxHeight ) + { + int nWidth = pFrame->mnMaxWidth; + int nHeight = pFrame->mnMaxHeight; + + ImplSalAddBorder( pFrame, nWidth, nHeight ); + + if( nWidth > 0 && nHeight > 0 ) // protect against int overflow due to INT_MAX initialisation + { + if ( pMinMax->ptMaxTrackSize.x > nWidth ) + pMinMax->ptMaxTrackSize.x = nWidth; + if ( pMinMax->ptMaxTrackSize.y > nHeight ) + pMinMax->ptMaxTrackSize.y = nHeight; + } + } + } + + ImplSalYieldMutexRelease(); + } + + return bRet; +} + +// retrieves the SalMenuItem pointer from a hMenu +// the pointer is stored in every item, so if no position +// is specified we just use the first item (ie, pos=0) +// if bByPosition is false then nPos denotes a menu id instead of a position +static WinSalMenuItem* ImplGetSalMenuItem( HMENU hMenu, UINT nPos, bool bByPosition=true ) +{ + MENUITEMINFOW mi = {}; + mi.cbSize = sizeof( mi ); + mi.fMask = MIIM_DATA; + if( !GetMenuItemInfoW( hMenu, nPos, bByPosition, &mi) ) + SAL_WARN("vcl", "GetMenuItemInfoW failed: " << WindowsErrorString(GetLastError())); + + return reinterpret_cast<WinSalMenuItem *>(mi.dwItemData); +} + +// returns the index of the currently selected item if any or -1 +static int ImplGetSelectedIndex( HMENU hMenu ) +{ + MENUITEMINFOW mi = {}; + mi.cbSize = sizeof( mi ); + mi.fMask = MIIM_STATE; + int n = GetMenuItemCount( hMenu ); + if( n != -1 ) + { + for(int i=0; i<n; i++ ) + { + if( !GetMenuItemInfoW( hMenu, i, TRUE, &mi) ) + SAL_WARN( "vcl", "GetMenuItemInfoW failed: " << WindowsErrorString( GetLastError() ) ); + else + { + if( mi.fState & MFS_HILITE ) + return i; + } + } + } + return -1; +} + +static LRESULT ImplMenuChar( HWND, WPARAM wParam, LPARAM lParam ) +{ + LRESULT nRet = MNC_IGNORE; + HMENU hMenu = reinterpret_cast<HMENU>(lParam); + OUString aMnemonic( "&" + OUStringChar(static_cast<sal_Unicode>(LOWORD(wParam))) ); + aMnemonic = aMnemonic.toAsciiLowerCase(); // we only have ascii mnemonics + + // search the mnemonic in the current menu + int nItemCount = GetMenuItemCount( hMenu ); + int nFound = 0; + int idxFound = -1; + int idxSelected = ImplGetSelectedIndex( hMenu ); + int idx = idxSelected != -1 ? idxSelected+1 : 0; // if duplicate mnemonics cycle through menu + for( int i=0; i< nItemCount; i++, idx++ ) + { + WinSalMenuItem* pSalMenuItem = ImplGetSalMenuItem( hMenu, idx % nItemCount ); + if( !pSalMenuItem ) + continue; + OUString aStr = pSalMenuItem->mText; + aStr = aStr.toAsciiLowerCase(); + if( aStr.indexOf( aMnemonic ) != -1 ) + { + if( idxFound == -1 ) + idxFound = idx % nItemCount; + if( nFound++ ) + break; // duplicate found + } + } + if( nFound == 1 ) + nRet = MAKELRESULT( idxFound, MNC_EXECUTE ); + else + // duplicate mnemonics, just select the next occurrence + nRet = MAKELRESULT( idxFound, MNC_SELECT ); + + return nRet; +} + +static LRESULT ImplMeasureItem( HWND hWnd, WPARAM wParam, LPARAM lParam ) +{ + LRESULT nRet = 0; + if( !wParam ) + { + // request was sent by a menu + nRet = 1; + MEASUREITEMSTRUCT *pMI = reinterpret_cast<LPMEASUREITEMSTRUCT>(lParam); + if( pMI->CtlType != ODT_MENU ) + return 0; + + WinSalMenuItem *pSalMenuItem = reinterpret_cast<WinSalMenuItem *>(pMI->itemData); + if( !pSalMenuItem ) + return 0; + + HDC hdc = GetDC( hWnd ); + SIZE strSize; + + NONCLIENTMETRICSW ncm = {}; + ncm.cbSize = sizeof( ncm ); + SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, 0, &ncm, 0 ); + + // Assume every menu item can be default and printed bold + //ncm.lfMenuFont.lfWeight = FW_BOLD; + + HFONT hfntOld = static_cast<HFONT>(SelectObject(hdc, CreateFontIndirectW( &ncm.lfMenuFont ))); + + // menu text and accelerator + OUString aStr(pSalMenuItem->mText); + if( pSalMenuItem->mAccelText.getLength() ) + { + aStr += " " + pSalMenuItem->mAccelText; + } + GetTextExtentPoint32W( hdc, o3tl::toW(aStr.getStr()), + aStr.getLength(), &strSize ); + + // image + Size bmpSize( 16, 16 ); + //if( pSalMenuItem->maBitmap ) + // bmpSize = pSalMenuItem->maBitmap.GetSizePixel(); + + // checkmark + Size checkSize( GetSystemMetrics( SM_CXMENUCHECK ), GetSystemMetrics( SM_CYMENUCHECK ) ); + + pMI->itemWidth = checkSize.Width() + 3 + bmpSize.Width() + 3 + strSize.cx; + pMI->itemHeight = std::max( std::max( checkSize.Height(), bmpSize.Height() ), tools::Long(strSize.cy) ); + pMI->itemHeight += 4; + + DeleteObject( SelectObject(hdc, hfntOld) ); + ReleaseDC( hWnd, hdc ); + } + + return nRet; +} + +static LRESULT ImplDrawItem(HWND, WPARAM wParam, LPARAM lParam ) +{ + LRESULT nRet = 0; + if( !wParam ) + { + // request was sent by a menu + nRet = 1; + DRAWITEMSTRUCT *pDI = reinterpret_cast<LPDRAWITEMSTRUCT>(lParam); + if( pDI->CtlType != ODT_MENU ) + return 0; + + WinSalMenuItem *pSalMenuItem = reinterpret_cast<WinSalMenuItem *>(pDI->itemData); + if( !pSalMenuItem ) + return 0; + + COLORREF clrPrevText, clrPrevBkgnd; + HFONT hfntOld; + HBRUSH hbrOld; + bool fChecked = (pDI->itemState & ODS_CHECKED); + bool fSelected = (pDI->itemState & ODS_SELECTED); + bool fDisabled = (pDI->itemState & (ODS_DISABLED | ODS_GRAYED)); + + // Set the appropriate foreground and background colors. + RECT aRect = pDI->rcItem; + + if ( fDisabled ) + clrPrevText = SetTextColor( pDI->hDC, GetSysColor( COLOR_GRAYTEXT ) ); + else + clrPrevText = SetTextColor( pDI->hDC, GetSysColor( fSelected ? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT ) ); + + DWORD colBackground = GetSysColor( fSelected ? COLOR_HIGHLIGHT : COLOR_MENU ); + clrPrevBkgnd = SetBkColor( pDI->hDC, colBackground ); + + hbrOld = static_cast<HBRUSH>(SelectObject( pDI->hDC, CreateSolidBrush( GetBkColor( pDI->hDC ) ) )); + + // Fill background + if(!PatBlt( pDI->hDC, aRect.left, aRect.top, aRect.right-aRect.left, aRect.bottom-aRect.top, PATCOPY )) + SAL_WARN("vcl", "PatBlt failed: " << WindowsErrorString(GetLastError())); + + int lineHeight = aRect.bottom-aRect.top; + + int x = aRect.left; + int y = aRect.top; + + int checkWidth = GetSystemMetrics( SM_CXMENUCHECK ); + int checkHeight = GetSystemMetrics( SM_CYMENUCHECK ); + if( fChecked ) + { + RECT r; + r.left = 0; + r.top = 0; + r.right = checkWidth; + r.bottom = checkWidth; + HDC memDC = CreateCompatibleDC( pDI->hDC ); + HBITMAP memBmp = CreateCompatibleBitmap( pDI->hDC, checkWidth, checkHeight ); + HBITMAP hOldBmp = static_cast<HBITMAP>(SelectObject( memDC, memBmp )); + DrawFrameControl( memDC, &r, DFC_MENU, DFCS_MENUCHECK ); + BitBlt( pDI->hDC, x, y+(lineHeight-checkHeight)/2, checkWidth, checkHeight, memDC, 0, 0, SRCAND ); + DeleteObject( SelectObject( memDC, hOldBmp ) ); + DeleteDC( memDC ); + } + x += checkWidth+3; + + //Size bmpSize = aBitmap.GetSizePixel(); + Size bmpSize(16, 16); + if( !pSalMenuItem->maBitmap.IsEmpty() ) + { + Bitmap aBitmap( pSalMenuItem->maBitmap ); + + // set transparent pixels to background color + if( fDisabled ) + colBackground = RGB(255,255,255); + aBitmap.Replace( COL_LIGHTMAGENTA, + Color( GetRValue(colBackground),GetGValue(colBackground),GetBValue(colBackground) )); + + WinSalBitmap* pSalBmp = static_cast<WinSalBitmap*>(aBitmap.ImplGetSalBitmap().get()); + HGLOBAL hDrawDIB = pSalBmp->ImplGethDIB(); + + if( hDrawDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB )); + PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + + WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD ); + + HBITMAP hBmp = CreateDIBitmap( pDI->hDC, &pBI->bmiHeader, CBM_INIT, pBits, pBI, DIB_RGB_COLORS ); + GlobalUnlock( hDrawDIB ); + + HBRUSH hbrIcon = CreateSolidBrush( GetSysColor( COLOR_GRAYTEXT ) ); + DrawStateW( pDI->hDC, hbrIcon, nullptr, reinterpret_cast<LPARAM>(hBmp), WPARAM(0), + x, y+(lineHeight-bmpSize.Height())/2, bmpSize.Width(), bmpSize.Height(), + DST_BITMAP | (fDisabled ? (fSelected ? DSS_MONO : DSS_DISABLED) : DSS_NORMAL) ); + + DeleteObject( hbrIcon ); + DeleteObject( hBmp ); + } + + } + x += bmpSize.Width() + 3; + aRect.left = x; + + NONCLIENTMETRICSW ncm = {}; + ncm.cbSize = sizeof( ncm ); + SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, 0, &ncm, 0 ); + + // Print default menu entry with bold font + //if ( pDI->itemState & ODS_DEFAULT ) + // ncm.lfMenuFont.lfWeight = FW_BOLD; + + hfntOld = static_cast<HFONT>(SelectObject(pDI->hDC, CreateFontIndirectW( &ncm.lfMenuFont ))); + + SIZE strSize; + OUString aStr( pSalMenuItem->mText ); + GetTextExtentPoint32W( pDI->hDC, o3tl::toW(aStr.getStr()), + aStr.getLength(), &strSize ); + + if(!DrawStateW( pDI->hDC, nullptr, nullptr, + reinterpret_cast<LPARAM>(aStr.getStr()), + WPARAM(0), aRect.left, aRect.top + (lineHeight - strSize.cy)/2, 0, 0, + DST_PREFIXTEXT | (fDisabled && !fSelected ? DSS_DISABLED : DSS_NORMAL) ) ) + SAL_WARN("vcl", "DrawStateW failed: " << WindowsErrorString(GetLastError())); + + if( pSalMenuItem->mAccelText.getLength() ) + { + SIZE strSizeA; + aStr = pSalMenuItem->mAccelText; + GetTextExtentPoint32W( pDI->hDC, o3tl::toW(aStr.getStr()), + aStr.getLength(), &strSizeA ); + TEXTMETRICW tm; + GetTextMetricsW( pDI->hDC, &tm ); + + // position the accelerator string to the right but leave space for the + // (potential) submenu arrow (tm.tmMaxCharWidth) + if(!DrawStateW( pDI->hDC, nullptr, nullptr, + reinterpret_cast<LPARAM>(aStr.getStr()), + WPARAM(0), aRect.right-strSizeA.cx-tm.tmMaxCharWidth, aRect.top + (lineHeight - strSizeA.cy)/2, 0, 0, + DST_TEXT | (fDisabled && !fSelected ? DSS_DISABLED : DSS_NORMAL) ) ) + SAL_WARN("vcl", "DrawStateW failed: " << WindowsErrorString(GetLastError())); + } + + // Restore the original font and colors. + DeleteObject( SelectObject( pDI->hDC, hbrOld ) ); + DeleteObject( SelectObject( pDI->hDC, hfntOld) ); + SetTextColor(pDI->hDC, clrPrevText); + SetBkColor(pDI->hDC, clrPrevBkgnd); + } + return nRet; +} + +static bool ImplHandleMenuActivate( HWND hWnd, WPARAM wParam, LPARAM ) +{ + // Menu activation + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + HMENU hMenu = reinterpret_cast<HMENU>(wParam); + // WORD nPos = LOWORD (lParam); + // bool bWindowMenu = (bool) HIWORD(lParam); + + // Send activate and deactivate together, so we have not keep track of opened menus + // this will be enough to have the menus updated correctly + SalMenuEvent aMenuEvt; + WinSalMenuItem *pSalMenuItem = ImplGetSalMenuItem( hMenu, 0 ); + if( pSalMenuItem ) + aMenuEvt.mpMenu = pSalMenuItem->mpMenu; + else + aMenuEvt.mpMenu = nullptr; + + bool nRet = pFrame->CallCallback( SalEvent::MenuActivate, &aMenuEvt ); + if( nRet ) + nRet = pFrame->CallCallback( SalEvent::MenuDeactivate, &aMenuEvt ); + if( nRet ) + pFrame->mLastActivatedhMenu = hMenu; + + return nRet; +} + +static bool ImplHandleMenuSelect( HWND hWnd, WPARAM wParam, LPARAM lParam ) +{ + // Menu selection + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + WORD nId = LOWORD(wParam); // menu item or submenu index + WORD nFlags = HIWORD(wParam); + HMENU hMenu = reinterpret_cast<HMENU>(lParam); + + // check if we have to process the message + if( !GetSalData()->IsKnownMenuHandle( hMenu ) ) + return false; + + bool bByPosition = false; + if( nFlags & MF_POPUP ) + bByPosition = true; + + bool nRet = false; + if ( hMenu && !pFrame->mLastActivatedhMenu ) + { + // we never activated a menu (ie, no WM_INITMENUPOPUP has occurred yet) + // which means this must be the menubar -> send activation/deactivation + SalMenuEvent aMenuEvt; + WinSalMenuItem *pSalMenuItem = ImplGetSalMenuItem( hMenu, nId, bByPosition ); + if( pSalMenuItem ) + aMenuEvt.mpMenu = pSalMenuItem->mpMenu; + else + aMenuEvt.mpMenu = nullptr; + + nRet = pFrame->CallCallback( SalEvent::MenuActivate, &aMenuEvt ); + if( nRet ) + nRet = pFrame->CallCallback( SalEvent::MenuDeactivate, &aMenuEvt ); + if( nRet ) + pFrame->mLastActivatedhMenu = hMenu; + } + + if( !hMenu && nFlags == 0xFFFF ) + { + // all menus are closed, reset activation logic + pFrame->mLastActivatedhMenu = nullptr; + } + + if( hMenu ) + { + // hMenu must be saved, as it is not passed in WM_COMMAND which always occurs after a selection + // if a menu is closed due to a command selection then hMenu is NULL, but WM_COMMAND comes later + // so we must not overwrite it in this case + pFrame->mSelectedhMenu = hMenu; + + // send highlight event + if( nFlags & MF_POPUP ) + { + // submenu selected + // wParam now carries an index instead of an id -> retrieve id + MENUITEMINFOW mi = {}; + mi.cbSize = sizeof( mi ); + mi.fMask = MIIM_ID; + if( GetMenuItemInfoW( hMenu, LOWORD(wParam), TRUE, &mi) ) + nId = sal::static_int_cast<WORD>(mi.wID); + } + + SalMenuEvent aMenuEvt; + aMenuEvt.mnId = nId; + WinSalMenuItem *pSalMenuItem = ImplGetSalMenuItem( hMenu, nId, false ); + if( pSalMenuItem ) + aMenuEvt.mpMenu = pSalMenuItem->mpMenu; + else + aMenuEvt.mpMenu = nullptr; + + nRet = pFrame->CallCallback( SalEvent::MenuHighlight, &aMenuEvt ); + } + + return nRet; +} + +static bool ImplHandleCommand( HWND hWnd, WPARAM wParam, LPARAM ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + bool nRet = false; + if( !HIWORD(wParam) ) + { + // Menu command + WORD nId = LOWORD(wParam); + if( nId ) // zero for separators + { + SalMenuEvent aMenuEvt; + aMenuEvt.mnId = nId; + WinSalMenuItem *pSalMenuItem = ImplGetSalMenuItem( pFrame->mSelectedhMenu, nId, false ); + if( pSalMenuItem ) + aMenuEvt.mpMenu = pSalMenuItem->mpMenu; + else + aMenuEvt.mpMenu = nullptr; + + nRet = pFrame->CallCallback( SalEvent::MenuCommand, &aMenuEvt ); + } + } + return nRet; +} + +static bool ImplHandleSysCommand( HWND hWnd, WPARAM wParam, LPARAM lParam ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( !pFrame ) + return false; + + WPARAM nCommand = wParam & 0xFFF0; + + if (pFrame->isFullScreen()) + { + bool bMaximize = IsZoomed( pFrame->mhWnd ); + bool bMinimize = IsIconic( pFrame->mhWnd ); + if ( (nCommand == SC_SIZE) || + (!bMinimize && (nCommand == SC_MOVE)) || + (!bMaximize && (nCommand == SC_MAXIMIZE)) || + (bMaximize && (nCommand == SC_RESTORE)) ) + { + return true; + } + } + + if ( nCommand == SC_MOVE ) + { + WinSalTimer* pTimer = static_cast<WinSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer ); + if ( pTimer ) + pTimer->SetForceRealTimer( true ); + } + + if ( nCommand == SC_KEYMENU ) + { + // do not process SC_KEYMENU if we have a native menu + // Windows should handle this + if( GetMenu( hWnd ) ) + return false; + + // Process here KeyMenu events only for Alt to activate the MenuBar, + // or if a SysChild window is in focus, as Alt-key-combinations are + // only processed via this event + if ( !LOWORD( lParam ) ) + { + // Only trigger if no other key is pressed. + // Contrary to Docu the CharCode is delivered with the x-coordinate + // that is pressed in addition. + // Also 32 for space, 99 for c, 100 for d, ... + // As this is not documented, we check the state of the space-bar + if ( GetKeyState( VK_SPACE ) & 0x8000 ) + return false; + + // to avoid activating the MenuBar for Alt+MouseKey + if ( (GetKeyState( VK_LBUTTON ) & 0x8000) || + (GetKeyState( VK_RBUTTON ) & 0x8000) || + (GetKeyState( VK_MBUTTON ) & 0x8000) || + (GetKeyState( VK_SHIFT ) & 0x8000) ) + return true; + + SalKeyEvent aKeyEvt; + aKeyEvt.mnCode = KEY_MENU; + aKeyEvt.mnCharCode = 0; + aKeyEvt.mnRepeat = 0; + bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt ); + pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt ); + return nRet; + } + else + { + // check if a SysChild is in focus + HWND hFocusWnd = ::GetFocus(); + if ( hFocusWnd && ImplFindSalObject( hFocusWnd ) ) + { + char cKeyCode = static_cast<char>(static_cast<unsigned char>(LOWORD( lParam ))); + // LowerCase + if ( (cKeyCode >= 65) && (cKeyCode <= 90) ) + cKeyCode += 32; + // We only accept 0-9 and A-Z; all other keys have to be + // processed by the SalObj hook + if ( ((cKeyCode >= 48) && (cKeyCode <= 57)) || + ((cKeyCode >= 97) && (cKeyCode <= 122)) ) + { + sal_uInt16 nModCode = 0; + if ( GetKeyState( VK_SHIFT ) & 0x8000 ) + nModCode |= KEY_SHIFT; + if ( GetKeyState( VK_CONTROL ) & 0x8000 ) + nModCode |= KEY_MOD1; + nModCode |= KEY_MOD2; + + SalKeyEvent aKeyEvt; + if ( (cKeyCode >= 48) && (cKeyCode <= 57) ) + aKeyEvt.mnCode = KEY_0+(cKeyCode-48); + else + aKeyEvt.mnCode = KEY_A+(cKeyCode-97); + aKeyEvt.mnCode |= nModCode; + aKeyEvt.mnCharCode = cKeyCode; + aKeyEvt.mnRepeat = 0; + bool nRet = pFrame->CallCallback( SalEvent::KeyInput, &aKeyEvt ); + pFrame->CallCallback( SalEvent::KeyUp, &aKeyEvt ); + return nRet; + } + } + } + } + + return false; +} + +static void ImplHandleInputLangChange( HWND hWnd, WPARAM, LPARAM lParam ) +{ + ImplSalYieldMutexAcquireWithWait(); + + // check if we support IME + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + + if ( !pFrame ) + return; + + if ( pFrame->mbIME && pFrame->mhDefIMEContext ) + { + HKL hKL = reinterpret_cast<HKL>(lParam); + UINT nImeProps = ImmGetProperty( hKL, IGP_PROPERTY ); + + pFrame->mbSpezIME = (nImeProps & IME_PROP_SPECIAL_UI) != 0; + pFrame->mbAtCursorIME = (nImeProps & IME_PROP_AT_CARET) != 0; + pFrame->mbHandleIME = !pFrame->mbSpezIME; + } + + // trigger input language and codepage update + UINT nLang = pFrame->mnInputLang; + ImplUpdateInputLang( pFrame ); + + // notify change + if( nLang != pFrame->mnInputLang ) + pFrame->CallCallback( SalEvent::InputLanguageChange, nullptr ); + + // reinit spec. keys + GetSalData()->initKeyCodeMap(); + + ImplSalYieldMutexRelease(); +} + +static void ImplUpdateIMECursorPos( WinSalFrame* pFrame, HIMC hIMC ) +{ + COMPOSITIONFORM aForm = {}; + + // get cursor position and from it calculate default position + // for the composition window + SalExtTextInputPosEvent aPosEvt; + pFrame->CallCallback( SalEvent::ExtTextInputPos, &aPosEvt ); + if ( (aPosEvt.mnX == -1) && (aPosEvt.mnY == -1) ) + aForm.dwStyle |= CFS_DEFAULT; + else + { + aForm.dwStyle |= CFS_POINT; + aForm.ptCurrentPos.x = aPosEvt.mnX; + aForm.ptCurrentPos.y = aPosEvt.mnY; + } + ImmSetCompositionWindow( hIMC, &aForm ); + + // Because not all IME's use this values, we create + // a Windows caret to force the Position from the IME + if ( GetFocus() == pFrame->mhWnd ) + { + CreateCaret( pFrame->mhWnd, nullptr, + aPosEvt.mnWidth, aPosEvt.mnHeight ); + SetCaretPos( aPosEvt.mnX, aPosEvt.mnY ); + } +} + +static bool ImplHandleIMEStartComposition( HWND hWnd ) +{ + bool bDef = true; + + ImplSalYieldMutexAcquireWithWait(); + + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame ) + { + HIMC hIMC = ImmGetContext( hWnd ); + if ( hIMC ) + { + ImplUpdateIMECursorPos( pFrame, hIMC ); + ImmReleaseContext( hWnd, hIMC ); + } + + if ( pFrame->mbHandleIME ) + { + if ( pFrame->mbAtCursorIME ) + bDef = false; + } + } + + ImplSalYieldMutexRelease(); + + return bDef; +} + +static bool ImplHandleIMECompositionInput( WinSalFrame* pFrame, + HIMC hIMC, LPARAM lParam ) +{ + bool bDef = true; + + // Init Event + SalExtTextInputEvent aEvt; + aEvt.mpTextAttr = nullptr; + aEvt.mnCursorPos = 0; + aEvt.mnCursorFlags = 0; + + // If we get a result string, then we handle this input + if ( lParam & GCS_RESULTSTR ) + { + bDef = false; + + LONG nTextLen = ImmGetCompositionStringW( hIMC, GCS_RESULTSTR, nullptr, 0 ) / sizeof( WCHAR ); + if ( nTextLen >= 0 ) + { + auto pTextBuf = std::make_unique<WCHAR[]>(nTextLen); + ImmGetCompositionStringW( hIMC, GCS_RESULTSTR, pTextBuf.get(), nTextLen*sizeof( WCHAR ) ); + aEvt.maText = OUString( o3tl::toU(pTextBuf.get()), static_cast<sal_Int32>(nTextLen) ); + } + + aEvt.mnCursorPos = aEvt.maText.getLength(); + pFrame->CallCallback( SalEvent::ExtTextInput, &aEvt ); + pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); + ImplUpdateIMECursorPos( pFrame, hIMC ); + } + + // If the IME doesn't support OnSpot input, then there is nothing to do + if ( !pFrame->mbAtCursorIME ) + return !bDef; + + // If we get new Composition data, then we handle this new input + if ( (lParam & (GCS_COMPSTR | GCS_COMPATTR)) || + ((lParam & GCS_CURSORPOS) && !(lParam & GCS_RESULTSTR)) ) + { + bDef = false; + + std::unique_ptr<ExtTextInputAttr[]> pSalAttrAry; + LONG nTextLen = ImmGetCompositionStringW( hIMC, GCS_COMPSTR, nullptr, 0 ) / sizeof( WCHAR ); + if ( nTextLen > 0 ) + { + { + auto pTextBuf = std::make_unique<WCHAR[]>(nTextLen); + ImmGetCompositionStringW( hIMC, GCS_COMPSTR, pTextBuf.get(), nTextLen*sizeof( WCHAR ) ); + aEvt.maText = OUString( o3tl::toU(pTextBuf.get()), static_cast<sal_Int32>(nTextLen) ); + } + + std::unique_ptr<BYTE[]> pAttrBuf; + LONG nAttrLen = ImmGetCompositionStringW( hIMC, GCS_COMPATTR, nullptr, 0 ); + if ( nAttrLen > 0 ) + { + pAttrBuf.reset(new BYTE[nAttrLen]); + ImmGetCompositionStringW( hIMC, GCS_COMPATTR, pAttrBuf.get(), nAttrLen ); + } + + if ( pAttrBuf ) + { + sal_Int32 nTextLen2 = aEvt.maText.getLength(); + pSalAttrAry.reset(new ExtTextInputAttr[nTextLen2]); + memset( pSalAttrAry.get(), 0, nTextLen2*sizeof( sal_uInt16 ) ); + for( sal_Int32 i = 0; (i < nTextLen2) && (i < nAttrLen); i++ ) + { + BYTE nWinAttr = pAttrBuf.get()[i]; + ExtTextInputAttr nSalAttr; + if ( nWinAttr == ATTR_TARGET_CONVERTED ) + { + nSalAttr = ExtTextInputAttr::BoldUnderline; + aEvt.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE; + } + else if ( nWinAttr == ATTR_CONVERTED ) + nSalAttr = ExtTextInputAttr::DashDotUnderline; + else if ( nWinAttr == ATTR_TARGET_NOTCONVERTED ) + nSalAttr = ExtTextInputAttr::Highlight; + else if ( nWinAttr == ATTR_INPUT_ERROR ) + nSalAttr = ExtTextInputAttr::RedText | ExtTextInputAttr::DottedUnderline; + else /* ( nWinAttr == ATTR_INPUT ) */ + nSalAttr = ExtTextInputAttr::DottedUnderline; + pSalAttrAry[i] = nSalAttr; + } + + aEvt.mpTextAttr = pSalAttrAry.get(); + } + } + + // Only when we get new composition data, we must send this event + if ( (nTextLen > 0) || !(lParam & GCS_RESULTSTR) ) + { + // End the mode, if the last character is deleted + if ( !nTextLen ) + { + pFrame->CallCallback( SalEvent::ExtTextInput, &aEvt ); + pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); + } + else + { + // Because Cursor-Position and DeltaStart never updated + // from the korean input engine, we must handle this here + if ( lParam & CS_INSERTCHAR ) + { + aEvt.mnCursorPos = nTextLen; + if ( aEvt.mnCursorPos && (lParam & CS_NOMOVECARET) ) + aEvt.mnCursorPos--; + } + else + aEvt.mnCursorPos = LOWORD( ImmGetCompositionStringW( hIMC, GCS_CURSORPOS, nullptr, 0 ) ); + + if ( pFrame->mbCandidateMode ) + aEvt.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE; + if ( lParam & CS_NOMOVECARET ) + aEvt.mnCursorFlags |= EXTTEXTINPUT_CURSOR_OVERWRITE; + + pFrame->CallCallback( SalEvent::ExtTextInput, &aEvt ); + } + ImplUpdateIMECursorPos( pFrame, hIMC ); + } + } + + return !bDef; +} + +static bool ImplHandleIMEComposition( HWND hWnd, LPARAM lParam ) +{ + bool bDef = true; + ImplSalYieldMutexAcquireWithWait(); + + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame && (!lParam || (lParam & GCS_RESULTSTR)) ) + { + // reset the background mode for each text input, + // as some tools such as RichWin may have changed it + if ( pFrame->mpLocalGraphics && + pFrame->mpLocalGraphics->getHDC() ) + SetBkMode( pFrame->mpLocalGraphics->getHDC(), TRANSPARENT ); + } + + if ( pFrame && pFrame->mbHandleIME ) + { + if ( !lParam ) + { + SalExtTextInputEvent aEvt; + aEvt.mpTextAttr = nullptr; + aEvt.mnCursorPos = 0; + aEvt.mnCursorFlags = 0; + pFrame->CallCallback( SalEvent::ExtTextInput, &aEvt ); + pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr ); + } + else if ( lParam & (GCS_RESULTSTR | GCS_COMPSTR | GCS_COMPATTR | GCS_CURSORPOS) ) + { + HIMC hIMC = ImmGetContext( hWnd ); + if ( hIMC ) + { + if ( ImplHandleIMECompositionInput( pFrame, hIMC, lParam ) ) + bDef = false; + + ImmReleaseContext( hWnd, hIMC ); + } + } + } + + ImplSalYieldMutexRelease(); + return bDef; +} + +static bool ImplHandleIMEEndComposition( HWND hWnd ) +{ + bool bDef = true; + + ImplSalYieldMutexAcquireWithWait(); + + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame && pFrame->mbHandleIME ) + { + if ( pFrame->mbAtCursorIME ) + { + pFrame->mbCandidateMode = false; + bDef = false; + } + } + + ImplSalYieldMutexRelease(); + + return bDef; +} + +static bool ImplHandleAppCommand( HWND hWnd, LPARAM lParam, LRESULT & nRet ) +{ + MediaCommand nCommand; + switch( GET_APPCOMMAND_LPARAM(lParam) ) + { + case APPCOMMAND_MEDIA_CHANNEL_DOWN: nCommand = MediaCommand::ChannelDown; break; + case APPCOMMAND_MEDIA_CHANNEL_UP: nCommand = MediaCommand::ChannelUp; break; + case APPCOMMAND_MEDIA_NEXTTRACK: nCommand = MediaCommand::NextTrack; break; + case APPCOMMAND_MEDIA_PAUSE: nCommand = MediaCommand::Pause; break; + case APPCOMMAND_MEDIA_PLAY: nCommand = MediaCommand::Play; break; + case APPCOMMAND_MEDIA_PLAY_PAUSE: nCommand = MediaCommand::PlayPause; break; + case APPCOMMAND_MEDIA_PREVIOUSTRACK: nCommand = MediaCommand::PreviousTrack; break; + case APPCOMMAND_MEDIA_RECORD: nCommand = MediaCommand::Record; break; + case APPCOMMAND_MEDIA_REWIND: nCommand = MediaCommand::Rewind; break; + case APPCOMMAND_MEDIA_STOP: nCommand = MediaCommand::Stop; break; + case APPCOMMAND_MIC_ON_OFF_TOGGLE: nCommand = MediaCommand::MicOnOffToggle; break; + case APPCOMMAND_MICROPHONE_VOLUME_DOWN: nCommand = MediaCommand::MicrophoneVolumeDown; break; + case APPCOMMAND_MICROPHONE_VOLUME_MUTE: nCommand = MediaCommand::MicrophoneVolumeMute; break; + case APPCOMMAND_MICROPHONE_VOLUME_UP: nCommand = MediaCommand::MicrophoneVolumeUp; break; + case APPCOMMAND_VOLUME_DOWN: nCommand = MediaCommand::VolumeDown; break; + case APPCOMMAND_VOLUME_MUTE: nCommand = MediaCommand::VolumeMute; break; + case APPCOMMAND_VOLUME_UP: nCommand = MediaCommand::VolumeUp; break; + default: + return false; + } + + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + vcl::Window *pWindow = pFrame ? pFrame->GetWindow() : nullptr; + + if( pWindow ) + { + const Point aPoint; + CommandMediaData aMediaData(nCommand); + CommandEvent aCEvt( aPoint, CommandEventId::Media, false, &aMediaData ); + NotifyEvent aNCmdEvt( NotifyEventType::COMMAND, pWindow, &aCEvt ); + + if ( !ImplCallPreNotify( aNCmdEvt ) ) + { + pWindow->Command( aCEvt ); + nRet = 1; + return !aMediaData.GetPassThroughToOS(); + } + } + + return false; +} + +static void ImplHandleIMENotify( HWND hWnd, WPARAM wParam ) +{ + if ( wParam == WPARAM(IMN_OPENCANDIDATE) ) + { + ImplSalYieldMutexAcquireWithWait(); + + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame && pFrame->mbHandleIME && + pFrame->mbAtCursorIME ) + { + // we want to hide the cursor + pFrame->mbCandidateMode = true; + ImplHandleIMEComposition( hWnd, GCS_CURSORPOS ); + + HWND hWnd2 = pFrame->mhWnd; + HIMC hIMC = ImmGetContext( hWnd2 ); + if ( hIMC ) + { + LONG nBufLen = ImmGetCompositionStringW( hIMC, GCS_COMPSTR, nullptr, 0 ); + if ( nBufLen >= 1 ) + { + SalExtTextInputPosEvent aPosEvt; + pFrame->CallCallback( SalEvent::ExtTextInputPos, &aPosEvt ); + + // Vertical !!! + CANDIDATEFORM aForm; + aForm.dwIndex = 0; + aForm.dwStyle = CFS_EXCLUDE; + aForm.ptCurrentPos.x = aPosEvt.mnX; + aForm.ptCurrentPos.y = aPosEvt.mnY+1; + aForm.rcArea.left = aPosEvt.mnX; + aForm.rcArea.top = aPosEvt.mnY; + aForm.rcArea.right = aForm.rcArea.left+aPosEvt.mnExtWidth+1; + aForm.rcArea.bottom = aForm.rcArea.top+aPosEvt.mnHeight+1; + ImmSetCandidateWindow( hIMC, &aForm ); + } + + ImmReleaseContext( hWnd2, hIMC ); + } + } + + ImplSalYieldMutexRelease(); + } + else if ( wParam == WPARAM(IMN_CLOSECANDIDATE) ) + { + ImplSalYieldMutexAcquireWithWait(); + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + if ( pFrame ) + pFrame->mbCandidateMode = false; + ImplSalYieldMutexRelease(); + } +} + +static bool +ImplHandleGetObject(HWND hWnd, LPARAM lParam, WPARAM wParam, LRESULT & nRet) +{ + uno::Reference<accessibility::XMSAAService> xMSAA; + if (ImplSalYieldMutexTryToAcquire()) + { + if (!Application::GetSettings().GetMiscSettings().GetEnableATToolSupport()) + { + // IA2 should be enabled automatically + AllSettings aSettings = Application::GetSettings(); + MiscSettings aMisc = aSettings.GetMiscSettings(); + aMisc.SetEnableATToolSupport(true); + // The above is enough, since aMisc changes the same shared ImplMiscData as used in global + // settings, so no need to call aSettings.SetMiscSettings and Application::SetSettings + + if (!Application::GetSettings().GetMiscSettings().GetEnableATToolSupport()) + return false; // locked down somehow ? + } + + ImplSVData* pSVData = ImplGetSVData(); + + // Make sure to launch Accessibility only the following criteria are satisfied + // to avoid RFT interrupts regular accessibility processing + if ( !pSVData->mxAccessBridge.is() ) + { + if( !InitAccessBridge() ) + return false; + } + xMSAA.set(pSVData->mxAccessBridge, uno::UNO_QUERY); + ImplSalYieldMutexRelease(); + } + else + { // tdf#155794: access without locking: hopefully this should be fine + // as the bridge is typically inited in Desktop::Main() already and the + // WM_GETOBJECT is received only on the main thread and by the time in + // VCL shutdown when ImplSvData dies there should not be Windows any + // more that could receive messages. + xMSAA.set(ImplGetSVData()->mxAccessBridge, uno::UNO_QUERY); + } + + if ( xMSAA.is() ) + { + sal_Int32 lParam32 = static_cast<sal_Int32>(lParam); + sal_uInt32 wParam32 = static_cast<sal_uInt32>(wParam); + + // mhOnSetTitleWnd not set to reasonable value anywhere... + if ( lParam32 == OBJID_CLIENT ) + { + nRet = xMSAA->getAccObjectPtr( + reinterpret_cast<sal_Int64>(hWnd), lParam32, wParam32); + if (nRet != 0) + return true; + } + } + return false; +} + +static LRESULT ImplHandleIMEReconvertString( HWND hWnd, LPARAM lParam ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + LPRECONVERTSTRING pReconvertString = reinterpret_cast<LPRECONVERTSTRING>(lParam); + LRESULT nRet = 0; + SalSurroundingTextRequestEvent aEvt; + aEvt.maText.clear(); + aEvt.mnStart = aEvt.mnEnd = 0; + + UINT nImeProps = ImmGetProperty( GetKeyboardLayout( 0 ), IGP_SETCOMPSTR ); + if( (nImeProps & SCS_CAP_SETRECONVERTSTRING) == 0 ) + { + // This IME does not support reconversion. + return 0; + } + + if( !pReconvertString ) + { + // The first call for reconversion. + pFrame->CallCallback( SalEvent::StartReconversion, nullptr ); + + // Retrieve the surrounding text from the focused control. + pFrame->CallCallback( SalEvent::SurroundingTextRequest, &aEvt ); + + if( aEvt.maText.isEmpty()) + { + return 0; + } + + nRet = sizeof(RECONVERTSTRING) + (aEvt.maText.getLength() + 1) * sizeof(WCHAR); + } + else + { + // The second call for reconversion. + + // Retrieve the surrounding text from the focused control. + pFrame->CallCallback( SalEvent::SurroundingTextRequest, &aEvt ); + nRet = sizeof(RECONVERTSTRING) + (aEvt.maText.getLength() + 1) * sizeof(WCHAR); + + pReconvertString->dwStrOffset = sizeof(RECONVERTSTRING); + pReconvertString->dwStrLen = aEvt.maText.getLength(); + pReconvertString->dwCompStrOffset = aEvt.mnStart * sizeof(WCHAR); + pReconvertString->dwCompStrLen = aEvt.mnEnd - aEvt.mnStart; + pReconvertString->dwTargetStrOffset = pReconvertString->dwCompStrOffset; + pReconvertString->dwTargetStrLen = pReconvertString->dwCompStrLen; + + memcpy( pReconvertString + 1, aEvt.maText.getStr(), (aEvt.maText.getLength() + 1) * sizeof(WCHAR) ); + } + + // just return the required size of buffer to reconvert. + return nRet; +} + +static LRESULT ImplHandleIMEConfirmReconvertString( HWND hWnd, LPARAM lParam ) +{ + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + LPRECONVERTSTRING pReconvertString = reinterpret_cast<LPRECONVERTSTRING>(lParam); + SalSurroundingTextRequestEvent aEvt; + aEvt.maText.clear(); + aEvt.mnStart = aEvt.mnEnd = 0; + + pFrame->CallCallback( SalEvent::SurroundingTextRequest, &aEvt ); + + sal_uLong nTmpStart = pReconvertString->dwCompStrOffset / sizeof(WCHAR); + sal_uLong nTmpEnd = nTmpStart + pReconvertString->dwCompStrLen; + + if( nTmpStart != aEvt.mnStart || nTmpEnd != aEvt.mnEnd ) + { + SalSurroundingTextSelectionChangeEvent aSelEvt { nTmpStart, nTmpEnd }; + pFrame->CallCallback( SalEvent::SurroundingTextSelectionChange, &aSelEvt ); + } + + return TRUE; +} + +static LRESULT ImplHandleIMEQueryCharPosition( HWND hWnd, LPARAM lParam ) { + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + PIMECHARPOSITION pQueryCharPosition = reinterpret_cast<PIMECHARPOSITION>(lParam); + if ( pQueryCharPosition->dwSize < sizeof(IMECHARPOSITION) ) + return FALSE; + + SalQueryCharPositionEvent aEvt; + aEvt.mbValid = false; + aEvt.mnCharPos = pQueryCharPosition->dwCharPos; + + pFrame->CallCallback( SalEvent::QueryCharPosition, &aEvt ); + + if ( !aEvt.mbValid ) + return FALSE; + + if ( aEvt.mbVertical ) + { + // For vertical writing, the base line is left edge of the rectangle + // and the target position is top-right corner. + pQueryCharPosition->pt.x = aEvt.maCursorBound.getX() + aEvt.maCursorBound.GetWidth(); + pQueryCharPosition->pt.y = aEvt.maCursorBound.getY(); + pQueryCharPosition->cLineHeight = aEvt.maCursorBound.GetWidth(); + } + else + { + // For horizontal writing, the base line is the bottom edge of the rectangle. + // and the target position is top-left corner. + pQueryCharPosition->pt.x = aEvt.maCursorBound.getX(); + pQueryCharPosition->pt.y = aEvt.maCursorBound.getY(); + pQueryCharPosition->cLineHeight = aEvt.maCursorBound.GetHeight(); + } + + // Currently not supported but many IMEs usually ignore them. + pQueryCharPosition->rcDocument.left = 0; + pQueryCharPosition->rcDocument.top = 0; + pQueryCharPosition->rcDocument.right = 0; + pQueryCharPosition->rcDocument.bottom = 0; + + return TRUE; +} + +void SalTestMouseLeave() +{ + SalData* pSalData = GetSalData(); + + if ( pSalData->mhWantLeaveMsg && !::GetCapture() ) + { + POINT aPt; + GetCursorPos( &aPt ); + + // a one item cache, because this function is sometimes hot - if the cursor has not moved, then + // no need to call WindowFromPoint + static POINT cachedPoint; + if (cachedPoint.x == aPt.x && cachedPoint.y == aPt.y) + return; + cachedPoint = aPt; + + if ( pSalData->mhWantLeaveMsg != WindowFromPoint( aPt ) ) + SendMessageW( pSalData->mhWantLeaveMsg, SAL_MSG_MOUSELEAVE, 0, MAKELPARAM( aPt.x, aPt.y ) ); + } +} + +static bool ImplSalWheelMousePos( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam , + LRESULT& rResult ) +{ + POINT aPt; + POINT aScreenPt; + aScreenPt.x = static_cast<short>(LOWORD( lParam )); + aScreenPt.y = static_cast<short>(HIWORD( lParam )); + // find child window that is at this position + HWND hChildWnd; + HWND hWheelWnd = hWnd; + do + { + hChildWnd = hWheelWnd; + aPt = aScreenPt; + ScreenToClient( hChildWnd, &aPt ); + hWheelWnd = ChildWindowFromPointEx( hChildWnd, aPt, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT ); + } + while ( hWheelWnd && (hWheelWnd != hChildWnd) ); + if ( hWheelWnd && (hWheelWnd != hWnd) && + (hWheelWnd != ::GetFocus()) && IsWindowEnabled( hWheelWnd ) ) + { + rResult = SendMessageW( hWheelWnd, nMsg, wParam, lParam ); + return false; + } + + return true; +} + +static LRESULT CALLBACK SalFrameWndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, bool& rDef ) +{ + LRESULT nRet = 0; + static bool bInWheelMsg = false; + static bool bInQueryEnd = false; + + SAL_INFO("vcl.gdi.wndproc", "SalFrameWndProc(nMsg=" << nMsg << ", wParam=" << wParam << ", lParam=" << lParam << ")"); + + // By WM_CREATE we connect the frame with the window handle + if ( nMsg == WM_CREATE ) + { + // Save Window-Instance in Windowhandle + // Can also be used for the A-Version, because the struct + // to access lpCreateParams is the same structure + CREATESTRUCTW* pStruct = reinterpret_cast<CREATESTRUCTW*>(lParam); + WinSalFrame* pFrame = static_cast<WinSalFrame*>(pStruct->lpCreateParams); + if ( pFrame != nullptr ) + { + SetWindowPtr( hWnd, pFrame ); + + UpdateDarkMode(hWnd); + + // Set HWND already here, as data might be used already + // when messages are being sent by CreateWindow() + pFrame->mhWnd = hWnd; + pFrame->maSysData.hWnd = hWnd; + } + return 0; + } + + ImplSVData* pSVData = ImplGetSVData(); + // #i72707# TODO: the mbDeInit check will not be needed + // once all windows that are not properly closed on exit got fixed + if( pSVData->mbDeInit ) + return 0; + + if ( WM_USER_SYSTEM_WINDOW_ACTIVATED == nMsg ) + { + ImplHideSplash(); + return 0; + } + + switch( nMsg ) + { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_NCMOUSEMOVE: + case SAL_MSG_MOUSELEAVE: + ImplSalYieldMutexAcquireWithWait(); + rDef = !ImplHandleMouseMsg( hWnd, nMsg, wParam, lParam ); + ImplSalYieldMutexRelease(); + break; + + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + ImplSalYieldMutexAcquireWithWait(); + ImplCallClosePopupsHdl( hWnd ); // close popups... + ImplSalYieldMutexRelease(); + break; + + case WM_MOUSEACTIVATE: + if ( LOWORD( lParam ) == HTCLIENT ) + { + ImplSalYieldMutexAcquireWithWait(); + nRet = LRESULT(ImplHandleMouseActivateMsg( hWnd )); + ImplSalYieldMutexRelease(); + if ( nRet ) + { + nRet = MA_NOACTIVATE; + rDef = false; + } + } + break; + + case WM_KEYDOWN: + case WM_KEYUP: + case WM_DEADCHAR: + case WM_CHAR: + case WM_UNICHAR: // MCD, 2003-01-13, Support for WM_UNICHAR & Keyman 6.0 + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_SYSCHAR: + ImplSalYieldMutexAcquireWithWait(); + rDef = !ImplHandleKeyMsg( hWnd, nMsg, wParam, lParam, nRet ); + ImplSalYieldMutexRelease(); + break; + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + // protect against recursion, in case the message is returned + // by IE or the external window + if ( !bInWheelMsg ) + { + bInWheelMsg = true; + rDef = !ImplHandleWheelMsg( hWnd, nMsg, wParam, lParam ); + // If we did not process the message, re-check if here is a + // connected (?) window that we have to notify. + if ( rDef ) + rDef = ImplSalWheelMousePos( hWnd, nMsg, wParam, lParam, nRet ); + bInWheelMsg = false; + } + break; + + case WM_COMMAND: + ImplSalYieldMutexAcquireWithWait(); + rDef = !ImplHandleCommand( hWnd, wParam, lParam ); + ImplSalYieldMutexRelease(); + break; + + case WM_INITMENUPOPUP: + ImplSalYieldMutexAcquireWithWait(); + rDef = !ImplHandleMenuActivate( hWnd, wParam, lParam ); + ImplSalYieldMutexRelease(); + break; + + case WM_MENUSELECT: + ImplSalYieldMutexAcquireWithWait(); + rDef = !ImplHandleMenuSelect( hWnd, wParam, lParam ); + ImplSalYieldMutexRelease(); + break; + + case WM_SYSCOMMAND: + ImplSalYieldMutexAcquireWithWait(); + nRet = LRESULT(ImplHandleSysCommand( hWnd, wParam, lParam )); + ImplSalYieldMutexRelease(); + if ( nRet ) + rDef = false; + break; + + case WM_MENUCHAR: + nRet = ImplMenuChar( hWnd, wParam, lParam ); + if( nRet ) + rDef = false; + break; + + case WM_MEASUREITEM: + nRet = ImplMeasureItem(hWnd, wParam, lParam); + if( nRet ) + rDef = false; + break; + + case WM_DRAWITEM: + nRet = ImplDrawItem(hWnd, wParam, lParam); + if( nRet ) + rDef = false; + break; + + case WM_MOVE: + ImplHandleMoveMsg(hWnd, lParam); + rDef = false; + break; + case SAL_MSG_POSTMOVE: + ImplCallMoveHdl(hWnd); + rDef = false; + break; + case WM_SIZE: + ImplHandleSizeMsg(hWnd, wParam, lParam); + rDef = false; + break; + case SAL_MSG_POSTCALLSIZE: + ImplCallSizeHdl( hWnd ); + rDef = false; + break; + + case WM_GETMINMAXINFO: + if ( ImplHandleMinMax( hWnd, lParam ) ) + rDef = false; + break; + + case WM_ERASEBKGND: + nRet = 1; + rDef = false; + break; + case WM_PAINT: + ImplHandlePaintMsg( hWnd ); + rDef = false; + break; + case SAL_MSG_POSTPAINT: + ImplHandlePostPaintMsg( hWnd, reinterpret_cast<RECT*>(wParam) ); + rDef = false; + break; + + case SAL_MSG_FORCEPALETTE: + ImplHandleForcePalette( hWnd ); + rDef = false; + break; + + case WM_QUERYNEWPALETTE: + case SAL_MSG_POSTQUERYNEWPAL: + nRet = ImplHandlePalette( true, hWnd, nMsg, wParam, lParam, rDef ); + break; + + case WM_ACTIVATE: + // Getting activated, we also want to set our palette. + // We do this in Activate, so that other external child windows + // can overwrite our palette. Thus our palette is set only once + // and not recursively, as at all other places it is set only as + // the background palette. + if ( LOWORD( wParam ) != WA_INACTIVE ) + SendMessageW( hWnd, SAL_MSG_FORCEPALETTE, 0, 0 ); + break; + + case WM_ENABLE: + // #95133# a system dialog is opened/closed, using our app window as parent + { + WinSalFrame* pFrame = GetWindowPtr( hWnd ); + vcl::Window *pWin = nullptr; + if( pFrame ) + pWin = pFrame->GetWindow(); + + if( !wParam ) + { + pSVData->maAppData.mnModalMode++; + + ImplHideSplash(); + if( pWin ) + { + pWin->EnableInput( false, nullptr ); + pWin->IncModalCount(); // #106303# support frame based modal count + } + } + else + { + ImplGetSVData()->maAppData.mnModalMode--; + if( pWin ) + { + pWin->EnableInput( true, nullptr ); + pWin->DecModalCount(); // #106303# support frame based modal count + } + } + } + break; + + case WM_KILLFOCUS: + DestroyCaret(); + [[fallthrough]]; + case WM_SETFOCUS: + case SAL_MSG_POSTFOCUS: + ImplHandleFocusMsg( hWnd ); + rDef = false; + break; + + case WM_CLOSE: + ImplHandleCloseMsg( hWnd ); + rDef = false; + break; + + case WM_QUERYENDSESSION: + if( !bInQueryEnd ) + { + // handle queryendsession only once + bInQueryEnd = true; + nRet = LRESULT(!ImplHandleShutDownMsg( hWnd )); + rDef = false; + + // Issue #16314#: ImplHandleShutDownMsg causes a PostMessage in case of allowing shutdown. + // This posted message was never processed and cause Windows XP to hang after log off + // if there are multiple sessions and the current session wasn't the first one started. + // So if shutdown is allowed we assume that a post message was done and retrieve all + // messages in the message queue and dispatch them before we return control to the system. + + if ( nRet ) + { + SolarMutexGuard aGuard; + while ( Application::Reschedule( true ) ); + } + } + else + { + ImplSalYieldMutexAcquireWithWait(); + ImplSalYieldMutexRelease(); + rDef = true; + } + break; + + case WM_ENDSESSION: + if( !wParam ) + bInQueryEnd = false; // no shutdown: allow query again + nRet = FALSE; + rDef = false; + break; + + case WM_DISPLAYCHANGE: + case WM_SETTINGCHANGE: + case WM_DEVMODECHANGE: + case WM_FONTCHANGE: + case WM_SYSCOLORCHANGE: + case WM_TIMECHANGE: + ImplHandleSettingsChangeMsg( hWnd, nMsg, wParam, lParam ); + break; + + case WM_THEMECHANGED: + GetSalData()->mbThemeChanged = true; + break; + + case SAL_MSG_USEREVENT: + ImplHandleUserEvent( hWnd, lParam ); + rDef = false; + break; + + case SAL_MSG_CAPTUREMOUSE: + SetCapture( hWnd ); + rDef = false; + break; + case SAL_MSG_RELEASEMOUSE: + if ( ::GetCapture() == hWnd ) + ReleaseCapture(); + rDef = false; + break; + case SAL_MSG_TOTOP: + ImplSalToTop( hWnd, static_cast<SalFrameToTop>(wParam) ); + rDef = false; + break; + case SAL_MSG_SHOW: + ImplSalShow( hWnd, static_cast<bool>(wParam), static_cast<bool>(lParam) ); + rDef = false; + break; + case SAL_MSG_SETINPUTCONTEXT: + ImplSalFrameSetInputContext( hWnd, reinterpret_cast<const SalInputContext*>(lParam) ); + rDef = false; + break; + case SAL_MSG_ENDEXTTEXTINPUT: + ImplSalFrameEndExtTextInput( hWnd, static_cast<EndExtTextInputFlags>(wParam) ); + rDef = false; + break; + + case WM_INPUTLANGCHANGE: + ImplHandleInputLangChange( hWnd, wParam, lParam ); + break; + + case WM_IME_CHAR: + // #103487#, some IMEs (eg, those that do not work onspot) + // may send WM_IME_CHAR instead of WM_IME_COMPOSITION + // we just handle it like a WM_CHAR message - seems to work fine + ImplSalYieldMutexAcquireWithWait(); + rDef = !ImplHandleKeyMsg( hWnd, WM_CHAR, wParam, lParam, nRet ); + ImplSalYieldMutexRelease(); + break; + + case WM_IME_STARTCOMPOSITION: + rDef = ImplHandleIMEStartComposition( hWnd ); + break; + + case WM_IME_COMPOSITION: + rDef = ImplHandleIMEComposition( hWnd, lParam ); + break; + + case WM_IME_ENDCOMPOSITION: + rDef = ImplHandleIMEEndComposition( hWnd ); + break; + + case WM_IME_NOTIFY: + ImplHandleIMENotify( hWnd, wParam ); + break; + + case WM_GETOBJECT: + // tdf#155794: this must complete without taking SolarMutex + if ( ImplHandleGetObject( hWnd, lParam, wParam, nRet ) ) + { + rDef = false; + } + break; + + case WM_APPCOMMAND: + if( ImplHandleAppCommand( hWnd, lParam, nRet ) ) + { + rDef = false; + } + break; + case WM_IME_REQUEST: + if ( static_cast<sal_uIntPtr>(wParam) == IMR_RECONVERTSTRING ) + { + nRet = ImplHandleIMEReconvertString( hWnd, lParam ); + rDef = false; + } + else if( static_cast<sal_uIntPtr>(wParam) == IMR_CONFIRMRECONVERTSTRING ) + { + nRet = ImplHandleIMEConfirmReconvertString( hWnd, lParam ); + rDef = false; + } + else if ( static_cast<sal_uIntPtr>(wParam) == IMR_QUERYCHARPOSITION ) + { + if ( ImplSalYieldMutexTryToAcquire() ) + { + nRet = ImplHandleIMEQueryCharPosition( hWnd, lParam ); + ImplSalYieldMutexRelease(); + } + else + nRet = FALSE; + rDef = false; + } + break; + } + + return nRet; +} + +LRESULT CALLBACK SalFrameWndProcW( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam ) +{ + bool bDef = true; + LRESULT nRet = 0; + __try + { + nRet = SalFrameWndProc( hWnd, nMsg, wParam, lParam, bDef ); + } + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation())) + { + } + + if ( bDef ) + nRet = DefWindowProcW( hWnd, nMsg, wParam, lParam ); + return nRet; +} + +bool ImplHandleGlobalMsg( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, LRESULT& rlResult ) +{ + // handle all messages concerning all frames so they get processed only once + // Must work for Unicode and none Unicode + bool bResult = false; + if ( (nMsg == WM_PALETTECHANGED) || (nMsg == SAL_MSG_POSTPALCHANGED) ) + { + bResult = true; + rlResult = ImplHandlePalette( false, hWnd, nMsg, wParam, lParam, bResult ); + } + else if( nMsg == WM_DISPLAYCHANGE ) + { + WinSalSystem* pSys = static_cast<WinSalSystem*>(ImplGetSalSystem()); + if( pSys ) + pSys->clearMonitors(); + bResult = (pSys != nullptr); + } + return bResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/window/salmenu.cxx b/vcl/win/window/salmenu.cxx new file mode 100644 index 0000000000..91a15284ae --- /dev/null +++ b/vcl/win/window/salmenu.cxx @@ -0,0 +1,329 @@ +/* -*- 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 <svsys.h> + +#include <vcl/menu.hxx> +#include <vcl/sysdata.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <o3tl/safeint.hxx> + +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salframe.h> +#include <win/salmenu.h> + +#include <salgdi.hxx> + +static DWORD myerr=0; + +bool SalData::IsKnownMenuHandle( HMENU hMenu ) +{ + if( mhMenuSet.find( hMenu ) == mhMenuSet.end() ) + return false; + else + return true; +} + +// WinSalInst factory methods + +std::unique_ptr<SalMenu> WinSalInstance::CreateMenu( bool bMenuBar, Menu* ) +{ + WinSalMenu *pSalMenu = new WinSalMenu(); + + pSalMenu->mbMenuBar = bMenuBar; + pSalMenu->mhWnd = nullptr; + if( bMenuBar ) + pSalMenu->mhMenu = ::CreateMenu(); + else + pSalMenu->mhMenu = ::CreatePopupMenu(); + + if( pSalMenu->mhMenu ) + GetSalData()->mhMenuSet.insert( pSalMenu->mhMenu ); + + return std::unique_ptr<SalMenu>(pSalMenu); +} + +std::unique_ptr<SalMenuItem> WinSalInstance::CreateMenuItem( const SalItemParams & rItemData ) +{ + WinSalMenuItem *pSalMenuItem = new WinSalMenuItem(); + memset( &pSalMenuItem->mInfo, 0, sizeof( MENUITEMINFOW ) ); + pSalMenuItem->mInfo.cbSize = sizeof( MENUITEMINFOW ); + + if( rItemData.eType == MenuItemType::SEPARATOR ) + { + // separator + pSalMenuItem->mInfo.fMask = MIIM_TYPE; + pSalMenuItem->mInfo.fType = MFT_SEPARATOR; + } + else + { + // item + pSalMenuItem->mText = rItemData.aText; + pSalMenuItem->mpMenu = rItemData.pMenu; + pSalMenuItem->maBitmap= !!rItemData.aImage ? rItemData.aImage.GetBitmapEx().GetBitmap() : Bitmap(); + pSalMenuItem->mnId = rItemData.nId; + + // 'translate' mnemonics + pSalMenuItem->mText = pSalMenuItem->mText.replaceAll( "~", "&" ); + + pSalMenuItem->mInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID | MIIM_DATA; + pSalMenuItem->mInfo.fType = MFT_STRING; + pSalMenuItem->mInfo.dwTypeData = o3tl::toW(const_cast<sal_Unicode *>(pSalMenuItem->mText.getStr())); + pSalMenuItem->mInfo.cch = pSalMenuItem->mText.getLength(); + + pSalMenuItem->mInfo.wID = rItemData.nId; + pSalMenuItem->mInfo.dwItemData = reinterpret_cast<ULONG_PTR>(pSalMenuItem); // user data + } + + return std::unique_ptr<SalMenuItem>(pSalMenuItem); +} + +/* + * WinSalMenu + */ + +WinSalMenu::WinSalMenu() +{ + mhMenu = nullptr; + mbMenuBar = false; + mhWnd = nullptr; + mpParentMenu = nullptr; +} + +WinSalMenu::~WinSalMenu() +{ + // only required if not associated to a window... + GetSalData()->mhMenuSet.erase( mhMenu ); + ::DestroyMenu( mhMenu ); +} + +bool WinSalMenu::VisibleMenuBar() +{ + // The Win32 implementation never shows a native + // menubar. Thus, native menus are only visible + // when the menu is merged with an OLE container. + // The reason are missing tooltips, ownerdraw + // issues and accessibility which are better supported + // by VCL menus. + // Nevertheless, the native menus are always created + // and the application will properly react to all native + // menu messages. + + return false; +} + +void WinSalMenu::SetFrame( const SalFrame *pFrame ) +{ + if( pFrame ) + mhWnd = static_cast<const WinSalFrame*>(pFrame)->mhWnd; + else + mhWnd = nullptr; +} + +void WinSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) +{ + if( pSalMenuItem ) + { + WinSalMenuItem* pWItem = static_cast<WinSalMenuItem*>(pSalMenuItem); + if( nPos == MENU_APPEND ) + { + nPos = ::GetMenuItemCount( mhMenu ); + if( nPos == static_cast<unsigned>( -1 ) ) + return; + } + + if(!::InsertMenuItemW( mhMenu, nPos, TRUE, &pWItem->mInfo )) + myerr = GetLastError(); + else + pWItem->mpSalMenu = this; + } +} + +void WinSalMenu::RemoveItem( unsigned nPos ) +{ + int num = ::GetMenuItemCount( mhMenu ); + if( num != -1 && nPos < o3tl::make_unsigned(num) ) + { + WinSalMenuItem *pSalMenuItem = nullptr; + + MENUITEMINFOW mi = {}; + mi.cbSize = sizeof( mi ); + mi.fMask = MIIM_DATA; + if( !GetMenuItemInfoW( mhMenu, nPos, TRUE, &mi) ) + myerr = GetLastError(); + else + pSalMenuItem = reinterpret_cast<WinSalMenuItem *>(mi.dwItemData); + + if( !::RemoveMenu( mhMenu, nPos, MF_BYPOSITION ) ) + myerr = GetLastError(); + else + { + if( pSalMenuItem ) + pSalMenuItem->mpSalMenu = nullptr; + } + } +} + +static void ImplRemoveItemById( WinSalMenu *pSalMenu, unsigned nItemId ) +{ + if( !pSalMenu ) + return; + + WinSalMenuItem *pSalMenuItem = nullptr; + + MENUITEMINFOW mi = {}; + mi.cbSize = sizeof( mi ); + mi.fMask = MIIM_DATA; + if( !GetMenuItemInfoW( pSalMenu->mhMenu, nItemId, FALSE, &mi) ) + myerr = GetLastError(); + else + pSalMenuItem = reinterpret_cast<WinSalMenuItem *>(mi.dwItemData); + + if( !::RemoveMenu( pSalMenu->mhMenu, nItemId, MF_BYCOMMAND ) ) + myerr = GetLastError(); + else + { + if( pSalMenuItem ) + pSalMenuItem->mpSalMenu = nullptr; + } +} + +void WinSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos ) +{ + if( pSalMenuItem ) + { + WinSalMenuItem* pWMenuItem = static_cast<WinSalMenuItem*>(pSalMenuItem); + WinSalMenu* pWSubMenu = static_cast<WinSalMenu*>(pSubMenu); + if( pWMenuItem->mInfo.hSubMenu ) + { + GetSalData()->mhMenuSet.erase( pWMenuItem->mInfo.hSubMenu ); + ::DestroyMenu( pWMenuItem->mInfo.hSubMenu ); + } + + pWMenuItem->mInfo.fMask |= MIIM_SUBMENU; + if( !pSubMenu ) + pWMenuItem->mInfo.hSubMenu = nullptr; + else + { + pWMenuItem->mInfo.hSubMenu = pWSubMenu->mhMenu; + pWSubMenu->mpParentMenu = this; + } + + if(!::SetMenuItemInfoW( mhMenu, nPos, TRUE, &pWMenuItem->mInfo ) ) + myerr = GetLastError(); + } +} + +void WinSalMenu::CheckItem( unsigned nPos, bool bCheck ) +{ + ::CheckMenuItem(mhMenu, nPos, MF_BYPOSITION|(bCheck ? MF_CHECKED : MF_UNCHECKED)); +} + +void WinSalMenu::EnableItem( unsigned nPos, bool bEnable ) +{ + ::EnableMenuItem(mhMenu, nPos, MF_BYPOSITION|(bEnable ? MF_ENABLED : (MF_DISABLED|MF_GRAYED))); +} + +void WinSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const Image& rImage ) +{ + if( pSalMenuItem ) + { + WinSalMenuItem* pWItem = static_cast<WinSalMenuItem*>(pSalMenuItem); + if( !!rImage ) + pWItem->maBitmap = rImage.GetBitmapEx().GetBitmap(); + else + pWItem->maBitmap = Bitmap(); + } +} + +void WinSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText ) +{ + if( pSalMenuItem ) + { + WinSalMenuItem* pWItem = static_cast<WinSalMenuItem*>(pSalMenuItem); + pWItem->mText = rText; + // 'translate' mnemonics + pWItem->mText = pWItem->mText.replaceAll( "~", "&" ); + pWItem->mInfo.fMask = MIIM_TYPE | MIIM_DATA; + pWItem->mInfo.fType = MFT_STRING; + + // combine text and accelerator text + OUString aStr( pWItem->mText ); + if( pWItem->mAccelText.getLength() ) + { + aStr += "\t" + pWItem->mAccelText; + } + pWItem->mInfo.dwTypeData = o3tl::toW(const_cast<sal_Unicode *>(aStr.getStr())); + pWItem->mInfo.cch = aStr.getLength(); + + if(!::SetMenuItemInfoW( mhMenu, nPos, TRUE, &pWItem->mInfo )) + myerr = GetLastError(); + } +} + +void WinSalMenu::SetAccelerator( unsigned nPos, SalMenuItem* pSalMenuItem, const vcl::KeyCode&, const OUString& rKeyName ) +{ + if( pSalMenuItem ) + { + WinSalMenuItem* pWItem = static_cast<WinSalMenuItem*>(pSalMenuItem); + pWItem->mAccelText = rKeyName; + pWItem->mInfo.fMask = MIIM_TYPE | MIIM_DATA; + pWItem->mInfo.fType = MFT_STRING; + + // combine text and accelerator text + OUString aStr( pWItem->mText ); + if( pWItem->mAccelText.getLength() ) + { + aStr += "\t" + pWItem->mAccelText; + } + pWItem->mInfo.dwTypeData = o3tl::toW(const_cast<sal_Unicode *>(aStr.getStr())); + pWItem->mInfo.cch = aStr.getLength(); + + if(!::SetMenuItemInfoW( mhMenu, nPos, TRUE, &pWItem->mInfo )) + myerr = GetLastError(); + } +} + +void WinSalMenu::GetSystemMenuData( SystemMenuData* pData ) +{ + if( pData ) + pData->hMenu = mhMenu; +} + +/* + * SalMenuItem + */ + +WinSalMenuItem::WinSalMenuItem() +{ + memset( &mInfo, 0, sizeof( MENUITEMINFOW ) ); + mpMenu = nullptr; + mnId = 0xFFFF; + mpSalMenu = nullptr; +} + +WinSalMenuItem::~WinSalMenuItem() +{ + if( mpSalMenu ) + ImplRemoveItemById( mpSalMenu, mnId ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/window/salobj.cxx b/vcl/win/window/salobj.cxx new file mode 100644 index 0000000000..05ad16b0d4 --- /dev/null +++ b/vcl/win/window/salobj.cxx @@ -0,0 +1,679 @@ +/* -*- 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 <string.h> + +#include <svsys.h> + +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salframe.h> +#include <win/salobj.h> + +#include <comphelper/windowserrorstring.hxx> + +static bool ImplIsSysWindowOrChild( HWND hWndParent, HWND hWndChild ) +{ + if ( hWndParent == hWndChild ) + return true; + + HWND hTempWnd = ::GetParent( hWndChild ); + while ( hTempWnd ) + { + // stop searching if not a child window + if ( !(GetWindowStyle( hTempWnd ) & WS_CHILD) ) + return false; + if ( hTempWnd == hWndParent ) + return true; + hTempWnd = ::GetParent( hTempWnd ); + } + + return false; +} + +WinSalObject* ImplFindSalObject( HWND hWndChild ) +{ + SalData* pSalData = GetSalData(); + WinSalObject* pObject = pSalData->mpFirstObject; + while ( pObject ) + { + if ( ImplIsSysWindowOrChild( pObject->mhWndChild, hWndChild ) ) + return pObject; + + pObject = pObject->mpNextObject; + } + + return nullptr; +} + +static WinSalFrame* ImplFindSalObjectFrame( HWND hWnd ) +{ + WinSalFrame* pFrame = nullptr; + WinSalObject* pObject = ImplFindSalObject( hWnd ); + if ( pObject ) + { + // find matching frame + HWND hWnd2 = ::GetParent( pObject->mhWnd ); + pFrame = GetSalData()->mpFirstFrame; + while ( pFrame ) + { + if ( pFrame->mhWnd == hWnd2 ) + break; + + pFrame = pFrame->mpNextFrame; + } + } + + return pFrame; +} + +static LRESULT CALLBACK SalSysMsgProc( int nCode, WPARAM wParam, LPARAM lParam ) +{ + // Used for Unicode and none Unicode + SalData* pSalData = GetSalData(); + + if ( (nCode >= 0) && lParam ) + { + CWPSTRUCT* pData = reinterpret_cast<CWPSTRUCT*>(lParam); + if ( (pData->message != WM_KEYDOWN) && + (pData->message != WM_KEYUP) ) + pSalData->mnSalObjWantKeyEvt = 0; + + + // check if we need to process data for a SalObject-window + WinSalObject* pObject; + if ( pData->message == WM_SETFOCUS ) + { + pObject = ImplFindSalObject( pData->hwnd ); + if ( pObject ) + { + pObject->mhLastFocusWnd = pData->hwnd; + if ( ImplSalYieldMutexTryToAcquire() ) + { + pObject->CallCallback( SalObjEvent::GetFocus ); + ImplSalYieldMutexRelease(); + } + else + { + bool const ret = PostMessageW(pObject->mhWnd, SALOBJ_MSG_POSTFOCUS, 0, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + } + } + else if ( pData->message == WM_KILLFOCUS ) + { + pObject = ImplFindSalObject( pData->hwnd ); + if ( pObject && !ImplFindSalObject( reinterpret_cast<HWND>(pData->wParam) ) ) + { + // only call LoseFocus, if truly no child window gets the focus + if ( !pData->wParam || !ImplFindSalObject( reinterpret_cast<HWND>(pData->wParam) ) ) + { + if ( ImplSalYieldMutexTryToAcquire() ) + { + pObject->CallCallback( SalObjEvent::LoseFocus ); + ImplSalYieldMutexRelease(); + } + else + { + bool const ret = PostMessageW(pObject->mhWnd, SALOBJ_MSG_POSTFOCUS, 0, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + } + else + pObject->mhLastFocusWnd = reinterpret_cast<HWND>(pData->wParam); + } + } + } + + return CallNextHookEx( pSalData->mhSalObjMsgHook, nCode, wParam, lParam ); +} + +bool ImplSalPreDispatchMsg( const MSG* pMsg ) +{ + // Used for Unicode and none Unicode + SalData* pSalData = GetSalData(); + WinSalObject* pObject; + + if ( (pMsg->message == WM_LBUTTONDOWN) || + (pMsg->message == WM_RBUTTONDOWN) || + (pMsg->message == WM_MBUTTONDOWN) ) + { + ImplSalYieldMutexAcquireWithWait(); + pObject = ImplFindSalObject( pMsg->hwnd ); + if ( pObject && !pObject->IsMouseTransparent() ) + { + bool const ret = PostMessageW(pObject->mhWnd, SALOBJ_MSG_TOTOP, 0, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + ImplSalYieldMutexRelease(); + } + + if ( (pMsg->message == WM_KEYDOWN) || + (pMsg->message == WM_KEYUP) ) + { + // process KeyEvents even if the control does not process them itself + // SysKeys are processed as WM_SYSCOMMAND + // Char-Events are not processed, as they are not accelerator-relevant + bool bWantedKeyCode = false; + // A-Z, 0-9 only when combined with the Control-key + if ( ((pMsg->wParam >= 65) && (pMsg->wParam <= 90)) || + ((pMsg->wParam >= 48) && (pMsg->wParam <= 57)) ) + { + if ( GetKeyState( VK_CONTROL ) & 0x8000 ) + bWantedKeyCode = true; + } + else if ( ((pMsg->wParam >= VK_F1) && (pMsg->wParam <= VK_F24)) || + ((pMsg->wParam >= VK_SPACE) && (pMsg->wParam <= VK_HELP)) || + (pMsg->wParam == VK_BACK) || (pMsg->wParam == VK_TAB) || + (pMsg->wParam == VK_CLEAR) || (pMsg->wParam == VK_RETURN) || + (pMsg->wParam == VK_ESCAPE) ) + bWantedKeyCode = true; + if ( bWantedKeyCode ) + { + ImplSalYieldMutexAcquireWithWait(); + pObject = ImplFindSalObject( pMsg->hwnd ); + if ( pObject ) + pSalData->mnSalObjWantKeyEvt = pMsg->wParam; + ImplSalYieldMutexRelease(); + } + } + // check WM_SYSCHAR, to activate menu with Alt key + else if ( pMsg->message == WM_SYSCHAR ) + { + pSalData->mnSalObjWantKeyEvt = 0; + + sal_uInt16 nKeyCode = LOWORD( pMsg->wParam ); + // only 0-9 and A-Z + if ( ((nKeyCode >= 48) && (nKeyCode <= 57)) || + ((nKeyCode >= 65) && (nKeyCode <= 90)) || + ((nKeyCode >= 97) && (nKeyCode <= 122)) ) + { + bool bRet = false; + ImplSalYieldMutexAcquireWithWait(); + pObject = ImplFindSalObject( pMsg->hwnd ); + if ( pObject ) + { + if ( pMsg->hwnd == ::GetFocus() ) + { + WinSalFrame* pFrame = ImplFindSalObjectFrame( pMsg->hwnd ); + if ( pFrame ) + { + if ( ImplHandleSalObjSysCharMsg( pFrame->mhWnd, pMsg->wParam, pMsg->lParam ) ) + bRet = true; + } + } + } + ImplSalYieldMutexRelease(); + if ( bRet ) + return true; + } + } + else + pSalData->mnSalObjWantKeyEvt = 0; + + return false; +} + +void ImplSalPostDispatchMsg( const MSG* pMsg ) +{ + // Used for Unicode and none Unicode + SalData *pSalData = GetSalData(); + + if ( (pMsg->message == WM_KEYDOWN) || (pMsg->message == WM_KEYUP) ) + { + if ( pSalData->mnSalObjWantKeyEvt == pMsg->wParam ) + { + pSalData->mnSalObjWantKeyEvt = 0; + if ( pMsg->hwnd == ::GetFocus() ) + { + ImplSalYieldMutexAcquireWithWait(); + WinSalFrame* pFrame = ImplFindSalObjectFrame( pMsg->hwnd ); + if ( pFrame ) + ImplHandleSalObjKeyMsg( pFrame->mhWnd, pMsg->message, pMsg->wParam, pMsg->lParam ); + ImplSalYieldMutexRelease(); + } + } + } + + pSalData->mnSalObjWantKeyEvt = 0; +} + +static LRESULT CALLBACK SalSysObjWndProcW(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) +{ + switch( nMsg ) + { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + PAINTSTRUCT aPs; + BeginPaint( hWnd, &aPs ); + EndPaint( hWnd, &aPs ); + } + return 0; + + case WM_PARENTNOTIFY: + if (UINT nNotifyMsg = LOWORD(wParam); + (nNotifyMsg == WM_LBUTTONDOWN) || + (nNotifyMsg == WM_RBUTTONDOWN) || + (nNotifyMsg == WM_MBUTTONDOWN) ) + { + ImplSalYieldMutexAcquireWithWait(); + WinSalObject* pSysObj = GetSalObjWindowPtr(hWnd); + if ( pSysObj && !pSysObj->IsMouseTransparent() ) + pSysObj->CallCallback( SalObjEvent::ToTop ); + ImplSalYieldMutexRelease(); + } + break; + + case WM_MOUSEACTIVATE: + ImplSalYieldMutexAcquireWithWait(); + if (WinSalObject* pSysObj = GetSalObjWindowPtr(hWnd); + pSysObj && !pSysObj->IsMouseTransparent()) + { + bool const ret = PostMessageW( hWnd, SALOBJ_MSG_TOTOP, 0, 0 ); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + ImplSalYieldMutexRelease(); + break; + + case SALOBJ_MSG_TOTOP: + if ( ImplSalYieldMutexTryToAcquire() ) + { + if (WinSalObject* pSysObj = GetSalObjWindowPtr(hWnd)) + pSysObj->CallCallback(SalObjEvent::ToTop); + ImplSalYieldMutexRelease(); + return 0; + } + else + { + bool const ret = PostMessageW( hWnd, SALOBJ_MSG_TOTOP, 0, 0 ); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + break; + + case SALOBJ_MSG_POSTFOCUS: + if ( ImplSalYieldMutexTryToAcquire() ) + { + WinSalObject* pSysObj = GetSalObjWindowPtr(hWnd); + HWND hFocusWnd = ::GetFocus(); + SalObjEvent nEvent; + if ( hFocusWnd && ImplIsSysWindowOrChild( hWnd, hFocusWnd ) ) + nEvent = SalObjEvent::GetFocus; + else + nEvent = SalObjEvent::LoseFocus; + pSysObj->CallCallback( nEvent ); + ImplSalYieldMutexRelease(); + } + else + { + bool const ret = PostMessageW(hWnd, SALOBJ_MSG_POSTFOCUS, 0, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + } + return 0; + + case WM_SIZE: + if (HWND hWndChild = GetWindow(hWnd, GW_CHILD)) + { + SetWindowPos( hWndChild, + nullptr, 0, 0, static_cast<int>(LOWORD( lParam )), static_cast<int>(HIWORD( lParam )), + SWP_NOZORDER | SWP_NOACTIVATE ); + } + return 0; + + case WM_CREATE: + { + // Save the window instance at the window handle. + CREATESTRUCTW* pStruct = reinterpret_cast<CREATESTRUCTW*>(lParam); + WinSalObject* pSysObj = static_cast<WinSalObject*>(pStruct->lpCreateParams); + SetSalObjWindowPtr( hWnd, pSysObj ); + // set HWND already here, + // as instance data might be used during CreateWindow() events + pSysObj->mhWnd = hWnd; + return 0; + } + } + + return DefWindowProcW(hWnd, nMsg, wParam, lParam); +} + +static LRESULT CALLBACK SalSysObjChildWndProcW(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) +{ + switch( nMsg ) + { + // clear background for plugins + case WM_ERASEBKGND: + if (WinSalObject* pSysObj = GetSalObjWindowPtr(GetParent(hWnd)); + pSysObj && !pSysObj->IsEraseBackgroundEnabled()) + { + // do not erase background + return 1; + } + break; + + case WM_PAINT: + { + PAINTSTRUCT aPs; + BeginPaint( hWnd, &aPs ); + EndPaint( hWnd, &aPs ); + } + return 0; + + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + if (WinSalObject* pSysObj = GetSalObjWindowPtr(GetParent(hWnd)); + pSysObj && pSysObj->IsMouseTransparent()) + { + // forward mouse events to parent frame + HWND hWndParent = GetParent(pSysObj->mhWnd); + + // transform coordinates + POINT pt; + pt.x = static_cast<tools::Long>(LOWORD(lParam)); + pt.y = static_cast<tools::Long>(HIWORD(lParam)); + MapWindowPoints(hWnd, hWndParent, &pt, 1); + lParam = MAKELPARAM(static_cast<WORD>(pt.x), static_cast<WORD>(pt.y)); + + return SendMessageW(hWndParent, nMsg, wParam, lParam); + } + break; + } + + return DefWindowProcW(hWnd, nMsg, wParam, lParam); +} + +SalObject* ImplSalCreateObject( WinSalInstance* pInst, WinSalFrame* pParent ) +{ + SalData* pSalData = GetSalData(); + + // install hook, if it is the first SalObject + if ( !pSalData->mpFirstObject ) + { + pSalData->mhSalObjMsgHook = SetWindowsHookExW( WH_CALLWNDPROC, + SalSysMsgProc, + pSalData->mhInst, + pSalData->mnAppThreadId ); + } + + if ( !pSalData->mbObjClassInit ) + { + WNDCLASSEXW aWndClassEx; + aWndClassEx.cbSize = sizeof( aWndClassEx ); + aWndClassEx.style = 0; + aWndClassEx.lpfnWndProc = SalSysObjWndProcW; + aWndClassEx.cbClsExtra = 0; + aWndClassEx.cbWndExtra = SAL_OBJECT_WNDEXTRA; + aWndClassEx.hInstance = pSalData->mhInst; + aWndClassEx.hIcon = nullptr; + aWndClassEx.hIconSm = nullptr; + aWndClassEx.hCursor = LoadCursor( nullptr, IDC_ARROW ); + aWndClassEx.hbrBackground = nullptr; + aWndClassEx.lpszMenuName = nullptr; + aWndClassEx.lpszClassName = SAL_OBJECT_CLASSNAMEW; + if ( RegisterClassExW( &aWndClassEx ) ) + { + // Clean background first because of plugins. + aWndClassEx.cbWndExtra = 0; + aWndClassEx.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); + aWndClassEx.lpfnWndProc = SalSysObjChildWndProcW; + aWndClassEx.lpszClassName = SAL_OBJECT_CHILDCLASSNAMEW; + if ( RegisterClassExW( &aWndClassEx ) ) + pSalData->mbObjClassInit = true; + } + } + + if ( pSalData->mbObjClassInit ) + { + WinSalObject* pObject = new WinSalObject; + + // #135235# Clip siblings of this + // SystemChildWindow. Otherwise, DXCanvas (using a hidden + // SystemChildWindow) clobbers applets/plugins during + // animations . + HWND hWnd = CreateWindowExW( 0, SAL_OBJECT_CLASSNAMEW, L"", + WS_CHILD | WS_CLIPSIBLINGS, 0, 0, 0, 0, + pParent->mhWnd, nullptr, + pInst->mhInst, pObject ); + + HWND hWndChild = nullptr; + if ( hWnd ) + { + // #135235# Explicitly stack SystemChildWindows in + // the order they're created - since there's no notion + // of zorder. + SetWindowPos(hWnd,HWND_TOP,0,0,0,0, + SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOREDRAW|SWP_NOSIZE); + hWndChild = CreateWindowExW( 0, SAL_OBJECT_CHILDCLASSNAMEW, L"", + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE, + 0, 0, 0, 0, + hWnd, nullptr, + pInst->mhInst, nullptr ); + } + + if ( !hWndChild ) + { + SAL_WARN("vcl", "CreateWindowExW failed: " << WindowsErrorString(GetLastError())); + + delete pObject; + return nullptr; + } + + if ( hWnd ) + { + pObject->mhWnd = hWnd; + pObject->mhWndChild = hWndChild; + pObject->maSysData.hWnd = hWndChild; + return pObject; + } + } + + return nullptr; +} + +WinSalObject::WinSalObject() +{ + SalData* pSalData = GetSalData(); + + mhWnd = nullptr; + mhWndChild = nullptr; + mhLastFocusWnd = nullptr; + mpStdClipRgnData = nullptr; + + // Insert object in objectlist + mpNextObject = pSalData->mpFirstObject; + pSalData->mpFirstObject = this; +} + +WinSalObject::~WinSalObject() +{ + SalData* pSalData = GetSalData(); + + // remove frame from framelist + if ( this == pSalData->mpFirstObject ) + { + pSalData->mpFirstObject = mpNextObject; + + // remove hook, if it is the last SalObject + if ( !pSalData->mpFirstObject ) + UnhookWindowsHookEx( pSalData->mhSalObjMsgHook ); + } + else + { + WinSalObject* pTempObject = pSalData->mpFirstObject; + while ( pTempObject->mpNextObject != this ) + pTempObject = pTempObject->mpNextObject; + + pTempObject->mpNextObject = mpNextObject; + } + + // destroy cache data + delete [] reinterpret_cast<BYTE*>(mpStdClipRgnData); + + HWND hWndParent = ::GetParent( mhWnd ); + + if ( mhWndChild ) + DestroyWindow( mhWndChild ); + if ( mhWnd ) + DestroyWindow( mhWnd ); + + // reset palette, if no external child window is left, + // as they might have overwritten our palette + if ( hWndParent && + ::GetActiveWindow() == hWndParent && + !GetWindow( hWndParent, GW_CHILD ) ) + SendMessageW( hWndParent, SAL_MSG_FORCEPALETTE, 0, 0 ); +} + +void WinSalObject::ResetClipRegion() +{ + SetWindowRgn( mhWnd, nullptr, TRUE ); +} + +void WinSalObject::BeginSetClipRegion( sal_uInt32 nRectCount ) +{ + sal_uLong nRectBufSize = sizeof(RECT)*nRectCount; + if ( nRectCount < SAL_CLIPRECT_COUNT ) + { + if ( !mpStdClipRgnData ) + mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]); + mpClipRgnData = mpStdClipRgnData; + } + else + mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]); + mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER ); + mpClipRgnData->rdh.iType = RDH_RECTANGLES; + mpClipRgnData->rdh.nCount = nRectCount; + mpClipRgnData->rdh.nRgnSize = nRectBufSize; + SetRectEmpty( &(mpClipRgnData->rdh.rcBound) ); + mpNextClipRect = reinterpret_cast<RECT*>(&(mpClipRgnData->Buffer)); + mbFirstClipRect = true; +} + +void WinSalObject::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + RECT* pRect = mpNextClipRect; + RECT* pBoundRect = &(mpClipRgnData->rdh.rcBound); + tools::Long nRight = nX + nWidth; + tools::Long nBottom = nY + nHeight; + + if ( mbFirstClipRect ) + { + pBoundRect->left = nX; + pBoundRect->top = nY; + pBoundRect->right = nRight; + pBoundRect->bottom = nBottom; + mbFirstClipRect = false; + } + else + { + if ( nX < pBoundRect->left ) + pBoundRect->left = static_cast<int>(nX); + + if ( nY < pBoundRect->top ) + pBoundRect->top = static_cast<int>(nY); + + if ( nRight > pBoundRect->right ) + pBoundRect->right = static_cast<int>(nRight); + + if ( nBottom > pBoundRect->bottom ) + pBoundRect->bottom = static_cast<int>(nBottom); + } + + pRect->left = static_cast<int>(nX); + pRect->top = static_cast<int>(nY); + pRect->right = static_cast<int>(nRight); + pRect->bottom = static_cast<int>(nBottom); + mpNextClipRect++; +} + +void WinSalObject::EndSetClipRegion() +{ + HRGN hRegion; + + // create a ClipRegion from the vcl::Region data + if ( mpClipRgnData->rdh.nCount == 1 ) + { + RECT* pRect = &(mpClipRgnData->rdh.rcBound); + hRegion = CreateRectRgn( pRect->left, pRect->top, + pRect->right, pRect->bottom ); + } + else + { + sal_uLong nSize = mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER); + hRegion = ExtCreateRegion( nullptr, nSize, mpClipRgnData ); + if ( mpClipRgnData != mpStdClipRgnData ) + delete [] reinterpret_cast<BYTE*>(mpClipRgnData); + } + + SAL_WARN_IF( !hRegion, "vcl", "SalObject::EndSetClipRegion() - Can't create ClipRegion" ); + SetWindowRgn( mhWnd, hRegion, TRUE ); +} + +void WinSalObject::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) +{ + sal_uLong nStyle = 0; + bool bVisible = (GetWindowStyle( mhWnd ) & WS_VISIBLE) != 0; + if ( bVisible ) + { + ShowWindow( mhWnd, SW_HIDE ); + nStyle |= SWP_SHOWWINDOW; + } + SetWindowPos( mhWnd, nullptr, + static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight), + SWP_NOZORDER | SWP_NOACTIVATE | nStyle ); +} + +void WinSalObject::Show( bool bVisible ) +{ + if ( bVisible ) + ShowWindow( mhWnd, SW_SHOWNORMAL ); + else + ShowWindow( mhWnd, SW_HIDE ); +} + +void WinSalObject::Enable( bool bEnable ) +{ + EnableWindow( mhWnd, bEnable ); +} + +void WinSalObject::GrabFocus() +{ + if ( mhLastFocusWnd && + IsWindow( mhLastFocusWnd ) && + ImplIsSysWindowOrChild( mhWndChild, mhLastFocusWnd ) ) + ::SetFocus( mhLastFocusWnd ); + else + ::SetFocus( mhWndChild ); +} + +const SystemEnvData* WinSalObject::GetSystemData() const +{ + return &maSysData; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |