Files
odfstudio/vcl/qt5/QtInstance.cxx
T
Michael Weghorn 6a023bacb9 Return std::unique_ptr in Application::CreateMessageDialog
The caller of Application::CreateMessageDialog owns the
returned message dialog.

So far, it was returned by a plain weld::MessageDialog*.

Return a unique_ptr instead, to make clear the ownership
semantics. (Also adjust SalInstance::CreateMessageDialog
accordingly, which is called by Application::CreateMessageDialog
and implements the actual logic to create a message dialog.)

Adjust callers where needed.
Most callers were already using a smart pointer for the
returned message dialog anyway.

Change-Id: I7c06bb24b611bd67f5af89800adb13bf22dcc6fa
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211013
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
Tested-by: Jenkins
2026-09-16 10:35:19 +02:00

1020 lines
34 KiB
C++

/* -*- 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/.
*
* 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_emscripten.h>
#include <config_vclplug.h>
#include <QtAccessibleRegistry.hxx>
#include <QtAccessibleInterimChildWidget.hxx>
#include <QtClipboard.hxx>
#include <QtDragAndDrop.hxx>
#include <QtFilePicker.hxx>
#include <QtFrame.hxx>
#include <QtInstance.hxx>
#include <QtInstance.moc>
#include <QtInstanceBuilder.hxx>
#include <QtInstanceColorChooserDialog.hxx>
#include <QtInstanceMessageDialog.hxx>
#include <QtInstanceWidget.hxx>
#include <QtMenu.hxx>
#include <QtObject.hxx>
#include <QtOpenGLContext.hxx>
#include <QtSalInstance.hxx>
#include <QtSalInstanceBuilderWrapper.hxx>
#if USE_HEADLESS_CODE
#include <QtSvpSalInstance.hxx>
#endif
#include <QtSystem.hxx>
#include <QtTimer.hxx>
#include <SalYieldMutex.hxx>
#include <salvtables.hxx>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && ENABLE_GSTREAMER_1_0 && QT5_HAVE_GOBJECT
#include <unx/gstsink.hxx>
#endif
#include <vclpluginapi.h>
#if USE_HEADLESS_CODE
#include <unx/gendata.hxx>
#elif defined _WIN32
#include <win/saldata.hxx>
#endif
#include <comphelper/emscriptenthreading.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <osl/process.h>
#include <o3tl/temporary.hxx>
#include <o3tl/unreachable.hxx>
#include <sal/log.hxx>
#include <tools/debug.hxx>
#include <vcl/qt/QtUtils.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/sysdata.hxx>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <QtCore/QAbstractEventDispatcher>
#include <QtCore/QLibraryInfo>
#include <QtCore/QThread>
#include <QtGui/QScreen>
#include <QtGui/QStyleHints>
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QtWidgets/QMessageBox>
#ifdef __EMSCRIPTEN__
#include <QtCore/QtPlugin>
Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
#if defined DISABLE_DYNLOADING && ENABLE_QT6
#include <QtCore/QtResource>
#endif
#endif
#if defined __EMSCRIPTEN__ && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI \
&& !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
#include <emscripten/promise.h>
#include <emscripten/proxying.h>
#include <emscripten/threading.h>
#include <pthread.h>
#endif
#include <condition_variable>
#include <mutex>
namespace
{
/// TODO: not much Qt specific here? could be generalised, esp. for OSX...
/// this subclass allows for the transfer of a closure for running on the main
/// thread, to handle all the thread affine stuff in Qt; the SolarMutex is
/// "loaned" to the main thread for the execution of the closure.
/// @note it doesn't work to just use "emit" and signals/slots to move calls to
/// the main thread, because the other thread has the SolarMutex; the other
/// thread (typically) cannot release SolarMutex, because then the main thread
/// will handle all sorts of events and whatnot; this design ensures that the
/// main thread only runs the passed closure (unless the closure releases
/// SolarMutex itself, which should probably be avoided).
class QtYieldMutex : public SalYieldMutex
{
public:
/// flag only accessed on main thread:
/// main thread has "borrowed" SolarMutex from another thread
bool m_bNoYieldLock = false;
/// members for communication from non-main thread to main thread
std::mutex m_RunInMainMutex;
std::condition_variable m_InMainCondition;
bool m_isWakeUpMain = false;
std::function<void()> m_Closure; ///< code for main thread to run
/// members for communication from main thread to non-main thread
std::condition_variable m_ResultCondition;
bool m_isResultReady = false;
virtual bool IsCurrentThread() const override;
virtual void doAcquire(sal_uInt32 nLockCount) override;
virtual sal_uInt32 doRelease(bool const bUnlockAll) override;
};
}
bool QtYieldMutex::IsCurrentThread() const
{
QtInstance& rQtInstance = GetQtInstance();
if (rQtInstance.IsMainThread() && m_bNoYieldLock)
{
return true; // main thread has borrowed SolarMutex
}
return SalYieldMutex::IsCurrentThread();
}
void QtYieldMutex::doAcquire(sal_uInt32 nLockCount)
{
QtInstance& rQtInstance = GetQtInstance();
if (!rQtInstance.IsMainThread())
{
SalYieldMutex::doAcquire(nLockCount);
return;
}
if (m_bNoYieldLock)
{
return; // special case for main thread: borrowed from other thread
}
do // main thread acquire...
{
std::function<void()> func; // copy of closure on thread stack
{
std::unique_lock<std::mutex> g(m_RunInMainMutex);
if (m_aMutex.tryToAcquire())
{
// if there's a closure, the other thread holds m_aMutex
assert(!m_Closure);
m_isWakeUpMain = false;
--nLockCount; // have acquired once!
++m_nCount;
break;
}
m_InMainCondition.wait(g, [this]() { return m_isWakeUpMain; });
m_isWakeUpMain = false;
std::swap(func, m_Closure);
}
if (func)
{
assert(!m_bNoYieldLock);
m_bNoYieldLock = true; // execute closure with borrowed SolarMutex
func();
m_bNoYieldLock = false;
std::scoped_lock<std::mutex> g(m_RunInMainMutex);
assert(!m_isResultReady);
m_isResultReady = true;
m_ResultCondition.notify_all(); // unblock other thread
}
} while (true);
SalYieldMutex::doAcquire(nLockCount);
}
sal_uInt32 QtYieldMutex::doRelease(bool const bUnlockAll)
{
QtInstance& rQtInstance = GetQtInstance();
if (rQtInstance.IsMainThread() && m_bNoYieldLock)
{
return 1; // dummy value
}
std::scoped_lock<std::mutex> g(m_RunInMainMutex);
// read m_nCount before doRelease (it's guarded by m_aMutex)
bool const isReleased(bUnlockAll || m_nCount == 1);
sal_uInt32 nCount = SalYieldMutex::doRelease(bUnlockAll);
if (isReleased && !rQtInstance.IsMainThread())
{
m_isWakeUpMain = true;
m_InMainCondition.notify_all(); // unblock main thread
}
return nCount;
}
// this could be abstracted to be independent of Qt by passing in the
// event-trigger as another function parameter...
// it could also be a template of the return type, then it could return the
// result of func... but then how to handle the result in doAcquire?
void QtInstance::RunInMainThread(std::function<void()> func)
{
DBG_TESTSOLARMUTEX();
if (IsMainThread())
{
func();
return;
}
#if defined __EMSCRIPTEN__ && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI \
&& !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
if (pthread_self() == m_emscriptenThreadingData->eventHandlerThread)
{
EmscriptenLightweightRunInMainThread(func);
return;
}
#endif
QtYieldMutex* const pMutex(static_cast<QtYieldMutex*>(GetYieldMutex()));
{
std::scoped_lock<std::mutex> g(pMutex->m_RunInMainMutex);
assert(!pMutex->m_Closure);
pMutex->m_Closure = std::move(func);
// unblock main thread in case it is blocked on condition
pMutex->m_isWakeUpMain = true;
pMutex->m_InMainCondition.notify_all();
}
TriggerUserEventProcessing();
{
std::unique_lock<std::mutex> g(pMutex->m_RunInMainMutex);
pMutex->m_ResultCondition.wait(g, [pMutex]() { return pMutex->m_isResultReady; });
pMutex->m_isResultReady = false;
}
}
void QtInstance::EmscriptenLightweightRunInMainThread_(std::function<void()> func)
{
#if defined __EMSCRIPTEN__ && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI \
&& !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
if (pthread_self() != emscripten_main_runtime_thread_id())
{
SolarMutexReleaser release;
emscripten_sync_run_in_main_runtime_thread(
EM_FUNC_SIG_RETURN_VALUE_V | EM_FUNC_SIG_WITH_N_PARAMETERS(1)
| EM_FUNC_SIG_SET_PARAM(0, EM_FUNC_SIG_PARAM_P),
+[](void* pf) {
DBG_TESTNOTSOLARMUTEX();
SolarMutexGuard g;
(*static_cast<std::function<void()>*>(pf))();
},
&func);
return;
}
#endif
func();
}
bool QtInstance::useCairo()
{
static bool bUseCairo = getenv("SAL_VCL_QT_USE_QFONT") == nullptr;
return bUseCairo;
}
QtInstance::QtInstance()
#if defined _WIN32
#if USE_HEADLESS_CODE
: WindowsInstance(std::make_unique<QtYieldMutex>(), new GenericUnixSalData)
#else
: WindowsInstance(std::make_unique<QtYieldMutex>(), new SalData)
#endif
#elif defined MACOSX
: MacInstance()
#else
: SalGenericInstance(std::make_unique<QtYieldMutex>(), new GenericUnixSalData)
#endif
, m_pTimer(nullptr)
, m_bSleeping(false)
, m_aUpdateStyleTimer("vcl::qt5 m_aUpdateStyleTimer")
, m_bUpdateFonts(false)
, m_pActivePopup(nullptr)
{
m_pQApplication = CreateQApplication();
#if defined __EMSCRIPTEN__ && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI \
&& !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
m_emscriptenThreadingData = &comphelper::emscriptenthreading::getData();
#endif
ImplSVData* pSVData = ImplGetSVData();
pSVData->maNWFData.mbDockingAreaSeparateTB = true;
pSVData->maNWFData.mbFlatMenu = true;
pSVData->maNWFData.mbRolloverMenubar = true;
pSVData->maNWFData.mbNoFocusRects = true;
pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true;
QStyle* style = QApplication::style();
pSVData->maNWFData.mnMenuFormatBorderX = style->pixelMetric(QStyle::PM_MenuPanelWidth)
+ style->pixelMetric(QStyle::PM_MenuHMargin);
pSVData->maNWFData.mnMenuFormatBorderY = style->pixelMetric(QStyle::PM_MenuPanelWidth)
+ style->pixelMetric(QStyle::PM_MenuVMargin);
// this one needs to be blocking, so that the handling in main thread
// is processed before the thread emitting the signal continues
connect(this, &QtInstance::ImplYieldSignal, this, &QtInstance::ImplYield,
Qt::BlockingQueuedConnection);
// this one needs to be queued non-blocking
// in order to have this event arriving to correct event processing loop
connect(this, &QtInstance::deleteObjectLaterSignal, this,
[](QObject* pObject) { QtInstance::deleteObjectLater(pObject); }, Qt::QueuedConnection);
m_aUpdateStyleTimer.SetTimeout(50);
m_aUpdateStyleTimer.SetInvokeHandler(LINK(this, QtInstance, updateStyleHdl));
QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread());
connect(dispatcher, &QAbstractEventDispatcher::awake, this, [this]() { m_bSleeping = false; });
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this,
[this]() { m_bSleeping = true; });
connect(QGuiApplication::inputMethod(), &QInputMethod::localeChanged, this,
&QtInstance::localeChanged);
for (const QScreen* pCurScreen : QApplication::screens())
connectQScreenSignals(pCurScreen);
connect(qApp, &QGuiApplication::primaryScreenChanged, this, &QtInstance::primaryScreenChanged);
connect(qApp, &QGuiApplication::screenAdded, this, &QtInstance::screenAdded);
connect(qApp, &QGuiApplication::screenRemoved, this, &QtInstance::screenRemoved);
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this,
&QtInstance::colorSchemeChanged);
#endif
#ifndef __EMSCRIPTEN__
m_bSupportsOpenGL = true;
#elif !HAVE_EMSCRIPTEN_JSPI
ImplGetSVData()->maAppData.m_bUseSystemLoop = true;
#endif
}
QtInstance::~QtInstance()
{
// force freeing the QApplication before freeing the arguments,
// as it uses references to the provided arguments!
m_pQApplication.reset();
#if defined __EMSCRIPTEN__ && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI \
&& !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
comphelper::emscriptenthreading::tearDown();
#endif
}
void QtInstance::AfterAppInit()
{
// set the default application icon via desktop file just on Wayland,
// as this otherwise overrides the individual desktop icons on X11.
if (QGuiApplication::platformName() == "wayland")
QGuiApplication::setDesktopFileName(QStringLiteral("libreoffice-startcenter"));
QGuiApplication::setLayoutDirection(AllSettings::GetLayoutRTL() ? Qt::RightToLeft
: Qt::LeftToRight);
}
void QtInstance::localeChanged()
{
SolarMutexGuard aGuard;
const vcl::Window* pFocusWindow = Application::GetFocusWindow();
SalFrame* const pFocusFrame = pFocusWindow ? pFocusWindow->ImplGetFrame() : nullptr;
if (!pFocusFrame)
return;
const LanguageTag aTag(
toOUString(QGuiApplication::inputMethod()->locale().name().replace("_", "-")));
static_cast<QtFrame*>(pFocusFrame)->setInputLanguage(aTag.getLanguageType());
}
void QtInstance::deleteObjectLater(QObject* pObject) { pObject->deleteLater(); }
SalFrame* QtInstance::CreateChildFrame(SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle)
{
return CreateFrame(nStyle, nullptr);
}
SalFrame* QtInstance::CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle)
{
assert(!pParent || dynamic_cast<QtFrame*>(pParent));
return CreateFrame(nStyle, static_cast<QtFrame*>(pParent));
}
void QtInstance::DestroyFrame(SalFrame* pFrame)
{
if (pFrame)
{
assert(dynamic_cast<QtFrame*>(pFrame));
Q_EMIT deleteObjectLaterSignal(static_cast<QtFrame*>(pFrame));
}
}
SalObject* QtInstance::CreateObject(SalFrame* pParent, SystemWindowData*, bool bShow)
{
SolarMutexGuard aGuard;
assert(!pParent || dynamic_cast<QtFrame*>(pParent));
SalObject* pRet(nullptr);
RunInMainThread([&]() { pRet = new QtObject(static_cast<QtFrame*>(pParent), bShow); });
assert(pRet);
return pRet;
}
void QtInstance::DestroyObject(SalObject* pObject)
{
if (pObject)
{
assert(dynamic_cast<QtObject*>(pObject));
Q_EMIT deleteObjectLaterSignal(static_cast<QtObject*>(pObject));
}
}
std::unique_ptr<SalMenu> QtInstance::CreateMenu(bool bMenuBar, Menu* pVCLMenu)
{
SolarMutexGuard aGuard;
std::unique_ptr<SalMenu> pRet;
RunInMainThread([&pRet, bMenuBar, pVCLMenu]() {
QtMenu* pSalMenu = new QtMenu(bMenuBar);
pRet.reset(pSalMenu);
pSalMenu->SetMenu(pVCLMenu);
});
assert(pRet);
return pRet;
}
std::unique_ptr<SalMenuItem> QtInstance::CreateMenuItem(const SalItemParams& rItemData)
{
return std::unique_ptr<SalMenuItem>(new QtMenuItem(&rItemData));
}
SalTimer* QtInstance::CreateSalTimer()
{
m_pTimer = new QtTimer();
return m_pTimer;
}
SalSystem* QtInstance::CreateSalSystem() { return new QtSystem; }
bool QtInstance::ImplYield(bool bWait, bool bHandleAllCurrentEvents)
{
// Re-acquire the guard for user events when called via Q_EMIT ImplYieldSignal
SolarMutexGuard aGuard;
bool wasEvent = DispatchUserEvents(bHandleAllCurrentEvents);
if (!bHandleAllCurrentEvents && wasEvent)
return true;
/**
* Quoting the Qt docs: [QAbstractEventDispatcher::processEvents] processes
* pending events that match flags until there are no more events to process.
*/
SolarMutexReleaser aReleaser;
QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread());
if (bWait && !wasEvent)
wasEvent = dispatcher->processEvents(QEventLoop::WaitForMoreEvents);
else
wasEvent = dispatcher->processEvents(QEventLoop::AllEvents) || wasEvent;
return wasEvent;
}
bool QtInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
{
bool bWasEvent = false;
if (qApp->thread() == QThread::currentThread())
{
bWasEvent = ImplYield(bWait, bHandleAllCurrentEvents);
if (bWasEvent)
m_aWaitingYieldCond.set();
}
#if defined __EMSCRIPTEN__ && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI \
&& !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
else if (pthread_self() == m_emscriptenThreadingData->eventHandlerThread)
{
SolarMutexReleaser release;
struct Args
{
QtInstance* This;
bool bWait;
bool bHandleAllCurrentEvents;
bool& bWasEvent;
};
(void)emscripten_promise_await(emscripten_proxy_promise(
m_emscriptenThreadingData->proxyingQueue.queue, emscripten_main_runtime_thread_id(),
[](void* p) {
Args const& args = *static_cast<Args*>(p);
args.bWasEvent = args.This->DoYield(args.bWait, args.bHandleAllCurrentEvents);
},
&o3tl::temporary<Args>({ this, bWait, bHandleAllCurrentEvents, bWasEvent })));
}
#endif
else
{
{
SolarMutexReleaser aReleaser;
bWasEvent = Q_EMIT ImplYieldSignal(false, bHandleAllCurrentEvents);
}
if (!bWasEvent && bWait)
{
m_aWaitingYieldCond.reset();
SolarMutexReleaser aReleaser;
m_aWaitingYieldCond.wait();
bWasEvent = true;
}
}
return bWasEvent;
}
bool QtInstance::AnyInput(VclInputFlags nType)
{
bool bResult = false;
if (nType & VclInputFlags::TIMER)
bResult |= (m_pTimer && m_pTimer->remainingTime() == 0);
if (nType & VclInputFlags::OTHER)
bResult |= !m_bSleeping;
return bResult;
}
void QtInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {}
#ifndef __EMSCRIPTEN__
OpenGLContext* QtInstance::CreateOpenGLContext() { return new QtOpenGLContext; }
#endif
bool QtInstance::IsMainThread() const
{
return !qApp || (qApp->thread() == QThread::currentThread());
}
void QtInstance::TriggerUserEventProcessing()
{
QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(qApp->thread());
dispatcher->wakeUp();
}
void QtInstance::ProcessEvent(SalUserEvent aEvent)
{
#if defined __EMSCRIPTEN__ && ENABLE_QT6 && HAVE_EMSCRIPTEN_JSPI \
&& !HAVE_EMSCRIPTEN_PROXY_TO_PTHREAD
SolarMutexReleaser release;
(void)emscripten_promise_await(
emscripten_proxy_promise(m_emscriptenThreadingData->proxyingQueue.queue,
m_emscriptenThreadingData->eventHandlerThread,
[](void* p) {
auto& aEvent = *static_cast<SalUserEvent*>(p);
SolarMutexGuard g;
aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
},
&aEvent));
#else
aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
#endif
}
rtl::Reference<QtFilePicker>
QtInstance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
QFileDialog::FileMode eMode)
{
if (!IsMainThread())
{
SolarMutexGuard g;
rtl::Reference<QtFilePicker> pPicker;
RunInMainThread([&, this]() { pPicker = createPicker(context, eMode); });
assert(pPicker);
return pPicker;
}
return new QtFilePicker(context, eMode);
}
css::uno::Reference<css::ui::dialogs::XFilePicker2>
QtInstance::createFilePicker(const css::uno::Reference<css::uno::XComponentContext>& context)
{
return css::uno::Reference<css::ui::dialogs::XFilePicker2>(
createPicker(context, QFileDialog::ExistingFile));
}
css::uno::Reference<css::ui::dialogs::XFolderPicker2>
QtInstance::createFolderPicker(const css::uno::Reference<css::uno::XComponentContext>& context)
{
return css::uno::Reference<css::ui::dialogs::XFolderPicker2>(
createPicker(context, QFileDialog::Directory));
}
css::uno::Reference<css::datatransfer::clipboard::XClipboard>
QtInstance::CreateClipboard(ClipboardSelectionType eSelection)
{
// This could also use RunInMain, but SolarMutexGuard is enough
// since at this point we're not accessing the clipboard, just get the
// accessor to the clipboard.
SolarMutexGuard aGuard;
auto it = m_aClipboards.find(eSelection);
if (it != m_aClipboards.end())
return it->second;
rtl::Reference<QtClipboard> pClipboard = EmscriptenLightweightRunInMainThread([&eSelection] {
assert(QApplication::clipboard()->thread() == qApp->thread());
if (QtClipboard::isSupported(toQClipboardMode(eSelection)))
return rtl::Reference<QtClipboard>(new QtClipboard(eSelection));
SAL_WARN("vcl.qt", "Ignoring unsupported selection type");
return rtl::Reference<QtClipboard>();
});
if (pClipboard.is())
m_aClipboards[eSelection] = pClipboard;
return pClipboard;
}
css::uno::Reference<css::datatransfer::dnd::XDragSource>
QtInstance::ImplCreateDragSource(const SystemEnvData& rSysEnv)
{
return new QtDragSource(static_cast<QtFrame*>(rSysEnv.pSalFrame));
}
css::uno::Reference<css::datatransfer::dnd::XDropTarget>
QtInstance::ImplCreateDropTarget(const SystemEnvData& rSysEnv)
{
rtl::Reference<QtDropTarget> pDropTarget = new QtDropTarget;
QtFrame* pFrame = static_cast<QtFrame*>(rSysEnv.pSalFrame);
pFrame->registerDropTarget(pDropTarget.get());
pDropTarget->setActive(true);
return pDropTarget;
}
Platform QtInstance::GetPlatform() const
{
const QString sPlatformName = QGuiApplication::platformName();
if (sPlatformName == u"wayland")
return Platform::Wayland;
if (sPlatformName == u"xcb")
return Platform::Xcb;
if (sPlatformName == u"wasm")
return Platform::WASM;
if (sPlatformName == u"windows")
return Platform::Windows;
if (sPlatformName == u"cocoa")
return Platform::Mac;
assert(false && "Unsupported qt VCL platform");
return Platform::Other;
}
Toolkit QtInstance::GetToolkit() const { return Toolkit::Qt; }
OUString QtInstance::getToolkitId() const
{
return u"qt"_ustr + OUString::number(QT_VERSION_MAJOR);
}
OUString QtInstance::GetToolkitName() const
{
return getToolkitId() + u" (" + getRenderingBackendName() + u"+"
+ toOUString(QGuiApplication::platformName()) + u")";
};
IMPL_LINK_NOARG(QtInstance, updateStyleHdl, Timer*, void)
{
SolarMutexGuard aGuard;
SalFrame* pFrame = anyFrame();
if (pFrame)
{
pFrame->CallCallback(SalEvent::SettingsChanged, nullptr);
if (m_bUpdateFonts)
{
pFrame->CallCallback(SalEvent::FontChanged, nullptr);
m_bUpdateFonts = false;
}
}
}
void QtInstance::UpdateStyle(bool bFontsChanged)
{
if (bFontsChanged)
m_bUpdateFonts = true;
if (!m_aUpdateStyleTimer.IsActive())
m_aUpdateStyleTimer.Start();
}
void* QtInstance::CreateGStreamerSink(const SystemChildWindow* pWindow)
{
// As of 2021-09, qt-gstreamer is unmaintained and there is no Qt 6 video sink
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && ENABLE_GSTREAMER_1_0 && QT5_HAVE_GOBJECT
auto pSymbol = gstElementFactoryNameSymbol();
if (!pSymbol)
return nullptr;
const SystemEnvData* pEnvData = pWindow->GetSystemData();
if (!pEnvData)
return nullptr;
if (GetQtInstance().GetPlatform() != Platform::Wayland)
return nullptr;
GstElement* pVideosink = pSymbol("qwidget5videosink", "qwidget5videosink");
if (pVideosink)
{
QWidget* pQWidget = static_cast<QWidget*>(pEnvData->pWidget);
g_object_set(G_OBJECT(pVideosink), "widget", pQWidget, nullptr);
}
else
{
SAL_WARN("vcl.qt", "Couldn't initialize qwidget5videosink."
" Video playback might not work as expected."
" Please install Qt5 packages for QtGStreamer.");
// with no videosink explicitly set, GStreamer will open its own (misplaced) window(s) to display video
}
return pVideosink;
#else
Q_UNUSED(pWindow);
return nullptr;
#endif
}
void QtInstance::connectQScreenSignals(const QScreen* pScreen)
{
connect(pScreen, &QScreen::orientationChanged, this, &QtInstance::orientationChanged);
connect(pScreen, &QScreen::virtualGeometryChanged, this, &QtInstance::virtualGeometryChanged);
}
void QtInstance::notifyDisplayChanged()
{
SolarMutexGuard aGuard;
SalFrame* pAnyFrame = anyFrame();
if (pAnyFrame)
pAnyFrame->CallCallback(SalEvent::DisplayChanged, nullptr);
}
void QtInstance::orientationChanged(Qt::ScreenOrientation) { notifyDisplayChanged(); }
void QtInstance::primaryScreenChanged(QScreen*) { notifyDisplayChanged(); }
void QtInstance::screenAdded(QScreen* pScreen)
{
connectQScreenSignals(pScreen);
if (QApplication::screens().size() == 1)
notifyDisplayChanged();
}
void QtInstance::screenRemoved(QScreen*) { notifyDisplayChanged(); }
void QtInstance::colorSchemeChanged() { UpdateStyle(false); }
void QtInstance::virtualGeometryChanged(const QRect&) { notifyDisplayChanged(); }
QtFrame* QtInstance::CreateFrame(SalFrameStyleFlags nStyle, QtFrame* pParent)
{
SolarMutexGuard aGuard;
QtFrame* pFrame = nullptr;
RunInMainThread([&]() { pFrame = DoCreateFrame(nStyle, pParent); });
return pFrame;
}
std::unique_ptr<QApplication> QtInstance::CreateQApplication()
{
OString aVersion(qVersion());
SAL_INFO("vcl.qt", "qt version string is " << aVersion);
const sal_uInt32 nParams = osl_getCommandArgCount();
OUString aParam, aBin;
osl_getExecutableFile(&aParam.pData);
osl_getSystemPathFromFileURL(aParam.pData, &aBin.pData);
OString aExec = OUStringToOString(aBin, osl_getThreadTextEncoding());
m_pFakeArgvFreeable.emplace_back(strdup(aExec.getStr()));
m_pFakeArgvFreeable.emplace_back(strdup("--nocrashhandler"));
for (sal_uInt32 nIdx = 0; nIdx < nParams; ++nIdx)
{
osl_getCommandArg(nIdx, &aParam.pData);
if (aParam == "-display" && nIdx + 1 < nParams)
{
++nIdx;
osl_getCommandArg(nIdx, &aParam.pData);
OString aDisplay = OUStringToOString(aParam, osl_getThreadTextEncoding());
m_pFakeArgvFreeable.emplace_back(strdup("-display"));
m_pFakeArgvFreeable.emplace_back(strdup(aDisplay.getStr()));
continue;
}
else if (aParam.startsWith(u"-style=") || aParam.startsWith(u"-stylesheet="))
{
const OString sStyleArg = OUStringToOString(aParam, osl_getThreadTextEncoding());
m_pFakeArgvFreeable.emplace_back(strdup(sStyleArg.getStr()));
}
}
m_nFakeArgc = m_pFakeArgvFreeable.size();
m_pFakeArgv.reset(new char*[m_nFakeArgc]);
for (int i = 0; i < m_nFakeArgc; i++)
m_pFakeArgv[i] = m_pFakeArgvFreeable[i].get();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// for Qt 6, setting Qt::AA_EnableHighDpiScaling and Qt::AA_UseHighDpiPixmaps
// is deprecated, they're always enabled
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
// for scaled icons in the native menus
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
// force Qt::HighDpiScaleFactorRoundingPolicy::Round, which is the Qt 5 default
// policy and prevents incorrect rendering with the Qt 6 default policy
// Qt::HighDpiScaleFactorRoundingPolicy::PassThrough (tdf#159915)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::Round);
std::unique_ptr<QApplication> pQApp
= std::make_unique<QApplication>(m_nFakeArgc, m_pFakeArgv.get());
QApplication::setQuitOnLastWindowClosed(false);
return pQApp;
}
bool QtInstance::DoExecute()
{
if (!Application::IsUseSystemEventLoop())
return false;
#if defined __EMSCRIPTEN__
// For Emscripten, QApplication::exec() will unwind the stack by throwing a JavaScript
// exception, so we need to manually undo the call of AcquireYieldMutex() done in InitVCL:
ReleaseYieldMutex(false);
#endif
QApplication::exec();
#if defined __EMSCRIPTEN__
O3TL_UNREACHABLE;
#endif
return true;
}
void QtInstance::DoQuit()
{
if (Application::IsUseSystemEventLoop())
QApplication::quit();
}
void QtInstance::setActivePopup(QtFrame* pFrame)
{
assert(!pFrame || pFrame->isPopup());
m_pActivePopup = pFrame;
}
bool QtInstance::noNativeControls()
{
static const bool bNoNative = (getenv("SAL_VCL_QT_NO_NATIVE") != nullptr);
return bNoNative;
}
bool QtInstance::noWeldedWidgets()
{
static const bool bNoWeldedWidgets = (getenv("SAL_VCL_QT_NO_WELDED_WIDGETS") != nullptr);
return bNoWeldedWidgets;
}
bool QtInstance::isQtWeldingEnabled()
{
if (noWeldedWidgets())
return false;
// for now, require explicitly enabling use of QtInstanceBuilder via SAL_VCL_QT_USE_WELDED_WIDGETS
static const bool bUseWeldedWidgets = (getenv("SAL_VCL_QT_USE_WELDED_WIDGETS") != nullptr);
return bUseWeldedWidgets;
}
QWidget* QtInstance::GetQWidget(weld::Widget* pWidget)
{
if (!pWidget)
return nullptr;
if (QtInstanceWidget* pQtInstanceWidget = dynamic_cast<QtInstanceWidget*>(pWidget))
return pQtInstanceWidget->getQWidget();
// the parent is not welded/not a native Qt widget; get QWidget via frame
if (SalInstanceWidget* pSalWidget = dynamic_cast<SalInstanceWidget*>(pWidget))
{
if (vcl::Window* pWindow = pSalWidget->getWidget())
{
if (QtFrame* pFrame = static_cast<QtFrame*>(pWindow->ImplGetFrame()))
return &pFrame->GetQWidget();
}
}
return nullptr;
}
std::unique_ptr<weld::Builder>
QtInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
{
if (!isQtWeldingEnabled())
return SalInstance::CreateBuilder(pParent, rUIRoot, rUIFile);
if (QtInstanceBuilder::IsUIFileSupported(rUIFile, pParent))
{
QWidget* pQtParent = GetQWidget(pParent);
return std::make_unique<QtInstanceBuilder>(pQtParent, rUIRoot, rUIFile);
}
return std::make_unique<QtSalInstanceBuilderWrapper>(pParent, rUIRoot, rUIFile);
}
std::unique_ptr<weld::Builder> QtInstance::CreateInterimBuilder(vcl::Window* pParent,
const OUString& rUIRoot,
const OUString& rUIFile,
bool bAllowCycleFocusOut)
{
if (!isQtWeldingEnabled() || !QtInstanceBuilder::IsInterimUIFileSupported(rUIFile))
return SalInstance::CreateInterimBuilder(pParent, rUIRoot, rUIFile, bAllowCycleFocusOut);
VclPtr<SystemChildWindow> pEmbedWindow = VclPtr<SystemChildWindow>::Create(pParent, 0);
pEmbedWindow->Show(true, ShowFlags::NoActivate);
pEmbedWindow->set_expand(true);
const SystemEnvData* pEnvData = pEmbedWindow->GetSystemData();
assert(pEnvData);
QWidget* pWidget = static_cast<QWidget*>(pEnvData->pWidget);
// set property to identify native Qt widget's a11y parent in QtAccessibleWidget::customFactory
rtl::Reference<comphelper::OAccessible> pParentAccessible = pEmbedWindow->GetAccessible();
QObject* pParentObject = QtAccessibleRegistry::getQObject(pParentAccessible);
assert(pParentObject);
pWidget->setProperty(QtAccessibleInterimChildWidget::PROPERTY_INTERIM_PARENT,
QVariant::fromValue(pParentObject));
pWidget->show();
return std::make_unique<QtInstanceBuilder>(pWidget, rUIRoot, rUIFile);
}
std::unique_ptr<weld::MessageDialog>
QtInstance::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType,
VclButtonsType eButtonsType, const OUString& rPrimaryMessage)
{
SolarMutexGuard g;
if (!IsMainThread())
{
std::unique_ptr<weld::MessageDialog> pDialog;
RunInMainThread([&] {
pDialog = CreateMessageDialog(pParent, eMessageType, eButtonsType, rPrimaryMessage);
});
return pDialog;
}
if (QtInstance::noWeldedWidgets())
{
return SalInstance::CreateMessageDialog(pParent, eMessageType, eButtonsType,
rPrimaryMessage);
}
else
{
QWidget* pQtParent = GetQWidget(pParent);
QMessageBox* pMessageBox = new QMessageBox(pQtParent);
pMessageBox->setText(toQString(rPrimaryMessage));
pMessageBox->setIcon(vclMessageTypeToQtIcon(eMessageType));
pMessageBox->setWindowTitle(toQString(GetStandardMessageDialogText(eMessageType)));
std::unique_ptr<QtInstanceMessageDialog> pDialog
= std::make_unique<QtInstanceMessageDialog>(pMessageBox);
pDialog->addStandardButtons(eButtonsType);
return pDialog;
}
}
std::unique_ptr<weld::ColorChooserDialog>
QtInstance::CreateColorChooserDialog(weld::Window* pParent, vcl::ColorPickerMode)
{
SolarMutexGuard g;
std::unique_ptr<weld::ColorChooserDialog> pColorChooserDialog;
RunInMainThread([&] {
QColorDialog* pDialog = new QColorDialog(GetQWidget(pParent));
pColorChooserDialog = std::make_unique<QtInstanceColorChooserDialog>(pDialog);
});
return pColorChooserDialog;
};
static void initResources()
{
#if defined __EMSCRIPTEN__ && defined DISABLE_DYNLOADING && ENABLE_QT6
// Make sure the resources from Qt6's plugins/platforms/libqwasm.a are not stripped out of a
// statically linked binary (and this code cannot be directly in extern "C" create_SalInstance,
// as the expansion of Q_INIT_RESOURCE contains extern function declarations that would then
// erroneously be C function declarations):
Q_INIT_RESOURCE(wasmfonts);
Q_INIT_RESOURCE(wasmwindow);
#endif
}
extern "C" {
VCLPLUG_QT_PUBLIC SalInstance* create_SalInstance()
{
initResources();
#if USE_HEADLESS_CODE
if (QtInstance::useCairo())
return new QtSvpSalInstance();
#endif
return new QtSalInstance();
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */