summaryrefslogtreecommitdiffstats
path: root/vcl/qt5
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/qt5')
-rw-r--r--vcl/qt5/QtAccessibleEventListener.cxx439
-rw-r--r--vcl/qt5/QtAccessibleRegistry.cxx45
-rw-r--r--vcl/qt5/QtAccessibleWidget.cxx1848
-rw-r--r--vcl/qt5/QtBitmap.cxx186
-rw-r--r--vcl/qt5/QtClipboard.cxx259
-rw-r--r--vcl/qt5/QtData.cxx227
-rw-r--r--vcl/qt5/QtDragAndDrop.cxx249
-rw-r--r--vcl/qt5/QtFilePicker.cxx983
-rw-r--r--vcl/qt5/QtFont.cxx190
-rw-r--r--vcl/qt5/QtFontFace.cxx217
-rw-r--r--vcl/qt5/QtFrame.cxx1520
-rw-r--r--vcl/qt5/QtGraphics.cxx106
-rw-r--r--vcl/qt5/QtGraphics_Controls.cxx1168
-rw-r--r--vcl/qt5/QtGraphics_GDI.cxx710
-rw-r--r--vcl/qt5/QtGraphics_Text.cxx235
-rw-r--r--vcl/qt5/QtInstance.cxx771
-rw-r--r--vcl/qt5/QtInstance_Print.cxx139
-rw-r--r--vcl/qt5/QtMainWindow.cxx46
-rw-r--r--vcl/qt5/QtMenu.cxx920
-rw-r--r--vcl/qt5/QtObject.cxx155
-rw-r--r--vcl/qt5/QtOpenGLContext.cxx154
-rw-r--r--vcl/qt5/QtPainter.cxx58
-rw-r--r--vcl/qt5/QtPrinter.cxx27
-rw-r--r--vcl/qt5/QtSvpGraphics.cxx114
-rw-r--r--vcl/qt5/QtSvpSurface.cxx91
-rw-r--r--vcl/qt5/QtSvpVirtualDevice.hxx36
-rw-r--r--vcl/qt5/QtSystem.cxx30
-rw-r--r--vcl/qt5/QtTimer.cxx62
-rw-r--r--vcl/qt5/QtTools.cxx123
-rw-r--r--vcl/qt5/QtTransferable.cxx361
-rw-r--r--vcl/qt5/QtVirtualDevice.cxx85
-rw-r--r--vcl/qt5/QtWidget.cxx1024
-rw-r--r--vcl/qt5/QtX11Support.cxx48
-rw-r--r--vcl/qt5/QtXAccessible.cxx30
34 files changed, 12656 insertions, 0 deletions
diff --git a/vcl/qt5/QtAccessibleEventListener.cxx b/vcl/qt5/QtAccessibleEventListener.cxx
new file mode 100644
index 0000000000..0bf4dcddbf
--- /dev/null
+++ b/vcl/qt5/QtAccessibleEventListener.cxx
@@ -0,0 +1,439 @@
+/* -*- 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 <QtAccessibleEventListener.hxx>
+#include <QtAccessibleRegistry.hxx>
+#include <QtTools.hxx>
+
+#include <sal/log.hxx>
+
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
+#include <com/sun/star/accessibility/TextSegment.hpp>
+
+#include <QtGui/QAccessible>
+#include <QtGui/QAccessibleTextSelectionEvent>
+
+using namespace css;
+using namespace css::accessibility;
+using namespace css::lang;
+using namespace css::uno;
+
+QtAccessibleEventListener::QtAccessibleEventListener(QtAccessibleWidget* pAccessibleWidget)
+ : m_pAccessibleWidget(pAccessibleWidget)
+{
+}
+
+void QtAccessibleEventListener::HandleStateChangedEvent(
+ QAccessibleInterface* pQAccessibleInterface,
+ const css::accessibility::AccessibleEventObject& rEvent)
+{
+ QAccessible::State aState;
+
+ sal_Int64 nState = 0;
+ rEvent.NewValue >>= nState;
+ // States in 'QAccessibleStateChangeEvent' indicate what states have changed, so if e.g.
+ // an object loses focus (not just if it gains it), 'focus' state needs to be set to 'true',
+ // so retrieve the old/previous value from the event if necessary.
+ if (nState == AccessibleStateType::INVALID)
+ rEvent.OldValue >>= nState;
+
+ switch (nState)
+ {
+ case AccessibleStateType::ACTIVE:
+ aState.active = true;
+ break;
+ case AccessibleStateType::BUSY:
+ aState.busy = true;
+ break;
+ case AccessibleStateType::CHECKABLE:
+ aState.checkable = true;
+ break;
+ case AccessibleStateType::CHECKED:
+ aState.checked = true;
+ break;
+ case AccessibleStateType::COLLAPSE:
+ aState.collapsed = true;
+ break;
+ case AccessibleStateType::DEFAULT:
+ aState.defaultButton = true;
+ break;
+ case AccessibleStateType::ENABLED:
+ aState.disabled = true;
+ break;
+ case AccessibleStateType::EDITABLE:
+ aState.editable = true;
+ break;
+ case AccessibleStateType::EXPANDABLE:
+ aState.expandable = true;
+ break;
+ case AccessibleStateType::EXPANDED:
+ aState.expanded = true;
+ break;
+ case AccessibleStateType::FOCUSABLE:
+ aState.focusable = true;
+ break;
+ case AccessibleStateType::FOCUSED:
+ aState.focused = true;
+ break;
+ case AccessibleStateType::INVALID:
+ aState.invalid = true;
+ break;
+ case AccessibleStateType::VISIBLE:
+ aState.invisible = true;
+ break;
+ case AccessibleStateType::MODAL:
+ aState.modal = true;
+ break;
+ case AccessibleStateType::MOVEABLE:
+ aState.movable = true;
+ break;
+ case AccessibleStateType::MULTI_LINE:
+ // comment in Qt's qaccessible.h has this:
+ // "// quint64 singleLine : 1; // we have multi line, this is redundant."
+ case AccessibleStateType::SINGLE_LINE:
+ aState.multiLine = true;
+ break;
+ case AccessibleStateType::MULTI_SELECTABLE:
+ aState.multiSelectable = true;
+ break;
+ case AccessibleStateType::OFFSCREEN:
+ aState.offscreen = true;
+ break;
+ case AccessibleStateType::PRESSED:
+ aState.pressed = true;
+ break;
+ case AccessibleStateType::RESIZABLE:
+ aState.sizeable = true;
+ break;
+ case AccessibleStateType::SELECTABLE:
+ aState.selectable = true;
+ break;
+ case AccessibleStateType::SELECTED:
+ aState.selected = true;
+ break;
+ case AccessibleStateType::SHOWING:
+ {
+ // Qt does not have an equivalent for the SHOWING state,
+ // but has separate event types
+ QAccessible::Event eEventType;
+ sal_Int64 nNewState = 0;
+ if ((rEvent.NewValue >>= nNewState) && nNewState == AccessibleStateType::SHOWING)
+ eEventType = QAccessible::ObjectShow;
+ else
+ eEventType = QAccessible::ObjectHide;
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, eEventType));
+ break;
+ }
+ // These don't seem to have a matching Qt equivalent
+ case AccessibleStateType::ARMED:
+ case AccessibleStateType::DEFUNC:
+ case AccessibleStateType::HORIZONTAL:
+ case AccessibleStateType::ICONIFIED:
+ case AccessibleStateType::INDETERMINATE:
+ case AccessibleStateType::MANAGES_DESCENDANTS:
+ case AccessibleStateType::OPAQUE:
+ case AccessibleStateType::SENSITIVE:
+ case AccessibleStateType::STALE:
+ case AccessibleStateType::TRANSIENT:
+ case AccessibleStateType::VERTICAL:
+ default:
+ return;
+ }
+
+ QAccessible::updateAccessibility(
+ new QAccessibleStateChangeEvent(pQAccessibleInterface, aState));
+}
+
+void QtAccessibleEventListener::notifyEvent(const css::accessibility::AccessibleEventObject& aEvent)
+{
+ QAccessibleInterface* pQAccessibleInterface = m_pAccessibleWidget;
+
+ switch (aEvent.EventId)
+ {
+ case AccessibleEventId::NAME_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::NameChanged));
+ return;
+ case AccessibleEventId::DESCRIPTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::DescriptionChanged));
+ return;
+ case AccessibleEventId::ACTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::ActionChanged));
+ return;
+ case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
+ {
+ // Qt has a QAccessible::ActiveDescendantChanged event type, but events of
+ // that type are currently just ignored on Qt side and not forwarded to AT-SPI.
+ // Send a state change event for the focused state of the newly
+ // active descendant instead
+ uno::Reference<accessibility::XAccessible> xActiveAccessible;
+ aEvent.NewValue >>= xActiveAccessible;
+ if (!xActiveAccessible.is())
+ return;
+
+ QObject* pQtAcc = QtAccessibleRegistry::getQObject(xActiveAccessible);
+ QAccessibleInterface* pInterface = QAccessible::queryAccessibleInterface(pQtAcc);
+ QAccessible::State aState;
+ aState.focused = true;
+ QAccessible::updateAccessibility(new QAccessibleStateChangeEvent(pInterface, aState));
+ return;
+ }
+ case AccessibleEventId::CARET_CHANGED:
+ {
+ sal_Int32 nNewCursorPos = 0;
+ aEvent.NewValue >>= nNewCursorPos;
+ QAccessible::updateAccessibility(
+ new QAccessibleTextCursorEvent(pQAccessibleInterface, nNewCursorPos));
+ return;
+ }
+ case AccessibleEventId::CHILD:
+ {
+ Reference<XAccessible> xChild;
+ if (aEvent.NewValue >>= xChild)
+ {
+ assert(xChild.is()
+ && "AccessibleEventId::CHILD event NewValue without valid child set");
+ // tdf#159213 for now, workaround invalid events being sent and don't crash in release builds
+ if (!xChild.is())
+ return;
+ QAccessible::updateAccessibility(new QAccessibleEvent(
+ QtAccessibleRegistry::getQObject(xChild), QAccessible::ObjectCreated));
+ return;
+ }
+ if (aEvent.OldValue >>= xChild)
+ {
+ assert(xChild.is()
+ && "AccessibleEventId::CHILD event OldValue without valid child set");
+ // tdf#159213 for now, workaround invalid events being sent and don't crash in release builds
+ if (!xChild.is())
+ return;
+ QAccessible::updateAccessibility(new QAccessibleEvent(
+ QtAccessibleRegistry::getQObject(xChild), QAccessible::ObjectDestroyed));
+ return;
+ }
+ SAL_WARN("vcl.qt",
+ "Ignoring invalid AccessibleEventId::CHILD event without any child set.");
+ return;
+ }
+ case AccessibleEventId::HYPERTEXT_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::HypertextChanged));
+ return;
+ case AccessibleEventId::SELECTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::Selection));
+ return;
+ case AccessibleEventId::VISIBLE_DATA_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::VisibleDataChanged));
+ return;
+ case AccessibleEventId::TEXT_SELECTION_CHANGED:
+ {
+ QAccessibleTextInterface* pTextInterface = pQAccessibleInterface->textInterface();
+ if (!pTextInterface)
+ {
+ SAL_WARN("vcl.qt", "TEXT_SELECTION_CHANGED event received for object not "
+ "implementing text interface");
+ return;
+ }
+ int nStartOffset = 0;
+ int nEndOffset = 0;
+ pTextInterface->selection(0, &nStartOffset, &nEndOffset);
+ QAccessible::updateAccessibility(
+ new QAccessibleTextSelectionEvent(pQAccessibleInterface, nStartOffset, nEndOffset));
+ return;
+ }
+ case AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::AttributeChanged));
+ return;
+ case AccessibleEventId::TEXT_CHANGED:
+ {
+ TextSegment aDeletedText;
+ TextSegment aInsertedText;
+ if (aEvent.OldValue >>= aDeletedText)
+ {
+ QAccessible::updateAccessibility(
+ new QAccessibleTextRemoveEvent(pQAccessibleInterface, aDeletedText.SegmentStart,
+ toQString(aDeletedText.SegmentText)));
+ }
+ if (aEvent.NewValue >>= aInsertedText)
+ {
+ QAccessible::updateAccessibility(new QAccessibleTextInsertEvent(
+ pQAccessibleInterface, aInsertedText.SegmentStart,
+ toQString(aInsertedText.SegmentText)));
+ }
+ return;
+ }
+ case AccessibleEventId::TABLE_CAPTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableCaptionChanged));
+ return;
+ case AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED:
+ QAccessible::updateAccessibility(new QAccessibleEvent(
+ pQAccessibleInterface, QAccessible::TableColumnDescriptionChanged));
+ return;
+ case AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableColumnHeaderChanged));
+ return;
+ case AccessibleEventId::TABLE_MODEL_CHANGED:
+ {
+ AccessibleTableModelChange aChange;
+ aEvent.NewValue >>= aChange;
+
+ QAccessibleTableModelChangeEvent::ModelChangeType nType;
+ switch (aChange.Type)
+ {
+ case AccessibleTableModelChangeType::COLUMNS_INSERTED:
+ nType = QAccessibleTableModelChangeEvent::ColumnsInserted;
+ break;
+ case AccessibleTableModelChangeType::COLUMNS_REMOVED:
+ nType = QAccessibleTableModelChangeEvent::ColumnsRemoved;
+ break;
+ case AccessibleTableModelChangeType::ROWS_INSERTED:
+ nType = QAccessibleTableModelChangeEvent::RowsInserted;
+ break;
+ case AccessibleTableModelChangeType::ROWS_REMOVED:
+ nType = QAccessibleTableModelChangeEvent::RowsRemoved;
+ break;
+ case AccessibleTableModelChangeType::UPDATE:
+ nType = QAccessibleTableModelChangeEvent::DataChanged;
+ break;
+ default:
+ assert(false && "Unhandled AccessibleTableModelChangeType");
+ return;
+ }
+ QAccessibleTableModelChangeEvent* pTableEvent
+ = new QAccessibleTableModelChangeEvent(pQAccessibleInterface, nType);
+ pTableEvent->setFirstRow(aChange.FirstRow);
+ pTableEvent->setLastRow(aChange.LastRow);
+ pTableEvent->setFirstColumn(aChange.FirstColumn);
+ pTableEvent->setLastColumn(aChange.LastColumn);
+ QAccessible::updateAccessibility(pTableEvent);
+ return;
+ }
+ case AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED:
+ QAccessible::updateAccessibility(new QAccessibleEvent(
+ pQAccessibleInterface, QAccessible::TableRowDescriptionChanged));
+ return;
+ case AccessibleEventId::TABLE_ROW_HEADER_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableRowHeaderChanged));
+ return;
+ case AccessibleEventId::TABLE_SUMMARY_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableSummaryChanged));
+ return;
+ case AccessibleEventId::SELECTION_CHANGED_ADD:
+ case AccessibleEventId::SELECTION_CHANGED_REMOVE:
+ {
+ QAccessible::Event eEventType;
+ if (aEvent.EventId == AccessibleEventId::SELECTION_CHANGED_ADD)
+ eEventType = QAccessible::SelectionAdd;
+ else
+ eEventType = QAccessible::SelectionRemove;
+
+ uno::Reference<accessibility::XAccessible> xChildAcc;
+ aEvent.NewValue >>= xChildAcc;
+ if (!xChildAcc.is())
+ {
+ SAL_WARN("vcl.qt",
+ "Selection add/remove event without the (un)selected accessible set");
+ return;
+ }
+ Reference<XAccessibleContext> xContext = xChildAcc->getAccessibleContext();
+ if (!xContext.is())
+ {
+ SAL_WARN("vcl.qt", "No valid XAccessibleContext for (un)selected accessible.");
+ return;
+ }
+
+ // Qt expects the event to be sent for the (un)selected child
+ QObject* pChildObject = QtAccessibleRegistry::getQObject(xChildAcc);
+ assert(pChildObject);
+ QAccessible::updateAccessibility(new QAccessibleEvent(pChildObject, eEventType));
+ return;
+ }
+ case AccessibleEventId::SELECTION_CHANGED_WITHIN:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::SelectionWithin));
+ return;
+ case AccessibleEventId::PAGE_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::PageChanged));
+ return;
+ case AccessibleEventId::SECTION_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::SectionChanged));
+ return;
+ case AccessibleEventId::COLUMN_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TextColumnChanged));
+ return;
+ case AccessibleEventId::BOUNDRECT_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::LocationChanged));
+ return;
+ case AccessibleEventId::STATE_CHANGED:
+ HandleStateChangedEvent(pQAccessibleInterface, aEvent);
+ return;
+ case AccessibleEventId::VALUE_CHANGED:
+ {
+ QAccessibleValueInterface* pValueInterface = pQAccessibleInterface->valueInterface();
+ if (pValueInterface)
+ {
+ const QVariant aValue = pValueInterface->currentValue();
+ QAccessible::updateAccessibility(
+ new QAccessibleValueChangeEvent(pQAccessibleInterface, aValue));
+ }
+ return;
+ }
+ case AccessibleEventId::ROLE_CHANGED:
+ case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ case AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED:
+ case AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED:
+ case AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED:
+ case AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED:
+ case AccessibleEventId::LABEL_FOR_RELATION_CHANGED:
+ case AccessibleEventId::LABELED_BY_RELATION_CHANGED:
+ case AccessibleEventId::MEMBER_OF_RELATION_CHANGED:
+ case AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED:
+ case AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
+ case AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
+ case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS:
+ default:
+ SAL_WARN("vcl.qt", "Unmapped AccessibleEventId: " << aEvent.EventId);
+ return;
+ }
+}
+
+void QtAccessibleEventListener::disposing(const EventObject& /* Source */)
+{
+ assert(m_pAccessibleWidget);
+ m_pAccessibleWidget->invalidate();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtAccessibleRegistry.cxx b/vcl/qt5/QtAccessibleRegistry.cxx
new file mode 100644
index 0000000000..88f9abcfd1
--- /dev/null
+++ b/vcl/qt5/QtAccessibleRegistry.cxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <QtAccessibleRegistry.hxx>
+#include <QtXAccessible.hxx>
+
+#include <cassert>
+
+std::map<XAccessible*, QObject*> QtAccessibleRegistry::m_aMapping = {};
+
+QObject* QtAccessibleRegistry::getQObject(css::uno::Reference<XAccessible> xAcc)
+{
+ if (!xAcc.is())
+ return nullptr;
+
+ // look for existing entry in the map
+ auto entry = m_aMapping.find(xAcc.get());
+ if (entry != m_aMapping.end())
+ return entry->second;
+
+ // create a new object and remember it in the map
+ QtXAccessible* pQtAcc = new QtXAccessible(xAcc);
+ m_aMapping.emplace(xAcc.get(), pQtAcc);
+ return pQtAcc;
+}
+
+void QtAccessibleRegistry::insert(css::uno::Reference<XAccessible> xAcc, QObject* pQObject)
+{
+ assert(pQObject);
+ m_aMapping.emplace(xAcc.get(), pQObject);
+}
+
+void QtAccessibleRegistry::remove(css::uno::Reference<XAccessible> xAcc)
+{
+ assert(xAcc.is());
+ m_aMapping.erase(xAcc.get());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtAccessibleWidget.cxx b/vcl/qt5/QtAccessibleWidget.cxx
new file mode 100644
index 0000000000..7eadc33138
--- /dev/null
+++ b/vcl/qt5/QtAccessibleWidget.cxx
@@ -0,0 +1,1848 @@
+/* -*- 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 <QtAccessibleWidget.hxx>
+
+#include <QtGui/QAccessibleInterface>
+
+#include <QtAccessibleEventListener.hxx>
+#include <QtAccessibleRegistry.hxx>
+#include <QtFrame.hxx>
+#include <QtTools.hxx>
+#include <QtWidget.hxx>
+#include <QtXAccessible.hxx>
+
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleScrollType.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+#include <com/sun/star/accessibility/XAccessibleKeyBinding.hpp>
+#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <com/sun/star/accessibility/XAccessibleTableSelection.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <comphelper/AccessibleImplementationHelper.hxx>
+#include <o3tl/any.hxx>
+#include <sal/log.hxx>
+#include <vcl/accessibility/AccessibleTextAttributeHelper.hxx>
+
+using namespace css;
+using namespace css::accessibility;
+using namespace css::beans;
+using namespace css::uno;
+
+QtAccessibleWidget::QtAccessibleWidget(const Reference<XAccessible> xAccessible, QObject* pObject)
+ : m_xAccessible(xAccessible)
+ , m_pObject(pObject)
+{
+ Reference<XAccessibleContext> xContext = xAccessible->getAccessibleContext();
+ Reference<XAccessibleEventBroadcaster> xBroadcaster(xContext, UNO_QUERY);
+ if (xBroadcaster.is())
+ {
+ Reference<XAccessibleEventListener> xListener(new QtAccessibleEventListener(this));
+ xBroadcaster->addAccessibleEventListener(xListener);
+ }
+}
+
+void QtAccessibleWidget::invalidate()
+{
+ QtAccessibleRegistry::remove(m_xAccessible);
+ m_xAccessible.clear();
+}
+
+Reference<XAccessibleContext> QtAccessibleWidget::getAccessibleContextImpl() const
+{
+ Reference<XAccessibleContext> xAc;
+
+ if (m_xAccessible.is())
+ {
+ try
+ {
+ xAc = m_xAccessible->getAccessibleContext();
+ }
+ catch (css::lang::DisposedException /*ex*/)
+ {
+ SAL_WARN("vcl.qt", "Accessible context disposed already");
+ }
+ // sometimes getAccessibleContext throws also RuntimeException if context is no longer alive
+ catch (css::uno::RuntimeException /*ex*/)
+ {
+ // so let's catch it here, cuz otherwise soffice falls flat on its face
+ // with FatalError and nothing else
+ SAL_WARN("vcl.qt", "Accessible context no longer alive");
+ }
+ }
+
+ return xAc;
+}
+
+css::uno::Reference<css::accessibility::XAccessibleTable>
+QtAccessibleWidget::getAccessibleTableForParent() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return nullptr;
+
+ Reference<XAccessible> xParent = xAcc->getAccessibleParent();
+ if (!xParent.is())
+ return nullptr;
+
+ Reference<XAccessibleContext> xParentContext = xParent->getAccessibleContext();
+ if (!xParentContext.is())
+ return nullptr;
+
+ return Reference<XAccessibleTable>(xParentContext, UNO_QUERY);
+}
+
+QWindow* QtAccessibleWidget::window() const
+{
+ assert(m_pObject);
+ if (m_pObject->isWidgetType())
+ {
+ QWidget* pWidget = static_cast<QWidget*>(m_pObject);
+ QWidget* pWindow = pWidget->window();
+ if (pWindow)
+ return pWindow->windowHandle();
+ }
+
+ QAccessibleInterface* pParent = parent();
+ if (pParent)
+ return pParent->window();
+
+ return nullptr;
+}
+
+int QtAccessibleWidget::childCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ sal_Int64 nChildCount = xAc->getAccessibleChildCount();
+ if (nChildCount > std::numeric_limits<int>::max())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::childCount: Child count exceeds maximum int value, "
+ "returning max int.");
+ nChildCount = std::numeric_limits<int>::max();
+ }
+
+ return nChildCount;
+}
+
+int QtAccessibleWidget::indexOfChild(const QAccessibleInterface* pChild) const
+{
+ const QtAccessibleWidget* pAccessibleWidget = dynamic_cast<const QtAccessibleWidget*>(pChild);
+ if (!pAccessibleWidget)
+ {
+ SAL_WARN(
+ "vcl.qt",
+ "QtAccessibleWidget::indexOfChild called with child that is no QtAccessibleWidget");
+ return -1;
+ }
+
+ Reference<XAccessibleContext> xContext = pAccessibleWidget->getAccessibleContextImpl();
+ if (!xContext.is())
+ return -1;
+
+ sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent();
+ if (nChildIndex > std::numeric_limits<int>::max())
+ {
+ // use -2 when the child index is too large to fit into 32 bit to neither use the
+ // valid index of another child nor -1, which would e.g. make the Orca screen reader
+ // interpret the object as being a zombie
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::indexOfChild: Child index exceeds maximum int value, "
+ "returning -2.");
+ nChildIndex = -2;
+ }
+ return nChildIndex;
+}
+
+namespace
+{
+sal_Int16 lcl_matchQtTextBoundaryType(QAccessible::TextBoundaryType boundaryType)
+{
+ switch (boundaryType)
+ {
+ case QAccessible::CharBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::CHARACTER;
+ case QAccessible::WordBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::WORD;
+ case QAccessible::SentenceBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::SENTENCE;
+ case QAccessible::ParagraphBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::PARAGRAPH;
+ case QAccessible::LineBoundary:
+ return com::sun::star::accessibility::AccessibleTextType::LINE;
+ case QAccessible::NoBoundary:
+ // assert here, better handle it directly at call site
+ assert(false
+ && "No match for QAccessible::NoBoundary, handle it separately at call site.");
+ break;
+ default:
+ break;
+ }
+
+ SAL_WARN("vcl.qt", "Unmatched text boundary type: " << boundaryType);
+ return -1;
+}
+
+QAccessible::Relation lcl_matchUnoRelation(short relationType)
+{
+ // Qt semantics is the other way around
+ switch (relationType)
+ {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+ case AccessibleRelationType::CONTENT_FLOWS_FROM:
+ return QAccessible::FlowsTo;
+ case AccessibleRelationType::CONTENT_FLOWS_TO:
+ return QAccessible::FlowsFrom;
+#endif
+ case AccessibleRelationType::CONTROLLED_BY:
+ return QAccessible::Controller;
+ case AccessibleRelationType::CONTROLLER_FOR:
+ return QAccessible::Controlled;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+ case AccessibleRelationType::DESCRIBED_BY:
+ return QAccessible::DescriptionFor;
+#endif
+ case AccessibleRelationType::LABELED_BY:
+ return QAccessible::Label;
+ case AccessibleRelationType::LABEL_FOR:
+ return QAccessible::Labelled;
+ case AccessibleRelationType::INVALID:
+ case AccessibleRelationType::MEMBER_OF:
+ case AccessibleRelationType::SUB_WINDOW_OF:
+ case AccessibleRelationType::NODE_CHILD_OF:
+ default:
+ SAL_WARN("vcl.qt", "Unmatched relation: " << relationType);
+ return {};
+ }
+}
+
+void lcl_appendRelation(QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>* relations,
+ AccessibleRelation aRelation, QAccessible::Relation match)
+{
+ QAccessible::Relation aQRelation = lcl_matchUnoRelation(aRelation.RelationType);
+ // skip in case there's no Qt relation matching the filter
+ if (!(aQRelation & match))
+ return;
+
+ sal_uInt32 nTargetCount = aRelation.TargetSet.getLength();
+
+ for (sal_uInt32 i = 0; i < nTargetCount; i++)
+ {
+ Reference<XAccessible> xAccessible(aRelation.TargetSet[i], uno::UNO_QUERY);
+ relations->append(
+ { QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xAccessible)),
+ aQRelation });
+ }
+}
+}
+
+QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>
+QtAccessibleWidget::relations(QAccessible::Relation match) const
+{
+ QVector<QPair<QAccessibleInterface*, QAccessible::Relation>> relations;
+
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return relations;
+
+ Reference<XAccessibleRelationSet> xRelationSet = xAc->getAccessibleRelationSet();
+ if (xRelationSet.is())
+ {
+ int count = xRelationSet->getRelationCount();
+ for (int i = 0; i < count; i++)
+ {
+ AccessibleRelation aRelation = xRelationSet->getRelation(i);
+ lcl_appendRelation(&relations, aRelation, match);
+ }
+ }
+
+ return relations;
+}
+
+QAccessibleInterface* QtAccessibleWidget::focusChild() const
+{
+ /* if (m_pWindow->HasChildPathFocus())
+ return QAccessible::queryAccessibleInterface(
+ new QtXAccessible(m_xAccessible->getAccessibleContext()->getAccessibleChild(index))); */
+ return QAccessible::queryAccessibleInterface(object());
+}
+
+QRect QtAccessibleWidget::rect() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QRect();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ awt::Point aPoint = xAccessibleComponent->getLocationOnScreen();
+ awt::Size aSize = xAccessibleComponent->getSize();
+
+ return QRect(aPoint.X, aPoint.Y, aSize.Width, aSize.Height);
+}
+
+QAccessibleInterface* QtAccessibleWidget::parent() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ if (xAc->getAccessibleParent().is())
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xAc->getAccessibleParent()));
+
+ // go via the QObject hierarchy; some a11y objects like the application
+ // (at the root of the a11y hierarchy) are handled solely by Qt and have
+ // no LO-internal a11y objects associated with them
+ QObject* pObj = object();
+ if (pObj && pObj->parent())
+ return QAccessible::queryAccessibleInterface(pObj->parent());
+
+ // return app as parent for top-level objects
+ return QAccessible::queryAccessibleInterface(qApp);
+}
+
+QAccessibleInterface* QtAccessibleWidget::child(int index) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ if (index < 0 || index >= xAc->getAccessibleChildCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::child called with invalid index: " << index);
+ return nullptr;
+ }
+
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xAc->getAccessibleChild(index)));
+}
+
+QString QtAccessibleWidget::text(QAccessible::Text text) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QString();
+
+ switch (text)
+ {
+ case QAccessible::Name:
+ return toQString(xAc->getAccessibleName());
+ case QAccessible::Description:
+ case QAccessible::DebugDescription:
+ return toQString(xAc->getAccessibleDescription());
+ case QAccessible::Value:
+ case QAccessible::Help:
+ case QAccessible::Accelerator:
+ case QAccessible::UserText:
+ default:
+ return QString("Unknown");
+ }
+}
+QAccessible::Role QtAccessibleWidget::role() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QAccessible::NoRole;
+
+ switch (xAc->getAccessibleRole())
+ {
+ case AccessibleRole::UNKNOWN:
+ return QAccessible::NoRole;
+ case AccessibleRole::ALERT:
+ return QAccessible::AlertMessage;
+ case AccessibleRole::COLUMN_HEADER:
+ return QAccessible::ColumnHeader;
+ case AccessibleRole::CANVAS:
+ return QAccessible::Canvas;
+ case AccessibleRole::CHECK_BOX:
+ return QAccessible::CheckBox;
+ case AccessibleRole::CHECK_MENU_ITEM:
+ return QAccessible::MenuItem;
+ case AccessibleRole::COLOR_CHOOSER:
+ return QAccessible::ColorChooser;
+ case AccessibleRole::COMBO_BOX:
+ return QAccessible::ComboBox;
+ case AccessibleRole::DATE_EDITOR:
+ return QAccessible::EditableText;
+ case AccessibleRole::DESKTOP_ICON:
+ return QAccessible::Graphic;
+ case AccessibleRole::DESKTOP_PANE:
+ case AccessibleRole::DIRECTORY_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::DIALOG:
+ return QAccessible::Dialog;
+ case AccessibleRole::DOCUMENT:
+ return QAccessible::Document;
+ case AccessibleRole::EMBEDDED_OBJECT:
+ return QAccessible::UserRole;
+ case AccessibleRole::END_NOTE:
+ return QAccessible::Note;
+ case AccessibleRole::FILE_CHOOSER:
+ return QAccessible::Dialog;
+ case AccessibleRole::FILLER:
+ return QAccessible::Whitespace;
+ case AccessibleRole::FONT_CHOOSER:
+ return QAccessible::UserRole;
+ case AccessibleRole::FOOTER:
+ return QAccessible::Footer;
+ case AccessibleRole::FOOTNOTE:
+ return QAccessible::Note;
+ case AccessibleRole::FRAME: // top-level window with title bar
+ return QAccessible::Window;
+ case AccessibleRole::GLASS_PANE:
+ return QAccessible::UserRole;
+ case AccessibleRole::GRAPHIC:
+ return QAccessible::Graphic;
+ case AccessibleRole::GROUP_BOX:
+ return QAccessible::Grouping;
+ case AccessibleRole::HEADER:
+ return QAccessible::UserRole;
+ case AccessibleRole::HEADING:
+ return QAccessible::Heading;
+ case AccessibleRole::HYPER_LINK:
+ return QAccessible::Link;
+ case AccessibleRole::ICON:
+ return QAccessible::Graphic;
+ case AccessibleRole::INTERNAL_FRAME:
+ return QAccessible::UserRole;
+ case AccessibleRole::LABEL:
+ return QAccessible::StaticText;
+ case AccessibleRole::LAYERED_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::LIST:
+ return QAccessible::List;
+ case AccessibleRole::LIST_ITEM:
+ return QAccessible::ListItem;
+ case AccessibleRole::MENU:
+ case AccessibleRole::MENU_BAR:
+ return QAccessible::MenuBar;
+ case AccessibleRole::MENU_ITEM:
+ return QAccessible::MenuItem;
+ case AccessibleRole::NOTIFICATION:
+ return QAccessible::Notification;
+ case AccessibleRole::OPTION_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::PAGE_TAB:
+ return QAccessible::PageTab;
+ case AccessibleRole::PAGE_TAB_LIST:
+ return QAccessible::PageTabList;
+ case AccessibleRole::PANEL:
+ return QAccessible::Pane;
+ case AccessibleRole::PARAGRAPH:
+ case AccessibleRole::BLOCK_QUOTE:
+ return QAccessible::Paragraph;
+ case AccessibleRole::PASSWORD_TEXT:
+ // Qt API doesn't have a separate role to distinguish password edits,
+ // but a 'passwordEdit' state
+ return QAccessible::EditableText;
+ case AccessibleRole::POPUP_MENU:
+ return QAccessible::PopupMenu;
+ case AccessibleRole::PUSH_BUTTON:
+ return QAccessible::Button;
+ case AccessibleRole::PROGRESS_BAR:
+ return QAccessible::ProgressBar;
+ case AccessibleRole::RADIO_BUTTON:
+ return QAccessible::RadioButton;
+ case AccessibleRole::RADIO_MENU_ITEM:
+ return QAccessible::MenuItem;
+ case AccessibleRole::ROW_HEADER:
+ return QAccessible::RowHeader;
+ case AccessibleRole::ROOT_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::SCROLL_BAR:
+ return QAccessible::ScrollBar;
+ case AccessibleRole::SCROLL_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::SHAPE:
+ return QAccessible::Graphic;
+ case AccessibleRole::SEPARATOR:
+ return QAccessible::Separator;
+ case AccessibleRole::SLIDER:
+ return QAccessible::Slider;
+ case AccessibleRole::SPIN_BOX:
+ return QAccessible::SpinBox;
+ case AccessibleRole::SPLIT_PANE:
+ return QAccessible::Pane;
+ case AccessibleRole::STATUS_BAR:
+ return QAccessible::StatusBar;
+ case AccessibleRole::TABLE:
+ return QAccessible::Table;
+ case AccessibleRole::TABLE_CELL:
+ return QAccessible::Cell;
+ case AccessibleRole::TEXT:
+ return QAccessible::EditableText;
+ case AccessibleRole::TEXT_FRAME:
+ return QAccessible::UserRole;
+ case AccessibleRole::TOGGLE_BUTTON:
+ return QAccessible::Button;
+ case AccessibleRole::TOOL_BAR:
+ return QAccessible::ToolBar;
+ case AccessibleRole::TOOL_TIP:
+ return QAccessible::ToolTip;
+ case AccessibleRole::TREE:
+ return QAccessible::Tree;
+ case AccessibleRole::VIEW_PORT:
+ return QAccessible::UserRole;
+ case AccessibleRole::BUTTON_DROPDOWN:
+ return QAccessible::ButtonDropDown;
+ case AccessibleRole::BUTTON_MENU:
+ return QAccessible::ButtonMenu;
+ case AccessibleRole::CAPTION:
+ return QAccessible::StaticText;
+ case AccessibleRole::CHART:
+ return QAccessible::Chart;
+ case AccessibleRole::EDIT_BAR:
+ return QAccessible::Equation;
+ case AccessibleRole::FORM:
+ return QAccessible::Form;
+ case AccessibleRole::IMAGE_MAP:
+ return QAccessible::Graphic;
+ case AccessibleRole::NOTE:
+ return QAccessible::Note;
+ case AccessibleRole::RULER:
+ return QAccessible::UserRole;
+ case AccessibleRole::SECTION:
+ return QAccessible::Section;
+ case AccessibleRole::TREE_ITEM:
+ return QAccessible::TreeItem;
+ case AccessibleRole::TREE_TABLE:
+ return QAccessible::Tree;
+ case AccessibleRole::COMMENT:
+ return QAccessible::Note;
+ case AccessibleRole::COMMENT_END:
+ return QAccessible::UserRole;
+ case AccessibleRole::DOCUMENT_PRESENTATION:
+ return QAccessible::Document;
+ case AccessibleRole::DOCUMENT_SPREADSHEET:
+ return QAccessible::Document;
+ case AccessibleRole::DOCUMENT_TEXT:
+ return QAccessible::Document;
+ case AccessibleRole::STATIC:
+ return QAccessible::StaticText;
+ case AccessibleRole::WINDOW: // top-level window without title bar
+ return QAccessible::Window;
+ }
+
+ SAL_WARN("vcl.qt", "Unmapped role: " << getAccessibleContextImpl()->getAccessibleRole());
+ return QAccessible::NoRole;
+}
+
+namespace
+{
+void lcl_addState(QAccessible::State* state, sal_Int64 nState)
+{
+ switch (nState)
+ {
+ case AccessibleStateType::INVALID:
+ state->invalid = true;
+ break;
+ case AccessibleStateType::ACTIVE:
+ state->active = true;
+ break;
+ case AccessibleStateType::ARMED:
+ // No match
+ break;
+ case AccessibleStateType::BUSY:
+ state->busy = true;
+ break;
+ case AccessibleStateType::CHECKABLE:
+ state->checkable = true;
+ break;
+ case AccessibleStateType::CHECKED:
+ state->checked = true;
+ break;
+ case AccessibleStateType::EDITABLE:
+ state->editable = true;
+ break;
+ case AccessibleStateType::ENABLED:
+ state->disabled = false;
+ break;
+ case AccessibleStateType::EXPANDABLE:
+ state->expandable = true;
+ break;
+ case AccessibleStateType::EXPANDED:
+ state->expanded = true;
+ break;
+ case AccessibleStateType::FOCUSABLE:
+ state->focusable = true;
+ break;
+ case AccessibleStateType::FOCUSED:
+ state->focused = true;
+ break;
+ case AccessibleStateType::HORIZONTAL:
+ // No match
+ break;
+ case AccessibleStateType::ICONIFIED:
+ // No match
+ break;
+ case AccessibleStateType::INDETERMINATE:
+ state->checkStateMixed = true;
+ break;
+ case AccessibleStateType::MANAGES_DESCENDANTS:
+ // No match
+ break;
+ case AccessibleStateType::MODAL:
+ state->modal = true;
+ break;
+ case AccessibleStateType::MOVEABLE:
+ state->movable = true;
+ break;
+ case AccessibleStateType::MULTI_LINE:
+ state->multiLine = true;
+ break;
+ case AccessibleStateType::OPAQUE:
+ // No match
+ break;
+ case AccessibleStateType::PRESSED:
+ state->pressed = true;
+ break;
+ case AccessibleStateType::RESIZABLE:
+ state->sizeable = true;
+ break;
+ case AccessibleStateType::SELECTABLE:
+ state->selectable = true;
+ break;
+ case AccessibleStateType::SELECTED:
+ state->selected = true;
+ break;
+ case AccessibleStateType::SENSITIVE:
+ // No match
+ break;
+ case AccessibleStateType::SHOWING:
+ // No match
+ break;
+ case AccessibleStateType::SINGLE_LINE:
+ // No match
+ break;
+ case AccessibleStateType::STALE:
+ // No match
+ break;
+ case AccessibleStateType::TRANSIENT:
+ // No match
+ break;
+ case AccessibleStateType::VERTICAL:
+ // No match
+ break;
+ case AccessibleStateType::VISIBLE:
+ state->invisible = false;
+ break;
+ case AccessibleStateType::DEFAULT:
+ // No match
+ break;
+ case AccessibleStateType::DEFUNC:
+ state->invalid = true;
+ break;
+ case AccessibleStateType::MULTI_SELECTABLE:
+ state->multiSelectable = true;
+ break;
+ default:
+ SAL_WARN("vcl.qt", "Unmapped state: " << nState);
+ break;
+ }
+}
+}
+
+QAccessible::State QtAccessibleWidget::state() const
+{
+ QAccessible::State state;
+
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return state;
+
+ sal_Int64 nStateSet(xAc->getAccessibleStateSet());
+
+ for (int i = 0; i < 63; ++i)
+ {
+ sal_Int64 nState = sal_Int64(1) << i;
+ if (nStateSet & nState)
+ lcl_addState(&state, nState);
+ }
+
+ if (xAc->getAccessibleRole() == AccessibleRole::PASSWORD_TEXT)
+ state.passwordEdit = true;
+
+ return state;
+}
+
+QColor QtAccessibleWidget::foregroundColor() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QColor();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return toQColor(Color(ColorTransparency, xAccessibleComponent->getForeground()));
+}
+
+QColor QtAccessibleWidget::backgroundColor() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QColor();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return toQColor(Color(ColorTransparency, xAccessibleComponent->getBackground()));
+}
+
+void* QtAccessibleWidget::interface_cast(QAccessible::InterfaceType t)
+{
+ if (t == QAccessible::ActionInterface && accessibleProvidesInterface<XAccessibleAction>())
+ return static_cast<QAccessibleActionInterface*>(this);
+ if (t == QAccessible::TextInterface && accessibleProvidesInterface<XAccessibleText>())
+ return static_cast<QAccessibleTextInterface*>(this);
+ if (t == QAccessible::EditableTextInterface
+ && accessibleProvidesInterface<XAccessibleEditableText>())
+ return static_cast<QAccessibleEditableTextInterface*>(this);
+ if (t == QAccessible::ValueInterface && accessibleProvidesInterface<XAccessibleValue>())
+ return static_cast<QAccessibleValueInterface*>(this);
+ if (t == QAccessible::TableCellInterface)
+ {
+ // parent must be a table
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (xTable.is())
+ return static_cast<QAccessibleTableCellInterface*>(this);
+ }
+ if (t == QAccessible::TableInterface && accessibleProvidesInterface<XAccessibleTable>())
+ return static_cast<QAccessibleTableInterface*>(this);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ if (t == QAccessible::SelectionInterface && accessibleProvidesInterface<XAccessibleSelection>())
+ return static_cast<QAccessibleSelectionInterface*>(this);
+#endif
+ return nullptr;
+}
+
+bool QtAccessibleWidget::isValid() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ return xAc.is();
+}
+
+QObject* QtAccessibleWidget::object() const { return m_pObject; }
+
+void QtAccessibleWidget::setText(QAccessible::Text /* t */, const QString& /* text */) {}
+
+QAccessibleInterface* QtAccessibleWidget::childAt(int x, int y) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ // convert from screen to local coordinates
+ QPoint aLocalCoords = QPoint(x, y) - rect().topLeft();
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xAccessibleComponent->getAccessibleAtPoint(
+ awt::Point(aLocalCoords.x(), aLocalCoords.y()))));
+}
+
+QAccessibleInterface* QtAccessibleWidget::customFactory(const QString& classname, QObject* object)
+{
+ if (classname == QLatin1String("QtWidget") && object && object->isWidgetType())
+ {
+ QtWidget* pWidget = static_cast<QtWidget*>(object);
+ vcl::Window* pWindow = pWidget->frame().GetWindow();
+
+ if (pWindow)
+ {
+ css::uno::Reference<XAccessible> xAcc = pWindow->GetAccessible();
+ // insert into registry so the association between the XAccessible and the QtWidget
+ // is remembered rather than creating a different QtXAccessible when a QObject is needed later
+ QtAccessibleRegistry::insert(xAcc, object);
+ return new QtAccessibleWidget(xAcc, object);
+ }
+ }
+ if (classname == QLatin1String("QtXAccessible") && object)
+ {
+ QtXAccessible* pXAccessible = static_cast<QtXAccessible*>(object);
+ if (pXAccessible->m_xAccessible.is())
+ {
+ QtAccessibleWidget* pRet = new QtAccessibleWidget(pXAccessible->m_xAccessible, object);
+ // clear the reference in the QtXAccessible, no longer needed now that the QtAccessibleWidget holds one
+ pXAccessible->m_xAccessible.clear();
+ return pRet;
+ }
+ }
+
+ return nullptr;
+}
+
+// QAccessibleActionInterface
+QStringList QtAccessibleWidget::actionNames() const
+{
+ QStringList actionNames;
+ Reference<XAccessibleAction> xAccessibleAction(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xAccessibleAction.is())
+ return actionNames;
+
+ int count = xAccessibleAction->getAccessibleActionCount();
+ for (int i = 0; i < count; i++)
+ {
+ OUString desc = xAccessibleAction->getAccessibleActionDescription(i);
+ actionNames.append(toQString(desc));
+ }
+ return actionNames;
+}
+
+void QtAccessibleWidget::doAction(const QString& actionName)
+{
+ Reference<XAccessibleAction> xAccessibleAction(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xAccessibleAction.is())
+ return;
+
+ int index = actionNames().indexOf(actionName);
+ if (index == -1)
+ return;
+ xAccessibleAction->doAccessibleAction(index);
+}
+
+QStringList QtAccessibleWidget::keyBindingsForAction(const QString& actionName) const
+{
+ QStringList keyBindings;
+ Reference<XAccessibleAction> xAccessibleAction(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xAccessibleAction.is())
+ return keyBindings;
+
+ int index = actionNames().indexOf(actionName);
+ if (index == -1)
+ return keyBindings;
+
+ Reference<XAccessibleKeyBinding> xKeyBinding
+ = xAccessibleAction->getAccessibleActionKeyBinding(index);
+
+ if (!xKeyBinding.is())
+ return keyBindings;
+
+ int count = xKeyBinding->getAccessibleKeyBindingCount();
+ for (int i = 0; i < count; i++)
+ {
+ Sequence<awt::KeyStroke> keyStroke = xKeyBinding->getAccessibleKeyBinding(i);
+ keyBindings.append(toQString(comphelper::GetkeyBindingStrByXkeyBinding(keyStroke)));
+ }
+ return keyBindings;
+}
+
+// QAccessibleTextInterface
+void QtAccessibleWidget::addSelection(int /* startOffset */, int /* endOffset */)
+{
+ SAL_INFO("vcl.qt", "Unsupported QAccessibleTextInterface::addSelection");
+}
+
+// Text attributes are returned in format specified in IAccessible2 spec, since that
+// is what Qt handles:
+// https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes
+QString QtAccessibleWidget::attributes(int offset, int* startOffset, int* endOffset) const
+{
+ if (startOffset == nullptr || endOffset == nullptr)
+ return QString();
+
+ *startOffset = -1;
+ *endOffset = -1;
+
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ // handle special values for offset the same way base class's QAccessibleTextWidget::attributes does
+ // (as defined in IAccessible 2: -1 -> length, -2 -> cursor position)
+ if (offset == -2)
+ offset = cursorPosition();
+
+ const int nTextLength = characterCount();
+ if (offset == -1 || offset == nTextLength)
+ offset = nTextLength - 1;
+
+ if (offset < 0 || offset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::attributes called with invalid offset: " << offset);
+ return QString();
+ }
+
+ // Qt doesn't have the strict separation into text and object attributes, but also
+ // supports text-specific attributes that are object attributes according to the
+ // IAccessible2 spec.
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = 0;
+ const OUString aRet = AccessibleTextAttributeHelper::GetIAccessible2TextAttributes(
+ xText, IA2AttributeType::TextAttributes | IA2AttributeType::ObjectAttributes,
+ static_cast<sal_Int32>(offset), nStart, nEnd);
+ *startOffset = static_cast<int>(nStart);
+ *endOffset = static_cast<int>(nEnd);
+ return toQString(aRet);
+}
+
+int QtAccessibleWidget::characterCount() const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (xText.is())
+ return xText->getCharacterCount();
+ return 0;
+}
+
+QRect QtAccessibleWidget::characterRect(int nOffset) const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QRect();
+
+ if (nOffset < 0 || nOffset > xText->getCharacterCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::characterRect called with invalid offset: " << nOffset);
+ return QRect();
+ }
+
+ const awt::Rectangle aBounds = xText->getCharacterBounds(nOffset);
+ const QRect aRect(aBounds.X, aBounds.Y, aBounds.Width, aBounds.Height);
+ // convert to screen coordinates
+ const QRect aScreenPos = rect();
+ return aRect.translated(aScreenPos.x(), aScreenPos.y());
+}
+
+int QtAccessibleWidget::cursorPosition() const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (xText.is())
+ return xText->getCaretPosition();
+ return 0;
+}
+
+int QtAccessibleWidget::offsetAtPoint(const QPoint& rPoint) const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return -1;
+
+ // convert from screen to local coordinates
+ QPoint aLocalCoords = rPoint - rect().topLeft();
+ awt::Point aPoint(aLocalCoords.x(), aLocalCoords.y());
+ return xText->getIndexAtPoint(aPoint);
+}
+
+void QtAccessibleWidget::removeSelection(int /* selectionIndex */)
+{
+ SAL_INFO("vcl.qt", "Unsupported QAccessibleTextInterface::removeSelection");
+}
+
+void QtAccessibleWidget::scrollToSubstring(int startIndex, int endIndex)
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return;
+
+ sal_Int32 nTextLength = xText->getCharacterCount();
+ if (startIndex < 0 || startIndex > nTextLength || endIndex < 0 || endIndex > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::scrollToSubstring called with invalid offset.");
+ return;
+ }
+
+ xText->scrollSubstringTo(startIndex, endIndex, AccessibleScrollType_SCROLL_ANYWHERE);
+}
+
+void QtAccessibleWidget::selection(int selectionIndex, int* startOffset, int* endOffset) const
+{
+ if (!startOffset && !endOffset)
+ return;
+
+ Reference<XAccessibleText> xText;
+ if (selectionIndex == 0)
+ xText = Reference<XAccessibleText>(getAccessibleContextImpl(), UNO_QUERY);
+
+ if (startOffset)
+ *startOffset = xText.is() ? xText->getSelectionStart() : 0;
+ if (endOffset)
+ *endOffset = xText.is() ? xText->getSelectionEnd() : 0;
+}
+
+int QtAccessibleWidget::selectionCount() const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (xText.is() && !xText->getSelectedText().isEmpty())
+ return 1; // Only 1 selection supported atm
+ return 0;
+}
+
+void QtAccessibleWidget::setCursorPosition(int position)
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return;
+
+ if (position < 0 || position > xText->getCharacterCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::setCursorPosition called with invalid offset: " << position);
+ return;
+ }
+
+ xText->setCaretPosition(position);
+}
+
+void QtAccessibleWidget::setSelection(int /* selectionIndex */, int startOffset, int endOffset)
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return;
+
+ sal_Int32 nTextLength = xText->getCharacterCount();
+ if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::setSelection called with invalid offset.");
+ return;
+ }
+
+ xText->setSelection(startOffset, endOffset);
+}
+
+QString QtAccessibleWidget::text(int startOffset, int endOffset) const
+{
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ sal_Int32 nTextLength = xText->getCharacterCount();
+ if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::text called with invalid offset.");
+ return QString();
+ }
+
+ return toQString(xText->getTextRange(startOffset, endOffset));
+}
+
+QString QtAccessibleWidget::textAfterOffset(int nOffset,
+ QAccessible::TextBoundaryType eBoundaryType,
+ int* pStartOffset, int* pEndOffset) const
+{
+ if (pStartOffset == nullptr || pEndOffset == nullptr)
+ return QString();
+
+ *pStartOffset = -1;
+ *pEndOffset = -1;
+
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ const int nCharCount = characterCount();
+ // -1 is special value for text length
+ if (nOffset == -1)
+ nOffset = nCharCount;
+ else if (nOffset < -1 || nOffset > nCharCount)
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::textAfterOffset called with invalid offset: " << nOffset);
+ return QString();
+ }
+
+ if (eBoundaryType == QAccessible::NoBoundary)
+ {
+ if (nOffset == nCharCount)
+ return QString();
+ *pStartOffset = nOffset + 1;
+ *pEndOffset = nCharCount;
+ return text(nOffset + 1, nCharCount);
+ }
+
+ sal_Int16 nUnoBoundaryType = lcl_matchQtTextBoundaryType(eBoundaryType);
+ assert(nUnoBoundaryType > 0);
+ const TextSegment aSegment = xText->getTextBehindIndex(nOffset, nUnoBoundaryType);
+ *pStartOffset = aSegment.SegmentStart;
+ *pEndOffset = aSegment.SegmentEnd;
+ return toQString(aSegment.SegmentText);
+}
+
+QString QtAccessibleWidget::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
+ int* startOffset, int* endOffset) const
+{
+ if (startOffset == nullptr || endOffset == nullptr)
+ return QString();
+
+ const int nCharCount = characterCount();
+ if (boundaryType == QAccessible::NoBoundary)
+ {
+ *startOffset = 0;
+ *endOffset = nCharCount;
+ return text(0, nCharCount);
+ }
+
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ sal_Int16 nUnoBoundaryType = lcl_matchQtTextBoundaryType(boundaryType);
+ assert(nUnoBoundaryType > 0);
+
+ // special value of -1 for offset means text length
+ if (offset == -1)
+ offset = nCharCount;
+
+ if (offset < 0 || offset > nCharCount)
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::textAtOffset called with invalid offset: " << offset);
+ return QString();
+ }
+
+ const TextSegment segment = xText->getTextAtIndex(offset, nUnoBoundaryType);
+ *startOffset = segment.SegmentStart;
+ *endOffset = segment.SegmentEnd;
+ return toQString(segment.SegmentText);
+}
+
+QString QtAccessibleWidget::textBeforeOffset(int nOffset,
+ QAccessible::TextBoundaryType eBoundaryType,
+ int* pStartOffset, int* pEndOffset) const
+{
+ if (pStartOffset == nullptr || pEndOffset == nullptr)
+ return QString();
+
+ *pStartOffset = -1;
+ *pEndOffset = -1;
+
+ Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY);
+ if (!xText.is())
+ return QString();
+
+ const int nCharCount = characterCount();
+ // -1 is special value for text length
+ if (nOffset == -1)
+ nOffset = nCharCount;
+ else if (nOffset < -1 || nOffset > nCharCount)
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::textBeforeOffset called with invalid offset: " << nOffset);
+ return QString();
+ }
+
+ if (eBoundaryType == QAccessible::NoBoundary)
+ {
+ *pStartOffset = 0;
+ *pEndOffset = nOffset;
+ return text(0, nOffset);
+ }
+
+ sal_Int16 nUnoBoundaryType = lcl_matchQtTextBoundaryType(eBoundaryType);
+ assert(nUnoBoundaryType > 0);
+ const TextSegment aSegment = xText->getTextBeforeIndex(nOffset, nUnoBoundaryType);
+ *pStartOffset = aSegment.SegmentStart;
+ *pEndOffset = aSegment.SegmentEnd;
+ return toQString(aSegment.SegmentText);
+}
+
+// QAccessibleEditableTextInterface
+
+void QtAccessibleWidget::deleteText(int startOffset, int endOffset)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+
+ sal_Int32 nTextLength = xEditableText->getCharacterCount();
+ if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::deleteText called with invalid offset.");
+ return;
+ }
+
+ xEditableText->deleteText(startOffset, endOffset);
+}
+
+void QtAccessibleWidget::insertText(int offset, const QString& text)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+
+ if (offset < 0 || offset > xEditableText->getCharacterCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::insertText called with invalid offset: " << offset);
+ return;
+ }
+
+ xEditableText->insertText(toOUString(text), offset);
+}
+
+void QtAccessibleWidget::replaceText(int startOffset, int endOffset, const QString& text)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+
+ sal_Int32 nTextLength = xEditableText->getCharacterCount();
+ if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::replaceText called with invalid offset.");
+ return;
+ }
+
+ xEditableText->replaceText(startOffset, endOffset, toOUString(text));
+}
+
+// QAccessibleValueInterface
+QVariant QtAccessibleWidget::currentValue() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QVariant();
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return QVariant();
+ double aDouble = 0;
+ xValue->getCurrentValue() >>= aDouble;
+ return QVariant(aDouble);
+}
+
+QVariant QtAccessibleWidget::maximumValue() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QVariant();
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return QVariant();
+ double aDouble = 0;
+ xValue->getMaximumValue() >>= aDouble;
+ return QVariant(aDouble);
+}
+
+QVariant QtAccessibleWidget::minimumStepSize() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QVariant();
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return QVariant();
+ double dMinStep = 0;
+ xValue->getMinimumIncrement() >>= dMinStep;
+ return QVariant(dMinStep);
+}
+
+QVariant QtAccessibleWidget::minimumValue() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QVariant();
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return QVariant();
+ double aDouble = 0;
+ xValue->getMinimumValue() >>= aDouble;
+ return QVariant(aDouble);
+}
+
+void QtAccessibleWidget::setCurrentValue(const QVariant& value)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return;
+
+ // Different types of numerical values for XAccessibleValue are possible.
+ // If current value has an integer type, also use that for the new value, to make
+ // sure underlying implementations expecting that can handle the value properly.
+ const Any aCurrentValue = xValue->getCurrentValue();
+ if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_LONG)
+ xValue->setCurrentValue(Any(static_cast<sal_Int32>(value.toInt())));
+ else if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER)
+ xValue->setCurrentValue(Any(static_cast<sal_Int64>(value.toLongLong())));
+ else
+ xValue->setCurrentValue(Any(value.toDouble()));
+}
+
+// QAccessibleTableInterface
+QAccessibleInterface* QtAccessibleWidget::caption() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return nullptr;
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xTable->getAccessibleCaption()));
+}
+
+QAccessibleInterface* QtAccessibleWidget::cellAt(int row, int column) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return nullptr;
+
+ if (row < 0 || row >= xTable->getAccessibleRowCount() || column < 0
+ || column >= xTable->getAccessibleColumnCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::cellAt called with invalid row/column index ("
+ << row << ", " << column << ")");
+ return nullptr;
+ }
+
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xTable->getAccessibleCellAt(row, column)));
+}
+
+int QtAccessibleWidget::columnCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return 0;
+ return xTable->getAccessibleColumnCount();
+}
+
+QString QtAccessibleWidget::columnDescription(int column) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QString();
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return QString();
+ return toQString(xTable->getAccessibleColumnDescription(column));
+}
+
+bool QtAccessibleWidget::isColumnSelected(int nColumn) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return false;
+
+ if (nColumn < 0 || nColumn >= xTable->getAccessibleColumnCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::isColumnSelected called with invalid column index "
+ << nColumn);
+ return false;
+ }
+
+ return xTable->isAccessibleColumnSelected(nColumn);
+}
+
+bool QtAccessibleWidget::isRowSelected(int nRow) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return false;
+
+ if (nRow < 0 || nRow >= xTable->getAccessibleRowCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::isRowSelected called with invalid row index " << nRow);
+ return false;
+ }
+
+ return xTable->isAccessibleRowSelected(nRow);
+}
+
+void QtAccessibleWidget::modelChange(QAccessibleTableModelChangeEvent*) {}
+
+int QtAccessibleWidget::rowCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return 0;
+ return xTable->getAccessibleRowCount();
+}
+
+QString QtAccessibleWidget::rowDescription(int row) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QString();
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return QString();
+ return toQString(xTable->getAccessibleRowDescription(row));
+}
+
+bool QtAccessibleWidget::selectColumn(int column)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ if (column < 0 || column >= columnCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::selectColumn called with invalid column index " << column);
+ return false;
+ }
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->selectColumn(column);
+}
+
+bool QtAccessibleWidget::selectRow(int row)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ if (row < 0 || row >= rowCount())
+ {
+ SAL_WARN("vcl.qt", "QtAccessibleWidget::selectRow called with invalid row index " << row);
+ return false;
+ }
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->selectRow(row);
+}
+
+int QtAccessibleWidget::selectedCellCount() const { return selectedItemCount(); }
+
+QList<QAccessibleInterface*> QtAccessibleWidget::selectedCells() const { return selectedItems(); }
+
+int QtAccessibleWidget::selectedColumnCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return 0;
+ return xTable->getSelectedAccessibleColumns().getLength();
+}
+
+QList<int> QtAccessibleWidget::selectedColumns() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QList<int>();
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return QList<int>();
+ return toQList(xTable->getSelectedAccessibleColumns());
+}
+
+int QtAccessibleWidget::selectedRowCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return 0;
+ return xTable->getSelectedAccessibleRows().getLength();
+}
+
+QList<int> QtAccessibleWidget::selectedRows() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QList<int>();
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return QList<int>();
+ return toQList(xTable->getSelectedAccessibleRows());
+}
+
+QAccessibleInterface* QtAccessibleWidget::summary() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleTable> xTable(xAc, UNO_QUERY);
+ if (!xTable.is())
+ return nullptr;
+ return QAccessible::queryAccessibleInterface(
+ QtAccessibleRegistry::getQObject(xTable->getAccessibleSummary()));
+}
+
+bool QtAccessibleWidget::unselectColumn(int column)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->unselectColumn(column);
+}
+
+bool QtAccessibleWidget::unselectRow(int row)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->unselectRow(row);
+}
+
+// QAccessibleTableCellInterface
+QList<QAccessibleInterface*> QtAccessibleWidget::columnHeaderCells() const
+{
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return QList<QAccessibleInterface*>();
+
+ Reference<XAccessibleTable> xHeaders = xTable->getAccessibleColumnHeaders();
+ if (!xHeaders.is())
+ return QList<QAccessibleInterface*>();
+
+ const sal_Int32 nCol = columnIndex();
+ QList<QAccessibleInterface*> aHeaderCells;
+ for (sal_Int32 nRow = 0; nRow < xHeaders->getAccessibleRowCount(); nRow++)
+ {
+ Reference<XAccessible> xCell = xHeaders->getAccessibleCellAt(nRow, nCol);
+ QAccessibleInterface* pInterface
+ = QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xCell));
+ aHeaderCells.push_back(pInterface);
+ }
+ return aHeaderCells;
+}
+
+int QtAccessibleWidget::columnIndex() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return -1;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return -1;
+
+ const sal_Int64 nIndexInParent = xAcc->getAccessibleIndexInParent();
+ return xTable->getAccessibleColumn(nIndexInParent);
+}
+
+bool QtAccessibleWidget::isSelected() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return false;
+
+ const sal_Int32 nColumn = columnIndex();
+ const sal_Int32 nRow = rowIndex();
+ return xTable->isAccessibleSelected(nRow, nColumn);
+}
+
+int QtAccessibleWidget::columnExtent() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return -1;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return -1;
+
+ const sal_Int32 nColumn = columnIndex();
+ const sal_Int32 nRow = rowIndex();
+ return xTable->getAccessibleColumnExtentAt(nRow, nColumn);
+}
+
+QList<QAccessibleInterface*> QtAccessibleWidget::rowHeaderCells() const
+{
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return QList<QAccessibleInterface*>();
+
+ Reference<XAccessibleTable> xHeaders = xTable->getAccessibleRowHeaders();
+ if (!xHeaders.is())
+ return QList<QAccessibleInterface*>();
+
+ const sal_Int32 nRow = rowIndex();
+ QList<QAccessibleInterface*> aHeaderCells;
+ for (sal_Int32 nCol = 0; nCol < xHeaders->getAccessibleColumnCount(); nCol++)
+ {
+ Reference<XAccessible> xCell = xHeaders->getAccessibleCellAt(nRow, nCol);
+ QAccessibleInterface* pInterface
+ = QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xCell));
+ aHeaderCells.push_back(pInterface);
+ }
+ return aHeaderCells;
+}
+
+int QtAccessibleWidget::rowExtent() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return -1;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return -1;
+
+ const sal_Int32 nColumn = columnIndex();
+ const sal_Int32 nRow = rowIndex();
+ return xTable->getAccessibleRowExtentAt(nRow, nColumn);
+}
+
+int QtAccessibleWidget::rowIndex() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return -1;
+
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return -1;
+
+ const sal_Int64 nIndexInParent = xAcc->getAccessibleIndexInParent();
+ return xTable->getAccessibleRow(nIndexInParent);
+}
+
+QAccessibleInterface* QtAccessibleWidget::table() const
+{
+ Reference<XAccessibleTable> xTable = getAccessibleTableForParent();
+ if (!xTable.is())
+ return nullptr;
+
+ Reference<XAccessible> xTableAcc(xTable, UNO_QUERY);
+ if (!xTableAcc.is())
+ return nullptr;
+
+ return QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xTableAcc));
+}
+
+// QAccessibleSelectionInterface
+int QtAccessibleWidget::selectedItemCount() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return 0;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return 0;
+
+ sal_Int64 nSelected = xSelection->getSelectedAccessibleChildCount();
+ if (nSelected > std::numeric_limits<int>::max())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::selectedItemCount: Cell count exceeds maximum int value, "
+ "using max int.");
+ nSelected = std::numeric_limits<int>::max();
+ }
+ return nSelected;
+}
+
+QList<QAccessibleInterface*> QtAccessibleWidget::selectedItems() const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return QList<QAccessibleInterface*>();
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return QList<QAccessibleInterface*>();
+
+ QList<QAccessibleInterface*> aSelectedItems;
+ sal_Int64 nSelected = xSelection->getSelectedAccessibleChildCount();
+ if (nSelected > std::numeric_limits<int>::max())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::selectedItems: Cell count exceeds maximum int value, "
+ "using max int.");
+ nSelected = std::numeric_limits<int>::max();
+ }
+ for (sal_Int64 i = 0; i < nSelected; i++)
+ {
+ Reference<XAccessible> xChild = xSelection->getSelectedAccessibleChild(i);
+ QAccessibleInterface* pInterface
+ = QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xChild));
+ aSelectedItems.push_back(pInterface);
+ }
+ return aSelectedItems;
+}
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+QAccessibleInterface* QtAccessibleWidget::selectedItem(int nSelectionIndex) const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return nullptr;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return nullptr;
+
+ if (nSelectionIndex < 0 || nSelectionIndex >= xSelection->getSelectedAccessibleChildCount())
+ {
+ SAL_WARN("vcl.qt",
+ "QtAccessibleWidget::selectedItem called with invalid index: " << nSelectionIndex);
+ return nullptr;
+ }
+
+ Reference<XAccessible> xChild = xSelection->getSelectedAccessibleChild(nSelectionIndex);
+ if (!xChild)
+ return nullptr;
+
+ return QAccessible::queryAccessibleInterface(QtAccessibleRegistry::getQObject(xChild));
+}
+
+bool QtAccessibleWidget::isSelected(QAccessibleInterface* pItem) const
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ int nChildIndex = indexOfChild(pItem);
+ if (nChildIndex < 0)
+ return false;
+
+ return xSelection->isAccessibleChildSelected(nChildIndex);
+}
+
+bool QtAccessibleWidget::select(QAccessibleInterface* pItem)
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ int nChildIndex = indexOfChild(pItem);
+ if (nChildIndex < 0)
+ return false;
+
+ xSelection->selectAccessibleChild(nChildIndex);
+ return true;
+}
+
+bool QtAccessibleWidget::unselect(QAccessibleInterface* pItem)
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ int nChildIndex = indexOfChild(pItem);
+ if (nChildIndex < 0)
+ return false;
+
+ xSelection->deselectAccessibleChild(nChildIndex);
+ return true;
+}
+
+bool QtAccessibleWidget::selectAll()
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ xSelection->selectAllAccessibleChildren();
+ return true;
+}
+
+bool QtAccessibleWidget::clear()
+{
+ Reference<XAccessibleContext> xAcc = getAccessibleContextImpl();
+ if (!xAcc.is())
+ return false;
+
+ Reference<XAccessibleSelection> xSelection(xAcc, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ xSelection->clearAccessibleSelection();
+ return true;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtBitmap.cxx b/vcl/qt5/QtBitmap.cxx
new file mode 100644
index 0000000000..dd83c57c23
--- /dev/null
+++ b/vcl/qt5/QtBitmap.cxx
@@ -0,0 +1,186 @@
+/* -*- 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 <QtBitmap.hxx>
+#include <QtTools.hxx>
+#include <QtGraphics.hxx>
+
+#include <QtGui/QImage>
+#include <QtCore/QVector>
+#include <QtGui/QColor>
+
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+
+QtBitmap::QtBitmap() {}
+
+QtBitmap::QtBitmap(const QImage& rImage) { m_pImage.reset(new QImage(rImage)); }
+
+bool QtBitmap::Create(const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal)
+{
+ if (ePixelFormat == vcl::PixelFormat::INVALID)
+ return false;
+
+ if (ePixelFormat == vcl::PixelFormat::N8_BPP)
+ assert(256 >= rPal.GetEntryCount());
+
+ m_pImage.reset(new QImage(toQSize(rSize), getBitFormat(ePixelFormat)));
+ m_pImage->fill(Qt::transparent);
+ m_aPalette = rPal;
+
+ auto count = rPal.GetEntryCount();
+ if (count && m_pImage)
+ {
+ QVector<QRgb> aColorTable(count);
+ for (unsigned i = 0; i < count; ++i)
+ aColorTable[i] = qRgb(rPal[i].GetRed(), rPal[i].GetGreen(), rPal[i].GetBlue());
+ m_pImage->setColorTable(std::move(aColorTable));
+ }
+ return true;
+}
+
+bool QtBitmap::Create(const SalBitmap& rSalBmp)
+{
+ const QtBitmap* pBitmap = static_cast<const QtBitmap*>(&rSalBmp);
+ m_pImage.reset(new QImage(*pBitmap->m_pImage));
+ m_aPalette = pBitmap->m_aPalette;
+ return true;
+}
+
+bool QtBitmap::Create(const SalBitmap& rSalBmp, SalGraphics* pSalGraphics)
+{
+ const QtBitmap* pBitmap = static_cast<const QtBitmap*>(&rSalBmp);
+ QtGraphics* pGraphics = static_cast<QtGraphics*>(pSalGraphics);
+ QImage* pImage = pGraphics->getQImage();
+ m_pImage.reset(new QImage(pBitmap->m_pImage->convertToFormat(pImage->format())));
+ return true;
+}
+
+bool QtBitmap::Create(const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat)
+{
+ if (eNewPixelFormat == vcl::PixelFormat::INVALID)
+ return false;
+ const QtBitmap* pBitmap = static_cast<const QtBitmap*>(&rSalBmp);
+ m_pImage.reset(new QImage(pBitmap->m_pImage->convertToFormat(getBitFormat(eNewPixelFormat))));
+ return true;
+}
+
+bool QtBitmap::Create(const css::uno::Reference<css::rendering::XBitmapCanvas>& /*rBitmapCanvas*/,
+ Size& /*rSize*/, bool /*bMask*/)
+{
+ return false;
+}
+
+void QtBitmap::Destroy() { m_pImage.reset(); }
+
+Size QtBitmap::GetSize() const
+{
+ if (m_pImage)
+ return toSize(m_pImage->size());
+ return Size();
+}
+
+sal_uInt16 QtBitmap::GetBitCount() const
+{
+ if (m_pImage)
+ return getFormatBits(m_pImage->format());
+ return 0;
+}
+
+BitmapBuffer* QtBitmap::AcquireBuffer(BitmapAccessMode /*nMode*/)
+{
+ static const BitmapPalette aEmptyPalette;
+
+ if (!m_pImage)
+ return nullptr;
+
+ BitmapBuffer* pBuffer = new BitmapBuffer;
+
+ pBuffer->mnWidth = m_pImage->width();
+ pBuffer->mnHeight = m_pImage->height();
+ pBuffer->mnBitCount = getFormatBits(m_pImage->format());
+ pBuffer->mpBits = m_pImage->bits();
+ pBuffer->mnScanlineSize = m_pImage->bytesPerLine();
+
+ switch (pBuffer->mnBitCount)
+ {
+ case 1:
+ pBuffer->mnFormat = ScanlineFormat::N1BitMsbPal | ScanlineFormat::TopDown;
+ pBuffer->maPalette = m_aPalette;
+ break;
+ case 8:
+ pBuffer->mnFormat = ScanlineFormat::N8BitPal | ScanlineFormat::TopDown;
+ pBuffer->maPalette = m_aPalette;
+ break;
+ case 24:
+ pBuffer->mnFormat = ScanlineFormat::N24BitTcRgb | ScanlineFormat::TopDown;
+ pBuffer->maPalette = aEmptyPalette;
+ break;
+ case 32:
+ {
+#ifdef OSL_BIGENDIAN
+ pBuffer->mnFormat = ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown;
+#else
+ pBuffer->mnFormat = ScanlineFormat::N32BitTcBgra | ScanlineFormat::TopDown;
+#endif
+ pBuffer->maPalette = aEmptyPalette;
+ break;
+ }
+ default:
+ assert(false);
+ }
+
+ return pBuffer;
+}
+
+void QtBitmap::ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode)
+{
+ m_aPalette = pBuffer->maPalette;
+ auto count = m_aPalette.GetEntryCount();
+ if (pBuffer->mnBitCount != 4 && count)
+ {
+ QVector<QRgb> aColorTable(count);
+ for (unsigned i = 0; i < count; ++i)
+ aColorTable[i]
+ = qRgb(m_aPalette[i].GetRed(), m_aPalette[i].GetGreen(), m_aPalette[i].GetBlue());
+ m_pImage->setColorTable(std::move(aColorTable));
+ }
+ delete pBuffer;
+ if (nMode == BitmapAccessMode::Write)
+ InvalidateChecksum();
+}
+
+bool QtBitmap::GetSystemData(BitmapSystemData& /*rData*/) { return false; }
+
+bool QtBitmap::ScalingSupported() const { return false; }
+
+bool QtBitmap::Scale(const double& /*rScaleX*/, const double& /*rScaleY*/,
+ BmpScaleFlag /*nScaleFlag*/)
+{
+ return false;
+}
+
+bool QtBitmap::Replace(const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/,
+ sal_uInt8 /*nTol*/)
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtClipboard.cxx b/vcl/qt5/QtClipboard.cxx
new file mode 100644
index 0000000000..e9eb476fb2
--- /dev/null
+++ b/vcl/qt5/QtClipboard.cxx
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <QtClipboard.hxx>
+#include <QtClipboard.moc>
+
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+
+#include <QtWidgets/QApplication>
+
+#include <QtInstance.hxx>
+#include <QtTransferable.hxx>
+#include <QtTools.hxx>
+
+#include <cassert>
+#include <map>
+#include <utility>
+
+QtClipboard::QtClipboard(OUString aModeString, const QClipboard::Mode aMode)
+ : cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::datatransfer::clipboard::XFlushableClipboard,
+ XServiceInfo>(m_aMutex)
+ , m_aClipboardName(std::move(aModeString))
+ , m_aClipboardMode(aMode)
+ , m_bOwnClipboardChange(false)
+ , m_bDoClear(false)
+{
+ assert(isSupported(m_aClipboardMode));
+ // DirectConnection guarantees the changed slot runs in the same thread as the QClipboard
+ connect(QApplication::clipboard(), &QClipboard::changed, this, &QtClipboard::handleChanged,
+ Qt::DirectConnection);
+
+ // explicitly queue an event, so we can eventually ignore it
+ connect(this, &QtClipboard::clearClipboard, this, &QtClipboard::handleClearClipboard,
+ Qt::QueuedConnection);
+}
+
+css::uno::Reference<css::uno::XInterface> QtClipboard::create(const OUString& aModeString)
+{
+ static const std::map<OUString, QClipboard::Mode> aNameToClipboardMap
+ = { { "CLIPBOARD", QClipboard::Clipboard }, { "PRIMARY", QClipboard::Selection } };
+
+ assert(QApplication::clipboard()->thread() == qApp->thread());
+
+ auto iter = aNameToClipboardMap.find(aModeString);
+ if (iter != aNameToClipboardMap.end() && isSupported(iter->second))
+ return cppu::getXWeak(new QtClipboard(aModeString, iter->second));
+ SAL_WARN("vcl.qt", "Ignoring unrecognized clipboard type: '" << aModeString << "'");
+ return css::uno::Reference<css::uno::XInterface>();
+}
+
+void QtClipboard::flushClipboard()
+{
+ auto* pSalInst(GetQtInstance());
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([this]() {
+ if (!isOwner(m_aClipboardMode))
+ return;
+
+ QClipboard* pClipboard = QApplication::clipboard();
+ const QtMimeData* pQtMimeData
+ = dynamic_cast<const QtMimeData*>(pClipboard->mimeData(m_aClipboardMode));
+ assert(pQtMimeData);
+
+ QMimeData* pMimeCopy = nullptr;
+ if (pQtMimeData && pQtMimeData->deepCopy(&pMimeCopy))
+ {
+ m_bOwnClipboardChange = true;
+ pClipboard->setMimeData(pMimeCopy, m_aClipboardMode);
+ m_bOwnClipboardChange = false;
+ }
+ });
+}
+
+css::uno::Reference<css::datatransfer::XTransferable> QtClipboard::getContents()
+{
+#if defined(EMSCRIPTEN)
+ static QMimeData aMimeData;
+#endif
+ osl::MutexGuard aGuard(m_aMutex);
+
+ // if we're the owner, we might have the XTransferable from setContents. but
+ // maybe a non-LO clipboard change from within LO, like some C'n'P in the
+ // QFileDialog, might have invalidated m_aContents, so we need to check it too.
+ if (isOwner(m_aClipboardMode) && m_aContents.is())
+ return m_aContents;
+
+ // check if we can still use the shared QtClipboardTransferable
+ const QMimeData* pMimeData = QApplication::clipboard()->mimeData(m_aClipboardMode);
+#if defined(EMSCRIPTEN)
+ if (!pMimeData)
+ pMimeData = &aMimeData;
+#endif
+ if (m_aContents.is())
+ {
+ const auto* pTrans = dynamic_cast<QtClipboardTransferable*>(m_aContents.get());
+ assert(pTrans);
+ if (pTrans && pTrans->mimeData() == pMimeData)
+ return m_aContents;
+ }
+
+ m_aContents = new QtClipboardTransferable(m_aClipboardMode, pMimeData);
+ return m_aContents;
+}
+
+void QtClipboard::handleClearClipboard()
+{
+ if (!m_bDoClear)
+ return;
+ QApplication::clipboard()->clear(m_aClipboardMode);
+}
+
+void QtClipboard::setContents(
+ const css::uno::Reference<css::datatransfer::XTransferable>& xTrans,
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner)
+{
+ // it's actually possible to get a non-empty xTrans and an empty xClipboardOwner!
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner);
+ css::uno::Reference<css::datatransfer::XTransferable> xOldContents(m_aContents);
+ m_aContents = xTrans;
+ m_aOwner = xClipboardOwner;
+
+ m_bDoClear = !m_aContents.is();
+ if (!m_bDoClear)
+ {
+ m_bOwnClipboardChange = true;
+ QApplication::clipboard()->setMimeData(new QtMimeData(m_aContents), m_aClipboardMode);
+ m_bOwnClipboardChange = false;
+ }
+ else
+ {
+ assert(!m_aOwner.is());
+ Q_EMIT clearClipboard();
+ }
+
+ aGuard.clear();
+
+ // we have to notify only an owner change, since handleChanged can't
+ // access the previous owner anymore and can just handle lost ownership.
+ if (xOldOwner.is() && xOldOwner != xClipboardOwner)
+ xOldOwner->lostOwnership(this, xOldContents);
+}
+
+void QtClipboard::handleChanged(QClipboard::Mode aMode)
+{
+ if (aMode != m_aClipboardMode)
+ return;
+
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ // QtWayland will send a second change notification (seemingly without any
+ // trigger). And any C'n'P operation in the Qt file picker emits a signal,
+ // with LO still holding the clipboard ownership, but internally having lost
+ // it. So ignore any signal, which still delivers the internal QtMimeData
+ // as the clipboard content and is no "advertised" change.
+ if (!m_bOwnClipboardChange && isOwner(aMode)
+ && dynamic_cast<const QtMimeData*>(QApplication::clipboard()->mimeData(aMode)))
+ return;
+
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> xOldOwner(m_aOwner);
+ css::uno::Reference<css::datatransfer::XTransferable> xOldContents(m_aContents);
+ // ownership change from LO POV is handled in setContents
+ if (!m_bOwnClipboardChange)
+ {
+ m_aContents.clear();
+ m_aOwner.clear();
+ }
+
+ std::vector<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> aListeners(
+ m_aListeners);
+ css::datatransfer::clipboard::ClipboardEvent aEv;
+ aEv.Contents = getContents();
+
+ aGuard.clear();
+
+ if (!m_bOwnClipboardChange && xOldOwner.is())
+ xOldOwner->lostOwnership(this, xOldContents);
+ for (auto const& listener : aListeners)
+ listener->changedContents(aEv);
+}
+
+OUString QtClipboard::getImplementationName() { return "com.sun.star.datatransfer.QtClipboard"; }
+
+css::uno::Sequence<OUString> QtClipboard::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+}
+
+sal_Bool QtClipboard::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+OUString QtClipboard::getName() { return m_aClipboardName; }
+
+sal_Int8 QtClipboard::getRenderingCapabilities() { return 0; }
+
+void QtClipboard::addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ m_aListeners.push_back(listener);
+}
+
+void QtClipboard::removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ std::erase(m_aListeners, listener);
+}
+
+bool QtClipboard::isSupported(const QClipboard::Mode aMode)
+{
+ const QClipboard* pClipboard = QApplication::clipboard();
+ switch (aMode)
+ {
+ case QClipboard::Selection:
+ return pClipboard->supportsSelection();
+
+ case QClipboard::FindBuffer:
+ return pClipboard->supportsFindBuffer();
+
+ case QClipboard::Clipboard:
+ return true;
+ }
+ return false;
+}
+
+bool QtClipboard::isOwner(const QClipboard::Mode aMode)
+{
+ if (!isSupported(aMode))
+ return false;
+
+ const QClipboard* pClipboard = QApplication::clipboard();
+ switch (aMode)
+ {
+ case QClipboard::Selection:
+ return pClipboard->ownsSelection();
+
+ case QClipboard::FindBuffer:
+ return pClipboard->ownsFindBuffer();
+
+ case QClipboard::Clipboard:
+ return pClipboard->ownsClipboard();
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtData.cxx b/vcl/qt5/QtData.cxx
new file mode 100644
index 0000000000..cc2883ae80
--- /dev/null
+++ b/vcl/qt5/QtData.cxx
@@ -0,0 +1,227 @@
+/* -*- 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 <QtData.hxx>
+
+#include <QtGui/QBitmap>
+#include <QtGui/QCursor>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QStyle>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <vcl/ImageTree.hxx>
+
+#include <bitmaps.hlst>
+#include <cursor_hotspots.hxx>
+#include <unx/glyphcache.hxx>
+
+QtData::QtData()
+ : GenericUnixSalData()
+{
+ 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);
+}
+
+// outline dtor b/c of FreetypeManager incomplete type
+QtData::~QtData() {}
+
+static QCursor* getQCursorFromIconTheme(const OUString& rIconName, int nXHot, int nYHot)
+{
+ const OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ const OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+ auto xMemStream = ImageTree::get().getImageStream(rIconName, sIconTheme, sUILang);
+ if (!xMemStream)
+ return nullptr;
+ auto nLength = xMemStream->TellEnd();
+ if (!nLength)
+ {
+ SAL_WARN("vcl.qt", "Cannot load cursor pixmap from empty stream.");
+ return nullptr;
+ }
+
+ const unsigned char* pData = static_cast<const unsigned char*>(xMemStream->GetData());
+ QPixmap aPixmap;
+ aPixmap.loadFromData(pData, nLength);
+ return new QCursor(aPixmap, nXHot, nYHot);
+}
+
+#define MAKE_CURSOR(vcl_name, name, icon_name) \
+ case vcl_name: \
+ pCursor = getQCursorFromIconTheme(icon_name, name##curs_x_hot, name##curs_y_hot); \
+ break
+
+#define MAP_BUILTIN(vcl_name, qt_enum) \
+ case vcl_name: \
+ pCursor = new QCursor(qt_enum); \
+ break
+
+QCursor& QtData::getCursor(PointerStyle ePointerStyle)
+{
+ if (!m_aCursors[ePointerStyle])
+ {
+ QCursor* pCursor = nullptr;
+
+ switch (ePointerStyle)
+ {
+ MAP_BUILTIN(PointerStyle::Arrow, Qt::ArrowCursor);
+ MAP_BUILTIN(PointerStyle::Text, Qt::IBeamCursor);
+ MAP_BUILTIN(PointerStyle::Help, Qt::WhatsThisCursor);
+ MAP_BUILTIN(PointerStyle::Cross, Qt::CrossCursor);
+ MAP_BUILTIN(PointerStyle::Wait, Qt::WaitCursor);
+ MAP_BUILTIN(PointerStyle::NSize, Qt::SizeVerCursor);
+ MAP_BUILTIN(PointerStyle::SSize, Qt::SizeVerCursor);
+ MAP_BUILTIN(PointerStyle::WSize, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::ESize, Qt::SizeHorCursor);
+
+ MAP_BUILTIN(PointerStyle::NWSize, Qt::SizeFDiagCursor);
+ MAP_BUILTIN(PointerStyle::NESize, Qt::SizeBDiagCursor);
+ MAP_BUILTIN(PointerStyle::SWSize, Qt::SizeBDiagCursor);
+ MAP_BUILTIN(PointerStyle::SESize, Qt::SizeFDiagCursor);
+ MAP_BUILTIN(PointerStyle::WindowNSize, Qt::SizeVerCursor);
+ MAP_BUILTIN(PointerStyle::WindowSSize, Qt::SizeVerCursor);
+ MAP_BUILTIN(PointerStyle::WindowWSize, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::WindowESize, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::WindowNWSize, Qt::SizeFDiagCursor);
+ MAP_BUILTIN(PointerStyle::WindowNESize, Qt::SizeBDiagCursor);
+ MAP_BUILTIN(PointerStyle::WindowSWSize, Qt::SizeBDiagCursor);
+ MAP_BUILTIN(PointerStyle::WindowSESize, Qt::SizeFDiagCursor);
+
+ MAP_BUILTIN(PointerStyle::HSizeBar, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::VSizeBar, Qt::SizeVerCursor);
+
+ MAP_BUILTIN(PointerStyle::RefHand, Qt::PointingHandCursor);
+ MAP_BUILTIN(PointerStyle::Hand, Qt::OpenHandCursor);
+#if 0
+ MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL );
+#endif
+ MAP_BUILTIN(PointerStyle::HSplit, Qt::SizeHorCursor);
+ MAP_BUILTIN(PointerStyle::VSplit, Qt::SizeVerCursor);
+
+ MAP_BUILTIN(PointerStyle::Move, Qt::SizeAllCursor);
+
+ MAP_BUILTIN(PointerStyle::Null, Qt::BlankCursor);
+ MAKE_CURSOR(PointerStyle::Magnify, magnify_, RID_CURSOR_MAGNIFY);
+ MAKE_CURSOR(PointerStyle::Fill, fill_, RID_CURSOR_FILL);
+ MAKE_CURSOR(PointerStyle::MoveData, movedata_, RID_CURSOR_MOVE_DATA);
+ MAKE_CURSOR(PointerStyle::CopyData, copydata_, RID_CURSOR_COPY_DATA);
+ MAKE_CURSOR(PointerStyle::MoveFile, movefile_, RID_CURSOR_MOVE_FILE);
+ MAKE_CURSOR(PointerStyle::CopyFile, copyfile_, RID_CURSOR_COPY_FILE);
+ MAKE_CURSOR(PointerStyle::MoveFiles, movefiles_, RID_CURSOR_MOVE_FILES);
+ MAKE_CURSOR(PointerStyle::CopyFiles, copyfiles_, RID_CURSOR_COPY_FILES);
+ MAKE_CURSOR(PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED);
+ MAKE_CURSOR(PointerStyle::Rotate, rotate_, RID_CURSOR_ROTATE);
+ MAKE_CURSOR(PointerStyle::HShear, hshear_, RID_CURSOR_H_SHEAR);
+ MAKE_CURSOR(PointerStyle::VShear, vshear_, RID_CURSOR_V_SHEAR);
+ MAKE_CURSOR(PointerStyle::DrawLine, drawline_, RID_CURSOR_DRAW_LINE);
+ MAKE_CURSOR(PointerStyle::DrawRect, drawrect_, RID_CURSOR_DRAW_RECT);
+ MAKE_CURSOR(PointerStyle::DrawPolygon, drawpolygon_, RID_CURSOR_DRAW_POLYGON);
+ MAKE_CURSOR(PointerStyle::DrawBezier, drawbezier_, RID_CURSOR_DRAW_BEZIER);
+ MAKE_CURSOR(PointerStyle::DrawArc, drawarc_, RID_CURSOR_DRAW_ARC);
+ MAKE_CURSOR(PointerStyle::DrawPie, drawpie_, RID_CURSOR_DRAW_PIE);
+ MAKE_CURSOR(PointerStyle::DrawCircleCut, drawcirclecut_, RID_CURSOR_DRAW_CIRCLE_CUT);
+ MAKE_CURSOR(PointerStyle::DrawEllipse, drawellipse_, RID_CURSOR_DRAW_ELLIPSE);
+ MAKE_CURSOR(PointerStyle::DrawConnect, drawconnect_, RID_CURSOR_DRAW_CONNECT);
+ MAKE_CURSOR(PointerStyle::DrawText, drawtext_, RID_CURSOR_DRAW_TEXT);
+ MAKE_CURSOR(PointerStyle::Mirror, mirror_, RID_CURSOR_MIRROR);
+ MAKE_CURSOR(PointerStyle::Crook, crook_, RID_CURSOR_CROOK);
+ MAKE_CURSOR(PointerStyle::Crop, crop_, RID_CURSOR_CROP);
+ MAKE_CURSOR(PointerStyle::MovePoint, movepoint_, RID_CURSOR_MOVE_POINT);
+ MAKE_CURSOR(PointerStyle::MoveBezierWeight, movebezierweight_,
+ RID_CURSOR_MOVE_BEZIER_WEIGHT);
+ MAKE_CURSOR(PointerStyle::DrawFreehand, drawfreehand_, RID_CURSOR_DRAW_FREEHAND);
+ MAKE_CURSOR(PointerStyle::DrawCaption, drawcaption_, RID_CURSOR_DRAW_CAPTION);
+ MAKE_CURSOR(PointerStyle::LinkData, linkdata_, RID_CURSOR_LINK_DATA);
+ MAKE_CURSOR(PointerStyle::MoveDataLink, movedlnk_, RID_CURSOR_MOVE_DATA_LINK);
+ MAKE_CURSOR(PointerStyle::CopyDataLink, copydlnk_, RID_CURSOR_COPY_DATA_LINK);
+ MAKE_CURSOR(PointerStyle::LinkFile, linkfile_, RID_CURSOR_LINK_FILE);
+ MAKE_CURSOR(PointerStyle::MoveFileLink, moveflnk_, RID_CURSOR_MOVE_FILE_LINK);
+ MAKE_CURSOR(PointerStyle::CopyFileLink, copyflnk_, RID_CURSOR_COPY_FILE_LINK);
+ MAKE_CURSOR(PointerStyle::Chart, chart_, RID_CURSOR_CHART);
+ MAKE_CURSOR(PointerStyle::Detective, detective_, RID_CURSOR_DETECTIVE);
+ MAKE_CURSOR(PointerStyle::PivotCol, pivotcol_, RID_CURSOR_PIVOT_COLUMN);
+ MAKE_CURSOR(PointerStyle::PivotRow, pivotrow_, RID_CURSOR_PIVOT_ROW);
+ MAKE_CURSOR(PointerStyle::PivotField, pivotfld_, RID_CURSOR_PIVOT_FIELD);
+ MAKE_CURSOR(PointerStyle::PivotDelete, pivotdel_, RID_CURSOR_PIVOT_DELETE);
+ MAKE_CURSOR(PointerStyle::Chain, chain_, RID_CURSOR_CHAIN);
+ MAKE_CURSOR(PointerStyle::ChainNotAllowed, chainnot_, RID_CURSOR_CHAIN_NOT_ALLOWED);
+ MAKE_CURSOR(PointerStyle::AutoScrollN, asn_, RID_CURSOR_AUTOSCROLL_N);
+ MAKE_CURSOR(PointerStyle::AutoScrollS, ass_, RID_CURSOR_AUTOSCROLL_S);
+ MAKE_CURSOR(PointerStyle::AutoScrollW, asw_, RID_CURSOR_AUTOSCROLL_W);
+ MAKE_CURSOR(PointerStyle::AutoScrollE, ase_, RID_CURSOR_AUTOSCROLL_E);
+ MAKE_CURSOR(PointerStyle::AutoScrollNW, asnw_, RID_CURSOR_AUTOSCROLL_NW);
+ MAKE_CURSOR(PointerStyle::AutoScrollNE, asne_, RID_CURSOR_AUTOSCROLL_NE);
+ MAKE_CURSOR(PointerStyle::AutoScrollSW, assw_, RID_CURSOR_AUTOSCROLL_SW);
+ MAKE_CURSOR(PointerStyle::AutoScrollSE, asse_, RID_CURSOR_AUTOSCROLL_SE);
+ MAKE_CURSOR(PointerStyle::AutoScrollNS, asns_, RID_CURSOR_AUTOSCROLL_NS);
+ MAKE_CURSOR(PointerStyle::AutoScrollWE, aswe_, RID_CURSOR_AUTOSCROLL_WE);
+ MAKE_CURSOR(PointerStyle::AutoScrollNSWE, asnswe_, RID_CURSOR_AUTOSCROLL_NSWE);
+ MAKE_CURSOR(PointerStyle::TextVertical, vertcurs_, RID_CURSOR_TEXT_VERTICAL);
+
+ MAKE_CURSOR(PointerStyle::TabSelectS, tblsels_, RID_CURSOR_TAB_SELECT_S);
+ MAKE_CURSOR(PointerStyle::TabSelectE, tblsele_, RID_CURSOR_TAB_SELECT_E);
+ MAKE_CURSOR(PointerStyle::TabSelectSE, tblselse_, RID_CURSOR_TAB_SELECT_SE);
+ MAKE_CURSOR(PointerStyle::TabSelectW, tblselw_, RID_CURSOR_TAB_SELECT_W);
+ MAKE_CURSOR(PointerStyle::TabSelectSW, tblselsw_, RID_CURSOR_TAB_SELECT_SW);
+
+ MAKE_CURSOR(PointerStyle::HideWhitespace, hidewhitespace_, RID_CURSOR_HIDE_WHITESPACE);
+ MAKE_CURSOR(PointerStyle::ShowWhitespace, showwhitespace_, RID_CURSOR_SHOW_WHITESPACE);
+
+ MAKE_CURSOR(PointerStyle::FatCross, fatcross_, RID_CURSOR_FATCROSS);
+ default:
+ break;
+ }
+ if (!pCursor)
+ {
+ pCursor = new QCursor(Qt::ArrowCursor);
+ SAL_WARN("vcl.qt", "pointer " << static_cast<int>(ePointerStyle) << " not implemented");
+ }
+
+ m_aCursors[ePointerStyle].reset(pCursor);
+ }
+
+ return *m_aCursors[ePointerStyle];
+}
+
+void QtData::ErrorTrapPush() {}
+
+bool QtData::ErrorTrapPop(bool /*bIgnoreError*/) { return false; }
+
+bool QtData::noNativeControls()
+{
+ static const bool bNoNative
+ = ((nullptr != getenv("SAL_VCL_QT5_NO_NATIVE")) && (nullptr != ImplGetSVData())
+ && ImplGetSVData()->maAppData.mxToolkitName
+ && ImplGetSVData()->maAppData.mxToolkitName->match("qt5"));
+ return bNoNative;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtDragAndDrop.cxx b/vcl/qt5/QtDragAndDrop.cxx
new file mode 100644
index 0000000000..ffabc1bbba
--- /dev/null
+++ b/vcl/qt5/QtDragAndDrop.cxx
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+
+#include <QtDragAndDrop.hxx>
+#include <QtFrame.hxx>
+#include <QtTransferable.hxx>
+#include <QtWidget.hxx>
+
+#include <QtGui/QDrag>
+
+using namespace com::sun::star;
+
+QtDragSource::~QtDragSource() {}
+
+void QtDragSource::deinitialize() { m_pFrame = nullptr; }
+
+sal_Bool QtDragSource::isDragImageSupported() { return true; }
+
+sal_Int32 QtDragSource::getDefaultCursor(sal_Int8) { return 0; }
+
+void QtDragSource::initialize(const css::uno::Sequence<css::uno::Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw uno::RuntimeException("DragSource::initialize: Cannot install window event handler",
+ getXWeak());
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw uno::RuntimeException("DragSource::initialize: missing SalFrame", getXWeak());
+ }
+
+ m_pFrame = reinterpret_cast<QtFrame*>(nFrame);
+ m_pFrame->registerDragSource(this);
+}
+
+void QtDragSource::startDrag(
+ const datatransfer::dnd::DragGestureEvent& /*rEvent*/, sal_Int8 sourceActions,
+ sal_Int32 /*cursor*/, sal_Int32 /*image*/,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
+{
+ m_xListener = rListener;
+
+ if (m_pFrame)
+ {
+ QDrag* drag = new QDrag(m_pFrame->GetQWidget());
+ drag->setMimeData(new QtMimeData(rTrans));
+ // just a reminder that exec starts a nested event loop, so everything after
+ // this call is just executed, after D'n'D has finished!
+ drag->exec(toQtDropActions(sourceActions), getPreferredDropAction(sourceActions));
+ }
+
+ // the drop will eventually call fire_dragEnd, which will clear the listener.
+ // if D'n'D ends without success, we just get a leave event without any indicator,
+ // but the event loop will be terminated, so we have to try to inform the source of
+ // a failure in any way.
+ fire_dragEnd(datatransfer::dnd::DNDConstants::ACTION_NONE, false);
+}
+
+void QtDragSource::fire_dragEnd(sal_Int8 nAction, bool bDropSuccessful)
+{
+ if (!m_xListener.is())
+ return;
+
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = nAction;
+ aEv.DropSuccess = bDropSuccessful;
+
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+}
+
+OUString SAL_CALL QtDragSource::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclQtDragSource";
+}
+
+sal_Bool SAL_CALL QtDragSource::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL QtDragSource::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.QtDragSource" };
+}
+
+QtDropTarget::QtDropTarget()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ , m_bActive(false)
+ , m_nDefaultActions(0)
+{
+}
+
+OUString SAL_CALL QtDropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclQtDropTarget";
+}
+
+sal_Bool SAL_CALL QtDropTarget::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL QtDropTarget::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.QtDropTarget" };
+}
+
+QtDropTarget::~QtDropTarget() {}
+
+void QtDropTarget::deinitialize()
+{
+ m_pFrame = nullptr;
+ m_bActive = false;
+}
+
+void QtDropTarget::initialize(const uno::Sequence<uno::Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw uno::RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ getXWeak());
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw uno::RuntimeException("DropTarget::initialize: missing SalFrame", getXWeak());
+ }
+
+ m_nDropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
+
+ m_pFrame = reinterpret_cast<QtFrame*>(nFrame);
+ m_pFrame->registerDropTarget(this);
+ m_bActive = true;
+}
+
+void QtDropTarget::addDropTargetListener(
+ const uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ ::osl::Guard<::osl::Mutex> aGuard(m_aMutex);
+
+ m_aListeners.push_back(xListener);
+}
+
+void QtDropTarget::removeDropTargetListener(
+ const uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ ::osl::Guard<::osl::Mutex> aGuard(m_aMutex);
+
+ std::erase(m_aListeners, xListener);
+}
+
+sal_Bool QtDropTarget::isActive() { return m_bActive; }
+
+void QtDropTarget::setActive(sal_Bool bActive) { m_bActive = bActive; }
+
+sal_Int8 QtDropTarget::getDefaultActions() { return m_nDefaultActions; }
+
+void QtDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
+{
+ m_nDefaultActions = nDefaultActions;
+}
+
+void QtDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
+{
+ osl::ClearableGuard<::osl::Mutex> aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragEnter(dtde);
+ }
+}
+
+void QtDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
+{
+ osl::ClearableGuard<::osl::Mutex> aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ listener->dragOver(dtde);
+}
+
+void QtDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
+{
+ m_bDropSuccessful = true;
+
+ osl::ClearableGuard<osl::Mutex> aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ listener->drop(dtde);
+}
+
+void QtDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
+{
+ osl::ClearableGuard<::osl::Mutex> aGuard(m_aMutex);
+ std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(
+ m_aListeners);
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ listener->dragExit(dte);
+}
+
+void QtDropTarget::acceptDrag(sal_Int8 dragOperation) { m_nDropAction = dragOperation; }
+
+void QtDropTarget::rejectDrag() { m_nDropAction = 0; }
+
+void QtDropTarget::acceptDrop(sal_Int8 dropOperation) { m_nDropAction = dropOperation; }
+
+void QtDropTarget::rejectDrop() { m_nDropAction = 0; }
+
+void QtDropTarget::dropComplete(sal_Bool success)
+{
+ m_bDropSuccessful = (m_bDropSuccessful && success);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtFilePicker.cxx b/vcl/qt5/QtFilePicker.cxx
new file mode 100644
index 0000000000..8a13ec6723
--- /dev/null
+++ b/vcl/qt5/QtFilePicker.cxx
@@ -0,0 +1,983 @@
+/* -*- 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 <fpicker/fpsofficeResMgr.hxx>
+#include <QtFilePicker.hxx>
+#include <QtFilePicker.moc>
+
+#include <QtFrame.hxx>
+#include <QtTools.hxx>
+#include <QtWidget.hxx>
+#include <QtInstance.hxx>
+
+#include <com/sun/star/awt/SystemDependentXWindow.hpp>
+#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/TerminationVetoException.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/SystemDependent.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ControlActions.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <cppuhelper/interfacecontainer.h>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+
+#include <QtCore/QDebug>
+#include <QtCore/QRegularExpression>
+#include <QtCore/QThread>
+#include <QtCore/QUrl>
+#include <QtGui/QClipboard>
+#include <QtGui/QWindow>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QComboBox>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QMessageBox>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QWidget>
+
+#include <unx/geninst.h>
+#include <fpicker/strings.hrc>
+#include <utility>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::uno;
+
+namespace
+{
+uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.dialogs.FilePicker", "com.sun.star.ui.dialogs.SystemFilePicker",
+ "com.sun.star.ui.dialogs.QtFilePicker" };
+}
+}
+
+QtFilePicker::QtFilePicker(css::uno::Reference<css::uno::XComponentContext> context,
+ QFileDialog::FileMode eMode, bool bUseNative)
+ : QtFilePicker_Base(m_aHelperMutex)
+ , m_context(std::move(context))
+ , m_bIsFolderPicker(eMode == QFileDialog::Directory)
+ , m_pParentWidget(nullptr)
+ , m_pFileDialog(new QFileDialog(nullptr, {}, QDir::homePath()))
+ , m_pExtraControls(new QWidget())
+{
+ m_pFileDialog->setOption(QFileDialog::DontUseNativeDialog, !bUseNative);
+
+ m_pFileDialog->setFileMode(eMode);
+ m_pFileDialog->setWindowModality(Qt::ApplicationModal);
+
+ if (m_bIsFolderPicker)
+ {
+ m_pFileDialog->setOption(QFileDialog::ShowDirsOnly, true);
+ m_pFileDialog->setWindowTitle(toQString(FpsResId(STR_SVT_FOLDERPICKER_DEFAULT_TITLE)));
+ }
+
+ m_pLayout = dynamic_cast<QGridLayout*>(m_pFileDialog->layout());
+
+ setMultiSelectionMode(false);
+
+ // XFilePickerListener notifications
+ connect(m_pFileDialog.get(), SIGNAL(filterSelected(const QString&)), this,
+ SLOT(filterSelected(const QString&)));
+ connect(m_pFileDialog.get(), SIGNAL(currentChanged(const QString&)), this,
+ SLOT(currentChanged(const QString&)));
+
+ // update automatic file extension when filter is changed
+ connect(m_pFileDialog.get(), SIGNAL(filterSelected(const QString&)), this,
+ SLOT(updateAutomaticFileExtension()));
+
+ connect(m_pFileDialog.get(), SIGNAL(finished(int)), this, SLOT(finished(int)));
+}
+
+QtFilePicker::~QtFilePicker()
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this]() {
+ // must delete it in main thread, otherwise
+ // QSocketNotifier::setEnabled() will crash us
+ m_pFileDialog.reset();
+ });
+}
+
+void SAL_CALL
+QtFilePicker::addFilePickerListener(const uno::Reference<XFilePickerListener>& xListener)
+{
+ SolarMutexGuard aGuard;
+ m_xListener = xListener;
+}
+
+void SAL_CALL QtFilePicker::removeFilePickerListener(const uno::Reference<XFilePickerListener>&)
+{
+ SolarMutexGuard aGuard;
+ m_xListener.clear();
+}
+
+void SAL_CALL QtFilePicker::setTitle(const OUString& title)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [this, &title]() { m_pFileDialog->setWindowTitle(toQString(title)); });
+}
+
+void QtFilePicker::prepareExecute()
+{
+ QWidget* pTransientParent = m_pParentWidget;
+ if (!pTransientParent)
+ {
+ vcl::Window* pWindow = ::Application::GetActiveTopWindow();
+ if (pWindow)
+ {
+ QtFrame* pFrame = dynamic_cast<QtFrame*>(pWindow->ImplGetFrame());
+ assert(pFrame);
+ if (pFrame)
+ pTransientParent = pFrame->asChild();
+ }
+ }
+
+ if (!m_aNamedFilterList.isEmpty())
+ m_pFileDialog->setNameFilters(m_aNamedFilterList);
+ if (!m_aCurrentFilter.isEmpty())
+ m_pFileDialog->selectNameFilter(m_aCurrentFilter);
+
+ updateAutomaticFileExtension();
+
+ uno::Reference<css::frame::XDesktop> xDesktop(css::frame::Desktop::create(m_context),
+ UNO_QUERY_THROW);
+
+ // will hide the window, so do before show
+ m_pFileDialog->setParent(pTransientParent, m_pFileDialog->windowFlags());
+ m_pFileDialog->show();
+ xDesktop->addTerminateListener(this);
+}
+
+void QtFilePicker::finished(int nResult)
+{
+ SolarMutexGuard g;
+ uno::Reference<css::frame::XDesktop> xDesktop(css::frame::Desktop::create(m_context),
+ UNO_QUERY_THROW);
+ xDesktop->removeTerminateListener(this);
+ m_pFileDialog->setParent(nullptr, m_pFileDialog->windowFlags());
+
+ if (m_xClosedListener.is())
+ {
+ const sal_Int16 nRet = (QFileDialog::Rejected == nResult) ? ExecutableDialogResults::CANCEL
+ : ExecutableDialogResults::OK;
+ css::ui::dialogs::DialogClosedEvent aEvent(*this, nRet);
+ m_xClosedListener->dialogClosed(aEvent);
+ m_xClosedListener.clear();
+ }
+}
+
+sal_Int16 SAL_CALL QtFilePicker::execute()
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ sal_uInt16 ret;
+ pSalInst->RunInMainThread([&ret, this]() { ret = execute(); });
+ return ret;
+ }
+
+ prepareExecute();
+ int result = m_pFileDialog->exec();
+
+ if (QFileDialog::Rejected == result)
+ return ExecutableDialogResults::CANCEL;
+ return ExecutableDialogResults::OK;
+}
+
+// XAsynchronousExecutableDialog functions
+void SAL_CALL QtFilePicker::setDialogTitle(const OUString& _rTitle) { setTitle(_rTitle); }
+
+void SAL_CALL
+QtFilePicker::startExecuteModal(const Reference<css::ui::dialogs::XDialogClosedListener>& xListener)
+{
+ m_xClosedListener = xListener;
+ prepareExecute();
+ m_pFileDialog->show();
+}
+
+void SAL_CALL QtFilePicker::setMultiSelectionMode(sal_Bool multiSelect)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, multiSelect]() {
+ if (m_bIsFolderPicker || m_pFileDialog->acceptMode() == QFileDialog::AcceptSave)
+ return;
+
+ m_pFileDialog->setFileMode(multiSelect ? QFileDialog::ExistingFiles
+ : QFileDialog::ExistingFile);
+ });
+}
+
+void SAL_CALL QtFilePicker::setDefaultName(const OUString& name)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &name]() { m_pFileDialog->selectFile(toQString(name)); });
+}
+
+void SAL_CALL QtFilePicker::setDisplayDirectory(const OUString& dir)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &dir]() {
+ QString qDir(toQString(dir));
+ m_pFileDialog->setDirectoryUrl(QUrl(qDir));
+ });
+}
+
+OUString SAL_CALL QtFilePicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+ OUString ret;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [&ret, this]() { ret = toOUString(m_pFileDialog->directoryUrl().toString()); });
+ return ret;
+}
+
+uno::Sequence<OUString> SAL_CALL QtFilePicker::getFiles()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq;
+}
+
+uno::Sequence<OUString> SAL_CALL QtFilePicker::getSelectedFiles()
+{
+ SolarMutexGuard g;
+ QList<QUrl> urls;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([&urls, this]() { urls = m_pFileDialog->selectedUrls(); });
+
+ uno::Sequence<OUString> seq(urls.size());
+ auto seqRange = asNonConstRange(seq);
+
+ auto const trans = css::uri::ExternalUriReferenceTranslator::create(m_context);
+ size_t i = 0;
+ for (const QUrl& aURL : urls)
+ {
+ // Unlike LO, QFileDialog (<https://doc.qt.io/qt-5/qfiledialog.html>) apparently always
+ // treats file-system pathnames as UTF-8--encoded, regardless of LANG/LC_CTYPE locale
+ // setting. And pathnames containing byte sequences that are not valid UTF-8 are apparently
+ // filtered out and not even displayed by QFileDialog, so aURL will always have a "payload"
+ // that matches the pathname's byte sequence. So the pathname's byte sequence (which
+ // happens to also be aURL's payload) in the LANG/LC_CTYPE encoding needs to be converted
+ // into LO's internal UTF-8 file URL encoding via
+ // XExternalUriReferenceTranslator::translateToInternal (which looks somewhat paradoxical as
+ // aURL.toEncoded() nominally already has a UTF-8 payload):
+ auto const extUrl = toOUString(aURL.toEncoded());
+ auto intUrl = trans->translateToInternal(extUrl);
+ if (intUrl.isEmpty())
+ {
+ // If translation failed, fall back to original URL:
+ SAL_WARN("vcl.qt", "cannot convert <" << extUrl << "> from locale encoding to UTF-8");
+ intUrl = extUrl;
+ }
+ seqRange[i++] = intUrl;
+ }
+
+ return seq;
+}
+
+void SAL_CALL QtFilePicker::appendFilter(const OUString& title, const OUString& filter)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, &title, &filter]() { appendFilter(title, filter); });
+ return;
+ }
+
+ // '/' need to be escaped else they are assumed to be mime types
+ QString sTitle = toQString(title).replace("/", "\\/");
+
+ QString sFilterName = sTitle;
+ // the Qt non-native file picker adds the extensions to the filter title, so strip them
+ if (m_pFileDialog->testOption(QFileDialog::DontUseNativeDialog))
+ {
+ int pos = sFilterName.indexOf(" (");
+ if (pos >= 0)
+ sFilterName.truncate(pos);
+ }
+
+ QString sGlobFilter = toQString(filter);
+
+ // LibreOffice gives us filters separated by ';' qt dialogs just want space separated
+ sGlobFilter.replace(";", " ");
+
+ // make sure "*.*" is not used as "all files"
+ sGlobFilter.replace("*.*", "*");
+
+ m_aNamedFilterList << QStringLiteral("%1 (%2)").arg(sFilterName, sGlobFilter);
+ m_aTitleToFilterMap[sTitle] = m_aNamedFilterList.constLast();
+ m_aNamedFilterToExtensionMap[m_aNamedFilterList.constLast()] = sGlobFilter;
+}
+
+void SAL_CALL QtFilePicker::setCurrentFilter(const OUString& title)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &title]() {
+ m_aCurrentFilter = m_aTitleToFilterMap.value(toQString(title).replace("/", "\\/"));
+ });
+}
+
+OUString SAL_CALL QtFilePicker::getCurrentFilter()
+{
+ SolarMutexGuard g;
+ QString filter;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([&filter, this]() {
+ filter = m_aTitleToFilterMap.key(m_pFileDialog->selectedNameFilter());
+ });
+
+ if (filter.isEmpty())
+ filter = "ODF Text Document (.odt)";
+ return toOUString(filter);
+}
+
+void SAL_CALL QtFilePicker::appendFilterGroup(const OUString& rGroupTitle,
+ const uno::Sequence<beans::StringPair>& filters)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread(
+ [this, &rGroupTitle, &filters]() { appendFilterGroup(rGroupTitle, filters); });
+ return;
+ }
+
+ const sal_uInt16 length = filters.getLength();
+ for (sal_uInt16 i = 0; i < length; ++i)
+ {
+ beans::StringPair aPair = filters[i];
+ appendFilter(aPair.First, aPair.Second);
+ }
+}
+
+uno::Any QtFilePicker::handleGetListValue(const QComboBox* pWidget, sal_Int16 nControlAction)
+{
+ uno::Any aAny;
+ switch (nControlAction)
+ {
+ case ControlActions::GET_ITEMS:
+ {
+ Sequence<OUString> aItemList(pWidget->count());
+ auto aItemListRange = asNonConstRange(aItemList);
+ for (sal_Int32 i = 0; i < pWidget->count(); ++i)
+ aItemListRange[i] = toOUString(pWidget->itemText(i));
+ aAny <<= aItemList;
+ break;
+ }
+ case ControlActions::GET_SELECTED_ITEM:
+ {
+ if (!pWidget->currentText().isEmpty())
+ aAny <<= toOUString(pWidget->currentText());
+ break;
+ }
+ case ControlActions::GET_SELECTED_ITEM_INDEX:
+ {
+ if (pWidget->currentIndex() >= 0)
+ aAny <<= static_cast<sal_Int32>(pWidget->currentIndex());
+ break;
+ }
+ default:
+ SAL_WARN("vcl.qt",
+ "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+ return aAny;
+}
+
+void QtFilePicker::handleSetListValue(QComboBox* pWidget, sal_Int16 nControlAction,
+ const uno::Any& rValue)
+{
+ switch (nControlAction)
+ {
+ case ControlActions::ADD_ITEM:
+ {
+ OUString sItem;
+ rValue >>= sItem;
+ pWidget->addItem(toQString(sItem));
+ break;
+ }
+ case ControlActions::ADD_ITEMS:
+ {
+ Sequence<OUString> aStringList;
+ rValue >>= aStringList;
+ for (auto const& sItem : std::as_const(aStringList))
+ pWidget->addItem(toQString(sItem));
+ break;
+ }
+ case ControlActions::DELETE_ITEM:
+ {
+ sal_Int32 nPos = 0;
+ rValue >>= nPos;
+ pWidget->removeItem(nPos);
+ break;
+ }
+ case ControlActions::DELETE_ITEMS:
+ {
+ pWidget->clear();
+ break;
+ }
+ case ControlActions::SET_SELECT_ITEM:
+ {
+ sal_Int32 nPos = 0;
+ rValue >>= nPos;
+ pWidget->setCurrentIndex(nPos);
+ break;
+ }
+ default:
+ SAL_WARN("vcl.qt",
+ "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+
+ pWidget->setEnabled(pWidget->count() > 0);
+}
+
+void SAL_CALL QtFilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
+ const uno::Any& value)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, controlId, nControlAction, &value]() {
+ setValue(controlId, nControlAction, value);
+ });
+ return;
+ }
+
+ if (m_aCustomWidgetsMap.contains(controlId))
+ {
+ QWidget* widget = m_aCustomWidgetsMap.value(controlId);
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(widget);
+ if (cb)
+ cb->setChecked(value.get<bool>());
+ else
+ {
+ QComboBox* combo = dynamic_cast<QComboBox*>(widget);
+ if (combo)
+ handleSetListValue(combo, nControlAction, value);
+ }
+ }
+ else
+ SAL_WARN("vcl.qt", "set value on unknown control " << controlId);
+}
+
+uno::Any SAL_CALL QtFilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ uno::Any ret;
+ pSalInst->RunInMainThread([&ret, this, controlId, nControlAction]() {
+ ret = getValue(controlId, nControlAction);
+ });
+ return ret;
+ }
+
+ uno::Any res(false);
+ if (m_aCustomWidgetsMap.contains(controlId))
+ {
+ QWidget* widget = m_aCustomWidgetsMap.value(controlId);
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(widget);
+ if (cb)
+ res <<= cb->isChecked();
+ else
+ {
+ QComboBox* combo = dynamic_cast<QComboBox*>(widget);
+ if (combo)
+ res = handleGetListValue(combo, nControlAction);
+ }
+ }
+ else
+ SAL_WARN("vcl.qt", "get value on unknown control " << controlId);
+
+ return res;
+}
+
+void SAL_CALL QtFilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, controlId, enable]() {
+ if (m_aCustomWidgetsMap.contains(controlId))
+ m_aCustomWidgetsMap.value(controlId)->setEnabled(enable);
+ else
+ SAL_WARN("vcl.qt", "enable unknown control " << controlId);
+ });
+}
+
+void SAL_CALL QtFilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, controlId, label]() { setLabel(controlId, label); });
+ return;
+ }
+
+ if (m_aCustomWidgetsMap.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(m_aCustomWidgetsMap.value(controlId));
+ if (cb)
+ cb->setText(toQString(label));
+ }
+ else
+ SAL_WARN("vcl.qt", "set label on unknown control " << controlId);
+}
+
+OUString SAL_CALL QtFilePicker::getLabel(sal_Int16 controlId)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ OUString ret;
+ pSalInst->RunInMainThread([&ret, this, controlId]() { ret = getLabel(controlId); });
+ return ret;
+ }
+
+ QString label;
+ if (m_aCustomWidgetsMap.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(m_aCustomWidgetsMap.value(controlId));
+ if (cb)
+ label = cb->text();
+ }
+ else
+ SAL_WARN("vcl.qt", "get label on unknown control " << controlId);
+
+ return toOUString(label);
+}
+
+QString QtFilePicker::getResString(TranslateId pResId)
+{
+ QString aResString;
+
+ if (!pResId)
+ return aResString;
+
+ aResString = toQString(FpsResId(pResId));
+
+ return aResString.replace('~', '&');
+}
+
+void QtFilePicker::addCustomControl(sal_Int16 controlId)
+{
+ QWidget* widget = nullptr;
+ QLabel* label = nullptr;
+ TranslateId resId;
+ QCheckBox* pCheckbox = nullptr;
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ resId = STR_SVT_FILEPICKER_AUTO_EXTENSION;
+ break;
+ case CHECKBOX_PASSWORD:
+ resId = STR_SVT_FILEPICKER_PASSWORD;
+ break;
+ case CHECKBOX_FILTEROPTIONS:
+ resId = STR_SVT_FILEPICKER_FILTER_OPTIONS;
+ break;
+ case CHECKBOX_READONLY:
+ resId = STR_SVT_FILEPICKER_READONLY;
+ break;
+ case CHECKBOX_LINK:
+ resId = STR_SVT_FILEPICKER_INSERT_AS_LINK;
+ break;
+ case CHECKBOX_PREVIEW:
+ resId = STR_SVT_FILEPICKER_SHOW_PREVIEW;
+ break;
+ case CHECKBOX_SELECTION:
+ resId = STR_SVT_FILEPICKER_SELECTION;
+ break;
+ case CHECKBOX_GPGENCRYPTION:
+ resId = STR_SVT_FILEPICKER_GPGENCRYPT;
+ break;
+ case PUSHBUTTON_PLAY:
+ resId = STR_SVT_FILEPICKER_PLAY;
+ break;
+ case LISTBOX_VERSION:
+ resId = STR_SVT_FILEPICKER_VERSION;
+ break;
+ case LISTBOX_TEMPLATE:
+ resId = STR_SVT_FILEPICKER_TEMPLATES;
+ break;
+ case LISTBOX_IMAGE_TEMPLATE:
+ resId = STR_SVT_FILEPICKER_IMAGE_TEMPLATE;
+ break;
+ case LISTBOX_IMAGE_ANCHOR:
+ resId = STR_SVT_FILEPICKER_IMAGE_ANCHOR;
+ break;
+ case LISTBOX_VERSION_LABEL:
+ case LISTBOX_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_ANCHOR_LABEL:
+ case LISTBOX_FILTER_SELECTOR:
+ break;
+ }
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ pCheckbox = new QCheckBox(getResString(resId), m_pExtraControls);
+ // to add/remove automatic file extension based on checkbox
+ connect(pCheckbox, SIGNAL(stateChanged(int)), this,
+ SLOT(updateAutomaticFileExtension()));
+ widget = pCheckbox;
+ break;
+ case CHECKBOX_PASSWORD:
+ case CHECKBOX_FILTEROPTIONS:
+ case CHECKBOX_READONLY:
+ case CHECKBOX_LINK:
+ case CHECKBOX_PREVIEW:
+ case CHECKBOX_SELECTION:
+ case CHECKBOX_GPGENCRYPTION:
+ widget = new QCheckBox(getResString(resId), m_pExtraControls);
+ break;
+ case PUSHBUTTON_PLAY:
+ break;
+ case LISTBOX_VERSION:
+ case LISTBOX_TEMPLATE:
+ case LISTBOX_IMAGE_ANCHOR:
+ case LISTBOX_IMAGE_TEMPLATE:
+ case LISTBOX_FILTER_SELECTOR:
+ label = new QLabel(getResString(resId), m_pExtraControls);
+ widget = new QComboBox(m_pExtraControls);
+ label->setBuddy(widget);
+ break;
+ case LISTBOX_VERSION_LABEL:
+ case LISTBOX_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_ANCHOR_LABEL:
+ break;
+ }
+
+ if (widget)
+ {
+ const int row = m_pLayout->rowCount();
+ if (label)
+ m_pLayout->addWidget(label, row, 0);
+ m_pLayout->addWidget(widget, row, 1);
+ m_aCustomWidgetsMap.insert(controlId, widget);
+ }
+}
+
+void SAL_CALL QtFilePicker::initialize(const uno::Sequence<uno::Any>& args)
+{
+ // parameter checking
+ uno::Any arg;
+ if (args.getLength() == 0)
+ throw lang::IllegalArgumentException("no arguments", static_cast<XFilePicker2*>(this), 1);
+
+ arg = args[0];
+
+ if ((arg.getValueType() != cppu::UnoType<sal_Int16>::get())
+ && (arg.getValueType() != cppu::UnoType<sal_Int8>::get()))
+ {
+ throw lang::IllegalArgumentException("invalid argument type",
+ static_cast<XFilePicker2*>(this), 1);
+ }
+
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, args]() { initialize(args); });
+ return;
+ }
+
+ m_aNamedFilterToExtensionMap.clear();
+ m_aNamedFilterList.clear();
+ m_aTitleToFilterMap.clear();
+ m_aCurrentFilter.clear();
+
+ sal_Int16 templateId = -1;
+ arg >>= templateId;
+
+ QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen;
+ switch (templateId)
+ {
+ case FILEOPEN_SIMPLE:
+ break;
+
+ case FILESAVE_SIMPLE:
+ acceptMode = QFileDialog::AcceptSave;
+ break;
+
+ case FILESAVE_AUTOEXTENSION:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_PASSWORD);
+ addCustomControl(CHECKBOX_GPGENCRYPTION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_PASSWORD);
+ addCustomControl(CHECKBOX_GPGENCRYPTION);
+ addCustomControl(CHECKBOX_FILTEROPTIONS);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_SELECTION:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_SELECTION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ acceptMode = QFileDialog::AcceptSave;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(LISTBOX_TEMPLATE);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ addCustomControl(LISTBOX_IMAGE_TEMPLATE);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ addCustomControl(LISTBOX_IMAGE_ANCHOR);
+ break;
+
+ case FILEOPEN_PLAY:
+ addCustomControl(PUSHBUTTON_PLAY);
+ break;
+
+ case FILEOPEN_LINK_PLAY:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(PUSHBUTTON_PLAY);
+ break;
+
+ case FILEOPEN_READONLY_VERSION:
+ addCustomControl(CHECKBOX_READONLY);
+ addCustomControl(LISTBOX_VERSION);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ break;
+
+ case FILEOPEN_PREVIEW:
+ addCustomControl(CHECKBOX_PREVIEW);
+ break;
+
+ default:
+ throw lang::IllegalArgumentException("Unknown template",
+ static_cast<XFilePicker2*>(this), 1);
+ }
+
+ TranslateId resId;
+ switch (acceptMode)
+ {
+ case QFileDialog::AcceptOpen:
+ resId = STR_FILEDLG_OPEN;
+ break;
+ case QFileDialog::AcceptSave:
+ resId = STR_FILEDLG_SAVE;
+ m_pFileDialog->setFileMode(QFileDialog::AnyFile);
+ break;
+ }
+
+ m_pFileDialog->setAcceptMode(acceptMode);
+ m_pFileDialog->setWindowTitle(getResString(resId));
+
+ css::uno::Reference<css::awt::XWindow> xParentWindow;
+ if (args.getLength() > 1)
+ args[1] >>= xParentWindow;
+ if (!xParentWindow.is())
+ return;
+
+ css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysWinPeer(xParentWindow,
+ css::uno::UNO_QUERY);
+ if (!xSysWinPeer.is())
+ return;
+
+ // the sal_*Int8 handling is strange, but it's public API - no way around
+ css::uno::Sequence<sal_Int8> aProcessIdent(16);
+ rtl_getGlobalProcessId(reinterpret_cast<sal_uInt8*>(aProcessIdent.getArray()));
+ uno::Any aAny
+ = xSysWinPeer->getWindowHandle(aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW);
+ css::awt::SystemDependentXWindow xSysWin;
+ aAny >>= xSysWin;
+
+ const auto& pFrames = pSalInst->getFrames();
+ const tools::Long aWindowHandle = xSysWin.WindowHandle;
+ const auto it
+ = std::find_if(pFrames.begin(), pFrames.end(), [&aWindowHandle](auto pFrame) -> bool {
+ const SystemEnvData* pData = pFrame->GetSystemData();
+ return pData && tools::Long(pData->GetWindowHandle(pFrame)) == aWindowHandle;
+ });
+ if (it != pFrames.end())
+ m_pParentWidget = static_cast<QtFrame*>(*it)->asChild();
+}
+
+void SAL_CALL QtFilePicker::cancel() { m_pFileDialog->reject(); }
+
+void SAL_CALL QtFilePicker::disposing(const lang::EventObject& rEvent)
+{
+ uno::Reference<XFilePickerListener> xFilePickerListener(rEvent.Source, uno::UNO_QUERY);
+
+ if (xFilePickerListener.is())
+ {
+ removeFilePickerListener(xFilePickerListener);
+ }
+}
+
+void SAL_CALL QtFilePicker::queryTermination(const css::lang::EventObject&)
+{
+ throw css::frame::TerminationVetoException();
+}
+
+void SAL_CALL QtFilePicker::notifyTermination(const css::lang::EventObject&)
+{
+ SolarMutexGuard aGuard;
+ m_pFileDialog->reject();
+}
+
+OUString SAL_CALL QtFilePicker::getImplementationName()
+{
+ return "com.sun.star.ui.dialogs.QtFilePicker";
+}
+
+sal_Bool SAL_CALL QtFilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL QtFilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+void QtFilePicker::updateAutomaticFileExtension()
+{
+ bool bSetAutoExtension
+ = getValue(CHECKBOX_AUTOEXTENSION, ControlActions::GET_SELECTED_ITEM).get<bool>();
+ if (bSetAutoExtension)
+ {
+ QString sSuffix = m_aNamedFilterToExtensionMap.value(m_pFileDialog->selectedNameFilter());
+ // string is "*.<SUFFIX>" if a specific filter was selected that has exactly one possible file extension
+ if (sSuffix.lastIndexOf("*.") == 0)
+ {
+ sSuffix = sSuffix.remove("*.");
+ m_pFileDialog->setDefaultSuffix(sSuffix);
+ }
+ else
+ {
+ // fall back to setting none otherwise
+ SAL_INFO(
+ "vcl.qt",
+ "Unable to retrieve unambiguous file extension. Will not add any automatically.");
+ bSetAutoExtension = false;
+ }
+ }
+
+ if (!bSetAutoExtension)
+ m_pFileDialog->setDefaultSuffix("");
+}
+
+void QtFilePicker::filterSelected(const QString&)
+{
+ FilePickerEvent aEvent;
+ aEvent.ElementId = LISTBOX_FILTER;
+ SAL_INFO("vcl.qt", "filter changed");
+ if (m_xListener.is())
+ m_xListener->controlStateChanged(aEvent);
+}
+
+void QtFilePicker::currentChanged(const QString&)
+{
+ FilePickerEvent aEvent;
+ SAL_INFO("vcl.qt", "file selection changed");
+ if (m_xListener.is())
+ m_xListener->fileSelectionChanged(aEvent);
+}
+
+OUString QtFilePicker::getDirectory()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq[0];
+}
+
+void QtFilePicker::setDescription(const OUString&) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtFont.cxx b/vcl/qt5/QtFont.cxx
new file mode 100644
index 0000000000..e3a6c0b0a9
--- /dev/null
+++ b/vcl/qt5/QtFont.cxx
@@ -0,0 +1,190 @@
+/* -*- 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 <QtFont.hxx>
+#include <QtTools.hxx>
+
+#include <QtGui/QFont>
+#include <QtGui/QRawFont>
+#include <QtGui/QPainterPath>
+
+static inline void applyWeight(QtFont& rFont, FontWeight eWeight)
+{
+ switch (eWeight)
+ {
+ case WEIGHT_THIN:
+ rFont.setWeight(QFont::Thin);
+ break;
+ case WEIGHT_ULTRALIGHT:
+ rFont.setWeight(QFont::ExtraLight);
+ break;
+ case WEIGHT_LIGHT:
+ rFont.setWeight(QFont::Light);
+ break;
+ case WEIGHT_SEMILIGHT:
+ [[fallthrough]];
+ case WEIGHT_NORMAL:
+ rFont.setWeight(QFont::Normal);
+ break;
+ case WEIGHT_MEDIUM:
+ rFont.setWeight(QFont::Medium);
+ break;
+ case WEIGHT_SEMIBOLD:
+ rFont.setWeight(QFont::DemiBold);
+ break;
+ case WEIGHT_BOLD:
+ rFont.setWeight(QFont::Bold);
+ break;
+ case WEIGHT_ULTRABOLD:
+ rFont.setWeight(QFont::ExtraBold);
+ break;
+ case WEIGHT_BLACK:
+ rFont.setWeight(QFont::Black);
+ break;
+ default:
+ break;
+ }
+}
+
+static inline void applyStretch(QtFont& rFont, FontWidth eWidthType)
+{
+ switch (eWidthType)
+ {
+ case WIDTH_DONTKNOW:
+ rFont.setStretch(QFont::AnyStretch);
+ break;
+ case WIDTH_ULTRA_CONDENSED:
+ rFont.setStretch(QFont::UltraCondensed);
+ break;
+ case WIDTH_EXTRA_CONDENSED:
+ rFont.setStretch(QFont::ExtraCondensed);
+ break;
+ case WIDTH_CONDENSED:
+ rFont.setStretch(QFont::Condensed);
+ break;
+ case WIDTH_SEMI_CONDENSED:
+ rFont.setStretch(QFont::SemiCondensed);
+ break;
+ case WIDTH_NORMAL:
+ rFont.setStretch(QFont::Unstretched);
+ break;
+ case WIDTH_SEMI_EXPANDED:
+ rFont.setStretch(QFont::SemiExpanded);
+ break;
+ case WIDTH_EXPANDED:
+ rFont.setStretch(QFont::Expanded);
+ break;
+ case WIDTH_EXTRA_EXPANDED:
+ rFont.setStretch(QFont::ExtraExpanded);
+ break;
+ case WIDTH_ULTRA_EXPANDED:
+ rFont.setStretch(QFont::UltraExpanded);
+ break;
+ default:
+ break;
+ }
+}
+
+static inline void applyStyle(QtFont& rFont, FontItalic eItalic)
+{
+ switch (eItalic)
+ {
+ case ITALIC_NONE:
+ rFont.setStyle(QFont::Style::StyleNormal);
+ break;
+ case ITALIC_OBLIQUE:
+ rFont.setStyle(QFont::Style::StyleOblique);
+ break;
+ case ITALIC_NORMAL:
+ rFont.setStyle(QFont::Style::StyleItalic);
+ break;
+ default:
+ break;
+ }
+}
+
+QtFont::QtFont(const vcl::font::PhysicalFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+{
+ setFamily(toQString(rPFF.GetFamilyName()));
+ applyWeight(*this, rPFF.GetWeight());
+ setPixelSize(rFSP.mnHeight);
+ applyStretch(*this, rPFF.GetWidthType());
+ applyStyle(*this, rFSP.GetItalic());
+}
+
+bool QtFont::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool) const
+{
+ rB2DPolyPoly.clear();
+ basegfx::B2DPolygon aPart;
+ QRawFont aRawFont(QRawFont::fromFont(*this));
+ QPainterPath aQPath = aRawFont.pathForGlyph(nId);
+
+ for (int a(0); a < aQPath.elementCount(); a++)
+ {
+ const QPainterPath::Element aQElement = aQPath.elementAt(a);
+
+ switch (aQElement.type)
+ {
+ case QPainterPath::MoveToElement:
+ {
+ if (aPart.count())
+ {
+ aPart.setClosed(true);
+ rB2DPolyPoly.append(aPart);
+ aPart.clear();
+ }
+
+ aPart.append(basegfx::B2DPoint(aQElement.x, aQElement.y));
+ break;
+ }
+ case QPainterPath::LineToElement:
+ {
+ aPart.append(basegfx::B2DPoint(aQElement.x, aQElement.y));
+ break;
+ }
+ case QPainterPath::CurveToElement:
+ {
+ const QPainterPath::Element aQ2 = aQPath.elementAt(++a);
+ const QPainterPath::Element aQ3 = aQPath.elementAt(++a);
+ aPart.appendBezierSegment(basegfx::B2DPoint(aQElement.x, aQElement.y),
+ basegfx::B2DPoint(aQ2.x, aQ2.y),
+ basegfx::B2DPoint(aQ3.x, aQ3.y));
+ break;
+ }
+ case QPainterPath::CurveToDataElement:
+ {
+ break;
+ }
+ }
+ }
+
+ if (aPart.count())
+ {
+ aPart.setClosed(true);
+ rB2DPolyPoly.append(aPart);
+ aPart.clear();
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtFontFace.cxx b/vcl/qt5/QtFontFace.cxx
new file mode 100644
index 0000000000..351f597395
--- /dev/null
+++ b/vcl/qt5/QtFontFace.cxx
@@ -0,0 +1,217 @@
+/* -*- 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 <unotools/fontdefs.hxx>
+
+#include <QtFontFace.hxx>
+#include <QtFont.hxx>
+#include <QtTools.hxx>
+
+#include <font/LogicalFontInstance.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontCollection.hxx>
+
+#include <QtGui/QFont>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QFontInfo>
+#include <QtGui/QRawFont>
+#include <utility>
+
+using namespace vcl;
+
+QtFontFace::QtFontFace(const QtFontFace& rSrc)
+ : vcl::font::PhysicalFontFace(rSrc)
+ , m_aFontId(rSrc.m_aFontId)
+ , m_eFontIdType(rSrc.m_eFontIdType)
+{
+}
+
+FontWeight QtFontFace::toFontWeight(const int nWeight)
+{
+ if (nWeight <= QFont::Thin)
+ return WEIGHT_THIN;
+ if (nWeight <= QFont::ExtraLight)
+ return WEIGHT_ULTRALIGHT;
+ if (nWeight <= QFont::Light)
+ return WEIGHT_LIGHT;
+ if (nWeight <= QFont::Normal)
+ return WEIGHT_NORMAL;
+ if (nWeight <= QFont::Medium)
+ return WEIGHT_MEDIUM;
+ if (nWeight <= QFont::DemiBold)
+ return WEIGHT_SEMIBOLD;
+ if (nWeight <= QFont::Bold)
+ return WEIGHT_BOLD;
+ if (nWeight <= QFont::ExtraBold)
+ return WEIGHT_ULTRABOLD;
+ return WEIGHT_BLACK;
+}
+
+FontWidth QtFontFace::toFontWidth(const int nStretch)
+{
+ if (nStretch == 0) // QFont::AnyStretch since Qt 5.8
+ return WIDTH_DONTKNOW;
+ if (nStretch <= QFont::UltraCondensed)
+ return WIDTH_ULTRA_CONDENSED;
+ if (nStretch <= QFont::ExtraCondensed)
+ return WIDTH_EXTRA_CONDENSED;
+ if (nStretch <= QFont::Condensed)
+ return WIDTH_CONDENSED;
+ if (nStretch <= QFont::SemiCondensed)
+ return WIDTH_SEMI_CONDENSED;
+ if (nStretch <= QFont::Unstretched)
+ return WIDTH_NORMAL;
+ if (nStretch <= QFont::SemiExpanded)
+ return WIDTH_SEMI_EXPANDED;
+ if (nStretch <= QFont::Expanded)
+ return WIDTH_EXPANDED;
+ if (nStretch <= QFont::ExtraExpanded)
+ return WIDTH_EXTRA_EXPANDED;
+ return WIDTH_ULTRA_EXPANDED;
+}
+
+FontItalic QtFontFace::toFontItalic(const QFont::Style eStyle)
+{
+ switch (eStyle)
+ {
+ case QFont::StyleNormal:
+ return ITALIC_NONE;
+ case QFont::StyleItalic:
+ return ITALIC_NORMAL;
+ case QFont::StyleOblique:
+ return ITALIC_OBLIQUE;
+ }
+
+ return ITALIC_NONE;
+}
+
+void QtFontFace::fillAttributesFromQFont(const QFont& rFont, FontAttributes& rFA)
+{
+ QFontInfo aFontInfo(rFont);
+
+ rFA.SetFamilyName(toOUString(aFontInfo.family()));
+ rFA.SetStyleName(toOUString(aFontInfo.styleName()));
+ rFA.SetPitch(aFontInfo.fixedPitch() ? PITCH_FIXED : PITCH_VARIABLE);
+ rFA.SetWeight(QtFontFace::toFontWeight(aFontInfo.weight()));
+ rFA.SetItalic(QtFontFace::toFontItalic(aFontInfo.style()));
+ rFA.SetWidthType(QtFontFace::toFontWidth(rFont.stretch()));
+}
+
+QtFontFace* QtFontFace::fromQFont(const QFont& rFont)
+{
+ FontAttributes aFA;
+ fillAttributesFromQFont(rFont, aFA);
+ return new QtFontFace(aFA, rFont.toString(), FontIdType::Font);
+}
+
+QtFontFace* QtFontFace::fromQFontDatabase(const QString& aFamily, const QString& aStyle)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ auto const isFixedPitch = QFontDatabase::isFixedPitch(aFamily, aStyle);
+ auto const weigh = QFontDatabase::weight(aFamily, aStyle);
+ auto const italic = QFontDatabase::italic(aFamily, aStyle);
+ auto const aPointList = QFontDatabase::pointSizes(aFamily, aStyle);
+#else
+ QFontDatabase aFDB;
+ auto const isFixedPitch = aFDB.isFixedPitch(aFamily, aStyle);
+ auto const weigh = aFDB.weight(aFamily, aStyle);
+ auto const italic = aFDB.italic(aFamily, aStyle);
+ auto const aPointList = aFDB.pointSizes(aFamily, aStyle);
+#endif
+
+ FontAttributes aFA;
+
+ aFA.SetFamilyName(toOUString(aFamily));
+ aFA.SetStyleName(toOUString(aStyle));
+ aFA.SetPitch(isFixedPitch ? PITCH_FIXED : PITCH_VARIABLE);
+ aFA.SetWeight(QtFontFace::toFontWeight(weigh));
+ aFA.SetItalic(italic ? ITALIC_NORMAL : ITALIC_NONE);
+
+ int nPointSize = 0;
+ if (!aPointList.empty())
+ nPointSize = aPointList[0];
+
+ return new QtFontFace(aFA, aFamily + "," + aStyle + "," + QString::number(nPointSize),
+ FontIdType::FontDB);
+}
+
+QtFontFace::QtFontFace(const FontAttributes& rFA, QString aFontID, const FontIdType eFontIdType)
+ : PhysicalFontFace(rFA)
+ , m_aFontId(std::move(aFontID))
+ , m_eFontIdType(eFontIdType)
+{
+}
+
+sal_IntPtr QtFontFace::GetFontId() const { return reinterpret_cast<sal_IntPtr>(&m_aFontId); }
+
+QFont QtFontFace::CreateFont() const
+{
+ QFont aFont;
+ switch (m_eFontIdType)
+ {
+ case FontDB:
+ {
+ QStringList aStrList = m_aFontId.split(",");
+ if (3 == aStrList.size())
+ {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ aFont = QFontDatabase::font(aStrList[0], aStrList[1], aStrList[2].toInt());
+#else
+ QFontDatabase aFDB;
+ aFont = aFDB.font(aStrList[0], aStrList[1], aStrList[2].toInt());
+#endif
+ }
+ else
+ SAL_WARN("vcl.qt", "Invalid QFontDatabase font ID " << m_aFontId);
+ break;
+ }
+ case Font:
+ bool bRet = aFont.fromString(m_aFontId);
+ SAL_WARN_IF(!bRet, "vcl.qt", "Failed to create QFont from ID: " << m_aFontId);
+ Q_UNUSED(bRet);
+ break;
+ }
+ return aFont;
+}
+
+rtl::Reference<LogicalFontInstance>
+QtFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
+{
+ return new QtFont(*this, rFSD);
+}
+
+hb_blob_t* QtFontFace::GetHbTable(hb_tag_t nTag) const
+{
+ char pTagName[5] = { '\0' };
+ hb_tag_to_string(nTag, pTagName);
+
+ QFont aFont = CreateFont();
+ QRawFont aRawFont(QRawFont::fromFont(aFont));
+ QByteArray aTable = aRawFont.fontTable(pTagName);
+ const sal_uInt32 nLength = aTable.size();
+
+ hb_blob_t* pBlob = nullptr;
+ if (nLength > 0)
+ pBlob = hb_blob_create(aTable.data(), nLength, HB_MEMORY_MODE_DUPLICATE, nullptr, nullptr);
+ return pBlob;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtFrame.cxx b/vcl/qt5/QtFrame.cxx
new file mode 100644
index 0000000000..24dcb5ff6f
--- /dev/null
+++ b/vcl/qt5/QtFrame.cxx
@@ -0,0 +1,1520 @@
+/* -*- 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 <QtFrame.hxx>
+#include <QtFrame.moc>
+
+#include <QtData.hxx>
+#include <QtDragAndDrop.hxx>
+#include <QtFontFace.hxx>
+#include <QtGraphics.hxx>
+#include <QtInstance.hxx>
+#include <QtMainWindow.hxx>
+#include <QtMenu.hxx>
+#include <QtSvpGraphics.hxx>
+#include <QtSystem.hxx>
+#include <QtTools.hxx>
+#include <QtTransferable.hxx>
+#if CHECK_ANY_QT_USING_X11
+#include <QtX11Support.hxx>
+#endif
+
+#include <QtCore/QMimeData>
+#include <QtCore/QPoint>
+#include <QtCore/QSize>
+#include <QtCore/QThread>
+#include <QtGui/QDragMoveEvent>
+#include <QtGui/QDropEvent>
+#include <QtGui/QIcon>
+#include <QtGui/QWindow>
+#include <QtGui/QScreen>
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+#include <QtGui/QStyleHints>
+#endif
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QMainWindow>
+#if CHECK_QT5_USING_X11
+#include <QtX11Extras/QX11Info>
+#endif
+
+#include <window.h>
+#include <vcl/syswin.hxx>
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include <cairo.h>
+#include <headless/svpgdi.hxx>
+
+#include <unx/fontmanager.hxx>
+
+static void SvpDamageHandler(void* handle, sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
+{
+ QtFrame* pThis = static_cast<QtFrame*>(handle);
+ pThis->Damage(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
+}
+
+namespace
+{
+sal_Int32 screenNumber(const QScreen* pScreen)
+{
+ const QList<QScreen*> screens = QApplication::screens();
+
+ sal_Int32 nScreen = 0;
+ bool bFound = false;
+ for (const QScreen* pCurScreen : screens)
+ {
+ if (pScreen == pCurScreen)
+ {
+ bFound = true;
+ break;
+ }
+ nScreen++;
+ }
+
+ return bFound ? nScreen : -1;
+}
+}
+
+QtFrame::QtFrame(QtFrame* pParent, SalFrameStyleFlags nStyle, bool bUseCairo)
+ : m_pTopLevel(nullptr)
+ , m_bUseCairo(bUseCairo)
+ , m_bNullRegion(true)
+ , m_bGraphicsInUse(false)
+ , m_ePointerStyle(PointerStyle::Arrow)
+ , m_pDragSource(nullptr)
+ , m_pDropTarget(nullptr)
+ , m_bInDrag(false)
+ , m_bDefaultSize(true)
+ , m_bDefaultPos(true)
+ , m_bFullScreen(false)
+ , m_bFullScreenSpanAll(false)
+#if CHECK_ANY_QT_USING_X11
+ , m_nKeyModifiers(ModKeyFlags::NONE)
+#endif
+ , m_nInputLanguage(LANGUAGE_DONTKNOW)
+{
+ QtInstance* pInst = GetQtInstance();
+ pInst->insertFrame(this);
+
+ m_aDamageHandler.handle = this;
+ m_aDamageHandler.damaged = ::SvpDamageHandler;
+
+ if (nStyle & SalFrameStyleFlags::DEFAULT) // ensure default style
+ {
+ nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE
+ | SalFrameStyleFlags::CLOSEABLE;
+ nStyle &= ~SalFrameStyleFlags::FLOAT;
+ }
+
+ m_nStyle = nStyle;
+ m_pParent = pParent;
+
+ Qt::WindowFlags aWinFlags(Qt::Widget);
+ if (!(nStyle & SalFrameStyleFlags::SYSTEMCHILD))
+ {
+ if (nStyle & SalFrameStyleFlags::INTRO)
+ aWinFlags = Qt::SplashScreen;
+ // floating toolbars are frameless tool windows
+ // + they must be able to receive keyboard focus
+ else if ((nStyle & SalFrameStyleFlags::FLOAT)
+ && (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION))
+ aWinFlags = Qt::Tool | Qt::FramelessWindowHint;
+ else if (nStyle & SalFrameStyleFlags::TOOLTIP)
+ aWinFlags = Qt::ToolTip;
+ // Can't use Qt::Popup, because it grabs the input focus and generates a focus-out event,
+ // instantly auto-closing the LO's editable ComboBox popup.
+ // On X11, the alternative Qt::Window | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint
+ // seems to work well enough, but at least on Wayland and WASM, this results in problems.
+ // So while using Qt::ToolTip, the popups are wrongly advertised via accessibility, at least
+ // the GUI seems to work on all platforms... what a mess.
+ else if (isPopup())
+ aWinFlags = Qt::ToolTip | Qt::FramelessWindowHint;
+ else if (nStyle & SalFrameStyleFlags::TOOLWINDOW)
+ aWinFlags = Qt::Tool;
+ // top level windows can't be transient in Qt, so make them dialogs, if they have a parent. At least
+ // the plasma shell relies on this setting to skip dialogs in the window list. And Qt Xcb will just
+ // set transient for the types Dialog, Sheet, Tool, SplashScreen, ToolTip, Drawer and Popup.
+ else if (nStyle & SalFrameStyleFlags::DIALOG || m_pParent)
+ aWinFlags = Qt::Dialog;
+ else
+ aWinFlags = Qt::Window;
+ }
+
+ if (aWinFlags == Qt::Window)
+ {
+ m_pTopLevel = new QtMainWindow(*this, aWinFlags);
+ m_pQWidget = new QtWidget(*this);
+ m_pTopLevel->setCentralWidget(m_pQWidget);
+ m_pTopLevel->setFocusProxy(m_pQWidget);
+ }
+ else
+ {
+ m_pQWidget = new QtWidget(*this, aWinFlags);
+ // from Qt's POV the popup window doesn't have the input focus, so we must force tooltips...
+ if (isPopup())
+ m_pQWidget->setAttribute(Qt::WA_AlwaysShowToolTips);
+ }
+
+ FillSystemEnvData(m_aSystemData, reinterpret_cast<sal_IntPtr>(this), m_pQWidget);
+
+ QWindow* pChildWindow = windowHandle();
+ connect(pChildWindow, &QWindow::screenChanged, this, &QtFrame::screenChanged);
+
+ if (pParent && !(pParent->m_nStyle & SalFrameStyleFlags::PLUG))
+ {
+ QWindow* pParentWindow = pParent->windowHandle();
+ if (pParentWindow && pChildWindow && (pParentWindow != pChildWindow))
+ pChildWindow->setTransientParent(pParentWindow);
+ }
+
+ SetIcon(SV_ICON_ID_OFFICE);
+}
+
+void QtFrame::screenChanged(QScreen*) { m_pQWidget->fakeResize(); }
+
+void QtFrame::FillSystemEnvData(SystemEnvData& rData, sal_IntPtr pWindow, QWidget* pWidget)
+{
+ assert(rData.platform == SystemEnvData::Platform::Invalid);
+ assert(rData.toolkit == SystemEnvData::Toolkit::Invalid);
+ if (QGuiApplication::platformName() == "wayland")
+ rData.platform = SystemEnvData::Platform::Wayland;
+ else if (QGuiApplication::platformName() == "xcb")
+ rData.platform = SystemEnvData::Platform::Xcb;
+ else if (QGuiApplication::platformName() == "wasm")
+ rData.platform = SystemEnvData::Platform::WASM;
+ else
+ {
+ // maybe add a SystemEnvData::Platform::Unsupported to avoid special cases and not abort?
+ SAL_WARN("vcl.qt",
+ "Unsupported qt VCL platform: " << toOUString(QGuiApplication::platformName()));
+ std::abort();
+ }
+
+ rData.toolkit = SystemEnvData::Toolkit::Qt;
+ rData.aShellWindow = pWindow;
+ rData.pWidget = pWidget;
+}
+
+QtFrame::~QtFrame()
+{
+ QtInstance* pInst = GetQtInstance();
+ pInst->eraseFrame(this);
+ delete asChild();
+ m_aSystemData.aShellWindow = 0;
+}
+
+void QtFrame::Damage(sal_Int32 nExtentsX, sal_Int32 nExtentsY, sal_Int32 nExtentsWidth,
+ sal_Int32 nExtentsHeight) const
+{
+ m_pQWidget->update(scaledQRect(QRect(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight),
+ 1 / devicePixelRatioF()));
+}
+
+SalGraphics* QtFrame::AcquireGraphics()
+{
+ if (m_bGraphicsInUse)
+ return nullptr;
+
+ m_bGraphicsInUse = true;
+
+ if (m_bUseCairo)
+ {
+ if (!m_pSvpGraphics)
+ {
+ QSize aSize = m_pQWidget->size() * devicePixelRatioF();
+ m_pSvpGraphics.reset(new QtSvpGraphics(this));
+ m_pSurface.reset(
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32, aSize.width(), aSize.height()));
+ m_pSvpGraphics->setSurface(m_pSurface.get(),
+ basegfx::B2IVector(aSize.width(), aSize.height()));
+ cairo_surface_set_user_data(m_pSurface.get(), QtSvpGraphics::getDamageKey(),
+ &m_aDamageHandler, nullptr);
+ }
+ return m_pSvpGraphics.get();
+ }
+ else
+ {
+ if (!m_pQtGraphics)
+ {
+ m_pQtGraphics.reset(new QtGraphics(this));
+ m_pQImage.reset(
+ new QImage(m_pQWidget->size() * devicePixelRatioF(), Qt_DefaultFormat32));
+ m_pQImage->fill(Qt::transparent);
+ m_pQtGraphics->ChangeQImage(m_pQImage.get());
+ }
+ return m_pQtGraphics.get();
+ }
+}
+
+void QtFrame::ReleaseGraphics(SalGraphics* pSalGraph)
+{
+ (void)pSalGraph;
+ if (m_bUseCairo)
+ assert(pSalGraph == m_pSvpGraphics.get());
+ else
+ assert(pSalGraph == m_pQtGraphics.get());
+ m_bGraphicsInUse = false;
+}
+
+bool QtFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ QtInstance* pInst = GetQtInstance();
+ pInst->PostEvent(this, pData.release(), SalEvent::UserEvent);
+ return true;
+}
+
+QWidget* QtFrame::asChild() const
+{
+ if (m_pTopLevel)
+ return m_pTopLevel;
+ return m_pQWidget;
+}
+
+qreal QtFrame::devicePixelRatioF() const { return asChild()->devicePixelRatioF(); }
+
+bool QtFrame::isWindow() const { return asChild()->isWindow(); }
+
+QWindow* QtFrame::windowHandle() const
+{
+ // set attribute 'Qt::WA_NativeWindow' first to make sure a window handle actually exists
+ QWidget* pChild = asChild();
+ assert(pChild->window() == pChild);
+ switch (m_aSystemData.platform)
+ {
+ case SystemEnvData::Platform::Wayland:
+ case SystemEnvData::Platform::Xcb:
+ pChild->setAttribute(Qt::WA_NativeWindow);
+ break;
+ case SystemEnvData::Platform::WASM:
+ // no idea, why Qt::WA_NativeWindow breaks the menubar for EMSCRIPTEN
+ break;
+ case SystemEnvData::Platform::Invalid:
+ std::abort();
+ break;
+ }
+ return pChild->windowHandle();
+}
+
+QScreen* QtFrame::screen() const { return asChild()->screen(); }
+
+bool QtFrame::GetUseDarkMode() const
+{
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ const QStyleHints* pStyleHints = QApplication::styleHints();
+ return pStyleHints->colorScheme() == Qt::ColorScheme::Dark;
+#else
+ // use same mechanism for determining dark mode preference as xdg-desktop-portal-kde, s.
+ // https://invent.kde.org/plasma/xdg-desktop-portal-kde/-/blob/0a4237549debf9518f8cfbaf531456850c0729bd/src/settings.cpp#L213-227
+ const QPalette aPalette = QApplication::palette();
+ const int nWindowBackGroundGray = qGray(aPalette.window().color().rgb());
+ return nWindowBackGroundGray < 192;
+#endif
+}
+
+bool QtFrame::isMinimized() const { return asChild()->isMinimized(); }
+
+bool QtFrame::isMaximized() const { return asChild()->isMaximized(); }
+
+void QtFrame::SetWindowStateImpl(Qt::WindowStates eState)
+{
+ return asChild()->setWindowState(eState);
+}
+
+void QtFrame::SetTitle(const OUString& rTitle)
+{
+ QtInstance* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [this, rTitle]() { m_pQWidget->window()->setWindowTitle(toQString(rTitle)); });
+}
+
+void QtFrame::SetIcon(sal_uInt16 nIcon)
+{
+ if (m_nStyle
+ & (SalFrameStyleFlags::PLUG | SalFrameStyleFlags::SYSTEMCHILD
+ | SalFrameStyleFlags::FLOAT | SalFrameStyleFlags::INTRO
+ | SalFrameStyleFlags::OWNERDRAWDECORATION)
+ || !isWindow())
+ return;
+
+ QString appicon;
+
+ if (nIcon == SV_ICON_ID_TEXT)
+ appicon = "libreoffice-writer";
+ else if (nIcon == SV_ICON_ID_SPREADSHEET)
+ appicon = "libreoffice-calc";
+ else if (nIcon == SV_ICON_ID_DRAWING)
+ appicon = "libreoffice-draw";
+ else if (nIcon == SV_ICON_ID_PRESENTATION)
+ appicon = "libreoffice-impress";
+ else if (nIcon == SV_ICON_ID_DATABASE)
+ appicon = "libreoffice-base";
+ else if (nIcon == SV_ICON_ID_FORMULA)
+ appicon = "libreoffice-math";
+ else
+ appicon = "libreoffice-startcenter";
+
+ QIcon aIcon = QIcon::fromTheme(appicon);
+ m_pQWidget->window()->setWindowIcon(aIcon);
+}
+
+void QtFrame::SetMenu(SalMenu*) {}
+
+void QtFrame::SetExtendedFrameStyle(SalExtStyle /*nExtStyle*/) { /* not needed */}
+
+void QtFrame::Show(bool bVisible, bool bNoActivate)
+{
+ assert(m_pQWidget);
+ if (bVisible == asChild()->isVisible())
+ return;
+
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+
+ if (!bVisible) // hide
+ {
+ pSalInst->RunInMainThread([this]() { asChild()->setVisible(false); });
+ return;
+ }
+
+ // show
+ SetDefaultSize();
+
+ pSalInst->RunInMainThread([this, bNoActivate]() {
+ QWidget* const pChild = asChild();
+ pChild->setVisible(true);
+ pChild->raise();
+ if (!bNoActivate)
+ {
+ pChild->activateWindow();
+ pChild->setFocus();
+ }
+ });
+}
+
+void QtFrame::SetMinClientSize(tools::Long nWidth, tools::Long nHeight)
+{
+ if (!isChild())
+ {
+ const qreal fRatio = devicePixelRatioF();
+ asChild()->setMinimumSize(round(nWidth / fRatio), round(nHeight / fRatio));
+ }
+}
+
+void QtFrame::SetMaxClientSize(tools::Long nWidth, tools::Long nHeight)
+{
+ if (!isChild())
+ {
+ const qreal fRatio = devicePixelRatioF();
+ asChild()->setMaximumSize(round(nWidth / fRatio), round(nHeight / fRatio));
+ }
+}
+
+int QtFrame::menuBarOffset() const
+{
+ QtMainWindow* pTopLevel = m_pParent->GetTopLevelWindow();
+ if (pTopLevel && pTopLevel->menuBar() && pTopLevel->menuBar()->isVisible())
+ return round(pTopLevel->menuBar()->geometry().height() * devicePixelRatioF());
+ return 0;
+}
+
+void QtFrame::SetDefaultPos()
+{
+ if (!m_bDefaultPos)
+ return;
+
+ // center on parent
+ if (m_pParent)
+ {
+ const qreal fRatio = devicePixelRatioF();
+ QWidget* const pParentWin = m_pParent->asChild()->window();
+ QWidget* const pChildWin = asChild()->window();
+ QPoint aPos = (pParentWin->rect().center() - pChildWin->rect().center()) * fRatio;
+ aPos.ry() -= menuBarOffset();
+ SetPosSize(aPos.x(), aPos.y(), 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y);
+ assert(!m_bDefaultPos);
+ }
+ else
+ m_bDefaultPos = false;
+}
+
+Size QtFrame::CalcDefaultSize()
+{
+ assert(isWindow());
+
+ Size aSize;
+ if (!m_bFullScreen)
+ {
+ const QScreen* pScreen = screen();
+ if (!pScreen)
+ pScreen = QGuiApplication::screens().at(0);
+ aSize = bestmaxFrameSizeForScreenSize(toSize(pScreen->size()));
+ }
+ else
+ {
+ if (!m_bFullScreenSpanAll)
+ {
+ aSize = toSize(QGuiApplication::screens().at(maGeometry.screen())->size());
+ }
+ else
+ {
+ QScreen* pScreen = QGuiApplication::screenAt(QPoint(0, 0));
+ aSize = toSize(pScreen->availableVirtualGeometry().size());
+ }
+ }
+
+ return aSize;
+}
+
+void QtFrame::SetDefaultSize()
+{
+ if (!m_bDefaultSize)
+ return;
+
+ Size aDefSize = CalcDefaultSize();
+ SetPosSize(0, 0, aDefSize.Width(), aDefSize.Height(),
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT);
+ assert(!m_bDefaultSize);
+}
+
+void QtFrame::SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ sal_uInt16 nFlags)
+{
+ if (!isWindow() || isChild(true, false))
+ return;
+
+ if (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT))
+ {
+ if (isChild(false) || !m_pQWidget->isMaximized())
+ {
+ if (!(nFlags & SAL_FRAME_POSSIZE_WIDTH))
+ nWidth = maGeometry.width();
+ else if (!(nFlags & SAL_FRAME_POSSIZE_HEIGHT))
+ nHeight = maGeometry.height();
+
+ if (nWidth > 0 && nHeight > 0)
+ {
+ m_bDefaultSize = false;
+ const int nNewWidth = round(nWidth / devicePixelRatioF());
+ const int nNewHeight = round(nHeight / devicePixelRatioF());
+ if (m_nStyle & SalFrameStyleFlags::SIZEABLE)
+ asChild()->resize(nNewWidth, nNewHeight);
+ else
+ asChild()->setFixedSize(nNewWidth, nNewHeight);
+ }
+
+ // assume the resize happened
+ // needed for calculations and will eventually be corrected by events
+ if (nWidth > 0)
+ maGeometry.setWidth(nWidth);
+ if (nHeight > 0)
+ maGeometry.setHeight(nHeight);
+ }
+ }
+
+ if (!(nFlags & (SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y)))
+ {
+ if (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT))
+ SetDefaultPos();
+ return;
+ }
+
+ if (m_pParent)
+ {
+ const SalFrameGeometry& aParentGeometry = m_pParent->maGeometry;
+ if (QGuiApplication::isRightToLeft())
+ nX = aParentGeometry.x() + aParentGeometry.width() - nX - maGeometry.width() - 1;
+ else
+ nX += aParentGeometry.x();
+ nY += aParentGeometry.y() + menuBarOffset();
+ }
+
+ if (!(nFlags & SAL_FRAME_POSSIZE_X))
+ nX = maGeometry.x();
+ else if (!(nFlags & SAL_FRAME_POSSIZE_Y))
+ nY = maGeometry.y();
+
+ // assume the reposition happened
+ // needed for calculations and will eventually be corrected by events later
+ maGeometry.setPos({ nX, nY });
+
+ m_bDefaultPos = false;
+ asChild()->move(round(nX / devicePixelRatioF()), round(nY / devicePixelRatioF()));
+}
+
+void QtFrame::GetClientSize(tools::Long& rWidth, tools::Long& rHeight)
+{
+ rWidth = round(m_pQWidget->width() * devicePixelRatioF());
+ rHeight = round(m_pQWidget->height() * devicePixelRatioF());
+}
+
+void QtFrame::GetWorkArea(AbsoluteScreenPixelRectangle& rRect)
+{
+ if (!isWindow())
+ return;
+ QScreen* pScreen = screen();
+ if (!pScreen)
+ return;
+
+ QSize aSize = pScreen->availableVirtualSize() * devicePixelRatioF();
+ rRect = AbsoluteScreenPixelRectangle(0, 0, aSize.width(), aSize.height());
+}
+
+SalFrame* QtFrame::GetParent() const { return m_pParent; }
+
+void QtFrame::SetModal(bool bModal)
+{
+ if (!isWindow() || asChild()->isModal() == bModal)
+ return;
+
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, bModal]() {
+
+ QWidget* const pChild = asChild();
+ const bool bWasVisible = pChild->isVisible();
+
+ // modality change is only effective if the window is hidden
+ if (bWasVisible)
+ {
+ pChild->hide();
+ if (QGuiApplication::platformName() == "xcb")
+ {
+ SAL_WARN("vcl.qt", "SetModal called after Show - apply delay");
+ // tdf#152979 give QXcbConnection some time to avoid
+ // "qt.qpa.xcb: internal error: void QXcbWindow::setNetWmStateOnUnmappedWindow() called on mapped window"
+ QThread::msleep(100);
+ }
+ }
+
+ pChild->setWindowModality(bModal ? Qt::WindowModal : Qt::NonModal);
+
+ if (bWasVisible)
+ pChild->show();
+ });
+}
+
+bool QtFrame::GetModal() const { return isWindow() && windowHandle()->isModal(); }
+
+void QtFrame::SetWindowState(const vcl::WindowData* pState)
+{
+ if (!isWindow() || !pState || isChild(true, false))
+ return;
+
+ const vcl::WindowDataMask nMaxGeometryMask
+ = vcl::WindowDataMask::PosSize | vcl::WindowDataMask::MaximizedX
+ | vcl::WindowDataMask::MaximizedY | vcl::WindowDataMask::MaximizedWidth
+ | vcl::WindowDataMask::MaximizedHeight;
+
+ if ((pState->mask() & vcl::WindowDataMask::State)
+ && (pState->state() & vcl::WindowState::Maximized) && !isMaximized()
+ && (pState->mask() & nMaxGeometryMask) == nMaxGeometryMask)
+ {
+ const qreal fRatio = devicePixelRatioF();
+ QWidget* const pChild = asChild();
+ pChild->resize(ceil(pState->width() / fRatio), ceil(pState->height() / fRatio));
+ pChild->move(ceil(pState->x() / fRatio), ceil(pState->y() / fRatio));
+ SetWindowStateImpl(Qt::WindowMaximized);
+ }
+ else if (pState->mask() & vcl::WindowDataMask::PosSize)
+ {
+ sal_uInt16 nPosSizeFlags = 0;
+ if (pState->mask() & vcl::WindowDataMask::X)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
+ if (pState->mask() & vcl::WindowDataMask::Y)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
+ if (pState->mask() & vcl::WindowDataMask::Width)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if (pState->mask() & vcl::WindowDataMask::Height)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ SetPosSize(pState->x(), pState->y(), pState->width(), pState->height(), nPosSizeFlags);
+ }
+ else if (pState->mask() & vcl::WindowDataMask::State && !isChild())
+ {
+ if (pState->state() & vcl::WindowState::Maximized)
+ SetWindowStateImpl(Qt::WindowMaximized);
+ else if (pState->state() & vcl::WindowState::Minimized)
+ SetWindowStateImpl(Qt::WindowMinimized);
+ else
+ SetWindowStateImpl(Qt::WindowNoState);
+ }
+}
+
+bool QtFrame::GetWindowState(vcl::WindowData* pState)
+{
+ pState->setState(vcl::WindowState::Normal);
+ pState->setMask(vcl::WindowDataMask::State);
+ if (isMinimized())
+ pState->rState() |= vcl::WindowState::Minimized;
+ else if (isMaximized())
+ pState->rState() |= vcl::WindowState::Maximized;
+ else
+ {
+ // we want the frame position and the client area size
+ QRect rect = scaledQRect({ asChild()->pos(), asChild()->size() }, devicePixelRatioF());
+ pState->setPosSize(toRectangle(rect));
+ pState->rMask() |= vcl::WindowDataMask::PosSize;
+ }
+
+ return true;
+}
+
+void QtFrame::ShowFullScreen(bool bFullScreen, sal_Int32 nScreen)
+{
+ // only top-level windows can go fullscreen
+ assert(m_pTopLevel);
+
+ if (m_bFullScreen == bFullScreen)
+ return;
+
+ m_bFullScreen = bFullScreen;
+ m_bFullScreenSpanAll = m_bFullScreen && (nScreen < 0);
+
+ // show it if it isn't shown yet
+ if (!isWindow())
+ m_pTopLevel->show();
+
+ if (m_bFullScreen)
+ {
+ m_aRestoreGeometry = m_pTopLevel->geometry();
+ m_nRestoreScreen = maGeometry.screen();
+ SetScreenNumber(m_bFullScreenSpanAll ? m_nRestoreScreen : nScreen);
+ if (!m_bFullScreenSpanAll)
+ windowHandle()->showFullScreen();
+ else
+ windowHandle()->showNormal();
+ }
+ else
+ {
+ SetScreenNumber(m_nRestoreScreen);
+ windowHandle()->showNormal();
+ m_pTopLevel->setGeometry(m_aRestoreGeometry);
+ }
+}
+
+void QtFrame::StartPresentation(bool bStart)
+{
+#if CHECK_ANY_QT_USING_X11
+ // meh - so there's no Qt platform independent solution
+ // https://forum.qt.io/topic/38504/solved-qdialog-in-fullscreen-disable-os-screensaver
+ assert(m_aSystemData.platform != SystemEnvData::Platform::Invalid);
+ unsigned int nRootWindow(0);
+ std::optional<Display*> aDisplay;
+
+#if CHECK_QT5_USING_X11
+ if (QX11Info::isPlatformX11())
+ {
+ nRootWindow = QX11Info::appRootWindow();
+ aDisplay = QX11Info::display();
+ }
+#endif
+
+ m_SessionManagerInhibitor.inhibit(bStart, u"presentation", APPLICATION_INHIBIT_IDLE,
+ nRootWindow, aDisplay);
+#else
+ Q_UNUSED(bStart)
+#endif
+}
+
+void QtFrame::SetAlwaysOnTop(bool bOnTop)
+{
+ QWidget* const pWidget = asChild();
+ const Qt::WindowFlags flags = pWidget->windowFlags();
+ if (bOnTop)
+ pWidget->setWindowFlags(flags | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint);
+ else
+ pWidget->setWindowFlags(flags & ~(Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint));
+}
+
+void QtFrame::ToTop(SalFrameToTop nFlags)
+{
+ QtInstance* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, nFlags]() {
+ QWidget* const pWidget = asChild();
+ if (isWindow() && !(nFlags & SalFrameToTop::GrabFocusOnly))
+ pWidget->raise();
+ if ((nFlags & SalFrameToTop::RestoreWhenMin) || (nFlags & SalFrameToTop::ForegroundTask))
+ {
+ if (nFlags & SalFrameToTop::RestoreWhenMin)
+ pWidget->setWindowState(pWidget->windowState() & ~Qt::WindowMinimized);
+ pWidget->activateWindow();
+ }
+ else if ((nFlags & SalFrameToTop::GrabFocus) || (nFlags & SalFrameToTop::GrabFocusOnly))
+ {
+ if (!(nFlags & SalFrameToTop::GrabFocusOnly))
+ pWidget->activateWindow();
+ pWidget->setFocus(Qt::OtherFocusReason);
+ }
+ });
+}
+
+void QtFrame::SetPointer(PointerStyle ePointerStyle)
+{
+ if (ePointerStyle == m_ePointerStyle)
+ return;
+ m_ePointerStyle = ePointerStyle;
+
+ m_pQWidget->setCursor(GetQtData()->getCursor(ePointerStyle));
+}
+
+void QtFrame::CaptureMouse(bool bMouse)
+{
+ static const char* pEnv = getenv("SAL_NO_MOUSEGRABS");
+ if (pEnv && *pEnv)
+ return;
+
+ if (bMouse)
+ m_pQWidget->grabMouse();
+ else
+ m_pQWidget->releaseMouse();
+}
+
+void QtFrame::SetPointerPos(tools::Long nX, tools::Long nY)
+{
+ // some cursor already exists (and it has m_ePointerStyle shape)
+ // so here we just reposition it
+ QCursor::setPos(m_pQWidget->mapToGlobal(QPoint(nX, nY) / devicePixelRatioF()));
+}
+
+void QtFrame::Flush()
+{
+ // was: QGuiApplication::sync();
+ // but FIXME it causes too many issues, figure out sth better
+
+ // unclear if we need to also flush cairo surface - gtk3 backend
+ // does not do it. QPainter in QtWidget::paintEvent() is
+ // destroyed, so that state should be safely flushed.
+}
+
+bool QtFrame::ShowTooltip(const OUString& rText, const tools::Rectangle& rHelpArea)
+{
+ QRect aHelpArea(toQRect(rHelpArea));
+ if (QGuiApplication::isRightToLeft())
+ aHelpArea.moveLeft(maGeometry.width() - aHelpArea.width() - aHelpArea.left() - 1);
+ m_aTooltipText = rText;
+ m_aTooltipArea = aHelpArea;
+ return true;
+}
+
+void QtFrame::SetInputContext(SalInputContext* pContext)
+{
+ if (!pContext)
+ return;
+
+ if (!(pContext->mnOptions & InputContextFlags::Text))
+ return;
+
+ m_pQWidget->setAttribute(Qt::WA_InputMethodEnabled);
+}
+
+void QtFrame::EndExtTextInput(EndExtTextInputFlags /*nFlags*/)
+{
+ if (m_pQWidget)
+ m_pQWidget->endExtTextInput();
+}
+
+OUString QtFrame::GetKeyName(sal_uInt16 nKeyCode)
+{
+ vcl::KeyCode vclKeyCode(nKeyCode);
+ int nCode = vclKeyCode.GetCode();
+ int nRetCode = 0;
+
+ if (nCode >= KEY_0 && nCode <= KEY_9)
+ nRetCode = (nCode - KEY_0) + Qt::Key_0;
+ else if (nCode >= KEY_A && nCode <= KEY_Z)
+ nRetCode = (nCode - KEY_A) + Qt::Key_A;
+ else if (nCode >= KEY_F1 && nCode <= KEY_F26)
+ nRetCode = (nCode - KEY_F1) + Qt::Key_F1;
+ else
+ {
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ nRetCode = Qt::Key_Down;
+ break;
+ case KEY_UP:
+ nRetCode = Qt::Key_Up;
+ break;
+ case KEY_LEFT:
+ nRetCode = Qt::Key_Left;
+ break;
+ case KEY_RIGHT:
+ nRetCode = Qt::Key_Right;
+ break;
+ case KEY_HOME:
+ nRetCode = Qt::Key_Home;
+ break;
+ case KEY_END:
+ nRetCode = Qt::Key_End;
+ break;
+ case KEY_PAGEUP:
+ nRetCode = Qt::Key_PageUp;
+ break;
+ case KEY_PAGEDOWN:
+ nRetCode = Qt::Key_PageDown;
+ break;
+ case KEY_RETURN:
+ nRetCode = Qt::Key_Return;
+ break;
+ case KEY_ESCAPE:
+ nRetCode = Qt::Key_Escape;
+ break;
+ case KEY_TAB:
+ nRetCode = Qt::Key_Tab;
+ break;
+ case KEY_BACKSPACE:
+ nRetCode = Qt::Key_Backspace;
+ break;
+ case KEY_SPACE:
+ nRetCode = Qt::Key_Space;
+ break;
+ case KEY_INSERT:
+ nRetCode = Qt::Key_Insert;
+ break;
+ case KEY_DELETE:
+ nRetCode = Qt::Key_Delete;
+ break;
+ case KEY_ADD:
+ nRetCode = Qt::Key_Plus;
+ break;
+ case KEY_SUBTRACT:
+ nRetCode = Qt::Key_Minus;
+ break;
+ case KEY_MULTIPLY:
+ nRetCode = Qt::Key_Asterisk;
+ break;
+ case KEY_DIVIDE:
+ nRetCode = Qt::Key_Slash;
+ break;
+ case KEY_POINT:
+ nRetCode = Qt::Key_Period;
+ break;
+ case KEY_COMMA:
+ nRetCode = Qt::Key_Comma;
+ break;
+ case KEY_LESS:
+ nRetCode = Qt::Key_Less;
+ break;
+ case KEY_GREATER:
+ nRetCode = Qt::Key_Greater;
+ break;
+ case KEY_EQUAL:
+ nRetCode = Qt::Key_Equal;
+ break;
+ case KEY_FIND:
+ nRetCode = Qt::Key_Find;
+ break;
+ case KEY_CONTEXTMENU:
+ nRetCode = Qt::Key_Menu;
+ break;
+ case KEY_HELP:
+ nRetCode = Qt::Key_Help;
+ break;
+ case KEY_UNDO:
+ nRetCode = Qt::Key_Undo;
+ break;
+ case KEY_REPEAT:
+ nRetCode = Qt::Key_Redo;
+ break;
+ case KEY_TILDE:
+ nRetCode = Qt::Key_AsciiTilde;
+ break;
+ case KEY_QUOTELEFT:
+ nRetCode = Qt::Key_QuoteLeft;
+ break;
+ case KEY_BRACKETLEFT:
+ nRetCode = Qt::Key_BracketLeft;
+ break;
+ case KEY_BRACKETRIGHT:
+ nRetCode = Qt::Key_BracketRight;
+ break;
+ case KEY_NUMBERSIGN:
+ nRetCode = Qt::Key_NumberSign;
+ break;
+ case KEY_XF86FORWARD:
+ nRetCode = Qt::Key_Forward;
+ break;
+ case KEY_XF86BACK:
+ nRetCode = Qt::Key_Back;
+ break;
+ case KEY_COLON:
+ nRetCode = Qt::Key_Colon;
+ break;
+ case KEY_SEMICOLON:
+ nRetCode = Qt::Key_Semicolon;
+ break;
+
+ // Special cases
+ case KEY_COPY:
+ nRetCode = Qt::Key_Copy;
+ break;
+ case KEY_CUT:
+ nRetCode = Qt::Key_Cut;
+ break;
+ case KEY_PASTE:
+ nRetCode = Qt::Key_Paste;
+ break;
+ case KEY_OPEN:
+ nRetCode = Qt::Key_Open;
+ break;
+ }
+ }
+
+ if (vclKeyCode.IsShift())
+ nRetCode += Qt::SHIFT;
+ if (vclKeyCode.IsMod1())
+ nRetCode += Qt::CTRL;
+ if (vclKeyCode.IsMod2())
+ nRetCode += Qt::ALT;
+
+ QKeySequence keySeq(nRetCode);
+ OUString sKeyName = toOUString(keySeq.toString());
+
+ return sKeyName;
+}
+
+bool QtFrame::MapUnicodeToKeyCode(sal_Unicode /*aUnicode*/, LanguageType /*aLangType*/,
+ vcl::KeyCode& /*rKeyCode*/)
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType QtFrame::GetInputLanguage() { return m_nInputLanguage; }
+
+void QtFrame::setInputLanguage(LanguageType nInputLanguage)
+{
+ if (nInputLanguage == m_nInputLanguage)
+ return;
+ m_nInputLanguage = nInputLanguage;
+ CallCallback(SalEvent::InputLanguageChange, nullptr);
+}
+
+static Color toColor(const QColor& rColor)
+{
+ return Color(rColor.red(), rColor.green(), rColor.blue());
+}
+
+static bool toVclFont(const QFont& rQFont, const css::lang::Locale& rLocale, vcl::Font& rVclFont)
+{
+ FontAttributes aFA;
+ QtFontFace::fillAttributesFromQFont(rQFont, aFA);
+
+ bool bFound = psp::PrintFontManager::get().matchFont(aFA, rLocale);
+ SAL_INFO("vcl.qt", "font match result for '"
+ << rQFont.family() << "': "
+ << (bFound ? OUString::Concat("'") + aFA.GetFamilyName() + "'"
+ : OUString("failed")));
+
+ if (!bFound)
+ return false;
+
+ QFontInfo qFontInfo(rQFont);
+ int nPointHeight = qFontInfo.pointSize();
+ if (nPointHeight <= 0)
+ nPointHeight = rQFont.pointSize();
+
+ vcl::Font aFont(aFA.GetFamilyName(), Size(0, nPointHeight));
+ if (aFA.GetWeight() != WEIGHT_DONTKNOW)
+ aFont.SetWeight(aFA.GetWeight());
+ if (aFA.GetWidthType() != WIDTH_DONTKNOW)
+ aFont.SetWidthType(aFA.GetWidthType());
+ if (aFA.GetItalic() != ITALIC_DONTKNOW)
+ aFont.SetItalic(aFA.GetItalic());
+ if (aFA.GetPitch() != PITCH_DONTKNOW)
+ aFont.SetPitch(aFA.GetPitch());
+
+ rVclFont = aFont;
+ return true;
+}
+
+void QtFrame::UpdateSettings(AllSettings& rSettings)
+{
+ if (QtData::noNativeControls())
+ return;
+
+ StyleSettings style(rSettings.GetStyleSettings());
+ const css::lang::Locale aLocale = rSettings.GetUILanguageTag().getLocale();
+
+ // General settings
+ QPalette pal = QApplication::palette();
+
+ style.SetToolbarIconSize(ToolbarIconSize::Large);
+
+ Color aFore = toColor(pal.color(QPalette::Active, QPalette::WindowText));
+ Color aBack = toColor(pal.color(QPalette::Active, QPalette::Window));
+ Color aText = toColor(pal.color(QPalette::Active, QPalette::Text));
+ Color aBase = toColor(pal.color(QPalette::Active, QPalette::Base));
+ Color aButn = toColor(pal.color(QPalette::Active, QPalette::ButtonText));
+ Color aMid = toColor(pal.color(QPalette::Active, QPalette::Mid));
+ Color aHigh = toColor(pal.color(QPalette::Active, QPalette::Highlight));
+ Color aHighText = toColor(pal.color(QPalette::Active, QPalette::HighlightedText));
+ Color aLink = toColor(pal.color(QPalette::Active, QPalette::Link));
+ Color aVisitedLink = toColor(pal.color(QPalette::Active, QPalette::LinkVisited));
+
+ style.SetSkipDisabledInMenus(true);
+
+ // Foreground
+ style.SetRadioCheckTextColor(aFore);
+ style.SetLabelTextColor(aFore);
+ style.SetDialogTextColor(aFore);
+ style.SetGroupTextColor(aFore);
+
+ // Text
+ style.SetFieldTextColor(aText);
+ style.SetFieldRolloverTextColor(aText);
+ style.SetListBoxWindowTextColor(aText);
+ style.SetWindowTextColor(aText);
+ style.SetToolTextColor(aText);
+
+ // Base
+ style.SetFieldColor(aBase);
+ style.SetWindowColor(aBase);
+ style.SetActiveTabColor(aBase);
+ style.SetListBoxWindowBackgroundColor(aBase);
+ style.SetAlternatingRowColor(toColor(pal.color(QPalette::Active, QPalette::AlternateBase)));
+
+ // Buttons
+ style.SetDefaultButtonTextColor(aButn);
+ style.SetButtonTextColor(aButn);
+ style.SetDefaultActionButtonTextColor(aButn);
+ style.SetActionButtonTextColor(aButn);
+ style.SetFlatButtonTextColor(aButn);
+ style.SetDefaultButtonRolloverTextColor(aButn);
+ style.SetButtonRolloverTextColor(aButn);
+ style.SetDefaultActionButtonRolloverTextColor(aButn);
+ style.SetActionButtonRolloverTextColor(aButn);
+ style.SetFlatButtonRolloverTextColor(aButn);
+ style.SetDefaultButtonPressedRolloverTextColor(aButn);
+ style.SetButtonPressedRolloverTextColor(aButn);
+ style.SetDefaultActionButtonPressedRolloverTextColor(aButn);
+ style.SetActionButtonPressedRolloverTextColor(aButn);
+ style.SetFlatButtonPressedRolloverTextColor(aButn);
+
+ // Tabs
+ style.SetTabTextColor(aButn);
+ style.SetTabRolloverTextColor(aButn);
+ style.SetTabHighlightTextColor(aButn);
+
+ // Disable color
+ style.SetDisableColor(toColor(pal.color(QPalette::Disabled, QPalette::WindowText)));
+
+ // Background
+ style.BatchSetBackgrounds(aBack);
+ style.SetInactiveTabColor(aBack);
+
+ // Workspace
+ style.SetWorkspaceColor(aMid);
+
+ // Selection
+ // https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/305
+ style.SetAccentColor(aHigh);
+ style.SetHighlightColor(aHigh);
+ style.SetHighlightTextColor(aHighText);
+ style.SetListBoxWindowHighlightColor(aHigh);
+ style.SetListBoxWindowHighlightTextColor(aHighText);
+ style.SetActiveColor(aHigh);
+ style.SetActiveTextColor(aHighText);
+
+ // Links
+ style.SetLinkColor(aLink);
+ style.SetVisitedLinkColor(aVisitedLink);
+
+ // Tooltip
+ style.SetHelpColor(toColor(QToolTip::palette().color(QPalette::Active, QPalette::ToolTipBase)));
+ style.SetHelpTextColor(
+ toColor(QToolTip::palette().color(QPalette::Active, QPalette::ToolTipText)));
+
+ // Menu
+ std::unique_ptr<QMenuBar> pMenuBar = std::make_unique<QMenuBar>();
+ QPalette qMenuCG = pMenuBar->palette();
+
+ // Menu text and background color, theme specific
+ Color aMenuFore = toColor(qMenuCG.color(QPalette::WindowText));
+ Color aMenuBack = toColor(qMenuCG.color(QPalette::Window));
+
+ style.SetMenuTextColor(aMenuFore);
+ style.SetMenuBarTextColor(style.GetPersonaMenuBarTextColor().value_or(aMenuFore));
+ style.SetMenuColor(aMenuBack);
+ style.SetMenuBarColor(aMenuBack);
+ style.SetMenuHighlightColor(toColor(qMenuCG.color(QPalette::Highlight)));
+ style.SetMenuHighlightTextColor(toColor(qMenuCG.color(QPalette::HighlightedText)));
+
+ // set special menubar highlight text color
+ if (QApplication::style()->inherits("HighContrastStyle"))
+ ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor
+ = toColor(qMenuCG.color(QPalette::HighlightedText));
+ else
+ ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor = aMenuFore;
+
+ // set menubar rollover color
+ if (pMenuBar->style()->styleHint(QStyle::SH_MenuBar_MouseTracking))
+ {
+ style.SetMenuBarRolloverColor(toColor(qMenuCG.color(QPalette::Highlight)));
+ style.SetMenuBarRolloverTextColor(ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor);
+ }
+ else
+ {
+ style.SetMenuBarRolloverColor(aMenuBack);
+ style.SetMenuBarRolloverTextColor(aMenuFore);
+ }
+ style.SetMenuBarHighlightTextColor(style.GetMenuHighlightTextColor());
+
+ // Default fonts
+ vcl::Font aFont;
+ if (toVclFont(QApplication::font(), aLocale, aFont))
+ {
+ style.BatchSetFonts(aFont, aFont);
+ aFont.SetWeight(WEIGHT_BOLD);
+ style.SetTitleFont(aFont);
+ style.SetFloatTitleFont(aFont);
+ }
+
+ // Tooltip font
+ if (toVclFont(QToolTip::font(), aLocale, aFont))
+ style.SetHelpFont(aFont);
+
+ // Menu bar font
+ if (toVclFont(pMenuBar->font(), aLocale, aFont))
+ style.SetMenuFont(aFont);
+
+ // Icon theme
+ const bool bPreferDarkTheme = GetUseDarkMode();
+ style.SetPreferredIconTheme(toOUString(QIcon::themeName()), bPreferDarkTheme);
+
+ // Scroll bar size
+ style.SetScrollBarSize(QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent));
+ style.SetMinThumbSize(QApplication::style()->pixelMetric(QStyle::PM_ScrollBarSliderMin));
+
+ // These colors are used for the ruler text and marks
+ style.SetShadowColor(toColor(pal.color(QPalette::Disabled, QPalette::WindowText)));
+ style.SetDarkShadowColor(toColor(pal.color(QPalette::Inactive, QPalette::WindowText)));
+
+ // Cursor blink interval
+ int nFlashTime = QApplication::cursorFlashTime();
+ style.SetCursorBlinkTime(nFlashTime != 0 ? nFlashTime / 2 : STYLE_CURSOR_NOBLINKTIME);
+
+ rSettings.SetStyleSettings(style);
+}
+
+void QtFrame::Beep() { QApplication::beep(); }
+
+SalFrame::SalPointerState QtFrame::GetPointerState()
+{
+ SalPointerState aState;
+ aState.maPos = toPoint(QCursor::pos() * devicePixelRatioF());
+ aState.maPos.Move(-maGeometry.x(), -maGeometry.y());
+ aState.mnState = GetMouseModCode(QGuiApplication::mouseButtons())
+ | GetKeyModCode(QGuiApplication::keyboardModifiers());
+ return aState;
+}
+
+KeyIndicatorState QtFrame::GetIndicatorState() { return KeyIndicatorState(); }
+
+void QtFrame::SimulateKeyPress(sal_uInt16 nKeyCode)
+{
+ SAL_WARN("vcl.qt", "missing simulate keypress " << nKeyCode);
+}
+
+// don't set QWidget parents; this breaks popups on Wayland, like the LO ComboBox or ColorPicker!
+void QtFrame::SetParent(SalFrame* pNewParent) { m_pParent = static_cast<QtFrame*>(pNewParent); }
+
+void QtFrame::SetPluginParent(SystemParentData* /*pNewParent*/)
+{
+ //FIXME: no SetPluginParent impl. for qt5
+}
+
+void QtFrame::ResetClipRegion() { m_bNullRegion = true; }
+
+void QtFrame::BeginSetClipRegion(sal_uInt32)
+{
+ m_aRegion = QRegion(QRect(QPoint(0, 0), m_pQWidget->size()));
+}
+
+void QtFrame::UnionClipRegion(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ m_aRegion
+ = m_aRegion.united(scaledQRect(QRect(nX, nY, nWidth, nHeight), 1 / devicePixelRatioF()));
+}
+
+void QtFrame::EndSetClipRegion() { m_bNullRegion = false; }
+
+void QtFrame::SetScreenNumber(unsigned int nScreen)
+{
+ if (!isWindow())
+ return;
+
+ QWindow* const pWindow = windowHandle();
+ if (!pWindow)
+ return;
+
+ QList<QScreen*> screens = QApplication::screens();
+ if (static_cast<int>(nScreen) < screens.size() || m_bFullScreenSpanAll)
+ {
+ QRect screenGeo;
+
+ if (!m_bFullScreenSpanAll)
+ {
+ screenGeo = QGuiApplication::screens().at(nScreen)->geometry();
+ pWindow->setScreen(QApplication::screens()[nScreen]);
+ }
+ else // special case: fullscreen over all available screens
+ {
+ assert(m_bFullScreen);
+ // left-most screen
+ QScreen* pScreen = QGuiApplication::screenAt(QPoint(0, 0));
+ // entire virtual desktop
+ screenGeo = pScreen->availableVirtualGeometry();
+ pWindow->setScreen(pScreen);
+ pWindow->setGeometry(screenGeo);
+ nScreen = screenNumber(pScreen);
+ }
+
+ // setScreen by itself has no effect, explicitly move the widget to
+ // the new screen
+ asChild()->move(screenGeo.topLeft());
+ }
+ else
+ {
+ // index outta bounds, use primary screen
+ QScreen* primaryScreen = QApplication::primaryScreen();
+ pWindow->setScreen(primaryScreen);
+ nScreen = static_cast<sal_uInt32>(screenNumber(primaryScreen));
+ }
+
+ maGeometry.setScreen(nScreen);
+}
+
+void QtFrame::SetApplicationID(const OUString& rWMClass)
+{
+#if CHECK_QT5_USING_X11
+ assert(m_aSystemData.platform != SystemEnvData::Platform::Invalid);
+ if (m_aSystemData.platform != SystemEnvData::Platform::Xcb || !m_pTopLevel)
+ return;
+
+ QtX11Support::setApplicationID(m_pTopLevel->winId(), rWMClass);
+#else
+ Q_UNUSED(rWMClass);
+#endif
+}
+
+void QtFrame::ResolveWindowHandle(SystemEnvData& rData) const
+{
+ if (!rData.pWidget)
+ return;
+ assert(rData.platform != SystemEnvData::Platform::Invalid);
+ if (rData.platform != SystemEnvData::Platform::Wayland)
+ rData.SetWindowHandle(static_cast<QWidget*>(rData.pWidget)->winId());
+}
+
+bool QtFrame::GetUseReducedAnimation() const { return GetQtInstance()->GetUseReducedAnimation(); }
+
+// Drag'n'drop foo
+
+void QtFrame::registerDragSource(QtDragSource* pDragSource)
+{
+ assert(!m_pDragSource);
+ m_pDragSource = pDragSource;
+}
+
+void QtFrame::deregisterDragSource(QtDragSource const* pDragSource)
+{
+ assert(m_pDragSource == pDragSource);
+ (void)pDragSource;
+ m_pDragSource = nullptr;
+}
+
+void QtFrame::registerDropTarget(QtDropTarget* pDropTarget)
+{
+ assert(!m_pDropTarget);
+ m_pDropTarget = pDropTarget;
+
+ QtInstance* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this]() { m_pQWidget->setAcceptDrops(true); });
+}
+
+void QtFrame::deregisterDropTarget(QtDropTarget const* pDropTarget)
+{
+ assert(m_pDropTarget == pDropTarget);
+ (void)pDropTarget;
+ m_pDropTarget = nullptr;
+}
+
+static css::uno::Reference<css::datatransfer::XTransferable>
+lcl_getXTransferable(const QMimeData* pMimeData)
+{
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
+ const QtMimeData* pQtMimeData = dynamic_cast<const QtMimeData*>(pMimeData);
+ if (!pQtMimeData)
+ xTransferable = new QtDnDTransferable(pMimeData);
+ else
+ xTransferable = pQtMimeData->xTransferable();
+ return xTransferable;
+}
+
+static sal_Int8 lcl_getUserDropAction(const QDropEvent* pEvent, const sal_Int8 nSourceActions,
+ const QMimeData* pMimeData)
+{
+// we completely ignore all proposals by the Qt event, as they don't
+// match at all with the preferred LO DnD actions.
+// check the key modifiers to detect a user-overridden DnD action
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ const Qt::KeyboardModifiers eKeyMod = pEvent->modifiers();
+#else
+ const Qt::KeyboardModifiers eKeyMod = pEvent->keyboardModifiers();
+#endif
+ sal_Int8 nUserDropAction = 0;
+ if ((eKeyMod & Qt::ShiftModifier) && !(eKeyMod & Qt::ControlModifier))
+ nUserDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ else if ((eKeyMod & Qt::ControlModifier) && !(eKeyMod & Qt::ShiftModifier))
+ nUserDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ else if ((eKeyMod & Qt::ShiftModifier) && (eKeyMod & Qt::ControlModifier))
+ nUserDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ nUserDropAction &= nSourceActions;
+
+ // select the default DnD action, if there isn't a user preference
+ if (0 == nUserDropAction)
+ {
+ // default LO internal action is move, but default external action is copy
+ nUserDropAction = dynamic_cast<const QtMimeData*>(pMimeData)
+ ? css::datatransfer::dnd::DNDConstants::ACTION_MOVE
+ : css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ nUserDropAction &= nSourceActions;
+
+ // if the default doesn't match any allowed source action, fall back to the
+ // preferred of all allowed source actions
+ if (0 == nUserDropAction)
+ nUserDropAction = toVclDropAction(getPreferredDropAction(nSourceActions));
+
+ // this is "our" preference, but actually we would even prefer any default,
+ // if there is any
+ nUserDropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
+ }
+ return nUserDropAction;
+}
+
+void QtFrame::handleDragMove(QDragMoveEvent* pEvent)
+{
+ assert(m_pDropTarget);
+
+ // prepare our suggested drop action for the drop target
+ const sal_Int8 nSourceActions = toVclDropActions(pEvent->possibleActions());
+ const QMimeData* pMimeData = pEvent->mimeData();
+ const sal_Int8 nUserDropAction = lcl_getUserDropAction(pEvent, nSourceActions, pMimeData);
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ const Point aPos = toPoint(pEvent->position().toPoint() * devicePixelRatioF());
+#else
+ const Point aPos = toPoint(pEvent->pos() * devicePixelRatioF());
+#endif
+
+ css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(m_pDropTarget);
+ aEvent.Context = static_cast<css::datatransfer::dnd::XDropTargetDragContext*>(m_pDropTarget);
+ aEvent.LocationX = aPos.X();
+ aEvent.LocationY = aPos.Y();
+ aEvent.DropAction = nUserDropAction;
+ aEvent.SourceActions = nSourceActions;
+
+ // ask the drop target to accept our drop action
+ if (!m_bInDrag)
+ {
+ aEvent.SupportedDataFlavors = lcl_getXTransferable(pMimeData)->getTransferDataFlavors();
+ m_pDropTarget->fire_dragEnter(aEvent);
+ m_bInDrag = true;
+ }
+ else
+ m_pDropTarget->fire_dragOver(aEvent);
+
+ // the drop target accepted our drop action => inform Qt
+ if (m_pDropTarget->proposedDropAction() != 0)
+ {
+ pEvent->setDropAction(getPreferredDropAction(m_pDropTarget->proposedDropAction()));
+ pEvent->accept();
+ }
+ else // or maybe someone else likes it?
+ pEvent->ignore();
+}
+
+void QtFrame::handleDrop(QDropEvent* pEvent)
+{
+ assert(m_pDropTarget);
+
+ // prepare our suggested drop action for the drop target
+ const sal_Int8 nSourceActions = toVclDropActions(pEvent->possibleActions());
+ const sal_Int8 nUserDropAction
+ = lcl_getUserDropAction(pEvent, nSourceActions, pEvent->mimeData());
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ const Point aPos = toPoint(pEvent->position().toPoint() * devicePixelRatioF());
+#else
+ const Point aPos = toPoint(pEvent->pos() * devicePixelRatioF());
+#endif
+
+ css::datatransfer::dnd::DropTargetDropEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(m_pDropTarget);
+ aEvent.Context = static_cast<css::datatransfer::dnd::XDropTargetDropContext*>(m_pDropTarget);
+ aEvent.LocationX = aPos.X();
+ aEvent.LocationY = aPos.Y();
+ aEvent.SourceActions = nSourceActions;
+ aEvent.DropAction = nUserDropAction;
+ aEvent.Transferable = lcl_getXTransferable(pEvent->mimeData());
+
+ // ask the drop target to accept our drop action
+ m_pDropTarget->fire_drop(aEvent);
+ m_bInDrag = false;
+
+ const bool bDropSuccessful = m_pDropTarget->dropSuccessful();
+ const sal_Int8 nDropAction = m_pDropTarget->proposedDropAction();
+
+ // inform the drag source of the drag-origin frame of the drop result
+ if (pEvent->source())
+ {
+ QtWidget* pWidget = dynamic_cast<QtWidget*>(pEvent->source());
+ assert(pWidget); // AFAIK there shouldn't be any non-Qt5Widget as source in LO itself
+ if (pWidget)
+ pWidget->frame().m_pDragSource->fire_dragEnd(nDropAction, bDropSuccessful);
+ }
+
+ // the drop target accepted our drop action => inform Qt
+ if (bDropSuccessful)
+ {
+ pEvent->setDropAction(getPreferredDropAction(nDropAction));
+ pEvent->accept();
+ }
+ else // or maybe someone else likes it?
+ pEvent->ignore();
+}
+
+void QtFrame::handleDragLeave()
+{
+ css::datatransfer::dnd::DropTargetEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(m_pDropTarget);
+ m_pDropTarget->fire_dragExit(aEvent);
+ m_bInDrag = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtGraphics.cxx b/vcl/qt5/QtGraphics.cxx
new file mode 100644
index 0000000000..d809556ce2
--- /dev/null
+++ b/vcl/qt5/QtGraphics.cxx
@@ -0,0 +1,106 @@
+/* -*- 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 <QtGraphics.hxx>
+
+#include <QtData.hxx>
+#include <QtFont.hxx>
+#include <QtFrame.hxx>
+#include <QtGraphics_Controls.hxx>
+#include <QtPainter.hxx>
+
+#include <QtGui/QImage>
+#include <QtGui/QPainter>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QWidget>
+
+QtGraphics::QtGraphics( QtFrame *pFrame, QImage *pQImage )
+ : m_pFrame( pFrame )
+ , m_pTextStyle{ nullptr, }
+ , m_aTextColor( 0x00, 0x00, 0x00 )
+{
+ m_pBackend = std::make_unique<QtGraphicsBackend>(m_pFrame, pQImage);
+
+ if (!initWidgetDrawBackends(false))
+ {
+ if (!QtData::noNativeControls())
+ m_pWidgetDraw.reset(new QtGraphics_Controls(*this));
+ }
+ if (m_pFrame)
+ setDevicePixelRatioF(m_pFrame->devicePixelRatioF());
+}
+
+QtGraphics::~QtGraphics() { ReleaseFonts(); }
+
+void QtGraphics::ChangeQImage(QImage* pQImage)
+{
+ m_pBackend->setQImage(pQImage);
+ m_pBackend->ResetClipRegion();
+}
+
+SalGraphicsImpl* QtGraphics::GetImpl() const { return m_pBackend.get(); }
+
+SystemGraphicsData QtGraphics::GetGraphicsData() const { return SystemGraphicsData(); }
+
+#if ENABLE_CAIRO_CANVAS
+
+bool QtGraphics::SupportsCairo() const { return false; }
+
+cairo::SurfaceSharedPtr
+QtGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
+{
+ return nullptr;
+}
+
+cairo::SurfaceSharedPtr QtGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/,
+ int /*y*/, int /*width*/, int /*height*/) const
+{
+ return nullptr;
+}
+
+cairo::SurfaceSharedPtr QtGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/,
+ const BitmapSystemData& /*rData*/,
+ const Size& /*rSize*/) const
+{
+ return nullptr;
+}
+
+css::uno::Any QtGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/,
+ const basegfx::B2ISize& /*rSize*/) const
+{
+ return css::uno::Any();
+}
+
+#endif
+
+void QtGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(dynamic_cast<QtGraphics_Controls*>(m_pWidgetDraw.get()));
+ assert(!rDamagedRegion.IsEmpty());
+
+ QImage* pImage = static_cast<QtGraphics_Controls*>(m_pWidgetDraw.get())->getImage();
+ QImage blit(*pImage);
+ blit.setDevicePixelRatio(1);
+ QtPainter aPainter(*m_pBackend);
+ aPainter.drawImage(QPoint(rDamagedRegion.Left(), rDamagedRegion.Top()), blit);
+ aPainter.update(toQRect(rDamagedRegion));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtGraphics_Controls.cxx b/vcl/qt5/QtGraphics_Controls.cxx
new file mode 100644
index 0000000000..81ab7a7edc
--- /dev/null
+++ b/vcl/qt5/QtGraphics_Controls.cxx
@@ -0,0 +1,1168 @@
+/* -*- 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 <QtGraphics_Controls.hxx>
+
+#include <QtGui/QPainter>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QFrame>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QLineEdit>
+
+#include <QtTools.hxx>
+#include <QtGraphicsBase.hxx>
+#include <vcl/decoview.hxx>
+
+/**
+ Conversion function between VCL ControlState together with
+ ImplControlValue and Qt state flags.
+ @param nControlState State of the widget (default, focused, ...) in Native Widget Framework.
+ @param aValue Value held by the widget (on, off, ...)
+*/
+static QStyle::State vclStateValue2StateFlag(ControlState nControlState,
+ const ImplControlValue& aValue)
+{
+ QStyle::State nState
+ = ((nControlState & ControlState::ENABLED) ? QStyle::State_Enabled : QStyle::State_None)
+ | ((nControlState & ControlState::FOCUSED)
+ ? QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange
+ : QStyle::State_None)
+ | ((nControlState & ControlState::PRESSED) ? QStyle::State_Sunken : QStyle::State_None)
+ | ((nControlState & ControlState::SELECTED) ? QStyle::State_Selected : QStyle::State_None)
+ | ((nControlState & ControlState::ROLLOVER) ? QStyle::State_MouseOver
+ : QStyle::State_None);
+
+ switch (aValue.getTristateVal())
+ {
+ case ButtonValue::On:
+ nState |= QStyle::State_On;
+ break;
+ case ButtonValue::Off:
+ nState |= QStyle::State_Off;
+ break;
+ case ButtonValue::Mixed:
+ nState |= QStyle::State_NoChange;
+ break;
+ default:
+ break;
+ }
+
+ return nState;
+}
+
+static void lcl_ApplyBackgroundColorToStyleOption(QStyleOption& rOption,
+ const Color& rBackgroundColor)
+{
+ if (rBackgroundColor != COL_AUTO)
+ {
+ QColor aColor = toQColor(rBackgroundColor);
+ for (QPalette::ColorRole role : { QPalette::Window, QPalette::Button, QPalette::Base })
+ rOption.palette.setColor(role, aColor);
+ }
+}
+
+QtGraphics_Controls::QtGraphics_Controls(const QtGraphicsBase& rGraphics)
+ : m_rGraphics(rGraphics)
+{
+}
+
+bool QtGraphics_Controls::isNativeControlSupported(ControlType type, ControlPart part)
+{
+ switch (type)
+ {
+ case ControlType::Tooltip:
+ case ControlType::Progress:
+ case ControlType::ListNode:
+ return (part == ControlPart::Entire);
+
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ return (part == ControlPart::Entire) || (part == ControlPart::Focus);
+
+ case ControlType::ListHeader:
+ return (part == ControlPart::Button);
+
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ case ControlType::Combobox:
+ case ControlType::Toolbar:
+ case ControlType::Frame:
+ case ControlType::Scrollbar:
+ case ControlType::WindowBackground:
+ case ControlType::Fixedline:
+ return true;
+
+ case ControlType::Listbox:
+ return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
+
+ case ControlType::Spinbox:
+ return (part == ControlPart::Entire || part == ControlPart::HasBackgroundTexture);
+
+ case ControlType::Slider:
+ return (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea);
+
+ case ControlType::TabItem:
+ case ControlType::TabPane:
+ return ((part == ControlPart::Entire) || part == ControlPart::TabPaneWithHeader);
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+inline int QtGraphics_Controls::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option,
+ const QWidget* pWidget)
+{
+ return QApplication::style()->pixelMetric(metric, option, pWidget);
+}
+
+inline QSize QtGraphics_Controls::sizeFromContents(QStyle::ContentsType type,
+ const QStyleOption* option,
+ const QSize& contentsSize)
+{
+ return QApplication::style()->sizeFromContents(type, option, contentsSize);
+}
+
+inline QRect QtGraphics_Controls::subControlRect(QStyle::ComplexControl control,
+ const QStyleOptionComplex* option,
+ QStyle::SubControl subControl)
+{
+ return QApplication::style()->subControlRect(control, option, subControl);
+}
+
+inline QRect QtGraphics_Controls::subElementRect(QStyle::SubElement element,
+ const QStyleOption* option)
+{
+ return QApplication::style()->subElementRect(element, option);
+}
+
+void QtGraphics_Controls::draw(QStyle::ControlElement element, QStyleOption& rOption, QImage* image,
+ const Color& rBackgroundColor, QStyle::State const state, QRect rect)
+{
+ const QRect& targetRect = !rect.isNull() ? rect : image->rect();
+
+ rOption.state |= state;
+ rOption.rect = downscale(targetRect);
+
+ lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
+
+ QPainter painter(image);
+ QApplication::style()->drawControl(element, &rOption, &painter);
+}
+
+void QtGraphics_Controls::draw(QStyle::PrimitiveElement element, QStyleOption& rOption,
+ QImage* image, const Color& rBackgroundColor,
+ QStyle::State const state, QRect rect)
+{
+ const QRect& targetRect = !rect.isNull() ? rect : image->rect();
+
+ rOption.state |= state;
+ rOption.rect = downscale(targetRect);
+
+ lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
+
+ QPainter painter(image);
+ QApplication::style()->drawPrimitive(element, &rOption, &painter);
+}
+
+void QtGraphics_Controls::draw(QStyle::ComplexControl element, QStyleOptionComplex& rOption,
+ QImage* image, const Color& rBackgroundColor,
+ QStyle::State const state)
+{
+ const QRect& targetRect = image->rect();
+
+ rOption.state |= state;
+ rOption.rect = downscale(targetRect);
+
+ lcl_ApplyBackgroundColorToStyleOption(rOption, rBackgroundColor);
+
+ QPainter painter(image);
+ QApplication::style()->drawComplexControl(element, &rOption, &painter);
+}
+
+void QtGraphics_Controls::drawFrame(QStyle::PrimitiveElement element, QImage* image,
+ const Color& rBackgroundColor, QStyle::State const& state,
+ bool bClip, QStyle::PixelMetric eLineMetric)
+{
+ const int fw = pixelMetric(eLineMetric);
+ QStyleOptionFrame option;
+ option.frameShape = QFrame::StyledPanel;
+ option.state = QStyle::State_Sunken | state;
+ option.lineWidth = fw;
+
+ QRect aRect = downscale(image->rect());
+ option.rect = aRect;
+
+ lcl_ApplyBackgroundColorToStyleOption(option, rBackgroundColor);
+
+ QPainter painter(image);
+ if (bClip)
+ painter.setClipRegion(QRegion(aRect).subtracted(aRect.adjusted(fw, fw, -fw, -fw)));
+ QApplication::style()->drawPrimitive(element, &option, &painter);
+}
+
+void QtGraphics_Controls::fillQStyleOptionTab(const ImplControlValue& value, QStyleOptionTab& sot)
+{
+ const TabitemValue& rValue = static_cast<const TabitemValue&>(value);
+ if (rValue.isFirst())
+ sot.position = rValue.isLast() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::Beginning;
+ else if (rValue.isLast())
+ sot.position = rValue.isFirst() ? QStyleOptionTab::OnlyOneTab : QStyleOptionTab::End;
+ else
+ sot.position = QStyleOptionTab::Middle;
+}
+
+void QtGraphics_Controls::fullQStyleOptionTabWidgetFrame(QStyleOptionTabWidgetFrame& option,
+ bool bDownscale)
+{
+ option.state = QStyle::State_Enabled;
+ option.rightCornerWidgetSize = QSize(0, 0);
+ option.leftCornerWidgetSize = QSize(0, 0);
+ int nLineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
+ option.lineWidth = bDownscale ? std::max(1, downscale(nLineWidth, Round::Ceil)) : nLineWidth;
+ option.midLineWidth = 0;
+ option.shape = QTabBar::RoundedNorth;
+}
+
+bool QtGraphics_Controls::drawNativeControl(ControlType type, ControlPart part,
+ const tools::Rectangle& rControlRegion,
+ ControlState nControlState,
+ const ImplControlValue& value, const OUString&,
+ const Color& rBackgroundColor)
+{
+ bool nativeSupport = isNativeControlSupported(type, part);
+ if (!nativeSupport)
+ {
+ assert(!nativeSupport && "drawNativeControl called without native support!");
+ return false;
+ }
+
+ if (m_lastPopupRect.isValid()
+ && (type != ControlType::MenuPopup || part != ControlPart::MenuItem))
+ m_lastPopupRect = QRect();
+
+ bool returnVal = true;
+
+ QRect widgetRect = toQRect(rControlRegion);
+
+ //if no image, or resized, make a new image
+ if (!m_image || m_image->size() != widgetRect.size())
+ {
+ m_image.reset(new QImage(widgetRect.width(), widgetRect.height(),
+ QImage::Format_ARGB32_Premultiplied));
+ m_image->setDevicePixelRatio(m_rGraphics.devicePixelRatioF());
+ }
+
+ // Default image color - just once
+ switch (type)
+ {
+ case ControlType::MenuPopup:
+ if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
+ {
+ // it is necessary to fill the background transparently first, as this
+ // is painted after menuitem highlight, otherwise there would be a grey area
+ m_image->fill(Qt::transparent);
+ break;
+ }
+ [[fallthrough]]; // QPalette::Window
+ case ControlType::Menubar:
+ case ControlType::WindowBackground:
+ m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
+ break;
+ case ControlType::Tooltip:
+ m_image->fill(QApplication::palette().color(QPalette::ToolTipBase).rgb());
+ break;
+ case ControlType::Scrollbar:
+ if ((part == ControlPart::DrawBackgroundVert)
+ || (part == ControlPart::DrawBackgroundHorz))
+ {
+ m_image->fill(QApplication::palette().color(QPalette::Window).rgb());
+ break;
+ }
+ [[fallthrough]]; // Qt::transparent
+ default:
+ m_image->fill(Qt::transparent);
+ break;
+ }
+
+ if (type == ControlType::Pushbutton)
+ {
+ const PushButtonValue& rPBValue = static_cast<const PushButtonValue&>(value);
+ if (part == ControlPart::Focus)
+ // Nothing to do. Drawing focus separately is not needed because that's
+ // already handled by the ControlState::FOCUSED state being set when
+ // drawing the entire control
+ return true;
+ assert(part == ControlPart::Entire);
+ QStyleOptionButton option;
+ if (nControlState & ControlState::DEFAULT)
+ option.features |= QStyleOptionButton::DefaultButton;
+ if (rPBValue.m_bFlatButton)
+ option.features |= QStyleOptionButton::Flat;
+ draw(QStyle::CE_PushButton, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Menubar)
+ {
+ if (part == ControlPart::MenuItem)
+ {
+ QStyleOptionMenuItem option;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ if ((nControlState & ControlState::ROLLOVER)
+ && QApplication::style()->styleHint(QStyle::SH_MenuBar_MouseTracking))
+ option.state |= QStyle::State_Selected;
+
+ if (nControlState
+ & ControlState::SELECTED) // Passing State_Sunken is currently not documented.
+ option.state |= QStyle::State_Sunken; // But some kinds of QStyle interpret it.
+
+ draw(QStyle::CE_MenuBarItem, option, m_image.get(), rBackgroundColor);
+ }
+ else if (part == ControlPart::Entire)
+ {
+ QStyleOptionMenuItem option;
+ draw(QStyle::CE_MenuBarEmptyArea, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else
+ {
+ returnVal = false;
+ }
+ }
+ else if (type == ControlType::MenuPopup)
+ {
+ assert(part == ControlPart::MenuItem ? m_lastPopupRect.isValid()
+ : !m_lastPopupRect.isValid());
+ if (part == ControlPart::MenuItem)
+ {
+ QStyleOptionMenuItem option;
+ draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ // HACK: LO core first paints the entire popup and only then it paints menu items,
+ // but QMenu::paintEvent() paints popup frame after all items. That means highlighted
+ // items here would paint the highlight over the frame border. Since calls to ControlPart::MenuItem
+ // are always preceded by calls to ControlPart::Entire, just remember the size for the whole
+ // popup (otherwise not possible to get here) and draw the border afterwards.
+ QRect framerect(m_lastPopupRect.topLeft() - widgetRect.topLeft(),
+ widgetRect.size().expandedTo(m_lastPopupRect.size()));
+ QStyleOptionFrame frame;
+ draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), framerect);
+ }
+ else if (part == ControlPart::Separator)
+ {
+ QStyleOptionMenuItem option;
+ option.menuItemType = QStyleOptionMenuItem::Separator;
+ // Painting the whole menu item area results in different background
+ // with at least Plastique style, so clip only to the separator itself
+ // (QSize( 2, 2 ) is hardcoded in Qt)
+ option.rect = m_image->rect();
+ QSize size = sizeFromContents(QStyle::CT_MenuItem, &option, QSize(2, 2));
+ QRect rect = m_image->rect();
+ QPoint center = rect.center();
+ rect.setHeight(size.height());
+ rect.moveCenter(center);
+ option.state |= vclStateValue2StateFlag(nControlState, value);
+ option.rect = rect;
+
+ QPainter painter(m_image.get());
+ // don't paint over popup frame border (like the hack above, but here it can be simpler)
+ const int fw = pixelMetric(QStyle::PM_MenuPanelWidth);
+ painter.setClipRect(rect.adjusted(fw, 0, -fw, 0));
+ QApplication::style()->drawControl(QStyle::CE_MenuItem, &option, &painter);
+ }
+ else if (part == ControlPart::MenuItemCheckMark || part == ControlPart::MenuItemRadioMark)
+ {
+ QStyleOptionMenuItem option;
+ option.checkType = (part == ControlPart::MenuItemCheckMark)
+ ? QStyleOptionMenuItem::NonExclusive
+ : QStyleOptionMenuItem::Exclusive;
+ option.checked = bool(nControlState & ControlState::PRESSED);
+ // widgetRect is now the rectangle for the checkbox/radiobutton itself, but Qt
+ // paints the whole menu item, so translate position (and it'll be clipped);
+ // it is also necessary to fill the background transparently first, as this
+ // is painted after menuitem highlight, otherwise there would be a grey area
+ assert(value.getType() == ControlType::MenuPopup);
+ const MenupopupValue* menuVal = static_cast<const MenupopupValue*>(&value);
+ QRect menuItemRect(toQRect(menuVal->maItemRect));
+ QRect rect(menuItemRect.topLeft() - widgetRect.topLeft(),
+ widgetRect.size().expandedTo(menuItemRect.size()));
+ // checkboxes are always displayed next to images in menus, so are never centered
+ const int focus_size = pixelMetric(QStyle::PM_FocusFrameHMargin);
+ rect.moveTo(-focus_size, rect.y());
+ draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState & ~ControlState::PRESSED, value), rect);
+ }
+ else if (part == ControlPart::Entire)
+ {
+ QStyleOptionMenuItem option;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ draw(QStyle::PE_PanelMenu, option, m_image.get(), rBackgroundColor);
+ // Try hard to get any frame!
+ QStyleOptionFrame frame;
+ draw(QStyle::PE_FrameMenu, frame, m_image.get(), rBackgroundColor);
+ draw(QStyle::PE_FrameWindow, frame, m_image.get(), rBackgroundColor);
+ m_lastPopupRect = widgetRect;
+ }
+ else
+ returnVal = false;
+ }
+ else if ((type == ControlType::Toolbar) && (part == ControlPart::Button))
+ {
+ QStyleOptionToolButton option;
+
+ option.arrowType = Qt::NoArrow;
+ option.subControls = QStyle::SC_ToolButton;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ option.state |= QStyle::State_Raised | QStyle::State_Enabled | QStyle::State_AutoRaise;
+
+ draw(QStyle::CC_ToolButton, option, m_image.get(), rBackgroundColor);
+ }
+ else if ((type == ControlType::Toolbar) && (part == ControlPart::Entire))
+ {
+ QStyleOptionToolBar option;
+ draw(QStyle::CE_ToolBar, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if ((type == ControlType::Toolbar)
+ && (part == ControlPart::ThumbVert || part == ControlPart::ThumbHorz))
+ {
+ // reduce paint area only to the handle area
+ const int handleExtend = pixelMetric(QStyle::PM_ToolBarHandleExtent);
+ QStyleOption option;
+ QRect aRect = m_image->rect();
+ if (part == ControlPart::ThumbVert)
+ {
+ aRect.setWidth(handleExtend);
+ option.state = QStyle::State_Horizontal;
+ }
+ else
+ aRect.setHeight(handleExtend);
+ draw(QStyle::PE_IndicatorToolBarHandle, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), aRect);
+ }
+ else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox)
+ {
+ drawFrame(QStyle::PE_FrameLineEdit, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), false);
+ }
+ else if (type == ControlType::Combobox)
+ {
+ QStyleOptionComboBox option;
+ option.editable = true;
+ draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Listbox)
+ {
+ QStyleOptionComboBox option;
+ option.editable = false;
+ switch (part)
+ {
+ case ControlPart::ListboxWindow:
+ drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), true,
+ QStyle::PM_ComboBoxFrameWidth);
+ break;
+ case ControlPart::SubEdit:
+ draw(QStyle::CE_ComboBoxLabel, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ case ControlPart::Entire:
+ draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ case ControlPart::ButtonDown:
+ option.subControls = QStyle::SC_ComboBoxArrow;
+ draw(QStyle::CC_ComboBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ default:
+ returnVal = false;
+ break;
+ }
+ }
+ else if (type == ControlType::ListNode)
+ {
+ QStyleOption option;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ option.state |= QStyle::State_Item | QStyle::State_Children;
+
+ if (value.getTristateVal() == ButtonValue::On)
+ option.state |= QStyle::State_Open;
+
+ draw(QStyle::PE_IndicatorBranch, option, m_image.get(), rBackgroundColor);
+ }
+ else if (type == ControlType::ListHeader)
+ {
+ QStyleOptionHeader option;
+ draw(QStyle::CE_HeaderSection, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Checkbox)
+ {
+ if (part == ControlPart::Entire)
+ {
+ QStyleOptionButton option;
+ // clear FOCUSED bit, focus is drawn separately
+ nControlState &= ~ControlState::FOCUSED;
+ draw(QStyle::CE_CheckBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (part == ControlPart::Focus)
+ {
+ QStyleOptionFocusRect option;
+ draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ }
+ else if (type == ControlType::Scrollbar)
+ {
+ if ((part == ControlPart::DrawBackgroundVert) || (part == ControlPart::DrawBackgroundHorz))
+ {
+ QStyleOptionSlider option;
+ assert(value.getType() == ControlType::Scrollbar);
+ const ScrollbarValue* sbVal = static_cast<const ScrollbarValue*>(&value);
+
+ //if the scroll bar is active (aka not degenerate... allow for hover events)
+ if (sbVal->mnVisibleSize < sbVal->mnMax)
+ option.state = QStyle::State_MouseOver;
+
+ bool horizontal = (part == ControlPart::DrawBackgroundHorz); //horizontal or vertical
+ option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
+ if (horizontal)
+ option.state |= QStyle::State_Horizontal;
+
+ // If the scrollbar has a mnMin == 0 and mnMax == 0 then mnVisibleSize is set to -1?!
+ // I don't know if a negative mnVisibleSize makes any sense, so just handle this case
+ // without crashing LO with a SIGFPE in the Qt library.
+ const tools::Long nVisibleSize
+ = (sbVal->mnMin == sbVal->mnMax) ? 0 : sbVal->mnVisibleSize;
+
+ option.minimum = sbVal->mnMin;
+ option.maximum = sbVal->mnMax - nVisibleSize;
+ option.maximum = qMax(option.maximum, option.minimum); // bnc#619772
+ option.sliderValue = sbVal->mnCur;
+ option.sliderPosition = sbVal->mnCur;
+ option.pageStep = nVisibleSize;
+ if (part == ControlPart::DrawBackgroundHorz)
+ option.upsideDown
+ = (QGuiApplication::isRightToLeft()
+ && sbVal->maButton1Rect.Left() < sbVal->maButton2Rect.Left())
+ || (QGuiApplication::isLeftToRight()
+ && sbVal->maButton1Rect.Left() > sbVal->maButton2Rect.Left());
+
+ //setup the active control... always the slider
+ if (sbVal->mnThumbState & ControlState::ROLLOVER)
+ option.activeSubControls = QStyle::SC_ScrollBarSlider;
+
+ draw(QStyle::CC_ScrollBar, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else
+ {
+ returnVal = false;
+ }
+ }
+ else if (type == ControlType::Spinbox)
+ {
+ QStyleOptionSpinBox option;
+ option.frame = true;
+
+ // determine active control
+ if (value.getType() == ControlType::SpinButtons)
+ {
+ const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&value);
+ if (pSpinVal->mnUpperState & ControlState::PRESSED)
+ option.activeSubControls |= QStyle::SC_SpinBoxUp;
+ if (pSpinVal->mnLowerState & ControlState::PRESSED)
+ option.activeSubControls |= QStyle::SC_SpinBoxDown;
+ if (pSpinVal->mnUpperState & ControlState::ENABLED)
+ option.stepEnabled |= QAbstractSpinBox::StepUpEnabled;
+ if (pSpinVal->mnLowerState & ControlState::ENABLED)
+ option.stepEnabled |= QAbstractSpinBox::StepDownEnabled;
+ if (pSpinVal->mnUpperState & ControlState::ROLLOVER)
+ option.state = QStyle::State_MouseOver;
+ if (pSpinVal->mnLowerState & ControlState::ROLLOVER)
+ option.state = QStyle::State_MouseOver;
+ }
+
+ draw(QStyle::CC_SpinBox, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Radiobutton)
+ {
+ if (part == ControlPart::Entire)
+ {
+ QStyleOptionButton option;
+ // clear FOCUSED bit, focus is drawn separately
+ nControlState &= ~ControlState::FOCUSED;
+ draw(QStyle::CE_RadioButton, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (part == ControlPart::Focus)
+ {
+ QStyleOptionFocusRect option;
+ draw(QStyle::PE_FrameFocusRect, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ }
+ else if (type == ControlType::Tooltip)
+ {
+ QStyleOption option;
+ draw(QStyle::PE_PanelTipLabel, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Frame)
+ {
+ drawFrame(QStyle::PE_Frame, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::WindowBackground)
+ {
+ // Nothing to do - see "Default image color" switch ^^
+ }
+ else if (type == ControlType::Fixedline)
+ {
+ QStyleOptionMenuItem option;
+ option.menuItemType = QStyleOptionMenuItem::Separator;
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ option.state |= QStyle::State_Item;
+
+ draw(QStyle::CE_MenuItem, option, m_image.get(), rBackgroundColor);
+ }
+ else if (type == ControlType::Slider
+ && (part == ControlPart::TrackHorzArea || part == ControlPart::TrackVertArea))
+ {
+ assert(value.getType() == ControlType::Slider);
+ const SliderValue* slVal = static_cast<const SliderValue*>(&value);
+ QStyleOptionSlider option;
+
+ option.state = vclStateValue2StateFlag(nControlState, value);
+ option.maximum = slVal->mnMax;
+ option.minimum = slVal->mnMin;
+ option.sliderPosition = option.sliderValue = slVal->mnCur;
+ bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
+ option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
+ if (horizontal)
+ option.state |= QStyle::State_Horizontal;
+
+ draw(QStyle::CC_Slider, option, m_image.get(), rBackgroundColor);
+ }
+ else if (type == ControlType::Progress && part == ControlPart::Entire)
+ {
+ QStyleOptionProgressBar option;
+ option.minimum = 0;
+ option.maximum = widgetRect.width();
+ option.progress = value.getNumericVal();
+
+ draw(QStyle::CE_ProgressBar, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::TabItem && part == ControlPart::Entire)
+ {
+ QStyleOptionTab sot;
+ fillQStyleOptionTab(value, sot);
+ draw(QStyle::CE_TabBarTabShape, sot, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::TabPane && part == ControlPart::Entire)
+ {
+ const TabPaneValue& rValue = static_cast<const TabPaneValue&>(value);
+
+ // get the overlap size for the tabs, so they will overlap the frame
+ QStyleOptionTab tabOverlap;
+ tabOverlap.shape = QTabBar::RoundedNorth;
+ TabPaneValue::m_nOverlap = pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap);
+
+ QStyleOptionTabWidgetFrame option;
+ fullQStyleOptionTabWidgetFrame(option, false);
+ option.tabBarRect = toQRect(rValue.m_aTabHeaderRect);
+ option.selectedTabRect
+ = rValue.m_aSelectedTabRect.IsEmpty() ? QRect() : toQRect(rValue.m_aSelectedTabRect);
+ option.tabBarSize = toQSize(rValue.m_aTabHeaderRect.GetSize());
+ option.rect = m_image->rect();
+ QRect aRect = subElementRect(QStyle::SE_TabWidgetTabPane, &option);
+ draw(QStyle::PE_FrameTabWidget, option, m_image.get(), rBackgroundColor,
+ vclStateValue2StateFlag(nControlState, value), aRect);
+ }
+ else
+ {
+ returnVal = false;
+ }
+
+ return returnVal;
+}
+
+bool QtGraphics_Controls::getNativeControlRegion(ControlType type, ControlPart part,
+ const tools::Rectangle& controlRegion,
+ ControlState controlState,
+ const ImplControlValue& val, const OUString&,
+ tools::Rectangle& nativeBoundingRegion,
+ tools::Rectangle& nativeContentRegion)
+{
+ bool retVal = false;
+
+ QRect boundingRect = toQRect(controlRegion);
+ QRect contentRect = boundingRect;
+ QStyleOptionComplex styleOption;
+
+ switch (type)
+ {
+ // Metrics of the push button
+ case ControlType::Pushbutton:
+ if (part == ControlPart::Entire)
+ {
+ styleOption.state = vclStateValue2StateFlag(controlState, val);
+
+ if (controlState & ControlState::DEFAULT)
+ {
+ int size = upscale(pixelMetric(QStyle::PM_ButtonDefaultIndicator, &styleOption),
+ Round::Ceil);
+ boundingRect.adjust(-size, -size, size, size);
+ retVal = true;
+ }
+ }
+ else if (part == ControlPart::Focus)
+ retVal = true;
+ break;
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ {
+ // we have to get stable borders, otherwise layout loops.
+ // so we simply only scale the detected borders.
+ QStyleOptionFrame fo;
+ fo.frameShape = QFrame::StyledPanel;
+ fo.state = QStyle::State_Sunken;
+ fo.lineWidth = pixelMetric(QStyle::PM_DefaultFrameWidth);
+ fo.rect = downscale(contentRect);
+ fo.rect.setSize(sizeFromContents(QStyle::CT_LineEdit, &fo, fo.rect.size()));
+ QRect aSubRect = subElementRect(QStyle::SE_LineEditContents, &fo);
+
+ // VCL tests borders with small defaults before layout, where Qt returns no sub-rect,
+ // so this gets us at least some frame.
+ int nLine = upscale(fo.lineWidth, Round::Ceil);
+ int nLeft = qMin(-nLine, upscale(fo.rect.left() - aSubRect.left(), Round::Floor));
+ int nTop = qMin(-nLine, upscale(fo.rect.top() - aSubRect.top(), Round::Floor));
+ int nRight = qMax(nLine, upscale(fo.rect.right() - aSubRect.right(), Round::Ceil));
+ int nBottom = qMax(nLine, upscale(fo.rect.bottom() - aSubRect.bottom(), Round::Ceil));
+ boundingRect.adjust(nLeft, nTop, nRight, nBottom);
+
+ // tdf#150451: ensure a minimum size that fits text content + frame at top and bottom.
+ // Themes may use the widget type for determining the actual frame width to use,
+ // so pass a dummy QLineEdit
+ //
+ // NOTE: This is currently only done here for the minimum size calculation and
+ // not above because the handling for edit boxes here and in the calling code
+ // currently does all kinds of "interesting" things like doing extra size adjustments
+ // or passing the content rect where the bounding rect would be expected,...
+ // Ideally this should be cleaned up in the callers and all platform integrations
+ // to adhere to what the doc in vcl/inc/WidgetDrawInterface.hxx says, but this
+ // here keeps it working with existing code for now.
+ // (s.a. discussion in https://gerrit.libreoffice.org/c/core/+/146516 for more details)
+ QLineEdit aDummyEdit;
+ const int nFrameWidth = pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, &aDummyEdit);
+ QFontMetrics aFontMetrics(QApplication::font());
+ const int minHeight = upscale(aFontMetrics.height() + 2 * nFrameWidth, Round::Floor);
+ if (boundingRect.height() < minHeight)
+ {
+ const int nDiff = minHeight - boundingRect.height();
+ boundingRect.setHeight(boundingRect.height() + nDiff);
+ contentRect.setHeight(contentRect.height() + nDiff);
+ }
+
+ retVal = true;
+ break;
+ }
+ case ControlType::Checkbox:
+ if (part == ControlPart::Entire)
+ {
+ styleOption.state = vclStateValue2StateFlag(controlState, val);
+
+ int nWidth = pixelMetric(QStyle::PM_IndicatorWidth, &styleOption);
+ int nHeight = pixelMetric(QStyle::PM_IndicatorHeight, &styleOption);
+ contentRect.setSize(upscale(QSize(nWidth, nHeight), Round::Ceil));
+
+ int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
+ int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
+ contentRect.adjust(0, 0, 2 * upscale(nHMargin, Round::Ceil),
+ 2 * upscale(nVMargin, Round::Ceil));
+
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ break;
+ case ControlType::Combobox:
+ case ControlType::Listbox:
+ {
+ QStyleOptionComboBox cbo;
+
+ cbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
+ cbo.state = vclStateValue2StateFlag(controlState, val);
+
+ switch (part)
+ {
+ case ControlPart::Entire:
+ {
+ // find out the minimum size that should be used
+ // assume contents is a text line
+ QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
+ QFontMetrics aFontMetrics(QApplication::font());
+ aContentSize.setHeight(aFontMetrics.height());
+ QSize aMinSize = upscale(
+ sizeFromContents(QStyle::CT_ComboBox, &cbo, aContentSize), Round::Ceil);
+ if (aMinSize.height() > contentRect.height())
+ contentRect.setHeight(aMinSize.height());
+ boundingRect = contentRect;
+ retVal = true;
+ break;
+ }
+ case ControlPart::ButtonDown:
+ {
+ contentRect = upscale(
+ subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxArrow));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ }
+ case ControlPart::SubEdit:
+ {
+ contentRect = upscale(
+ subControlRect(QStyle::CC_ComboBox, &cbo, QStyle::SC_ComboBoxEditField));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case ControlType::Spinbox:
+ {
+ QStyleOptionSpinBox sbo;
+ sbo.frame = true;
+
+ sbo.rect = downscale(QRect(0, 0, contentRect.width(), contentRect.height()));
+ sbo.state = vclStateValue2StateFlag(controlState, val);
+
+ switch (part)
+ {
+ case ControlPart::Entire:
+ {
+ QSize aContentSize = downscale(contentRect.size(), Round::Ceil);
+ QFontMetrics aFontMetrics(QApplication::font());
+ aContentSize.setHeight(aFontMetrics.height());
+ QSize aMinSize = upscale(
+ sizeFromContents(QStyle::CT_SpinBox, &sbo, aContentSize), Round::Ceil);
+ if (aMinSize.height() > contentRect.height())
+ contentRect.setHeight(aMinSize.height());
+ boundingRect = contentRect;
+ retVal = true;
+ break;
+ }
+ case ControlPart::ButtonUp:
+ contentRect
+ = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxUp));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ case ControlPart::ButtonDown:
+ contentRect
+ = upscale(subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxDown));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ case ControlPart::SubEdit:
+ contentRect = upscale(
+ subControlRect(QStyle::CC_SpinBox, &sbo, QStyle::SC_SpinBoxEditField));
+ contentRect.translate(boundingRect.left(), boundingRect.top());
+ retVal = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case ControlType::MenuPopup:
+ {
+ int h, w;
+ switch (part)
+ {
+ case ControlPart::MenuItemCheckMark:
+ h = upscale(pixelMetric(QStyle::PM_IndicatorHeight), Round::Floor);
+ w = upscale(pixelMetric(QStyle::PM_IndicatorWidth), Round::Floor);
+ retVal = true;
+ break;
+ case ControlPart::MenuItemRadioMark:
+ h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Floor);
+ w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Floor);
+ retVal = true;
+ break;
+ default:
+ break;
+ }
+ if (retVal)
+ {
+ contentRect = QRect(0, 0, w, h);
+ boundingRect = contentRect;
+ }
+ break;
+ }
+ case ControlType::Frame:
+ {
+ if (part == ControlPart::Border)
+ {
+ int nFrameWidth = upscale(pixelMetric(QStyle::PM_DefaultFrameWidth), Round::Ceil);
+ contentRect.adjust(nFrameWidth, nFrameWidth, -nFrameWidth, -nFrameWidth);
+ retVal = true;
+ }
+ break;
+ }
+ case ControlType::Radiobutton:
+ {
+ const int h = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorHeight), Round::Ceil);
+ const int w = upscale(pixelMetric(QStyle::PM_ExclusiveIndicatorWidth), Round::Ceil);
+
+ contentRect = QRect(boundingRect.left(), boundingRect.top(), w, h);
+ int nHMargin = pixelMetric(QStyle::PM_FocusFrameHMargin, &styleOption);
+ int nVMargin = pixelMetric(QStyle::PM_FocusFrameVMargin, &styleOption);
+ contentRect.adjust(0, 0, upscale(2 * nHMargin, Round::Ceil),
+ upscale(2 * nVMargin, Round::Ceil));
+ boundingRect = contentRect;
+
+ retVal = true;
+ break;
+ }
+ case ControlType::Slider:
+ {
+ const int w = upscale(pixelMetric(QStyle::PM_SliderLength), Round::Ceil);
+ if (part == ControlPart::ThumbHorz)
+ {
+ contentRect
+ = QRect(boundingRect.left(), boundingRect.top(), w, boundingRect.height());
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ else if (part == ControlPart::ThumbVert)
+ {
+ contentRect
+ = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), w);
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ break;
+ }
+ case ControlType::Toolbar:
+ {
+ const int nWorH = upscale(pixelMetric(QStyle::PM_ToolBarHandleExtent), Round::Ceil);
+ if (part == ControlPart::ThumbHorz)
+ {
+ contentRect
+ = QRect(boundingRect.left(), boundingRect.top(), boundingRect.width(), nWorH);
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ else if (part == ControlPart::ThumbVert)
+ {
+ contentRect
+ = QRect(boundingRect.left(), boundingRect.top(), nWorH, boundingRect.height());
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ else if (part == ControlPart::Button)
+ {
+ QStyleOptionToolButton option;
+ option.arrowType = Qt::NoArrow;
+ option.features = QStyleOptionToolButton::None;
+ option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
+ contentRect = upscale(
+ subControlRect(QStyle::CC_ToolButton, &option, QStyle::SC_ToolButton));
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ break;
+ }
+ case ControlType::Scrollbar:
+ {
+ // core can't handle 3-button scrollbars well, so we fix that in hitTestNativeControl(),
+ // for the rest also provide the track area (i.e. area not taken by buttons)
+ if (part == ControlPart::TrackVertArea || part == ControlPart::TrackHorzArea)
+ {
+ QStyleOptionSlider option;
+ bool horizontal = (part == ControlPart::TrackHorzArea); //horizontal or vertical
+ option.orientation = horizontal ? Qt::Horizontal : Qt::Vertical;
+ if (horizontal)
+ option.state |= QStyle::State_Horizontal;
+ // getNativeControlRegion usually gets ImplControlValue as 'val' (i.e. not the proper
+ // subclass), so use random sensible values (doesn't matter anyway, as the wanted
+ // geometry here depends only on button sizes)
+ option.maximum = 10;
+ option.minimum = 0;
+ option.sliderPosition = option.sliderValue = 4;
+ option.pageStep = 2;
+ // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
+ // widget and screen coordinates the same. QStyle functions should use screen
+ // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
+ // and sometimes uses widget coordinates.
+ option.rect = downscale(QRect({ 0, 0 }, contentRect.size()));
+ contentRect = upscale(
+ subControlRect(QStyle::CC_ScrollBar, &option, QStyle::SC_ScrollBarGroove));
+ contentRect.translate(boundingRect.left()
+ - (contentRect.width() - boundingRect.width()),
+ boundingRect.top());
+ boundingRect = contentRect;
+ retVal = true;
+ }
+ break;
+ }
+ case ControlType::TabItem:
+ {
+ QStyleOptionTab sot;
+ fillQStyleOptionTab(val, sot);
+ QSize aMinSize = upscale(sizeFromContents(QStyle::CT_TabBarTab, &sot,
+ downscale(contentRect.size(), Round::Ceil)),
+ Round::Ceil);
+ contentRect.setSize(aMinSize);
+ boundingRect = contentRect;
+ retVal = true;
+ break;
+ }
+ case ControlType::TabPane:
+ {
+ const TabPaneValue& rValue = static_cast<const TabPaneValue&>(val);
+ QStyleOptionTabWidgetFrame sotwf;
+ fullQStyleOptionTabWidgetFrame(sotwf, true);
+ QSize contentSize(
+ std::max(rValue.m_aTabHeaderRect.GetWidth(), controlRegion.GetWidth()),
+ rValue.m_aTabHeaderRect.GetHeight() + controlRegion.GetHeight());
+ QSize aMinSize = upscale(
+ sizeFromContents(QStyle::CT_TabWidget, &sotwf, downscale(contentSize, Round::Ceil)),
+ Round::Ceil);
+ contentRect.setSize(aMinSize);
+ boundingRect = contentRect;
+ retVal = true;
+ break;
+ }
+ default:
+ break;
+ }
+ if (retVal)
+ {
+ nativeBoundingRegion = toRectangle(boundingRect);
+ nativeContentRegion = toRectangle(contentRect);
+ }
+
+ return retVal;
+}
+
+/** Test whether the position is in the native widget.
+ If the return value is true, bIsInside contains information whether
+ aPos was or was not inside the native widget specified by the
+ nType/nPart combination.
+*/
+bool QtGraphics_Controls::hitTestNativeControl(ControlType nType, ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ const Point& rPos, bool& rIsInside)
+{
+ if (nType == ControlType::Scrollbar)
+ {
+ if (nPart != ControlPart::ButtonUp && nPart != ControlPart::ButtonDown
+ && nPart != ControlPart::ButtonLeft && nPart != ControlPart::ButtonRight)
+ { // we adjust only for buttons (because some scrollbars have 3 buttons,
+ // and LO core doesn't handle such scrollbars well)
+ return false;
+ }
+ rIsInside = false;
+ bool bHorizontal = (nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight);
+ QRect rect = toQRect(rControlRegion);
+ QPoint pos(rPos.X(), rPos.Y());
+ // Adjust coordinates to make the widget appear to be at (0,0), i.e. make
+ // widget and screen coordinates the same. QStyle functions should use screen
+ // coordinates but at least QPlastiqueStyle::subControlRect() is buggy
+ // and sometimes uses widget coordinates.
+ pos -= rect.topLeft();
+ rect.moveTo(0, 0);
+ QStyleOptionSlider options;
+ options.orientation = bHorizontal ? Qt::Horizontal : Qt::Vertical;
+ if (bHorizontal)
+ options.state |= QStyle::State_Horizontal;
+ options.rect = rect;
+ // some random sensible values, since we call this code only for scrollbar buttons,
+ // the slider position does not exactly matter
+ options.maximum = 10;
+ options.minimum = 0;
+ options.sliderPosition = options.sliderValue = 4;
+ options.pageStep = 2;
+ QStyle::SubControl control
+ = QApplication::style()->hitTestComplexControl(QStyle::CC_ScrollBar, &options, pos);
+ if (nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonLeft)
+ rIsInside = (control == QStyle::SC_ScrollBarSubLine);
+ else // DOWN, RIGHT
+ rIsInside = (control == QStyle::SC_ScrollBarAddLine);
+ return true;
+ }
+ return false;
+}
+
+inline int QtGraphics_Controls::downscale(int size, Round eRound)
+{
+ return static_cast<int>(eRound == Round::Ceil ? ceil(size / m_rGraphics.devicePixelRatioF())
+ : floor(size / m_rGraphics.devicePixelRatioF()));
+}
+
+inline int QtGraphics_Controls::upscale(int size, Round eRound)
+{
+ return static_cast<int>(eRound == Round::Ceil ? ceil(size * m_rGraphics.devicePixelRatioF())
+ : floor(size * m_rGraphics.devicePixelRatioF()));
+}
+
+inline QRect QtGraphics_Controls::downscale(const QRect& rect)
+{
+ return QRect(downscale(rect.x(), Round::Floor), downscale(rect.y(), Round::Floor),
+ downscale(rect.width(), Round::Ceil), downscale(rect.height(), Round::Ceil));
+}
+
+inline QRect QtGraphics_Controls::upscale(const QRect& rect)
+{
+ return QRect(upscale(rect.x(), Round::Floor), upscale(rect.y(), Round::Floor),
+ upscale(rect.width(), Round::Ceil), upscale(rect.height(), Round::Ceil));
+}
+
+inline QSize QtGraphics_Controls::downscale(const QSize& size, Round eRound)
+{
+ return QSize(downscale(size.width(), eRound), downscale(size.height(), eRound));
+}
+
+inline QSize QtGraphics_Controls::upscale(const QSize& size, Round eRound)
+{
+ return QSize(upscale(size.width(), eRound), upscale(size.height(), eRound));
+}
+
+inline QPoint QtGraphics_Controls::upscale(const QPoint& point, Round eRound)
+{
+ return QPoint(upscale(point.x(), eRound), upscale(point.y(), eRound));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtGraphics_GDI.cxx b/vcl/qt5/QtGraphics_GDI.cxx
new file mode 100644
index 0000000000..2005de80f7
--- /dev/null
+++ b/vcl/qt5/QtGraphics_GDI.cxx
@@ -0,0 +1,710 @@
+/* -*- 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 <QtGraphics.hxx>
+
+#include <QtBitmap.hxx>
+#include <QtPainter.hxx>
+
+#include <sal/log.hxx>
+
+#include <QtGui/QPainter>
+#include <QtGui/QScreen>
+#include <QtGui/QWindow>
+#include <QtWidgets/QWidget>
+
+#include <numeric>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+
+QtGraphicsBackend::QtGraphicsBackend(QtFrame* pFrame, QImage* pQImage)
+ : m_pFrame(pFrame)
+ , m_pQImage(pQImage)
+ , m_oLineColor(std::in_place, 0x00, 0x00, 0x00)
+ , m_oFillColor(std::in_place, 0xFF, 0xFF, 0XFF)
+ , m_eCompositionMode(QPainter::CompositionMode_SourceOver)
+{
+ ResetClipRegion();
+}
+
+QtGraphicsBackend::~QtGraphicsBackend() {}
+
+const basegfx::B2DPoint aHalfPointOfs(0.5, 0.5);
+
+static void AddPolygonToPath(QPainterPath& rPath, const basegfx::B2DPolygon& rPolygon,
+ bool bClosePath, bool bPixelSnap, bool bLineDraw)
+{
+ const int nPointCount = rPolygon.count();
+ // short circuit if there is nothing to do
+ if (nPointCount == 0)
+ return;
+
+ const bool bHasCurves = rPolygon.areControlPointsUsed();
+ for (int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
+ {
+ int nClosedIdx = nPointIdx;
+ if (nPointIdx >= nPointCount)
+ {
+ // prepare to close last curve segment if needed
+ if (bClosePath && (nPointIdx == nPointCount))
+ nClosedIdx = 0;
+ else
+ break;
+ }
+
+ basegfx::B2DPoint aPoint = rPolygon.getB2DPoint(nClosedIdx);
+
+ if (bPixelSnap)
+ {
+ // snap device coordinates to full pixels
+ aPoint.setX(basegfx::fround(aPoint.getX()));
+ aPoint.setY(basegfx::fround(aPoint.getY()));
+ }
+
+ if (bLineDraw)
+ aPoint += aHalfPointOfs;
+ if (!nPointIdx)
+ {
+ // first point => just move there
+ rPath.moveTo(aPoint.getX(), aPoint.getY());
+ continue;
+ }
+
+ bool bPendingCurve = false;
+ if (bHasCurves)
+ {
+ bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
+ bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
+ }
+
+ if (!bPendingCurve) // line segment
+ rPath.lineTo(aPoint.getX(), aPoint.getY());
+ else // cubic bezier segment
+ {
+ basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
+ basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
+ if (bLineDraw)
+ {
+ aCP1 += aHalfPointOfs;
+ aCP2 += aHalfPointOfs;
+ }
+ rPath.cubicTo(aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
+ aPoint.getY());
+ }
+ }
+
+ if (bClosePath)
+ rPath.closeSubpath();
+}
+
+static bool AddPolyPolygonToPath(QPainterPath& rPath, const basegfx::B2DPolyPolygon& rPolyPoly,
+ bool bPixelSnap, bool bLineDraw)
+{
+ if (rPolyPoly.count() == 0)
+ return false;
+ for (auto const& rPolygon : rPolyPoly)
+ {
+ AddPolygonToPath(rPath, rPolygon, true, bPixelSnap, bLineDraw);
+ }
+ return true;
+}
+
+void QtGraphicsBackend::setClipRegion(const vcl::Region& rRegion)
+{
+ if (rRegion.IsRectangle())
+ {
+ m_aClipRegion = toQRect(rRegion.GetBoundRect());
+ if (!m_aClipPath.isEmpty())
+ {
+ QPainterPath aPath;
+ m_aClipPath.swap(aPath);
+ }
+ }
+ else if (!rRegion.HasPolyPolygonOrB2DPolyPolygon())
+ {
+ QRegion aQRegion;
+ RectangleVector aRectangles;
+ rRegion.GetRegionRectangles(aRectangles);
+ for (const auto& rRect : aRectangles)
+ aQRegion += toQRect(rRect);
+ m_aClipRegion = aQRegion;
+ if (!m_aClipPath.isEmpty())
+ {
+ QPainterPath aPath;
+ m_aClipPath.swap(aPath);
+ }
+ }
+ else
+ {
+ QPainterPath aPath;
+ const basegfx::B2DPolyPolygon aPolyClip(rRegion.GetAsB2DPolyPolygon());
+ AddPolyPolygonToPath(aPath, aPolyClip, !getAntiAlias(), false);
+ m_aClipPath.swap(aPath);
+ if (!m_aClipRegion.isEmpty())
+ {
+ QRegion aRegion;
+ m_aClipRegion.swap(aRegion);
+ }
+ }
+}
+
+void QtGraphicsBackend::ResetClipRegion()
+{
+ if (m_pQImage)
+ m_aClipRegion = QRegion(m_pQImage->rect());
+ else
+ m_aClipRegion = QRegion();
+ if (!m_aClipPath.isEmpty())
+ {
+ QPainterPath aPath;
+ m_aClipPath.swap(aPath);
+ }
+}
+
+void QtGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY)
+{
+ QtPainter aPainter(*this);
+ aPainter.drawPoint(nX, nY);
+ aPainter.update(nX, nY, 1, 1);
+}
+
+void QtGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
+{
+ QtPainter aPainter(*this);
+ aPainter.setPen(toQColor(nColor));
+ aPainter.setPen(Qt::SolidLine);
+ aPainter.drawPoint(nX, nY);
+ aPainter.update(nX, nY, 1, 1);
+}
+
+void QtGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2)
+{
+ QtPainter aPainter(*this);
+ aPainter.drawLine(nX1, nY1, nX2, nY2);
+
+ if (nX1 > nX2)
+ std::swap(nX1, nX2);
+ if (nY1 > nY2)
+ std::swap(nY1, nY2);
+ aPainter.update(nX1, nY1, nX2 - nX1 + 1, nY2 - nY1 + 1);
+}
+
+void QtGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ if (!m_oFillColor && !m_oLineColor)
+ return;
+
+ QtPainter aPainter(*this, true);
+ if (m_oFillColor)
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
+ if (m_oLineColor)
+ aPainter.drawRect(nX, nY, nWidth - 1, nHeight - 1);
+ aPainter.update(nX, nY, nWidth, nHeight);
+}
+
+void QtGraphicsBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ if (0 == nPoints)
+ return;
+
+ QtPainter aPainter(*this);
+ QPoint* pPoints = new QPoint[nPoints];
+ QPoint aTopLeft(pPtAry->getX(), pPtAry->getY());
+ QPoint aBottomRight = aTopLeft;
+ for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
+ {
+ pPoints[i] = QPoint(pPtAry->getX(), pPtAry->getY());
+ if (pPtAry->getX() < aTopLeft.x())
+ aTopLeft.setX(pPtAry->getX());
+ if (pPtAry->getY() < aTopLeft.y())
+ aTopLeft.setY(pPtAry->getY());
+ if (pPtAry->getX() > aBottomRight.x())
+ aBottomRight.setX(pPtAry->getX());
+ if (pPtAry->getY() > aBottomRight.y())
+ aBottomRight.setY(pPtAry->getY());
+ }
+ aPainter.drawPolyline(pPoints, nPoints);
+ delete[] pPoints;
+ aPainter.update(QRect(aTopLeft, aBottomRight));
+}
+
+void QtGraphicsBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
+{
+ QtPainter aPainter(*this, true);
+ QPolygon aPolygon(nPoints);
+ for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
+ aPolygon.setPoint(i, pPtAry->getX(), pPtAry->getY());
+ aPainter.drawPolygon(aPolygon);
+ aPainter.update(aPolygon.boundingRect());
+}
+
+void QtGraphicsBackend::drawPolyPolygon(sal_uInt32 nPolyCount, const sal_uInt32* pPoints,
+ const Point** ppPtAry)
+{
+ // ignore invisible polygons
+ if (!m_oFillColor && !m_oLineColor)
+ return;
+
+ QPainterPath aPath;
+ for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if (nPoints > 1)
+ {
+ const Point* pPtAry = ppPtAry[nPoly];
+ aPath.moveTo(pPtAry->getX(), pPtAry->getY());
+ pPtAry++;
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
+ aPath.lineTo(pPtAry->getX(), pPtAry->getY());
+ aPath.closeSubpath();
+ }
+ }
+
+ QtPainter aPainter(*this, true);
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+}
+
+void QtGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ // ignore invisible polygons
+ if (!m_oFillColor && !m_oLineColor)
+ return;
+ if ((fTransparency >= 1.0) || (fTransparency < 0))
+ return;
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ QPainterPath aPath;
+ // ignore empty polygons
+ if (!AddPolyPolygonToPath(aPath, aPolyPolygon, !getAntiAlias(), m_oLineColor.has_value()))
+ return;
+
+ QtPainter aPainter(*this, true, 255 * (1.0 - fTransparency));
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+}
+
+bool QtGraphicsBackend::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::drawPolygonBezier(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/,
+ const Point* const* /*pPtAry*/,
+ const PolyFlags* const* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolyLine, double fTransparency,
+ double fLineWidth,
+ const std::vector<double>* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle, bool bPixelSnapHairline)
+{
+ if (!m_oFillColor && !m_oLineColor)
+ {
+ return true;
+ }
+
+ // MM01 check done for simple reasons
+ if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0)
+ {
+ return true;
+ }
+
+ // 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));
+ basegfx::B2DPolyPolygon aPolyPolygonLine;
+
+ if (bStrokeUsed)
+ {
+ // apply LineStyle
+ basegfx::utils::applyLineDashing(rPolyLine, // source
+ *pStroke, // pattern
+ &aPolyPolygonLine, // target for lines
+ nullptr, // target for gaps
+ fDotDashLength); // full length if available
+ }
+ else
+ {
+ // no line dashing, just copy
+ aPolyPolygonLine.append(rPolyLine);
+ }
+
+ // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+ aPolyPolygonLine.transform(rObjectToDevice);
+ if (bPixelSnapHairline)
+ {
+ aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
+ }
+
+ // tdf#124848 get correct LineWidth in discrete coordinates,
+ if (fLineWidth == 0) // hairline
+ fLineWidth = 1.0;
+ else // Adjust line width for object-to-device scale.
+ fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
+
+ // setup poly-polygon path
+ QPainterPath aPath;
+
+ // MM01 todo - I assume that this is OKAY to be done in one run for Qt,
+ // but this NEEDS to be checked/verified
+ for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ {
+ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+ AddPolygonToPath(aPath, aPolyLine, aPolyLine.isClosed(), !getAntiAlias(), true);
+ }
+
+ QtPainter aPainter(*this, false, 255 * (1.0 - fTransparency));
+
+ // setup line attributes
+ QPen aPen = aPainter.pen();
+ aPen.setWidth(fLineWidth);
+
+ switch (eLineJoin)
+ {
+ case basegfx::B2DLineJoin::Bevel:
+ aPen.setJoinStyle(Qt::BevelJoin);
+ break;
+ case basegfx::B2DLineJoin::Round:
+ aPen.setJoinStyle(Qt::RoundJoin);
+ break;
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ aPen.setMiterLimit(1.0 / sin(fMiterMinimumAngle / 2.0));
+ aPen.setJoinStyle(Qt::MiterJoin);
+ break;
+ }
+
+ switch (eLineCap)
+ {
+ default: // css::drawing::LineCap_BUTT:
+ aPen.setCapStyle(Qt::FlatCap);
+ break;
+ case css::drawing::LineCap_ROUND:
+ aPen.setCapStyle(Qt::RoundCap);
+ break;
+ case css::drawing::LineCap_SQUARE:
+ aPen.setCapStyle(Qt::SquareCap);
+ break;
+ }
+
+ aPainter.setPen(aPen);
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+ return true;
+}
+
+bool QtGraphicsBackend::drawGradient(const tools::PolyPolygon& /*rPolyPolygon*/,
+ const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/,
+ SalGradient const& /*rGradient*/)
+{
+ return false;
+}
+
+void QtGraphicsBackend::drawScaledImage(const SalTwoRect& rPosAry, const QImage& rImage)
+{
+ QtPainter aPainter(*this);
+ QRect aSrcRect(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ QRect aDestRect(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
+ aPainter.drawImage(aDestRect, rImage, aSrcRect);
+ aPainter.update(aDestRect);
+}
+
+void QtGraphicsBackend::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
+ tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool /*bWindowInvalidate*/)
+{
+ if (nDestX == nSrcX && nDestY == nSrcY)
+ return;
+
+ SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+
+ QImage* pImage = m_pQImage;
+ QImage aImage = pImage->copy(aTR.mnSrcX, aTR.mnSrcY, aTR.mnSrcWidth, aTR.mnSrcHeight);
+ pImage = &aImage;
+ aTR.mnSrcX = 0;
+ aTR.mnSrcY = 0;
+
+ drawScaledImage(aTR, *pImage);
+}
+
+void QtGraphicsBackend::copyBits(const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ QImage aImage, *pImage;
+ SalTwoRect aPosAry = rPosAry;
+
+ if (!pSrcGraphics)
+ {
+ pImage = m_pQImage;
+ aImage
+ = pImage->copy(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ pImage = &aImage;
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ }
+ else
+ pImage = static_cast<QtGraphics*>(pSrcGraphics)->getQImage();
+
+ drawScaledImage(aPosAry, *pImage);
+}
+
+void QtGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ const QImage* pImage = static_cast<const QtBitmap*>(&rSalBitmap)->GetQImage();
+
+ assert(pImage);
+
+ drawScaledImage(rPosAry, *pImage);
+}
+
+void QtGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& /*rSalBitmap*/,
+ const SalBitmap& /*rTransparentBitmap*/)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ assert(rPosAry.mnSrcWidth == rPosAry.mnDestWidth);
+ assert(rPosAry.mnSrcHeight == rPosAry.mnDestHeight);
+}
+
+void QtGraphicsBackend::drawMask(const SalTwoRect& rPosAry, const SalBitmap& /*rSalBitmap*/,
+ Color /*nMaskColor*/)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ assert(rPosAry.mnSrcWidth == rPosAry.mnDestWidth);
+ assert(rPosAry.mnSrcHeight == rPosAry.mnDestHeight);
+}
+
+std::shared_ptr<SalBitmap> QtGraphicsBackend::getBitmap(tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight)
+{
+ return std::make_shared<QtBitmap>(m_pQImage->copy(nX, nY, nWidth, nHeight));
+}
+
+Color QtGraphicsBackend::getPixel(tools::Long nX, tools::Long nY)
+{
+ return Color(ColorTransparency, m_pQImage->pixel(nX, nY));
+}
+
+void QtGraphicsBackend::invert(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, SalInvert nFlags)
+{
+ QtPainter aPainter(*this);
+ if (SalInvert::N50 & nFlags)
+ {
+ aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
+ QBrush aBrush(Qt::white, Qt::Dense4Pattern);
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aBrush);
+ }
+ else
+ {
+ if (SalInvert::TrackFrame & nFlags)
+ {
+ aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
+ QPen aPen(Qt::white);
+ aPen.setStyle(Qt::DotLine);
+ aPainter.setPen(aPen);
+ aPainter.drawRect(nX, nY, nWidth, nHeight);
+ }
+ else
+ {
+ aPainter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
+ aPainter.fillRect(nX, nY, nWidth, nHeight, Qt::white);
+ }
+ }
+ aPainter.update(nX, nY, nWidth, nHeight);
+}
+
+void QtGraphicsBackend::invert(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/,
+ SalInvert /*nFlags*/)
+{
+}
+
+bool QtGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/, void* /*pPtr*/, sal_uInt32 /*nSize*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::blendBitmap(const SalTwoRect&, const SalBitmap& /*rBitmap*/)
+{
+ return false;
+}
+
+bool QtGraphicsBackend::blendAlphaBitmap(const SalTwoRect&, const SalBitmap& /*rSrcBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+static QImage getAlphaImage(const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap)
+{
+ assert(rSourceBitmap.GetSize() == rAlphaBitmap.GetSize());
+ assert(rAlphaBitmap.GetBitCount() == 8 || rAlphaBitmap.GetBitCount() == 1);
+
+ QImage aAlphaMask = *static_cast<const QtBitmap*>(&rAlphaBitmap)->GetQImage();
+
+ const QImage* pBitmap = static_cast<const QtBitmap*>(&rSourceBitmap)->GetQImage();
+ QImage aImage = pBitmap->convertToFormat(Qt_DefaultFormat32);
+ aImage.setAlphaChannel(aAlphaMask);
+ return aImage;
+}
+
+bool QtGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ drawScaledImage(rPosAry, getAlphaImage(rSourceBitmap, rAlphaBitmap));
+ return true;
+}
+
+bool QtGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap, double fAlpha)
+{
+ QImage aImage;
+ if (!pAlphaBitmap)
+ aImage = *static_cast<const QtBitmap*>(&rSourceBitmap)->GetQImage();
+ else
+ aImage = getAlphaImage(rSourceBitmap, *pAlphaBitmap);
+
+ const basegfx::B2DVector aXRel = rX - rNull;
+ const basegfx::B2DVector aYRel = rY - rNull;
+
+ QtPainter aPainter(*this);
+ aPainter.setOpacity(fAlpha);
+ aPainter.setTransform(QTransform(aXRel.getX() / aImage.width(), aXRel.getY() / aImage.width(),
+ aYRel.getX() / aImage.height(), aYRel.getY() / aImage.height(),
+ rNull.getX(), rNull.getY()));
+ aPainter.drawImage(QPoint(0, 0), aImage);
+ aPainter.update(aImage.rect());
+ return true;
+}
+
+bool QtGraphicsBackend::hasFastDrawTransformedBitmap() const { return false; }
+
+bool QtGraphicsBackend::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency)
+{
+ if (!m_oFillColor && !m_oLineColor)
+ return true;
+ assert(nTransparency <= 100);
+ if (nTransparency > 100)
+ nTransparency = 100;
+ QtPainter aPainter(*this, true, (100 - nTransparency) * (255.0 / 100));
+ if (m_oFillColor)
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
+ if (m_oLineColor)
+ aPainter.drawRect(nX, nY, nWidth - 1, nHeight - 1);
+ aPainter.update(nX, nY, nWidth, nHeight);
+ return true;
+}
+
+sal_uInt16 QtGraphicsBackend::GetBitCount() const { return getFormatBits(m_pQImage->format()); }
+
+tools::Long QtGraphicsBackend::GetGraphicsWidth() const { return m_pQImage->width(); }
+
+void QtGraphicsBackend::SetLineColor() { m_oLineColor = std::nullopt; }
+
+void QtGraphicsBackend::SetLineColor(Color nColor) { m_oLineColor = nColor; }
+
+void QtGraphicsBackend::SetFillColor() { m_oFillColor = std::nullopt; }
+
+void QtGraphicsBackend::SetFillColor(Color nColor) { m_oFillColor = nColor; }
+
+void QtGraphicsBackend::SetXORMode(bool bSet, bool)
+{
+ if (bSet)
+ m_eCompositionMode = QPainter::CompositionMode_Xor;
+ else
+ m_eCompositionMode = QPainter::CompositionMode_SourceOver;
+}
+
+void QtGraphicsBackend::SetROPLineColor(SalROPColor /*nROPColor*/) {}
+
+void QtGraphicsBackend::SetROPFillColor(SalROPColor /*nROPColor*/) {}
+
+bool QtGraphicsBackend::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void QtGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+ if (!m_pFrame)
+ return;
+
+ QScreen* pScreen = m_pFrame->GetQWidget()->screen();
+ rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
+ rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtGraphics_Text.cxx b/vcl/qt5/QtGraphics_Text.cxx
new file mode 100644
index 0000000000..19837d510f
--- /dev/null
+++ b/vcl/qt5/QtGraphics_Text.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 <QtGraphics.hxx>
+#include <QtFontFace.hxx>
+#include <QtFont.hxx>
+#include <QtPainter.hxx>
+
+#include <vcl/fontcharmap.hxx>
+#include <unx/geninst.h>
+#include <unx/fontmanager.hxx>
+#include <unx/glyphcache.hxx>
+
+#include <sallayout.hxx>
+#include <font/PhysicalFontCollection.hxx>
+
+#include <QtGui/QGlyphRun>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QRawFont>
+#include <QtCore/QStringList>
+
+void QtGraphics::SetTextColor(Color nColor) { m_aTextColor = nColor; }
+
+void QtGraphics::SetFont(LogicalFontInstance* pReqFont, int nFallbackLevel)
+{
+ // release the text styles
+ for (int i = nFallbackLevel; i < MAX_FALLBACK; ++i)
+ {
+ if (!m_pTextStyle[i])
+ break;
+ m_pTextStyle[i].clear();
+ }
+
+ if (!pReqFont)
+ return;
+
+ m_pTextStyle[nFallbackLevel] = static_cast<QtFont*>(pReqFont);
+}
+
+void QtGraphics::GetFontMetric(FontMetricDataRef& rFMD, int nFallbackLevel)
+{
+ QRawFont aRawFont(QRawFont::fromFont(*m_pTextStyle[nFallbackLevel]));
+ QtFontFace::fillAttributesFromQFont(*m_pTextStyle[nFallbackLevel], *rFMD);
+
+ rFMD->ImplCalcLineSpacing(m_pTextStyle[nFallbackLevel].get());
+ rFMD->ImplInitBaselines(m_pTextStyle[nFallbackLevel].get());
+
+ rFMD->SetSlant(0);
+ rFMD->SetWidth(aRawFont.averageCharWidth());
+
+ rFMD->SetMinKashida(m_pTextStyle[nFallbackLevel]->GetKashidaWidth());
+}
+
+FontCharMapRef QtGraphics::GetFontCharMap() const
+{
+ if (!m_pTextStyle[0])
+ return FontCharMapRef(new FontCharMap());
+ return m_pTextStyle[0]->GetFontFace()->GetFontCharMap();
+}
+
+bool QtGraphics::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const
+{
+ if (!m_pTextStyle[0])
+ return false;
+ return m_pTextStyle[0]->GetFontFace()->GetFontCapabilities(rFontCapabilities);
+}
+
+void QtGraphics::GetDevFontList(vcl::font::PhysicalFontCollection* pPFC)
+{
+ static const bool bUseFontconfig = (nullptr == getenv("SAL_VCL_QT5_NO_FONTCONFIG"));
+
+ if (pPFC->Count())
+ return;
+
+ FreetypeManager& rFontManager = FreetypeManager::get();
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ ::std::vector<psp::fontID> aList;
+
+ rMgr.getFontList(aList);
+ for (auto const& nFontId : aList)
+ {
+ auto const* pFont = rMgr.getFont(nFontId);
+ if (!pFont)
+ continue;
+
+ // normalize face number to the FreetypeManager
+ int nFaceNum = rMgr.getFontFaceNumber(nFontId);
+ int nVariantNum = rMgr.getFontFaceVariation(nFontId);
+
+ // inform FreetypeManager about this font provided by the PsPrint subsystem
+ FontAttributes aFA = pFont->m_aFontAttributes;
+ aFA.IncreaseQualityBy(4096);
+ const OString& rFileName = rMgr.getFontFileSysPath(nFontId);
+ rFontManager.AddFontFile(rFileName, nFaceNum, nVariantNum, nFontId, aFA);
+ }
+
+ if (bUseFontconfig)
+ SalGenericInstance::RegisterFontSubstitutors(pPFC);
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ for (auto& family : QFontDatabase::families())
+ for (auto& style : QFontDatabase::styles(family))
+ pPFC->Add(QtFontFace::fromQFontDatabase(family, style));
+#else
+ QFontDatabase aFDB;
+ for (auto& family : aFDB.families())
+ for (auto& style : aFDB.styles(family))
+ pPFC->Add(QtFontFace::fromQFontDatabase(family, style));
+#endif
+}
+
+void QtGraphics::ClearDevFontCache() {}
+
+bool QtGraphics::AddTempDevFont(vcl::font::PhysicalFontCollection*, const OUString& /*rFileURL*/,
+ const OUString& /*rFontName*/)
+{
+ return false;
+}
+
+namespace
+{
+class QtCommonSalLayout : public GenericSalLayout
+{
+public:
+ QtCommonSalLayout(LogicalFontInstance& rLFI)
+ : GenericSalLayout(rLFI)
+ {
+ }
+
+ void SetOrientation(Degree10 nOrientation) { mnOrientation = nOrientation; }
+};
+}
+
+std::unique_ptr<GenericSalLayout> QtGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(m_pTextStyle[nFallbackLevel]);
+ if (!m_pTextStyle[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<QtCommonSalLayout>(*m_pTextStyle[nFallbackLevel]);
+}
+
+static QRawFont GetRawFont(const QFont& rFont, bool bWithoutHintingInTextDirection)
+{
+ QFont::HintingPreference eHinting = rFont.hintingPreference();
+ static bool bAllowDefaultHinting = getenv("SAL_ALLOW_DEFAULT_HINTING") != nullptr;
+ bool bAllowedHintStyle
+ = !bWithoutHintingInTextDirection || bAllowDefaultHinting
+ || (eHinting == QFont::PreferNoHinting || eHinting == QFont::PreferVerticalHinting);
+ if (bWithoutHintingInTextDirection && !bAllowedHintStyle)
+ {
+ QFont aFont(rFont);
+ aFont.setHintingPreference(QFont::PreferVerticalHinting);
+ return QRawFont::fromFont(aFont);
+ }
+ return QRawFont::fromFont(rFont);
+}
+
+void QtGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ const QtFont* pFont = static_cast<const QtFont*>(&rLayout.GetFont());
+ assert(pFont);
+ QRawFont aRawFont(GetRawFont(*pFont, rLayout.GetSubpixelPositioning()));
+
+ QVector<quint32> glyphIndexes;
+ QVector<QPointF> positions;
+
+ // prevent glyph rotation inside the SalLayout
+ // probably better to add a parameter to GetNextGlyphs?
+ QtCommonSalLayout* pQtLayout
+ = static_cast<QtCommonSalLayout*>(const_cast<GenericSalLayout*>(&rLayout));
+ Degree10 nOrientation = rLayout.GetOrientation();
+ if (nOrientation)
+ pQtLayout->SetOrientation(0_deg10);
+
+ basegfx::B2DPoint aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ glyphIndexes.push_back(pGlyph->glyphId());
+ positions.push_back(QPointF(aPos.getX(), aPos.getY()));
+ }
+
+ // seems to be common to try to layout an empty string...
+ if (positions.empty())
+ return;
+
+ if (nOrientation)
+ pQtLayout->SetOrientation(nOrientation);
+
+ QGlyphRun aGlyphRun;
+ aGlyphRun.setPositions(positions);
+ aGlyphRun.setGlyphIndexes(glyphIndexes);
+ aGlyphRun.setRawFont(aRawFont);
+
+ QtPainter aPainter(*m_pBackend);
+ QColor aColor = toQColor(m_aTextColor);
+ aPainter.setPen(aColor);
+
+ if (nOrientation)
+ {
+ // make text position the center of the rotation
+ // then rotate and move back
+ QRect window = aPainter.window();
+ window.moveTo(-positions[0].x(), -positions[0].y());
+ aPainter.setWindow(window);
+
+ QTransform p;
+ p.rotate(-static_cast<qreal>(nOrientation.get()) / 10.0);
+ p.translate(-positions[0].x(), -positions[0].y());
+ aPainter.setTransform(p);
+ }
+
+ aPainter.drawGlyphRun(QPointF(), aGlyphRun);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtInstance.cxx b/vcl/qt5/QtInstance.cxx
new file mode 100644
index 0000000000..4880c1bdec
--- /dev/null
+++ b/vcl/qt5/QtInstance.cxx
@@ -0,0 +1,771 @@
+/* -*- 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 <QtInstance.hxx>
+#include <QtInstance.moc>
+
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <QtBitmap.hxx>
+#include <QtClipboard.hxx>
+#include <QtData.hxx>
+#include <QtDragAndDrop.hxx>
+#include <QtFilePicker.hxx>
+#include <QtFrame.hxx>
+#include <QtMenu.hxx>
+#include <QtObject.hxx>
+#include <QtOpenGLContext.hxx>
+#include "QtSvpVirtualDevice.hxx"
+#include <QtSystem.hxx>
+#include <QtTimer.hxx>
+#include <QtVirtualDevice.hxx>
+
+#include <headless/svpvd.hxx>
+
+#include <QtCore/QAbstractEventDispatcher>
+#include <QtCore/QLibraryInfo>
+#include <QtCore/QThread>
+#include <QtGui/QScreen>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+
+#include <vclpluginapi.h>
+#include <tools/debug.hxx>
+#include <comphelper/flagguard.hxx>
+#include <dndhelper.hxx>
+#include <vcl/sysdata.hxx>
+#include <sal/log.hxx>
+#include <osl/process.h>
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && ENABLE_GSTREAMER_1_0 && QT5_HAVE_GOBJECT
+#include <unx/gstsink.hxx>
+#endif
+#include <headless/svpbmp.hxx>
+
+#include <mutex>
+#include <condition_variable>
+
+#ifdef EMSCRIPTEN
+#include <QtCore/QtPlugin>
+Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
+#endif
+
+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
+{
+ auto const* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (pSalInst->IsMainThread() && m_bNoYieldLock)
+ {
+ return true; // main thread has borrowed SolarMutex
+ }
+ return SalYieldMutex::IsCurrentThread();
+}
+
+void QtYieldMutex::doAcquire(sal_uInt32 nLockCount)
+{
+ auto const* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->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)
+{
+ auto const* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (pSalInst->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 && !pSalInst->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;
+ }
+
+ QtYieldMutex* const pMutex(static_cast<QtYieldMutex*>(GetYieldMutex()));
+ {
+ std::scoped_lock<std::mutex> g(pMutex->m_RunInMainMutex);
+ assert(!pMutex->m_Closure);
+ pMutex->m_Closure = 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;
+ }
+}
+
+OUString QtInstance::constructToolkitID(std::u16string_view sTKname)
+{
+ OUString sID(sTKname + OUString::Concat(u" ("));
+ if (m_bUseCairo)
+ sID += "cairo+";
+ else
+ sID += "qfont+";
+ sID += toOUString(QGuiApplication::platformName()) + OUString::Concat(u")");
+ return sID;
+}
+
+QtInstance::QtInstance(std::unique_ptr<QApplication>& pQApp)
+ : SalGenericInstance(std::make_unique<QtYieldMutex>())
+ , m_bUseCairo(nullptr == getenv("SAL_VCL_QT_USE_QFONT"))
+ , m_pTimer(nullptr)
+ , m_bSleeping(false)
+ , m_pQApplication(std::move(pQApp))
+ , m_aUpdateStyleTimer("vcl::qt5 m_aUpdateStyleTimer")
+ , m_bUpdateFonts(false)
+ , m_pActivePopup(nullptr)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ const OUString sToolkit = "qt" + OUString::number(QT_VERSION_MAJOR);
+ pSVData->maAppData.mxToolkitName = constructToolkitID(sToolkit);
+
+ // this one needs to be blocking, so that the handling in main thread
+ // is processed before the thread emitting the signal continues
+ connect(this, SIGNAL(ImplYieldSignal(bool, bool)), this, SLOT(ImplYield(bool, bool)),
+ 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);
+
+#ifndef EMSCRIPTEN
+ m_bSupportsOpenGL = true;
+#else
+ 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();
+}
+
+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.desktop"));
+ 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)
+{
+ SalFrame* pRet(nullptr);
+ RunInMainThread([&, this]() { pRet = new QtFrame(nullptr, nStyle, useCairo()); });
+ assert(pRet);
+ return pRet;
+}
+
+SalFrame* QtInstance::CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle)
+{
+ assert(!pParent || dynamic_cast<QtFrame*>(pParent));
+
+ SalFrame* pRet(nullptr);
+ RunInMainThread(
+ [&, this]() { pRet = new QtFrame(static_cast<QtFrame*>(pParent), nStyle, useCairo()); });
+ assert(pRet);
+ return pRet;
+}
+
+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)
+{
+ 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<SalVirtualDevice>
+QtInstance::CreateVirtualDevice(SalGraphics& rGraphics, tools::Long& nDX, tools::Long& nDY,
+ DeviceFormat /*eFormat*/, const SystemGraphicsData* pGd)
+{
+ if (m_bUseCairo)
+ {
+ SvpSalGraphics* pSvpSalGraphics = dynamic_cast<QtSvpGraphics*>(&rGraphics);
+ assert(pSvpSalGraphics);
+ // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
+ cairo_surface_t* pPreExistingTarget
+ = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
+ std::unique_ptr<SalVirtualDevice> pVD(
+ new QtSvpVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ pVD->SetSize(nDX, nDY);
+ return pVD;
+ }
+ else
+ {
+ std::unique_ptr<SalVirtualDevice> pVD(new QtVirtualDevice(/*scale*/ 1));
+ pVD->SetSize(nDX, nDY);
+ return pVD;
+ }
+}
+
+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; }
+
+std::shared_ptr<SalBitmap> QtInstance::CreateSalBitmap()
+{
+ if (m_bUseCairo)
+ return std::make_shared<SvpSalBitmap>();
+ else
+ return std::make_shared<QtBitmap>();
+}
+
+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();
+ }
+ 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;
+}
+
+OUString QtInstance::GetConnectionIdentifier() { return OUString(); }
+
+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)
+{
+ aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
+}
+
+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::uno::XInterface>
+QtInstance::CreateClipboard(const css::uno::Sequence<css::uno::Any>& arguments)
+{
+ OUString sel;
+ if (arguments.getLength() == 0)
+ {
+ sel = "CLIPBOARD";
+ }
+ else if (arguments.getLength() != 1 || !(arguments[0] >>= sel))
+ {
+ throw css::lang::IllegalArgumentException("bad QtInstance::CreateClipboard arguments",
+ css::uno::Reference<css::uno::XInterface>(), -1);
+ }
+
+ // 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(sel);
+ if (it != m_aClipboards.end())
+ return it->second;
+
+ css::uno::Reference<css::uno::XInterface> xClipboard = QtClipboard::create(sel);
+ if (xClipboard.is())
+ m_aClipboards[sel] = xClipboard;
+
+ return xClipboard;
+}
+
+css::uno::Reference<css::uno::XInterface>
+QtInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new QtDragSource(), pSysEnv->aShellWindow);
+}
+
+css::uno::Reference<css::uno::XInterface>
+QtInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new QtDropTarget(), pSysEnv->aShellWindow);
+}
+
+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 (pEnvData->platform != SystemEnvData::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::virtualGeometryChanged(const QRect&) { notifyDisplayChanged(); }
+
+void QtInstance::AllocFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
+ std::unique_ptr<int>& rFakeArgc,
+ std::vector<FreeableCStr>& rFakeArgvFreeable)
+{
+ OString aVersion(qVersion());
+ SAL_INFO("vcl.qt", "qt version string is " << aVersion);
+
+ const sal_uInt32 nParams = osl_getCommandArgCount();
+ sal_uInt32 nDisplayValueIdx = 0;
+ OUString aParam, aBin;
+
+ for (sal_uInt32 nIdx = 0; nIdx < nParams; ++nIdx)
+ {
+ osl_getCommandArg(nIdx, &aParam.pData);
+ if (aParam != "-display")
+ continue;
+ ++nIdx;
+ nDisplayValueIdx = nIdx;
+ }
+
+ osl_getExecutableFile(&aParam.pData);
+ osl_getSystemPathFromFileURL(aParam.pData, &aBin.pData);
+ OString aExec = OUStringToOString(aBin, osl_getThreadTextEncoding());
+
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ aFakeArgvFreeable.reserve(4);
+ aFakeArgvFreeable.emplace_back(strdup(aExec.getStr()));
+ aFakeArgvFreeable.emplace_back(strdup("--nocrashhandler"));
+ if (nDisplayValueIdx)
+ {
+ aFakeArgvFreeable.emplace_back(strdup("-display"));
+ osl_getCommandArg(nDisplayValueIdx, &aParam.pData);
+ OString aDisplay = OUStringToOString(aParam, osl_getThreadTextEncoding());
+ aFakeArgvFreeable.emplace_back(strdup(aDisplay.getStr()));
+ }
+ rFakeArgvFreeable.swap(aFakeArgvFreeable);
+
+ const int nFakeArgc = rFakeArgvFreeable.size();
+ rFakeArgv.reset(new char*[nFakeArgc]);
+ for (int i = 0; i < nFakeArgc; i++)
+ rFakeArgv[i] = rFakeArgvFreeable[i].get();
+
+ rFakeArgc.reset(new int);
+ *rFakeArgc = nFakeArgc;
+}
+
+void QtInstance::MoveFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
+ std::unique_ptr<int>& rFakeArgc,
+ std::vector<FreeableCStr>& rFakeArgvFreeable)
+{
+ m_pFakeArgv = std::move(rFakeArgv);
+ m_pFakeArgc = std::move(rFakeArgc);
+ m_pFakeArgvFreeable.swap(rFakeArgvFreeable);
+}
+
+std::unique_ptr<QApplication> QtInstance::CreateQApplication(int& nArgc, char** pArgv)
+{
+#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
+
+ FreeableCStr session_manager;
+ if (getenv("SESSION_MANAGER") != nullptr)
+ {
+ session_manager.reset(strdup(getenv("SESSION_MANAGER")));
+ unsetenv("SESSION_MANAGER");
+ }
+
+ std::unique_ptr<QApplication> pQApp = std::make_unique<QApplication>(nArgc, pArgv);
+
+ if (session_manager != nullptr)
+ {
+ // coverity[tainted_string] - trusted source for setenv
+ setenv("SESSION_MANAGER", session_manager.get(), 1);
+ }
+
+ QApplication::setQuitOnLastWindowClosed(false);
+ return pQApp;
+}
+
+bool QtInstance::DoExecute(int& nExitCode)
+{
+ const bool bIsOnSystemEventLoop = Application::IsOnSystemEventLoop();
+ if (bIsOnSystemEventLoop)
+ nExitCode = QApplication::exec();
+ return bIsOnSystemEventLoop;
+}
+
+void QtInstance::DoQuit()
+{
+ if (Application::IsOnSystemEventLoop())
+ QApplication::quit();
+}
+
+void QtInstance::setActivePopup(QtFrame* pFrame)
+{
+ assert(!pFrame || pFrame->isPopup());
+ m_pActivePopup = pFrame;
+}
+
+extern "C" {
+VCLPLUG_QT_PUBLIC SalInstance* create_SalInstance()
+{
+ std::unique_ptr<char* []> pFakeArgv;
+ std::unique_ptr<int> pFakeArgc;
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ QtInstance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ std::unique_ptr<QApplication> pQApp
+ = QtInstance::CreateQApplication(*pFakeArgc, pFakeArgv.get());
+
+ QtInstance* pInstance = new QtInstance(pQApp);
+ pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ new QtData();
+
+ return pInstance;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtInstance_Print.cxx b/vcl/qt5/QtInstance_Print.cxx
new file mode 100644
index 0000000000..e6396099db
--- /dev/null
+++ b/vcl/qt5/QtInstance_Print.cxx
@@ -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 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <QtInstance.hxx>
+#include <QtPrinter.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <printerinfomanager.hxx>
+
+#include <jobset.h>
+#include <print.h>
+#include <salptype.hxx>
+
+#include <unx/genpspgraphics.h>
+
+using namespace psp;
+
+/*
+ * static helpers
+ */
+
+static OUString getPdfDir(const PrinterInfo& rInfo)
+{
+ OUString aDir;
+ sal_Int32 nIndex = 0;
+ while (nIndex != -1)
+ {
+ OUString aToken(rInfo.m_aFeatures.getToken(0, ',', nIndex));
+ if (aToken.startsWith("pdf="))
+ {
+ sal_Int32 nPos = 0;
+ aDir = aToken.getToken(1, '=', nPos);
+ if (aDir.isEmpty())
+ if (auto const env = getenv("HOME"))
+ {
+ aDir = OStringToOUString(std::string_view(env), osl_getThreadTextEncoding());
+ }
+ break;
+ }
+ }
+ return aDir;
+}
+
+SalInfoPrinter* QtInstance::CreateInfoPrinter(SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pJobSetup)
+{
+ // create and initialize SalInfoPrinter
+ PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup);
+
+ return pPrinter;
+}
+
+void QtInstance::DestroyInfoPrinter(SalInfoPrinter* pPrinter) { delete pPrinter; }
+
+std::unique_ptr<SalPrinter> QtInstance::CreatePrinter(SalInfoPrinter* pInfoPrinter)
+{
+ // create and initialize SalPrinter
+ QtPrinter* pPrinter = new QtPrinter(pInfoPrinter);
+ pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
+
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+void QtInstance::GetPrinterQueueInfo(ImplPrnQueueList* pList)
+{
+ PrinterInfoManager& rManager(PrinterInfoManager::get());
+ static const char* pNoSyncDetection = getenv("SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION");
+ if (!pNoSyncDetection || !*pNoSyncDetection)
+ {
+ // #i62663# synchronize possible asynchronouse printer detection now
+ rManager.checkPrintersChanged(true);
+ }
+ ::std::vector<OUString> aPrinters;
+ rManager.listPrinters(aPrinters);
+
+ for (const auto& rPrinter : aPrinters)
+ {
+ const PrinterInfo& rInfo(rManager.getPrinterInfo(rPrinter));
+ // create new entry
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = rPrinter;
+ pInfo->maDriver = rInfo.m_aDriverName;
+ pInfo->maLocation = rInfo.m_aLocation;
+ pInfo->maComment = rInfo.m_aComment;
+
+ sal_Int32 nIndex = 0;
+ while (nIndex != -1)
+ {
+ OUString aToken(rInfo.m_aFeatures.getToken(0, ',', nIndex));
+ if (aToken.startsWith("pdf="))
+ {
+ pInfo->maLocation = getPdfDir(rInfo);
+ break;
+ }
+ }
+
+ pList->Add(std::move(pInfo));
+ }
+}
+
+void QtInstance::GetPrinterQueueState(SalPrinterQueueInfo*) {}
+
+OUString QtInstance::GetDefaultPrinter()
+{
+ PrinterInfoManager& rManager(PrinterInfoManager::get());
+ return rManager.getDefaultPrinter();
+}
+
+void QtInstance::PostPrintersChanged() {}
+
+std::unique_ptr<GenPspGraphics> QtInstance::CreatePrintGraphics()
+{
+ return std::make_unique<GenPspGraphics>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtMainWindow.cxx b/vcl/qt5/QtMainWindow.cxx
new file mode 100644
index 0000000000..5ff9ac9a81
--- /dev/null
+++ b/vcl/qt5/QtMainWindow.cxx
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <QtMainWindow.hxx>
+#include <QtMainWindow.moc>
+#include <QtAccessibleWidget.hxx>
+
+#include <QtGui/QAccessible>
+#include <QtGui/QCloseEvent>
+
+QtMainWindow::QtMainWindow(QtFrame& rFrame, Qt::WindowFlags f)
+ : QMainWindow(nullptr, f)
+ , m_rFrame(rFrame)
+{
+#ifndef EMSCRIPTEN
+ QAccessible::installFactory(QtAccessibleWidget::customFactory);
+#endif
+}
+
+void QtMainWindow::closeEvent(QCloseEvent* pEvent)
+{
+ bool bRet = false;
+ bRet = m_rFrame.CallCallback(SalEvent::Close, nullptr);
+
+ if (bRet)
+ pEvent->accept();
+ // SalEvent::Close returning false may mean that user has vetoed
+ // closing the frame ("you have unsaved changes" dialog for example)
+ // We shouldn't process the event in such case
+ else
+ pEvent->ignore();
+}
+
+void QtMainWindow::moveEvent(QMoveEvent* pEvent)
+{
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ m_rFrame.maGeometry.setPos(toPoint(pEvent->pos() * fRatio));
+ m_rFrame.CallCallback(SalEvent::Move, nullptr);
+}
diff --git a/vcl/qt5/QtMenu.cxx b/vcl/qt5/QtMenu.cxx
new file mode 100644
index 0000000000..93f3d6f5a3
--- /dev/null
+++ b/vcl/qt5/QtMenu.cxx
@@ -0,0 +1,920 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <QtMenu.hxx>
+#include <QtMenu.moc>
+
+#include <QtFrame.hxx>
+#include <QtInstance.hxx>
+#include <QtMainWindow.hxx>
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#include <QtWidgets/QActionGroup>
+#else
+#include <QtGui/QActionGroup>
+#endif
+
+#include <QtWidgets/QButtonGroup>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QPushButton>
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+#include <QtGui/QShortcut>
+#else
+#include <QtWidgets/QShortcut>
+#endif
+#include <QtWidgets/QStyle>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <strings.hrc>
+#include <bitmaps.hlst>
+
+#include <vcl/toolkit/floatwin.hxx>
+#include <window.h>
+
+// LO SalMenuButtonItem::mnId is sal_uInt16, so we go with -2, as -1 has a special meaning as automatic id
+constexpr int CLOSE_BUTTON_ID = -2;
+const QString gButtonGroupKey("QtMenu::ButtonGroup");
+
+static inline void lcl_force_menubar_layout_update(QMenuBar& rMenuBar)
+{
+ // just exists as a function to not comment it everywhere: forces reposition of the
+ // corner widget after its layout changes, which will otherwise just happen on resize.
+ // it unfortunatly has additional side effects; see QtMenu::GetMenuBarButtonRectPixel.
+ rMenuBar.adjustSize();
+}
+
+OUString QtMenu::m_sCurrentHelpId = u""_ustr;
+
+QtMenu::QtMenu(bool bMenuBar)
+ : mpVCLMenu(nullptr)
+ , mpParentSalMenu(nullptr)
+ , mpFrame(nullptr)
+ , mbMenuBar(bMenuBar)
+ , mpQMenuBar(nullptr)
+ , mpQMenu(nullptr)
+ , m_pButtonGroup(nullptr)
+{
+}
+
+bool QtMenu::VisibleMenuBar() { return true; }
+
+void QtMenu::InsertMenuItem(QtMenuItem* pSalMenuItem, unsigned nPos)
+{
+ sal_uInt16 nId = pSalMenuItem->mnId;
+ OUString aText = mpVCLMenu->GetItemText(nId);
+ NativeItemText(aText);
+ vcl::KeyCode nAccelKey = mpVCLMenu->GetAccelKey(nId);
+
+ pSalMenuItem->mpAction.reset();
+ pSalMenuItem->mpMenu.reset();
+
+ if (mbMenuBar)
+ {
+ // top-level menu
+ if (validateQMenuBar())
+ {
+ QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
+ connectHelpSignalSlots(pQMenu, pSalMenuItem);
+ pSalMenuItem->mpMenu.reset(pQMenu);
+
+ if ((nPos != MENU_APPEND)
+ && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenuBar->actions().size())))
+ {
+ mpQMenuBar->insertMenu(mpQMenuBar->actions()[nPos], pQMenu);
+ }
+ else
+ {
+ mpQMenuBar->addMenu(pQMenu);
+ }
+
+ // correct parent menu for generated menu
+ if (pSalMenuItem->mpSubMenu)
+ {
+ pSalMenuItem->mpSubMenu->mpQMenu = pQMenu;
+ }
+
+ connect(pQMenu, &QMenu::aboutToShow, this,
+ [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); });
+ connect(pQMenu, &QMenu::aboutToHide, this,
+ [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
+ }
+ }
+ else
+ {
+ if (!mpQMenu)
+ {
+ // no QMenu set, instantiate own one
+ mpOwnedQMenu.reset(new QMenu);
+ mpQMenu = mpOwnedQMenu.get();
+ connectHelpSignalSlots(mpQMenu, pSalMenuItem);
+ }
+
+ if (pSalMenuItem->mpSubMenu)
+ {
+ // submenu
+ QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
+ connectHelpSignalSlots(pQMenu, pSalMenuItem);
+ pSalMenuItem->mpMenu.reset(pQMenu);
+
+ if ((nPos != MENU_APPEND)
+ && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
+ {
+ mpQMenu->insertMenu(mpQMenu->actions()[nPos], pQMenu);
+ }
+ else
+ {
+ mpQMenu->addMenu(pQMenu);
+ }
+
+ // correct parent menu for generated menu
+ pSalMenuItem->mpSubMenu->mpQMenu = pQMenu;
+
+ ReinitializeActionGroup(nPos);
+
+ // clear all action groups since menu is recreated
+ pSalMenuItem->mpSubMenu->ResetAllActionGroups();
+
+ connect(pQMenu, &QMenu::aboutToShow, this,
+ [pSalMenuItem] { slotMenuAboutToShow(pSalMenuItem); });
+ connect(pQMenu, &QMenu::aboutToHide, this,
+ [pSalMenuItem] { slotMenuAboutToHide(pSalMenuItem); });
+ }
+ else
+ {
+ if (pSalMenuItem->mnType == MenuItemType::SEPARATOR)
+ {
+ QAction* pAction = new QAction(nullptr);
+ pSalMenuItem->mpAction.reset(pAction);
+ pAction->setSeparator(true);
+
+ if ((nPos != MENU_APPEND)
+ && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
+ {
+ mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction);
+ }
+ else
+ {
+ mpQMenu->addAction(pAction);
+ }
+
+ ReinitializeActionGroup(nPos);
+ }
+ else
+ {
+ // leaf menu
+ QAction* pAction = new QAction(toQString(aText), nullptr);
+ pSalMenuItem->mpAction.reset(pAction);
+
+ if ((nPos != MENU_APPEND)
+ && (static_cast<size_t>(nPos) < o3tl::make_unsigned(mpQMenu->actions().size())))
+ {
+ mpQMenu->insertAction(mpQMenu->actions()[nPos], pAction);
+ }
+ else
+ {
+ mpQMenu->addAction(pAction);
+ }
+
+ ReinitializeActionGroup(nPos);
+
+ UpdateActionGroupItem(pSalMenuItem);
+
+ pAction->setShortcut(toQString(nAccelKey.GetName()));
+
+ connect(pAction, &QAction::triggered, this,
+ [pSalMenuItem] { slotMenuTriggered(pSalMenuItem); });
+ connect(pAction, &QAction::hovered, this,
+ [pSalMenuItem] { slotMenuHovered(pSalMenuItem); });
+ }
+ }
+ }
+
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ pAction->setEnabled(pSalMenuItem->mbEnabled);
+ pAction->setVisible(pSalMenuItem->mbVisible);
+ }
+}
+
+void QtMenu::ReinitializeActionGroup(unsigned nPos)
+{
+ const unsigned nCount = GetItemCount();
+
+ if (nCount == 0)
+ {
+ return;
+ }
+
+ if (nPos == MENU_APPEND)
+ {
+ nPos = nCount - 1;
+ }
+ else if (nPos >= nCount)
+ {
+ return;
+ }
+
+ QtMenuItem* pPrevItem = (nPos > 0) ? GetItemAtPos(nPos - 1) : nullptr;
+ QtMenuItem* pCurrentItem = GetItemAtPos(nPos);
+ QtMenuItem* pNextItem = (nPos < nCount - 1) ? GetItemAtPos(nPos + 1) : nullptr;
+
+ if (pCurrentItem->mnType == MenuItemType::SEPARATOR)
+ {
+ pCurrentItem->mpActionGroup.reset();
+
+ // if it's inserted into middle of existing group, split it into two groups:
+ // first goes original group, after separator goes new group
+ if (pPrevItem && pPrevItem->mpActionGroup && pNextItem && pNextItem->mpActionGroup
+ && (pPrevItem->mpActionGroup == pNextItem->mpActionGroup))
+ {
+ std::shared_ptr<QActionGroup> pFirstActionGroup = pPrevItem->mpActionGroup;
+ auto pSecondActionGroup = std::make_shared<QActionGroup>(nullptr);
+ pSecondActionGroup->setExclusive(true);
+
+ auto actions = pFirstActionGroup->actions();
+
+ for (unsigned idx = nPos + 1; idx < nCount; ++idx)
+ {
+ QtMenuItem* pModifiedItem = GetItemAtPos(idx);
+
+ if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
+ {
+ break;
+ }
+
+ pModifiedItem->mpActionGroup = pSecondActionGroup;
+ auto action = pModifiedItem->getAction();
+
+ if (actions.contains(action))
+ {
+ pFirstActionGroup->removeAction(action);
+ pSecondActionGroup->addAction(action);
+ }
+ }
+ }
+ }
+ else
+ {
+ if (!pCurrentItem->mpActionGroup)
+ {
+ // unless element is inserted between two separators, or a separator and an end of vector, use neighbouring group since it's shared
+ if (pPrevItem && pPrevItem->mpActionGroup)
+ {
+ pCurrentItem->mpActionGroup = pPrevItem->mpActionGroup;
+ }
+ else if (pNextItem && pNextItem->mpActionGroup)
+ {
+ pCurrentItem->mpActionGroup = pNextItem->mpActionGroup;
+ }
+ else
+ {
+ pCurrentItem->mpActionGroup = std::make_shared<QActionGroup>(nullptr);
+ pCurrentItem->mpActionGroup->setExclusive(true);
+ }
+ }
+
+ // if there's also a different group after this element, merge it
+ if (pNextItem && pNextItem->mpActionGroup
+ && (pCurrentItem->mpActionGroup != pNextItem->mpActionGroup))
+ {
+ auto pFirstCheckedAction = pCurrentItem->mpActionGroup->checkedAction();
+ auto pSecondCheckedAction = pNextItem->mpActionGroup->checkedAction();
+ auto actions = pNextItem->mpActionGroup->actions();
+
+ // first move all actions from second group to first one, and if first group already has checked action,
+ // and second group also has a checked action, uncheck action from second group
+ for (auto action : actions)
+ {
+ pNextItem->mpActionGroup->removeAction(action);
+
+ if (pFirstCheckedAction && pSecondCheckedAction && (action == pSecondCheckedAction))
+ {
+ action->setChecked(false);
+ }
+
+ pCurrentItem->mpActionGroup->addAction(action);
+ }
+
+ // now replace all pointers to second group with pointers to first group
+ for (unsigned idx = nPos + 1; idx < nCount; ++idx)
+ {
+ QtMenuItem* pModifiedItem = GetItemAtPos(idx);
+
+ if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
+ {
+ break;
+ }
+
+ pModifiedItem->mpActionGroup = pCurrentItem->mpActionGroup;
+ }
+ }
+ }
+}
+
+void QtMenu::ResetAllActionGroups()
+{
+ for (unsigned nItem = 0; nItem < GetItemCount(); ++nItem)
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nItem);
+ pSalMenuItem->mpActionGroup.reset();
+ }
+}
+
+void QtMenu::UpdateActionGroupItem(const QtMenuItem* pSalMenuItem)
+{
+ QAction* pAction = pSalMenuItem->getAction();
+ if (!pAction)
+ return;
+
+ bool bChecked = mpVCLMenu->IsItemChecked(pSalMenuItem->mnId);
+ MenuItemBits itemBits = mpVCLMenu->GetItemBits(pSalMenuItem->mnId);
+
+ if (itemBits & MenuItemBits::RADIOCHECK)
+ {
+ pAction->setCheckable(true);
+
+ if (pSalMenuItem->mpActionGroup)
+ {
+ pSalMenuItem->mpActionGroup->addAction(pAction);
+ }
+
+ pAction->setChecked(bChecked);
+ }
+ else
+ {
+ pAction->setActionGroup(nullptr);
+
+ if (itemBits & MenuItemBits::CHECKABLE)
+ {
+ pAction->setCheckable(true);
+ pAction->setChecked(bChecked);
+ }
+ else
+ {
+ pAction->setChecked(false);
+ pAction->setCheckable(false);
+ }
+ }
+}
+
+void QtMenu::InsertItem(SalMenuItem* pSalMenuItem, unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+ QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem);
+
+ if (nPos == MENU_APPEND)
+ maItems.push_back(pItem);
+ else
+ maItems.insert(maItems.begin() + nPos, pItem);
+
+ pItem->mpParentMenu = this;
+
+ InsertMenuItem(pItem, nPos);
+}
+
+void QtMenu::RemoveItem(unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+
+ if (nPos >= maItems.size())
+ return;
+
+ QtMenuItem* pItem = maItems[nPos];
+ pItem->mpAction.reset();
+ pItem->mpMenu.reset();
+
+ maItems.erase(maItems.begin() + nPos);
+
+ // Recalculate action groups if necessary:
+ // if separator between two QActionGroups was removed,
+ // it may be needed to merge them
+ if (nPos > 0)
+ {
+ ReinitializeActionGroup(nPos - 1);
+ }
+}
+
+void QtMenu::SetSubMenu(SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+ QtMenuItem* pItem = static_cast<QtMenuItem*>(pSalMenuItem);
+ QtMenu* pQSubMenu = static_cast<QtMenu*>(pSubMenu);
+
+ pItem->mpSubMenu = pQSubMenu;
+ // at this point the pointer to parent menu may be outdated, update it too
+ pItem->mpParentMenu = this;
+
+ if (pQSubMenu != nullptr)
+ {
+ pQSubMenu->mpParentSalMenu = this;
+ pQSubMenu->mpQMenu = pItem->mpMenu.get();
+ }
+
+ // if it's not a menu bar item, then convert it to corresponding item if type if necessary.
+ // If submenu is present and it's an action, convert it to menu.
+ // If submenu is not present and it's a menu, convert it to action.
+ // It may be fine to proceed in any case, but by skipping other cases
+ // amount of unneeded actions taken should be reduced.
+ if (pItem->mpParentMenu->mbMenuBar || (pQSubMenu && pItem->mpMenu)
+ || ((!pQSubMenu) && pItem->mpAction))
+ {
+ return;
+ }
+
+ InsertMenuItem(pItem, nPos);
+}
+
+void QtMenu::SetFrame(const SalFrame* pFrame)
+{
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, pFrame]() { SetFrame(pFrame); });
+ return;
+ }
+
+ SolarMutexGuard aGuard;
+ assert(mbMenuBar);
+ mpFrame = const_cast<QtFrame*>(static_cast<const QtFrame*>(pFrame));
+
+ mpFrame->SetMenu(this);
+
+ QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
+ if (!pMainWindow)
+ return;
+
+ mpQMenuBar = new QMenuBar();
+ pMainWindow->setMenuBar(mpQMenuBar);
+
+ QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
+ if (pWidget)
+ {
+ m_pButtonGroup = pWidget->findChild<QButtonGroup*>(gButtonGroupKey);
+ assert(m_pButtonGroup);
+ connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
+ &QtMenu::slotMenuBarButtonClicked);
+ QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID));
+ if (pButton)
+ connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
+ }
+ else
+ m_pButtonGroup = nullptr;
+ mpQMenu = nullptr;
+
+ DoFullMenuUpdate(mpVCLMenu);
+}
+
+void QtMenu::DoFullMenuUpdate(Menu* pMenuBar)
+{
+ // clear action groups since menu is rebuilt
+ ResetAllActionGroups();
+ ShowCloseButton(false);
+
+ for (sal_Int32 nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++)
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nItem);
+ InsertMenuItem(pSalMenuItem, nItem);
+ SetItemImage(nItem, pSalMenuItem, pSalMenuItem->maImage);
+ const bool bShowDisabled
+ = bool(pMenuBar->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries)
+ || !bool(pMenuBar->GetMenuFlags() & MenuFlags::HideDisabledEntries);
+ const bool bVisible = pSalMenuItem->mbVisible
+ && (bShowDisabled || mpVCLMenu->IsItemEnabled(pSalMenuItem->mnId));
+ pSalMenuItem->getAction()->setVisible(bVisible);
+
+ if (pSalMenuItem->mpSubMenu != nullptr)
+ {
+ pMenuBar->HandleMenuActivateEvent(pSalMenuItem->mpSubMenu->GetMenu());
+ pSalMenuItem->mpSubMenu->DoFullMenuUpdate(pMenuBar);
+ pMenuBar->HandleMenuDeActivateEvent(pSalMenuItem->mpSubMenu->GetMenu());
+ }
+ }
+}
+
+void QtMenu::ShowItem(unsigned nPos, bool bShow)
+{
+ if (nPos < maItems.size())
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setVisible(bShow);
+ pSalMenuItem->mbVisible = bShow;
+ }
+}
+
+void QtMenu::SetItemBits(unsigned nPos, MenuItemBits)
+{
+ if (nPos < maItems.size())
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ UpdateActionGroupItem(pSalMenuItem);
+ }
+}
+
+void QtMenu::CheckItem(unsigned nPos, bool bChecked)
+{
+ if (nPos < maItems.size())
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ pAction->setCheckable(true);
+ pAction->setChecked(bChecked);
+ }
+ }
+}
+
+void QtMenu::EnableItem(unsigned nPos, bool bEnable)
+{
+ if (nPos < maItems.size())
+ {
+ QtMenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setEnabled(bEnable);
+ pSalMenuItem->mbEnabled = bEnable;
+ }
+}
+
+void QtMenu::SetItemText(unsigned, SalMenuItem* pItem, const OUString& rText)
+{
+ QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ OUString aText(rText);
+ NativeItemText(aText);
+ pAction->setText(toQString(aText));
+ }
+}
+
+void QtMenu::SetItemImage(unsigned, SalMenuItem* pItem, const Image& rImage)
+{
+ QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
+
+ // Save new image to use it in DoFullMenuUpdate
+ pSalMenuItem->maImage = rImage;
+
+ QAction* pAction = pSalMenuItem->getAction();
+ if (!pAction)
+ return;
+
+ pAction->setIcon(QPixmap::fromImage(toQImage(rImage)));
+}
+
+void QtMenu::SetAccelerator(unsigned, SalMenuItem* pItem, const vcl::KeyCode&,
+ const OUString& rText)
+{
+ QtMenuItem* pSalMenuItem = static_cast<QtMenuItem*>(pItem);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setShortcut(QKeySequence(toQString(rText), QKeySequence::PortableText));
+}
+
+void QtMenu::GetSystemMenuData(SystemMenuData*) {}
+
+QtMenu* QtMenu::GetTopLevel()
+{
+ QtMenu* pMenu = this;
+ while (pMenu->mpParentSalMenu)
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu;
+}
+
+bool QtMenu::validateQMenuBar() const
+{
+ if (!mpQMenuBar)
+ return false;
+ assert(mpFrame);
+ QtMainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
+ assert(pMainWindow);
+ const bool bValid = mpQMenuBar == pMainWindow->menuBar();
+ if (!bValid)
+ {
+ QtMenu* thisPtr = const_cast<QtMenu*>(this);
+ thisPtr->mpQMenuBar = nullptr;
+ }
+ return bValid;
+}
+
+void QtMenu::ShowMenuBar(bool bVisible)
+{
+ if (!validateQMenuBar())
+ return;
+
+ mpQMenuBar->setVisible(bVisible);
+ if (bVisible)
+ lcl_force_menubar_layout_update(*mpQMenuBar);
+}
+
+void QtMenu::slotMenuHovered(QtMenuItem* pItem)
+{
+ const OUString sHelpId = pItem->mpParentMenu->GetMenu()->GetHelpId(pItem->mnId);
+ m_sCurrentHelpId = sHelpId;
+}
+
+void QtMenu::slotShowHelp()
+{
+ SolarMutexGuard aGuard;
+ Help* pHelp = Application::GetHelp();
+ if (pHelp && !m_sCurrentHelpId.isEmpty())
+ {
+ pHelp->Start(m_sCurrentHelpId);
+ }
+}
+
+void QtMenu::slotMenuTriggered(QtMenuItem* pQItem)
+{
+ if (!pQItem)
+ return;
+
+ QtMenu* pSalMenu = pQItem->mpParentMenu;
+ QtMenu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+ auto mnId = pQItem->mnId;
+
+ // HACK to allow HandleMenuCommandEvent to "not-set" the checked button
+ // LO expects a signal before an item state change, so reset the check item
+ if (pQItem->mpAction->isCheckable()
+ && (!pQItem->mpActionGroup || pQItem->mpActionGroup->actions().size() <= 1))
+ pQItem->mpAction->setChecked(!pQItem->mpAction->isChecked());
+ pTopLevel->GetMenu()->HandleMenuCommandEvent(pMenu, mnId);
+}
+
+void QtMenu::slotMenuAboutToShow(QtMenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ QtMenu* pSalMenu = pQItem->mpSubMenu;
+ QtMenu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+
+ // following function may update the menu
+ pTopLevel->GetMenu()->HandleMenuActivateEvent(pMenu);
+ }
+}
+
+void QtMenu::slotMenuAboutToHide(QtMenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ QtMenu* pSalMenu = pQItem->mpSubMenu;
+ QtMenu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+
+ pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pMenu);
+ }
+}
+
+void QtMenu::NativeItemText(OUString& rItemText)
+{
+ // preserve literal '&'s in menu texts
+ rItemText = rItemText.replaceAll("&", "&&");
+
+ rItemText = rItemText.replace('~', '&');
+}
+
+void QtMenu::slotCloseDocument()
+{
+ MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ if (pVclMenuBar)
+ Application::PostUserEvent(pVclMenuBar->GetCloseButtonClickHdl());
+}
+
+void QtMenu::slotMenuBarButtonClicked(QAbstractButton* pButton)
+{
+ MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ if (pVclMenuBar)
+ {
+ SolarMutexGuard aGuard;
+ pVclMenuBar->HandleMenuButtonEvent(m_pButtonGroup->id(pButton));
+ }
+}
+
+QPushButton* QtMenu::ImplAddMenuBarButton(const QIcon& rIcon, const QString& rToolTip, int nId)
+{
+ if (!validateQMenuBar())
+ return nullptr;
+
+ QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
+ QHBoxLayout* pLayout;
+ if (!pWidget)
+ {
+ assert(!m_pButtonGroup);
+ pWidget = new QWidget(mpQMenuBar);
+ assert(!pWidget->layout());
+ pLayout = new QHBoxLayout();
+ pLayout->setContentsMargins(QMargins());
+ pLayout->setSpacing(0);
+ pWidget->setLayout(pLayout);
+ m_pButtonGroup = new QButtonGroup(pLayout);
+ m_pButtonGroup->setObjectName(gButtonGroupKey);
+ m_pButtonGroup->setExclusive(false);
+ connect(m_pButtonGroup, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this,
+ &QtMenu::slotMenuBarButtonClicked);
+ pWidget->show();
+ mpQMenuBar->setCornerWidget(pWidget, Qt::TopRightCorner);
+ }
+ else
+ pLayout = static_cast<QHBoxLayout*>(pWidget->layout());
+ assert(m_pButtonGroup);
+ assert(pLayout);
+
+ QPushButton* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId));
+ if (pButton)
+ ImplRemoveMenuBarButton(nId);
+
+ pButton = new QPushButton();
+ // we don't want the button to increase the QMenuBar height, so a fixed size square it is
+ const int nFixedLength
+ = mpQMenuBar->height() - 2 * mpQMenuBar->style()->pixelMetric(QStyle::PM_MenuBarVMargin);
+ pButton->setFixedSize(nFixedLength, nFixedLength);
+ pButton->setIcon(rIcon);
+ pButton->setFlat(true);
+ pButton->setFocusPolicy(Qt::NoFocus);
+ pButton->setToolTip(rToolTip);
+
+ m_pButtonGroup->addButton(pButton, nId);
+ int nPos = pLayout->count();
+ if (m_pButtonGroup->button(CLOSE_BUTTON_ID))
+ nPos--;
+ pLayout->insertWidget(nPos, pButton, 0, Qt::AlignCenter);
+ // show must happen after adding the button to the layout, otherwise the button is
+ // shown, but not correct in the layout, if at all! Some times the layout ignores it.
+ pButton->show();
+
+ lcl_force_menubar_layout_update(*mpQMenuBar);
+
+ return pButton;
+}
+
+bool QtMenu::AddMenuBarButton(const SalMenuButtonItem& rItem)
+{
+ if (!validateQMenuBar())
+ return false;
+ return !!ImplAddMenuBarButton(QIcon(QPixmap::fromImage(toQImage(rItem.maImage))),
+ toQString(rItem.maToolTipText), rItem.mnId);
+}
+
+void QtMenu::ImplRemoveMenuBarButton(int nId)
+{
+ if (!validateQMenuBar())
+ return;
+
+ assert(m_pButtonGroup);
+ auto* pButton = m_pButtonGroup->button(nId);
+ assert(pButton);
+ QWidget* pWidget = mpQMenuBar->cornerWidget(Qt::TopRightCorner);
+ assert(pWidget);
+ QLayout* pLayout = pWidget->layout();
+ m_pButtonGroup->removeButton(pButton);
+ pLayout->removeWidget(pButton);
+ delete pButton;
+
+ lcl_force_menubar_layout_update(*mpQMenuBar);
+}
+
+void QtMenu::connectHelpShortcut(QMenu* pMenu)
+{
+ assert(pMenu);
+ QKeySequence sequence(QKeySequence::HelpContents);
+ QShortcut* pQShortcut = new QShortcut(sequence, pMenu);
+ connect(pQShortcut, &QShortcut::activated, this, QtMenu::slotShowHelp);
+ connect(pQShortcut, &QShortcut::activatedAmbiguously, this, QtMenu::slotShowHelp);
+}
+
+void QtMenu::connectHelpSignalSlots(QMenu* pMenu, QtMenuItem* pSalMenuItem)
+{
+ // connect hovered signal of the menu's own action
+ QAction* pAction = pMenu->menuAction();
+ assert(pAction);
+ connect(pAction, &QAction::hovered, this, [pSalMenuItem] { slotMenuHovered(pSalMenuItem); });
+
+ // connect slot to handle Help key (F1)
+ connectHelpShortcut(pMenu);
+}
+
+void QtMenu::RemoveMenuBarButton(sal_uInt16 nId) { ImplRemoveMenuBarButton(nId); }
+
+tools::Rectangle QtMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame* pFrame)
+{
+#ifdef NDEBUG
+ Q_UNUSED(pFrame);
+#endif
+ if (!validateQMenuBar())
+ return tools::Rectangle();
+
+ assert(mpFrame == static_cast<QtFrame*>(pFrame));
+ assert(m_pButtonGroup);
+ auto* pButton = static_cast<QPushButton*>(m_pButtonGroup->button(nId));
+ assert(pButton);
+
+ // unfortunatly, calling lcl_force_menubar_layout_update results in a temporary wrong menubar size,
+ // but it's the correct minimal size AFAIK and the layout seems correct, so just adjust the width.
+ QPoint aPos = pButton->mapTo(mpFrame->asChild(), QPoint());
+ aPos.rx() += (mpFrame->asChild()->width() - mpQMenuBar->width());
+ return tools::Rectangle(toPoint(aPos), toSize(pButton->size()));
+}
+
+void QtMenu::ShowCloseButton(bool bShow)
+{
+ if (!validateQMenuBar())
+ return;
+
+ if (!bShow && !m_pButtonGroup)
+ return;
+
+ QPushButton* pButton = nullptr;
+ if (m_pButtonGroup)
+ pButton = static_cast<QPushButton*>(m_pButtonGroup->button(CLOSE_BUTTON_ID));
+ if (!bShow && !pButton)
+ return;
+
+ if (!pButton)
+ {
+ QIcon aIcon;
+ if (QIcon::hasThemeIcon("window-close-symbolic"))
+ aIcon = QIcon::fromTheme("window-close-symbolic");
+ else
+ aIcon = QIcon(
+ QPixmap::fromImage(toQImage(Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC))));
+ pButton = ImplAddMenuBarButton(aIcon, toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT)),
+ CLOSE_BUTTON_ID);
+ connect(pButton, &QPushButton::clicked, this, &QtMenu::slotCloseDocument);
+ }
+
+ if (bShow)
+ pButton->show();
+ else
+ pButton->hide();
+
+ lcl_force_menubar_layout_update(*mpQMenuBar);
+}
+
+bool QtMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
+ FloatWinPopupFlags nFlags)
+{
+ assert(mpQMenu);
+ DoFullMenuUpdate(mpVCLMenu);
+ mpQMenu->setTearOffEnabled(bool(nFlags & FloatWinPopupFlags::AllowTearOff));
+
+ const VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
+ AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
+
+ // tdf#154447 Menu bar height has to be added
+ QtFrame* pFrame = static_cast<QtFrame*>(pWin->ImplGetFrame());
+ assert(pFrame);
+ aFloatRect.SetPosY(aFloatRect.getY() + pFrame->menuBarOffset());
+
+ const QRect aRect = toQRect(aFloatRect, 1 / pFrame->devicePixelRatioF());
+ mpQMenu->exec(aRect.bottomLeft());
+
+ return true;
+}
+
+int QtMenu::GetMenuBarHeight() const
+{
+ if (!validateQMenuBar() || mpQMenuBar->isHidden())
+ return 0;
+
+ return mpQMenuBar->height();
+}
+
+QtMenuItem::QtMenuItem(const SalItemParams* pItemData)
+ : mpParentMenu(nullptr)
+ , mpSubMenu(nullptr)
+ , mnId(pItemData->nId)
+ , mnType(pItemData->eType)
+ , mbVisible(true)
+ , mbEnabled(true)
+ , maImage(pItemData->aImage)
+{
+}
+
+QAction* QtMenuItem::getAction() const
+{
+ if (mpMenu)
+ return mpMenu->menuAction();
+ if (mpAction)
+ return mpAction.get();
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtObject.cxx b/vcl/qt5/QtObject.cxx
new file mode 100644
index 0000000000..fbdc8e9b62
--- /dev/null
+++ b/vcl/qt5/QtObject.cxx
@@ -0,0 +1,155 @@
+/* -*- 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 <QtObject.hxx>
+#include <QtObject.moc>
+
+#include <QtFrame.hxx>
+#include <QtWidget.hxx>
+
+#include <QtGui/QGuiApplication>
+#include <QtGui/QKeyEvent>
+#include <QtGui/QMouseEvent>
+
+QtObject::QtObject(QtFrame* pParent, bool bShow)
+ : m_pParent(pParent)
+ , m_pQWidget(nullptr)
+ , m_bForwardKey(false)
+{
+ if (!m_pParent || !pParent->GetQWidget())
+ return;
+
+ m_pQWidget = new QtObjectWidget(*this);
+ if (bShow)
+ m_pQWidget->show();
+
+ QtFrame::FillSystemEnvData(m_aSystemData, reinterpret_cast<sal_IntPtr>(this), m_pQWidget);
+}
+
+QtObject::~QtObject()
+{
+ if (m_pQWidget)
+ {
+ m_pQWidget->setParent(nullptr);
+ delete m_pQWidget;
+ }
+}
+
+QWindow* QtObject::windowHandle() const
+{
+ return m_pQWidget ? m_pQWidget->windowHandle() : nullptr;
+}
+
+void QtObject::ResetClipRegion()
+{
+ if (m_pQWidget)
+ m_pRegion = QRegion(m_pQWidget->geometry());
+ else
+ m_pRegion = QRegion();
+}
+
+void QtObject::BeginSetClipRegion(sal_uInt32) { m_pRegion = QRegion(); }
+
+void QtObject::UnionClipRegion(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ m_pRegion += QRect(nX, nY, nWidth, nHeight);
+}
+
+void QtObject::EndSetClipRegion()
+{
+ if (m_pQWidget)
+ m_pRegion = m_pRegion.intersected(m_pQWidget->geometry());
+}
+
+void QtObject::SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight)
+{
+ if (m_pQWidget)
+ {
+ m_pQWidget->move(nX, nY);
+ m_pQWidget->setFixedSize(nWidth, nHeight);
+ }
+}
+
+void QtObject::Show(bool bVisible)
+{
+ if (m_pQWidget)
+ m_pQWidget->setVisible(bVisible);
+}
+
+void QtObject::SetForwardKey(bool bEnable) { m_bForwardKey = bEnable; }
+
+void QtObject::Reparent(SalFrame* pFrame)
+{
+ QtFrame* pNewParent = static_cast<QtFrame*>(pFrame);
+ if (m_pParent == pNewParent)
+ return;
+ m_pParent = pNewParent;
+ m_pQWidget->setParent(m_pParent->GetQWidget());
+}
+
+QtObjectWidget::QtObjectWidget(QtObject& rParent)
+ : QWidget(rParent.frame()->GetQWidget())
+ , m_rParent(rParent)
+{
+ assert(m_rParent.frame() && m_rParent.frame()->GetQWidget());
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAttribute(Qt::WA_OpaquePaintEvent);
+}
+
+void QtObjectWidget::focusInEvent(QFocusEvent*)
+{
+ SolarMutexGuard aGuard;
+ m_rParent.CallCallback(SalObjEvent::GetFocus);
+}
+
+void QtObjectWidget::focusOutEvent(QFocusEvent*)
+{
+ SolarMutexGuard aGuard;
+ m_rParent.CallCallback(SalObjEvent::LoseFocus);
+}
+
+void QtObjectWidget::mousePressEvent(QMouseEvent* pEvent)
+{
+ SolarMutexGuard aGuard;
+ m_rParent.CallCallback(SalObjEvent::ToTop);
+
+ if (m_rParent.forwardKey())
+ pEvent->ignore();
+}
+
+void QtObjectWidget::mouseReleaseEvent(QMouseEvent* pEvent)
+{
+ if (m_rParent.forwardKey())
+ pEvent->ignore();
+}
+
+void QtObjectWidget::keyReleaseEvent(QKeyEvent* pEvent)
+{
+ if (m_rParent.forwardKey())
+ pEvent->ignore();
+}
+
+void QtObjectWidget::keyPressEvent(QKeyEvent* pEvent)
+{
+ if (m_rParent.forwardKey())
+ pEvent->ignore();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtOpenGLContext.cxx b/vcl/qt5/QtOpenGLContext.cxx
new file mode 100644
index 0000000000..9dd75b69a1
--- /dev/null
+++ b/vcl/qt5/QtOpenGLContext.cxx
@@ -0,0 +1,154 @@
+/* -*- 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 <QtOpenGLContext.hxx>
+
+#include <epoxy/gl.h>
+
+#include <vcl/sysdata.hxx>
+#include <opengl/zone.hxx>
+#include <sal/log.hxx>
+
+#include <window.h>
+
+#include <QtObject.hxx>
+
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QWindow>
+
+bool QtOpenGLContext::g_bAnyCurrent = false;
+
+void QtOpenGLContext::swapBuffers()
+{
+ OpenGLZone aZone;
+
+ if (m_pContext && m_pWindow && m_pWindow->isExposed())
+ {
+ m_pContext->swapBuffers(m_pWindow);
+ }
+
+ BuffersSwapped();
+}
+
+void QtOpenGLContext::resetCurrent()
+{
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ if (m_pContext)
+ {
+ m_pContext->doneCurrent();
+ g_bAnyCurrent = false;
+ }
+}
+
+bool QtOpenGLContext::isCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && (QOpenGLContext::currentContext() == m_pContext);
+}
+
+bool QtOpenGLContext::isAnyCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && (QOpenGLContext::currentContext() != nullptr);
+}
+
+bool QtOpenGLContext::ImplInit()
+{
+ if (!m_pWindow)
+ {
+ SAL_WARN("vcl.opengl.qt", "failed to create window");
+ return false;
+ }
+
+ m_pWindow->setSurfaceType(QSurface::OpenGLSurface);
+ m_pWindow->create();
+
+ m_pContext = new QOpenGLContext(m_pWindow);
+ if (!m_pContext->create())
+ {
+ SAL_WARN("vcl.opengl.qt", "failed to create context");
+ return false;
+ }
+
+ m_pContext->makeCurrent(m_pWindow);
+ g_bAnyCurrent = true;
+
+ bool bRet = InitGL();
+ InitGLDebugging();
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ registerAsCurrent();
+
+ return bRet;
+}
+
+void QtOpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+ if (m_pContext && m_pWindow)
+ {
+ m_pContext->makeCurrent(m_pWindow);
+ g_bAnyCurrent = true;
+ }
+
+ registerAsCurrent();
+}
+
+void QtOpenGLContext::destroyCurrentContext()
+{
+ OpenGLZone aZone;
+
+ if (m_pContext)
+ {
+ m_pContext->doneCurrent();
+ g_bAnyCurrent = false;
+ }
+
+ if (glGetError() != GL_NO_ERROR)
+ {
+ SAL_WARN("vcl.opengl.qt", "glError: " << glGetError());
+ }
+}
+
+void QtOpenGLContext::initWindow()
+{
+ if (!m_pChildWindow)
+ {
+ SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+
+ if (m_pChildWindow)
+ {
+ InitChildWindow(m_pChildWindow.get());
+ }
+
+ m_pWindow
+ = static_cast<QtObject*>(m_pChildWindow->ImplGetWindowImpl()->mpSysObj)->windowHandle();
+}
diff --git a/vcl/qt5/QtPainter.cxx b/vcl/qt5/QtPainter.cxx
new file mode 100644
index 0000000000..115b4a82c6
--- /dev/null
+++ b/vcl/qt5/QtPainter.cxx
@@ -0,0 +1,58 @@
+/* -*- 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 <QtPainter.hxx>
+
+#include <QtGui/QColor>
+
+QtPainter::QtPainter(QtGraphicsBackend& rGraphics, bool bPrepareBrush, sal_uInt8 nTransparency)
+ : m_rGraphics(rGraphics)
+{
+ if (rGraphics.m_pQImage)
+ {
+ if (!begin(rGraphics.m_pQImage))
+ std::abort();
+ }
+ else
+ {
+ assert(rGraphics.m_pFrame);
+ if (!begin(rGraphics.m_pFrame->GetQWidget()))
+ std::abort();
+ }
+ if (!rGraphics.m_aClipPath.isEmpty())
+ setClipPath(rGraphics.m_aClipPath);
+ else
+ setClipRegion(rGraphics.m_aClipRegion);
+ if (rGraphics.m_oLineColor)
+ {
+ QColor aColor = toQColor(*rGraphics.m_oLineColor);
+ aColor.setAlpha(nTransparency);
+ setPen(aColor);
+ }
+ else
+ setPen(Qt::NoPen);
+ if (bPrepareBrush && rGraphics.m_oFillColor)
+ {
+ QColor aColor = toQColor(*rGraphics.m_oFillColor);
+ aColor.setAlpha(nTransparency);
+ setBrush(aColor);
+ }
+ setCompositionMode(rGraphics.m_eCompositionMode);
+ setRenderHint(QPainter::Antialiasing, m_rGraphics.getAntiAlias());
+}
diff --git a/vcl/qt5/QtPrinter.cxx b/vcl/qt5/QtPrinter.cxx
new file mode 100644
index 0000000000..346dd3f26e
--- /dev/null
+++ b/vcl/qt5/QtPrinter.cxx
@@ -0,0 +1,27 @@
+/* -*- 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 <QtPrinter.hxx>
+
+QtPrinter::QtPrinter(SalInfoPrinter* pInfoPrinter)
+ : PspSalPrinter(pInfoPrinter)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtSvpGraphics.cxx b/vcl/qt5/QtSvpGraphics.cxx
new file mode 100644
index 0000000000..903fee1f56
--- /dev/null
+++ b/vcl/qt5/QtSvpGraphics.cxx
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <salbmp.hxx>
+
+#include <config_cairo_canvas.h>
+
+#include <QtData.hxx>
+#include <QtFrame.hxx>
+#include <QtGraphics_Controls.hxx>
+#include <QtSvpGraphics.hxx>
+#include <QtSvpSurface.hxx>
+#include <QtTools.hxx>
+
+#include <QtGui/QScreen>
+#include <QtGui/QWindow>
+#include <QtWidgets/QWidget>
+
+QtSvpGraphics::QtSvpGraphics(QtFrame* pFrame)
+ : m_pFrame(pFrame)
+{
+ if (!QtData::noNativeControls())
+ m_pWidgetDraw.reset(new QtGraphics_Controls(*this));
+ if (m_pFrame)
+ setDevicePixelRatioF(m_pFrame->devicePixelRatioF());
+}
+
+QtSvpGraphics::~QtSvpGraphics() {}
+
+void QtSvpGraphics::updateQWidget() const
+{
+ if (!m_pFrame)
+ return;
+ QWidget* pQWidget = m_pFrame->GetQWidget();
+ if (pQWidget)
+ pQWidget->update(pQWidget->rect());
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool QtSvpGraphics::SupportsCairo() const { return true; }
+
+cairo::SurfaceSharedPtr
+QtSvpGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ return std::make_shared<cairo::QtSvpSurface>(rSurface);
+}
+
+cairo::SurfaceSharedPtr QtSvpGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int x,
+ int y, int width, int height) const
+{
+ return std::make_shared<cairo::QtSvpSurface>(this, x, y, width, height);
+}
+
+#endif
+
+static void QImage2BitmapBuffer(QImage& rImg, BitmapBuffer& rBuf)
+{
+ assert(rImg.width());
+ assert(rImg.height());
+
+ rBuf.mnWidth = rImg.width();
+ rBuf.mnHeight = rImg.height();
+ rBuf.mnBitCount = getFormatBits(rImg.format());
+ rBuf.mpBits = rImg.bits();
+ rBuf.mnScanlineSize = rImg.bytesPerLine();
+}
+
+void QtSvpGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(dynamic_cast<QtGraphics_Controls*>(m_pWidgetDraw.get()));
+ assert(!rDamagedRegion.IsEmpty());
+
+ QImage* pImage = static_cast<QtGraphics_Controls*>(m_pWidgetDraw.get())->getImage();
+ assert(pImage);
+ if (pImage->width() == 0 || pImage->height() == 0)
+ return;
+
+ BitmapBuffer aBuffer;
+ QImage2BitmapBuffer(*pImage, aBuffer);
+ SalTwoRect aTR(0, 0, pImage->width(), pImage->height(), rDamagedRegion.Left(),
+ rDamagedRegion.Top(), rDamagedRegion.GetWidth(), rDamagedRegion.GetHeight());
+
+ getSvpBackend()->drawBitmapBuffer(aTR, &aBuffer, CAIRO_OPERATOR_OVER);
+}
+
+void QtSvpGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+ if (!m_pFrame)
+ return;
+
+ QScreen* pScreen = m_pFrame->GetQWidget()->screen();
+ rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
+ rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtSvpSurface.cxx b/vcl/qt5/QtSvpSurface.cxx
new file mode 100644
index 0000000000..760419bd7f
--- /dev/null
+++ b/vcl/qt5/QtSvpSurface.cxx
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <utility>
+
+#include <QtSvpSurface.hxx>
+
+#include <QtSvpGraphics.hxx>
+
+#include <vcl/sysdata.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <basegfx/vector/b2isize.hxx>
+
+namespace
+{
+Size get_surface_size(cairo_surface_t* surface)
+{
+ cairo_t* cr = cairo_create(surface);
+ double x1, x2, y1, y2;
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+ cairo_destroy(cr);
+ return Size(x2 - x1, y2 - y1);
+}
+}
+
+namespace cairo
+{
+QtSvpSurface::QtSvpSurface(CairoSurfaceSharedPtr pSurface)
+ : m_pGraphics(nullptr)
+ , m_pCairoContext(nullptr)
+ , m_pSurface(std::move(pSurface))
+{
+}
+
+QtSvpSurface::QtSvpSurface(const QtSvpGraphics* pGraphics, int x, int y, int width, int height)
+ : m_pGraphics(pGraphics)
+ , m_pCairoContext(pGraphics->getCairoContext())
+{
+ cairo_surface_t* surface = cairo_get_target(m_pCairoContext);
+ m_pSurface.reset(cairo_surface_create_for_rectangle(surface, x, y, width, height),
+ &cairo_surface_destroy);
+}
+
+QtSvpSurface::~QtSvpSurface()
+{
+ if (m_pCairoContext)
+ cairo_destroy(m_pCairoContext);
+}
+
+CairoSharedPtr QtSvpSurface::getCairo() const
+{
+ return CairoSharedPtr(cairo_create(m_pSurface.get()), &cairo_destroy);
+}
+
+SurfaceSharedPtr QtSvpSurface::getSimilar(int cairo_content_type, int width, int height) const
+{
+ return std::make_shared<QtSvpSurface>(CairoSurfaceSharedPtr(
+ cairo_surface_create_similar(
+ m_pSurface.get(), static_cast<cairo_content_t>(cairo_content_type), width, height),
+ &cairo_surface_destroy));
+}
+
+void QtSvpSurface::flush() const
+{
+ cairo_surface_flush(m_pSurface.get());
+ if (m_pGraphics)
+ m_pGraphics->updateQWidget();
+}
+
+VclPtr<VirtualDevice> QtSvpSurface::createVirtualDevice() const
+{
+ SystemGraphicsData aSystemGraphicsData;
+
+ aSystemGraphicsData.nSize = sizeof(SystemGraphicsData);
+ aSystemGraphicsData.pSurface = m_pSurface.get();
+
+ return VclPtr<VirtualDevice>::Create(aSystemGraphicsData, get_surface_size(m_pSurface.get()),
+ DeviceFormat::WITHOUT_ALPHA);
+}
+
+} // namespace cairo
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtSvpVirtualDevice.hxx b/vcl/qt5/QtSvpVirtualDevice.hxx
new file mode 100644
index 0000000000..26247a6e1d
--- /dev/null
+++ b/vcl/qt5/QtSvpVirtualDevice.hxx
@@ -0,0 +1,36 @@
+/* -*- 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 <headless/svpvd.hxx>
+#include <QtSvpGraphics.hxx>
+
+class VCL_DLLPUBLIC QtSvpVirtualDevice : public SvpSalVirtualDevice
+{
+public:
+ QtSvpVirtualDevice(cairo_surface_t* pRefSurface, cairo_surface_t* pPreExistingTarget)
+ : SvpSalVirtualDevice(pRefSurface, pPreExistingTarget)
+ {
+ }
+
+ SalGraphics* AcquireGraphics() override { return AddGraphics(new QtSvpGraphics(nullptr)); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtSystem.cxx b/vcl/qt5/QtSystem.cxx
new file mode 100644
index 0000000000..d43e47832f
--- /dev/null
+++ b/vcl/qt5/QtSystem.cxx
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <QtGui/QGuiApplication>
+#include <QtGui/QScreen>
+
+#include <tools/gen.hxx>
+#include <QtSystem.hxx>
+#include <QtTools.hxx>
+
+unsigned int QtSystem::GetDisplayScreenCount() { return QGuiApplication::screens().size(); }
+
+AbsoluteScreenPixelRectangle QtSystem::GetDisplayScreenPosSizePixel(unsigned int nScreen)
+{
+ QRect qRect = QGuiApplication::screens().at(nScreen)->geometry();
+ return AbsoluteScreenPixelRectangle(toRectangle(scaledQRect(qRect, qApp->devicePixelRatio())));
+}
+
+int QtSystem::ShowNativeDialog(const OUString&, const OUString&, const std::vector<OUString>&)
+{
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtTimer.cxx b/vcl/qt5/QtTimer.cxx
new file mode 100644
index 0000000000..1a34213977
--- /dev/null
+++ b/vcl/qt5/QtTimer.cxx
@@ -0,0 +1,62 @@
+/* -*- 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 <QtTimer.hxx>
+#include <QtTimer.moc>
+
+#include <QtInstance.hxx>
+
+#include <QtWidgets/QApplication>
+#include <QtCore/QThread>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <svdata.hxx>
+
+QtTimer::QtTimer()
+{
+ m_aTimer.setSingleShot(true);
+ m_aTimer.setTimerType(Qt::PreciseTimer);
+ connect(&m_aTimer, SIGNAL(timeout()), this, SLOT(timeoutActivated()));
+ connect(this, SIGNAL(startTimerSignal(int)), this, SLOT(startTimer(int)));
+ connect(this, SIGNAL(stopTimerSignal()), this, SLOT(stopTimer()));
+}
+
+void QtTimer::timeoutActivated()
+{
+ SolarMutexGuard aGuard;
+ if (Application::IsOnSystemEventLoop())
+ {
+ const ImplSVData* pSVData = ImplGetSVData();
+ assert(pSVData && pSVData->mpDefInst);
+ static_cast<QtInstance*>(pSVData->mpDefInst)->DispatchUserEvents(true);
+ }
+ CallCallback();
+}
+
+void QtTimer::startTimer(int nMS) { m_aTimer.start(nMS); }
+
+void QtTimer::Start(sal_uInt64 nMS) { Q_EMIT startTimerSignal(nMS); }
+
+void QtTimer::stopTimer() { m_aTimer.stop(); }
+
+void QtTimer::Stop() { Q_EMIT stopTimerSignal(); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtTools.cxx b/vcl/qt5/QtTools.cxx
new file mode 100644
index 0000000000..030b3af2b5
--- /dev/null
+++ b/vcl/qt5/QtTools.cxx
@@ -0,0 +1,123 @@
+/* -*- 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 <QtTools.hxx>
+
+#include <cairo.h>
+
+#include <tools/stream.hxx>
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+
+#include <QtGui/QImage>
+
+void CairoDeleter::operator()(cairo_surface_t* pSurface) const { cairo_surface_destroy(pSurface); }
+
+sal_uInt16 GetKeyModCode(Qt::KeyboardModifiers eKeyModifiers)
+{
+ sal_uInt16 nCode = 0;
+ if (eKeyModifiers & Qt::ShiftModifier)
+ nCode |= KEY_SHIFT;
+ if (eKeyModifiers & Qt::ControlModifier)
+ nCode |= KEY_MOD1;
+ if (eKeyModifiers & Qt::AltModifier)
+ nCode |= KEY_MOD2;
+ if (eKeyModifiers & Qt::MetaModifier)
+ nCode |= KEY_MOD3;
+ return nCode;
+}
+
+sal_uInt16 GetMouseModCode(Qt::MouseButtons eButtons)
+{
+ sal_uInt16 nCode = 0;
+ if (eButtons & Qt::LeftButton)
+ nCode |= MOUSE_LEFT;
+ if (eButtons & Qt::MiddleButton)
+ nCode |= MOUSE_MIDDLE;
+ if (eButtons & Qt::RightButton)
+ nCode |= MOUSE_RIGHT;
+ return nCode;
+}
+
+Qt::DropActions toQtDropActions(sal_Int8 dragOperation)
+{
+ Qt::DropActions eRet = Qt::IgnoreAction;
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eRet |= Qt::CopyAction;
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eRet |= Qt::MoveAction;
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eRet |= Qt::LinkAction;
+ return eRet;
+}
+
+sal_Int8 toVclDropActions(Qt::DropActions dragOperation)
+{
+ sal_Int8 nRet(0);
+ if (dragOperation & Qt::CopyAction)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ if (dragOperation & Qt::MoveAction)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ if (dragOperation & Qt::LinkAction)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ return nRet;
+}
+
+sal_Int8 toVclDropAction(Qt::DropAction dragOperation)
+{
+ sal_Int8 nRet(0);
+ if (dragOperation == Qt::CopyAction)
+ nRet = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ else if (dragOperation == Qt::MoveAction)
+ nRet = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ else if (dragOperation == Qt::LinkAction)
+ nRet = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ return nRet;
+}
+
+Qt::DropAction getPreferredDropAction(sal_Int8 dragOperation)
+{
+ Qt::DropAction eAct = Qt::IgnoreAction;
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eAct = Qt::MoveAction;
+ else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eAct = Qt::CopyAction;
+ else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eAct = Qt::LinkAction;
+ return eAct;
+}
+
+QImage toQImage(const Image& rImage)
+{
+ QImage aImage;
+
+ if (!!rImage)
+ {
+ SvMemoryStream aMemStm;
+ auto rBitmapEx = rImage.GetBitmapEx();
+ vcl::PngImageWriter aWriter(aMemStm);
+ aWriter.write(rBitmapEx);
+ aImage.loadFromData(static_cast<const uchar*>(aMemStm.GetData()), aMemStm.TellEnd());
+ }
+
+ return aImage;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtTransferable.cxx b/vcl/qt5/QtTransferable.cxx
new file mode 100644
index 0000000000..d9e0beaa71
--- /dev/null
+++ b/vcl/qt5/QtTransferable.cxx
@@ -0,0 +1,361 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <QtTransferable.hxx>
+
+#include <comphelper/sequence.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <QtWidgets/QApplication>
+
+#include <QtInstance.hxx>
+#include <QtTools.hxx>
+
+#include <cassert>
+
+static bool lcl_textMimeInfo(std::u16string_view rMimeString, bool& bHaveNoCharset,
+ bool& bHaveUTF16, bool& bHaveUTF8)
+{
+ sal_Int32 nIndex = 0;
+ if (o3tl::getToken(rMimeString, 0, ';', nIndex) == u"text/plain")
+ {
+ std::u16string_view aToken(o3tl::getToken(rMimeString, 0, ';', nIndex));
+ if (aToken == u"charset=utf-16")
+ bHaveUTF16 = true;
+ else if (aToken == u"charset=utf-8")
+ bHaveUTF8 = true;
+ else if (aToken.empty())
+ bHaveNoCharset = true;
+ else // we just handle UTF-16 and UTF-8, everything else is "bytes"
+ return false;
+ return true;
+ }
+ return false;
+}
+
+QtTransferable::QtTransferable(const QMimeData* pMimeData)
+ : m_pMimeData(pMimeData)
+ , m_bProvideUTF16FromOtherEncoding(false)
+{
+ assert(pMimeData);
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL QtTransferable::getTransferDataFlavors()
+{
+ // it's just filled once, ever, so just try to get it without locking first
+ if (m_aMimeTypeSeq.hasElements())
+ return m_aMimeTypeSeq;
+
+ // better safe then sorry; preventing broken usage
+ // DnD should not be shared and Clipboard access runs in the GUI thread
+ osl::MutexGuard aGuard(m_aMutex);
+ if (m_aMimeTypeSeq.hasElements())
+ return m_aMimeTypeSeq;
+
+ QStringList aFormatList(m_pMimeData->formats());
+ // we might add the UTF-16 mime text variant later
+ const int nMimeTypeSeqSize = aFormatList.size() + 1;
+ bool bHaveNoCharset = false, bHaveUTF16 = false, bHaveUTF8 = false;
+ css::uno::Sequence<css::datatransfer::DataFlavor> aMimeTypeSeq(nMimeTypeSeqSize);
+ auto pMimeTypeSeq = aMimeTypeSeq.getArray();
+
+ css::datatransfer::DataFlavor aFlavor;
+ int nMimeTypeCount = 0;
+
+ for (const QString& rMimeType : aFormatList)
+ {
+ // filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
+ if (rMimeType.indexOf('/') == -1)
+ continue;
+
+ // gtk3 thinks it is not well defined - skip too
+ if (rMimeType == QStringLiteral("text/plain;charset=unicode"))
+ continue;
+
+ // LO doesn't like 'text/plain', so we have to provide UTF-16
+ bool bIsNoCharset = false, bIsUTF16 = false, bIsUTF8 = false;
+ if (lcl_textMimeInfo(toOUString(rMimeType), bIsNoCharset, bIsUTF16, bIsUTF8))
+ {
+ bHaveNoCharset |= bIsNoCharset;
+ bHaveUTF16 |= bIsUTF16;
+ bHaveUTF8 |= bIsUTF8;
+ if (bIsUTF16)
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ else
+ aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
+ }
+ else
+ aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
+
+ aFlavor.MimeType = toOUString(rMimeType);
+ assert(nMimeTypeCount < nMimeTypeSeqSize);
+ pMimeTypeSeq[nMimeTypeCount] = aFlavor;
+ nMimeTypeCount++;
+ }
+
+ m_bProvideUTF16FromOtherEncoding = (bHaveNoCharset || bHaveUTF8) && !bHaveUTF16;
+ if (m_bProvideUTF16FromOtherEncoding)
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ assert(nMimeTypeCount < nMimeTypeSeqSize);
+ pMimeTypeSeq[nMimeTypeCount] = aFlavor;
+ nMimeTypeCount++;
+ }
+
+ aMimeTypeSeq.realloc(nMimeTypeCount);
+
+ m_aMimeTypeSeq = aMimeTypeSeq;
+ return m_aMimeTypeSeq;
+}
+
+sal_Bool SAL_CALL
+QtTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
+{
+ const auto aSeq = getTransferDataFlavors();
+ return std::any_of(aSeq.begin(), aSeq.end(), [&](const css::datatransfer::DataFlavor& aFlavor) {
+ return rFlavor.MimeType == aFlavor.MimeType;
+ });
+}
+
+css::uno::Any SAL_CALL QtTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
+{
+ css::uno::Any aAny;
+ if (!isDataFlavorSupported(rFlavor))
+ return aAny;
+
+ if (rFlavor.MimeType == "text/plain;charset=utf-16")
+ {
+ OUString aString;
+ if (m_bProvideUTF16FromOtherEncoding)
+ {
+ if (m_pMimeData->hasFormat("text/plain;charset=utf-8"))
+ {
+ QByteArray aByteData(m_pMimeData->data(QStringLiteral("text/plain;charset=utf-8")));
+ aString = OUString::fromUtf8(reinterpret_cast<const char*>(aByteData.data()));
+ }
+ else
+ {
+ QByteArray aByteData(m_pMimeData->data(QStringLiteral("text/plain")));
+ aString = OUString(reinterpret_cast<const char*>(aByteData.data()),
+ aByteData.size(), osl_getThreadTextEncoding());
+ }
+ }
+ else
+ {
+ QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
+ aString = OUString(reinterpret_cast<const sal_Unicode*>(aByteData.data()),
+ aByteData.size() / 2);
+ }
+ aAny <<= aString;
+ }
+ else
+ {
+ QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
+ css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(aByteData.data()),
+ aByteData.size());
+ aAny <<= aSeq;
+ }
+
+ return aAny;
+}
+
+QtClipboardTransferable::QtClipboardTransferable(const QClipboard::Mode aMode,
+ const QMimeData* pMimeData)
+ : QtTransferable(pMimeData)
+ , m_aMode(aMode)
+{
+}
+
+bool QtClipboardTransferable::hasInFlightChanged() const
+{
+ const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode));
+ SAL_WARN_IF(bChanged, "vcl.qt", "In flight clipboard change detected - broken clipboard read!");
+ return bChanged;
+}
+
+css::uno::Any SAL_CALL
+QtClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
+{
+ css::uno::Any aAny;
+ auto* pSalInst(GetQtInstance());
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ aAny = QtTransferable::getTransferData(rFlavor);
+ });
+ return aAny;
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor>
+ SAL_CALL QtClipboardTransferable::getTransferDataFlavors()
+{
+ css::uno::Sequence<css::datatransfer::DataFlavor> aSeq;
+ auto* pSalInst(GetQtInstance());
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ aSeq = QtTransferable::getTransferDataFlavors();
+ });
+ return aSeq;
+}
+
+sal_Bool SAL_CALL
+QtClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
+{
+ bool bIsSupported = false;
+ auto* pSalInst(GetQtInstance());
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ bIsSupported = QtTransferable::isDataFlavorSupported(rFlavor);
+ });
+ return bIsSupported;
+}
+
+QtMimeData::QtMimeData(const css::uno::Reference<css::datatransfer::XTransferable>& xTrans)
+ : m_aContents(xTrans)
+ , m_bHaveNoCharset(false)
+ , m_bHaveUTF8(false)
+{
+ assert(xTrans.is());
+}
+
+bool QtMimeData::deepCopy(QMimeData** const pMimeCopy) const
+{
+ if (!pMimeCopy)
+ return false;
+
+ QMimeData* pMimeData = new QMimeData();
+ for (QString& format : formats())
+ {
+ QByteArray aData = data(format);
+ // Checking for custom MIME types
+ if (format.startsWith("application/x-qt"))
+ {
+ // Retrieving true format name
+ int indexBegin = format.indexOf('"') + 1;
+ int indexEnd = format.indexOf('"', indexBegin);
+ format = format.mid(indexBegin, indexEnd - indexBegin);
+ }
+ pMimeData->setData(format, aData);
+ }
+
+ *pMimeCopy = pMimeData;
+ return true;
+}
+
+QStringList QtMimeData::formats() const
+{
+ if (!m_aMimeTypeList.isEmpty())
+ return m_aMimeTypeList;
+
+ const css::uno::Sequence<css::datatransfer::DataFlavor> aFormats
+ = m_aContents->getTransferDataFlavors();
+ QStringList aList;
+ bool bHaveUTF16 = false;
+
+ for (const auto& rFlavor : aFormats)
+ {
+ aList << toQString(rFlavor.MimeType);
+ lcl_textMimeInfo(rFlavor.MimeType, m_bHaveNoCharset, bHaveUTF16, m_bHaveUTF8);
+ }
+
+ // we provide a locale encoded and a UTF-8 variant, if missing
+ if (m_bHaveNoCharset || bHaveUTF16 || m_bHaveUTF8)
+ {
+ // if there is a text representation from LO point of view, it'll be UTF-16
+ assert(bHaveUTF16);
+ if (!m_bHaveUTF8)
+ aList << QStringLiteral("text/plain;charset=utf-8");
+ if (!m_bHaveNoCharset)
+ aList << QStringLiteral("text/plain");
+ }
+
+ m_aMimeTypeList = aList;
+ return m_aMimeTypeList;
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+QVariant QtMimeData::retrieveData(const QString& mimeType, QVariant::Type) const
+#else
+QVariant QtMimeData::retrieveData(const QString& mimeType, QMetaType) const
+#endif
+{
+ if (!hasFormat(mimeType))
+ return QVariant();
+
+ css::datatransfer::DataFlavor aFlavor;
+ aFlavor.MimeType = toOUString(mimeType);
+ aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
+
+ bool bWantNoCharset = false, bWantUTF16 = false, bWantUTF8 = false;
+ if (lcl_textMimeInfo(aFlavor.MimeType, bWantNoCharset, bWantUTF16, bWantUTF8))
+ {
+ if ((bWantNoCharset && !m_bHaveNoCharset) || (bWantUTF8 && !m_bHaveUTF8))
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ }
+ else if (bWantUTF16)
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ }
+
+ css::uno::Any aValue;
+
+ try
+ {
+ // tdf#129809 take a reference in case m_aContents is replaced during this call
+ css::uno::Reference<com::sun::star::datatransfer::XTransferable> xCurrentContents(
+ m_aContents);
+ aValue = xCurrentContents->getTransferData(aFlavor);
+ }
+ catch (...)
+ {
+ }
+
+ QByteArray aByteArray;
+ if (aValue.getValueTypeClass() == css::uno::TypeClass_STRING)
+ {
+ OUString aString;
+ aValue >>= aString;
+
+ if (bWantUTF8)
+ {
+ OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
+ aByteArray = QByteArray(aUTF8String.getStr(), aUTF8String.getLength());
+ }
+ else if (bWantNoCharset)
+ {
+ OString aLocaleString(OUStringToOString(aString, osl_getThreadTextEncoding()));
+ aByteArray = QByteArray(aLocaleString.getStr(), aLocaleString.getLength());
+ }
+ else if (bWantUTF16)
+ {
+ aByteArray = QByteArray(reinterpret_cast<const char*>(aString.getStr()),
+ aString.getLength() * 2);
+ }
+ else
+ return QVariant(toQString(aString));
+ }
+ else
+ {
+ css::uno::Sequence<sal_Int8> aData;
+ aValue >>= aData;
+ aByteArray
+ = QByteArray(reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength());
+ }
+ return QVariant::fromValue(aByteArray);
+}
+
+bool QtMimeData::hasFormat(const QString& mimeType) const { return formats().contains(mimeType); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtVirtualDevice.cxx b/vcl/qt5/QtVirtualDevice.cxx
new file mode 100644
index 0000000000..22844f1df6
--- /dev/null
+++ b/vcl/qt5/QtVirtualDevice.cxx
@@ -0,0 +1,85 @@
+/* -*- 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 <QtVirtualDevice.hxx>
+
+#include <QtGraphics.hxx>
+#include <QtTools.hxx>
+
+#include <QtGui/QImage>
+
+QtVirtualDevice::QtVirtualDevice(double fScale)
+ : m_fScale(fScale)
+{
+}
+
+SalGraphics* QtVirtualDevice::AcquireGraphics()
+{
+ assert(m_pImage);
+ QtGraphics* pGraphics = new QtGraphics(m_pImage.get());
+ m_aGraphics.push_back(pGraphics);
+ return pGraphics;
+}
+
+void QtVirtualDevice::ReleaseGraphics(SalGraphics* pGraphics)
+{
+ std::erase(m_aGraphics, dynamic_cast<QtGraphics*>(pGraphics));
+ delete pGraphics;
+}
+
+bool QtVirtualDevice::SetSize(tools::Long nNewDX, tools::Long nNewDY)
+{
+ return SetSizeUsingBuffer(nNewDX, nNewDY, nullptr);
+}
+
+bool QtVirtualDevice::SetSizeUsingBuffer(tools::Long nNewDX, tools::Long nNewDY, sal_uInt8* pBuffer)
+{
+ if (nNewDX == 0)
+ nNewDX = 1;
+ if (nNewDY == 0)
+ nNewDY = 1;
+
+ if (m_pImage && m_aFrameSize.width() == nNewDX && m_aFrameSize.height() == nNewDY)
+ return true;
+
+ m_aFrameSize = QSize(nNewDX, nNewDY);
+
+ nNewDX *= m_fScale;
+ nNewDY *= m_fScale;
+
+ if (pBuffer)
+ m_pImage.reset(new QImage(pBuffer, nNewDX, nNewDY, Qt_DefaultFormat32));
+ else
+ m_pImage.reset(new QImage(nNewDX, nNewDY, Qt_DefaultFormat32));
+
+ m_pImage->fill(Qt::transparent);
+ m_pImage->setDevicePixelRatio(m_fScale);
+
+ // update device in existing graphics
+ for (auto pQtGraph : m_aGraphics)
+ pQtGraph->ChangeQImage(m_pImage.get());
+
+ return true;
+}
+
+tools::Long QtVirtualDevice::GetWidth() const { return m_pImage ? m_aFrameSize.width() : 0; }
+
+tools::Long QtVirtualDevice::GetHeight() const { return m_pImage ? m_aFrameSize.height() : 0; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtWidget.cxx b/vcl/qt5/QtWidget.cxx
new file mode 100644
index 0000000000..a7c4f32e92
--- /dev/null
+++ b/vcl/qt5/QtWidget.cxx
@@ -0,0 +1,1024 @@
+/* -*- 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 <QtWidget.hxx>
+#include <QtWidget.moc>
+
+#include <QtFrame.hxx>
+#include <QtGraphics.hxx>
+#include <QtInstance.hxx>
+#include <QtMainWindow.hxx>
+#include <QtSvpGraphics.hxx>
+#include <QtTransferable.hxx>
+#include <QtTools.hxx>
+
+#include <QtCore/QMimeData>
+#include <QtGui/QDrag>
+#include <QtGui/QFocusEvent>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QImage>
+#include <QtGui/QKeyEvent>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QPainter>
+#include <QtGui/QPaintEvent>
+#include <QtGui/QResizeEvent>
+#include <QtGui/QShowEvent>
+#include <QtGui/QTextCharFormat>
+#include <QtGui/QWheelEvent>
+#include <QtWidgets/QMainWindow>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QWidget>
+
+#include <cairo.h>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <window.h>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+#if CHECK_ANY_QT_USING_X11
+#define XK_MISCELLANY
+#include <X11/keysymdef.h>
+#endif
+
+using namespace com::sun::star;
+
+void QtWidget::paintEvent(QPaintEvent* pEvent)
+{
+ QPainter p(this);
+ if (!m_rFrame.m_bNullRegion)
+ p.setClipRegion(m_rFrame.m_aRegion);
+
+ QImage aImage;
+ if (m_rFrame.m_bUseCairo)
+ {
+ cairo_surface_t* pSurface = m_rFrame.m_pSurface.get();
+ cairo_surface_flush(pSurface);
+
+ aImage = QImage(cairo_image_surface_get_data(pSurface),
+ cairo_image_surface_get_width(pSurface),
+ cairo_image_surface_get_height(pSurface), Qt_DefaultFormat32);
+ }
+ else
+ aImage = *m_rFrame.m_pQImage;
+
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ aImage.setDevicePixelRatio(fRatio);
+ QRectF source(pEvent->rect().topLeft() * fRatio, pEvent->rect().size() * fRatio);
+ p.drawImage(pEvent->rect(), aImage, source);
+}
+
+void QtWidget::resizeEvent(QResizeEvent* pEvent)
+{
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ const int nWidth = ceil(pEvent->size().width() * fRatio);
+ const int nHeight = ceil(pEvent->size().height() * fRatio);
+
+ m_rFrame.maGeometry.setSize({ nWidth, nHeight });
+
+ if (m_rFrame.m_bUseCairo)
+ {
+ if (m_rFrame.m_pSurface)
+ {
+ const int nOldWidth = cairo_image_surface_get_width(m_rFrame.m_pSurface.get());
+ const int nOldHeight = cairo_image_surface_get_height(m_rFrame.m_pSurface.get());
+ if (nOldWidth != nWidth || nOldHeight != nHeight)
+ {
+ cairo_surface_t* pSurface
+ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
+ cairo_surface_set_user_data(pSurface, SvpSalGraphics::getDamageKey(),
+ &m_rFrame.m_aDamageHandler, nullptr);
+ m_rFrame.m_pSvpGraphics->setSurface(pSurface, basegfx::B2IVector(nWidth, nHeight));
+ UniqueCairoSurface old_surface(m_rFrame.m_pSurface.release());
+ m_rFrame.m_pSurface.reset(pSurface);
+
+ const int nMinWidth = qMin(nOldWidth, nWidth);
+ const int nMinHeight = qMin(nOldHeight, nHeight);
+ SalTwoRect rect(0, 0, nMinWidth, nMinHeight, 0, 0, nMinWidth, nMinHeight);
+ m_rFrame.m_pSvpGraphics->copySource(rect, old_surface.get());
+ }
+ }
+ }
+ else
+ {
+ if (m_rFrame.m_pQImage && m_rFrame.m_pQImage->size() != QSize(nWidth, nHeight))
+ {
+ QImage* pImage = new QImage(m_rFrame.m_pQImage->copy(0, 0, nWidth, nHeight));
+ m_rFrame.m_pQtGraphics->ChangeQImage(pImage);
+ m_rFrame.m_pQImage.reset(pImage);
+ }
+ }
+
+ m_rFrame.CallCallback(SalEvent::Resize, nullptr);
+}
+
+void QtWidget::fakeResize()
+{
+ QResizeEvent aEvent(size(), QSize());
+ resizeEvent(&aEvent);
+}
+
+void QtWidget::fillSalAbstractMouseEvent(const QtFrame& rFrame, const QInputEvent* pQEvent,
+ const QPoint& rPos, Qt::MouseButtons eButtons, int nWidth,
+ SalAbstractMouseEvent& aSalEvent)
+{
+ const qreal fRatio = rFrame.devicePixelRatioF();
+ const Point aPos = toPoint(rPos * fRatio);
+
+ aSalEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(nWidth * fRatio) - aPos.X();
+ aSalEvent.mnY = aPos.Y();
+ aSalEvent.mnTime = pQEvent->timestamp();
+ aSalEvent.mnCode = GetKeyModCode(pQEvent->modifiers()) | GetMouseModCode(eButtons);
+}
+
+#define FILL_SAME(rFrame, nWidth) \
+ fillSalAbstractMouseEvent(rFrame, pEvent, pEvent->pos(), pEvent->buttons(), nWidth, aEvent)
+
+void QtWidget::handleMouseButtonEvent(const QtFrame& rFrame, const QMouseEvent* pEvent)
+{
+ SalMouseEvent aEvent;
+ FILL_SAME(rFrame, rFrame.GetQWidget()->width());
+
+ switch (pEvent->button())
+ {
+ case Qt::LeftButton:
+ aEvent.mnButton = MOUSE_LEFT;
+ break;
+ case Qt::MiddleButton:
+ aEvent.mnButton = MOUSE_MIDDLE;
+ break;
+ case Qt::RightButton:
+ aEvent.mnButton = MOUSE_RIGHT;
+ break;
+ default:
+ return;
+ }
+
+ SalEvent nEventType;
+ if (pEvent->type() == QEvent::MouseButtonPress || pEvent->type() == QEvent::MouseButtonDblClick)
+ nEventType = SalEvent::MouseButtonDown;
+ else
+ nEventType = SalEvent::MouseButtonUp;
+ rFrame.CallCallback(nEventType, &aEvent);
+}
+
+void QtWidget::mousePressEvent(QMouseEvent* pEvent)
+{
+ handleMouseButtonEvent(m_rFrame, pEvent);
+ if (m_rFrame.isPopup()
+ && !geometry().translated(geometry().topLeft() * -1).contains(pEvent->pos()))
+ closePopup();
+}
+
+void QtWidget::mouseReleaseEvent(QMouseEvent* pEvent) { handleMouseButtonEvent(m_rFrame, pEvent); }
+
+void QtWidget::mouseMoveEvent(QMouseEvent* pEvent)
+{
+ SalMouseEvent aEvent;
+ FILL_SAME(m_rFrame, width());
+
+ aEvent.mnButton = 0;
+
+ m_rFrame.CallCallback(SalEvent::MouseMove, &aEvent);
+ pEvent->accept();
+}
+
+void QtWidget::handleMouseEnterLeaveEvents(const QtFrame& rFrame, QEvent* pQEvent)
+{
+ const qreal fRatio = rFrame.devicePixelRatioF();
+ const QWidget* pWidget = rFrame.GetQWidget();
+ const Point aPos = toPoint(pWidget->mapFromGlobal(QCursor::pos()) * fRatio);
+
+ SalMouseEvent aEvent;
+ aEvent.mnX
+ = QGuiApplication::isLeftToRight() ? aPos.X() : round(pWidget->width() * fRatio) - aPos.X();
+ aEvent.mnY = aPos.Y();
+ aEvent.mnTime = 0;
+ aEvent.mnButton = 0;
+ aEvent.mnCode = GetKeyModCode(QGuiApplication::keyboardModifiers())
+ | GetMouseModCode(QGuiApplication::mouseButtons());
+
+ SalEvent nEventType;
+ if (pQEvent->type() == QEvent::Enter)
+ nEventType = SalEvent::MouseMove;
+ else
+ nEventType = SalEvent::MouseLeave;
+ rFrame.CallCallback(nEventType, &aEvent);
+ pQEvent->accept();
+}
+
+void QtWidget::leaveEvent(QEvent* pEvent) { handleMouseEnterLeaveEvents(m_rFrame, pEvent); }
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+void QtWidget::enterEvent(QEnterEvent* pEvent)
+#else
+void QtWidget::enterEvent(QEvent* pEvent)
+#endif
+{
+ handleMouseEnterLeaveEvents(m_rFrame, pEvent);
+}
+
+void QtWidget::wheelEvent(QWheelEvent* pEvent)
+{
+ SalWheelMouseEvent aEvent;
+ fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->position().toPoint(), pEvent->buttons(),
+ width(), aEvent);
+
+ // mouse wheel ticks are 120, which we map to 3 lines.
+ // we have to accumulate for touch scroll to keep track of the absolute delta.
+
+ int nDelta = pEvent->angleDelta().y(), lines;
+ aEvent.mbHorz = nDelta == 0;
+ if (aEvent.mbHorz)
+ {
+ nDelta = (QGuiApplication::isLeftToRight() ? 1 : -1) * pEvent->angleDelta().x();
+ if (!nDelta)
+ return;
+
+ m_nDeltaX += nDelta;
+ lines = m_nDeltaX / 40;
+ m_nDeltaX = m_nDeltaX % 40;
+ }
+ else
+ {
+ m_nDeltaY += nDelta;
+ lines = m_nDeltaY / 40;
+ m_nDeltaY = m_nDeltaY % 40;
+ }
+
+ aEvent.mnDelta = nDelta;
+ aEvent.mnNotchDelta = nDelta < 0 ? -1 : 1;
+ aEvent.mnScrollLines = std::abs(lines);
+
+ m_rFrame.CallCallback(SalEvent::WheelMouse, &aEvent);
+ pEvent->accept();
+}
+
+void QtWidget::dragEnterEvent(QDragEnterEvent* event)
+{
+ if (dynamic_cast<const QtMimeData*>(event->mimeData()))
+ event->accept();
+ else
+ event->acceptProposedAction();
+}
+
+// also called when a drop is rejected
+void QtWidget::dragLeaveEvent(QDragLeaveEvent*) { m_rFrame.handleDragLeave(); }
+
+void QtWidget::dragMoveEvent(QDragMoveEvent* pEvent) { m_rFrame.handleDragMove(pEvent); }
+
+void QtWidget::dropEvent(QDropEvent* pEvent) { m_rFrame.handleDrop(pEvent); }
+
+void QtWidget::moveEvent(QMoveEvent* pEvent)
+{
+ // already handled by QtMainWindow::moveEvent
+ if (m_rFrame.m_pTopLevel)
+ return;
+
+ m_rFrame.maGeometry.setPos(toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF()));
+ m_rFrame.CallCallback(SalEvent::Move, nullptr);
+}
+
+void QtWidget::showEvent(QShowEvent*)
+{
+ QSize aSize(m_rFrame.GetQWidget()->size() * m_rFrame.devicePixelRatioF());
+ // forcing an immediate update somehow interferes with the hide + show
+ // sequence from QtFrame::SetModal, if the frame was already set visible,
+ // resulting in a hidden / unmapped window
+ SalPaintEvent aPaintEvt(0, 0, aSize.width(), aSize.height());
+ if (m_rFrame.isPopup())
+ GetQtInstance()->setActivePopup(&m_rFrame);
+ m_rFrame.CallCallback(SalEvent::Paint, &aPaintEvt);
+}
+
+void QtWidget::hideEvent(QHideEvent*)
+{
+ if (m_rFrame.isPopup() && GetQtInstance()->activePopup() == &m_rFrame)
+ GetQtInstance()->setActivePopup(nullptr);
+}
+
+void QtWidget::closeEvent(QCloseEvent* /*pEvent*/)
+{
+ m_rFrame.CallCallback(SalEvent::Close, nullptr);
+}
+
+static sal_uInt16 GetKeyCode(int keyval, Qt::KeyboardModifiers modifiers)
+{
+ sal_uInt16 nCode = 0;
+ if (keyval >= Qt::Key_0 && keyval <= Qt::Key_9)
+ nCode = KEY_0 + (keyval - Qt::Key_0);
+ else if (keyval >= Qt::Key_A && keyval <= Qt::Key_Z)
+ nCode = KEY_A + (keyval - Qt::Key_A);
+ else if (keyval >= Qt::Key_F1 && keyval <= Qt::Key_F26)
+ nCode = KEY_F1 + (keyval - Qt::Key_F1);
+ else if (modifiers.testFlag(Qt::KeypadModifier)
+ && (keyval == Qt::Key_Period || keyval == Qt::Key_Comma))
+ // Qt doesn't use a special keyval for decimal separator ("," or ".")
+ // on numerical keypad, but sets Qt::KeypadModifier in addition
+ nCode = KEY_DECIMAL;
+ else
+ {
+ switch (keyval)
+ {
+ case Qt::Key_Down:
+ nCode = KEY_DOWN;
+ break;
+ case Qt::Key_Up:
+ nCode = KEY_UP;
+ break;
+ case Qt::Key_Left:
+ nCode = KEY_LEFT;
+ break;
+ case Qt::Key_Right:
+ nCode = KEY_RIGHT;
+ break;
+ case Qt::Key_Home:
+ nCode = KEY_HOME;
+ break;
+ case Qt::Key_End:
+ nCode = KEY_END;
+ break;
+ case Qt::Key_PageUp:
+ nCode = KEY_PAGEUP;
+ break;
+ case Qt::Key_PageDown:
+ nCode = KEY_PAGEDOWN;
+ break;
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ nCode = KEY_RETURN;
+ break;
+ case Qt::Key_Escape:
+ nCode = KEY_ESCAPE;
+ break;
+ case Qt::Key_Tab:
+ // oddly enough, Qt doesn't send Shift-Tab event as 'Tab key pressed with Shift
+ // modifier' but as 'Backtab key pressed' (while its modifier bits are still
+ // set to Shift) -- so let's map both Key_Tab and Key_Backtab to VCL's KEY_TAB
+ case Qt::Key_Backtab:
+ nCode = KEY_TAB;
+ break;
+ case Qt::Key_Backspace:
+ nCode = KEY_BACKSPACE;
+ break;
+ case Qt::Key_Space:
+ nCode = KEY_SPACE;
+ break;
+ case Qt::Key_Insert:
+ nCode = KEY_INSERT;
+ break;
+ case Qt::Key_Delete:
+ nCode = KEY_DELETE;
+ break;
+ case Qt::Key_Plus:
+ nCode = KEY_ADD;
+ break;
+ case Qt::Key_Minus:
+ nCode = KEY_SUBTRACT;
+ break;
+ case Qt::Key_Asterisk:
+ nCode = KEY_MULTIPLY;
+ break;
+ case Qt::Key_Slash:
+ nCode = KEY_DIVIDE;
+ break;
+ case Qt::Key_Period:
+ nCode = KEY_POINT;
+ break;
+ case Qt::Key_Comma:
+ nCode = KEY_COMMA;
+ break;
+ case Qt::Key_Less:
+ nCode = KEY_LESS;
+ break;
+ case Qt::Key_Greater:
+ nCode = KEY_GREATER;
+ break;
+ case Qt::Key_Equal:
+ nCode = KEY_EQUAL;
+ break;
+ case Qt::Key_Find:
+ nCode = KEY_FIND;
+ break;
+ case Qt::Key_Menu:
+ nCode = KEY_CONTEXTMENU;
+ break;
+ case Qt::Key_Help:
+ nCode = KEY_HELP;
+ break;
+ case Qt::Key_Undo:
+ nCode = KEY_UNDO;
+ break;
+ case Qt::Key_Redo:
+ nCode = KEY_REPEAT;
+ break;
+ case Qt::Key_Cancel:
+ nCode = KEY_F11;
+ break;
+ case Qt::Key_AsciiTilde:
+ nCode = KEY_TILDE;
+ break;
+ case Qt::Key_QuoteLeft:
+ nCode = KEY_QUOTELEFT;
+ break;
+ case Qt::Key_BracketLeft:
+ nCode = KEY_BRACKETLEFT;
+ break;
+ case Qt::Key_BracketRight:
+ nCode = KEY_BRACKETRIGHT;
+ break;
+ case Qt::Key_NumberSign:
+ nCode = KEY_NUMBERSIGN;
+ break;
+ case Qt::Key_Forward:
+ nCode = KEY_XF86FORWARD;
+ break;
+ case Qt::Key_Back:
+ nCode = KEY_XF86BACK;
+ break;
+ case Qt::Key_Colon:
+ nCode = KEY_COLON;
+ break;
+ case Qt::Key_Semicolon:
+ nCode = KEY_SEMICOLON;
+ break;
+ case Qt::Key_Copy:
+ nCode = KEY_COPY;
+ break;
+ case Qt::Key_Cut:
+ nCode = KEY_CUT;
+ break;
+ case Qt::Key_Open:
+ nCode = KEY_OPEN;
+ break;
+ case Qt::Key_Paste:
+ nCode = KEY_PASTE;
+ break;
+ }
+ }
+
+ return nCode;
+}
+
+void QtWidget::commitText(QtFrame& rFrame, const QString& aText)
+{
+ SalExtTextInputEvent aInputEvent;
+ aInputEvent.mpTextAttr = nullptr;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.maText = toOUString(aText);
+ aInputEvent.mnCursorPos = aInputEvent.maText.getLength();
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel(&rFrame);
+ rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
+ if (!aDel.isDeleted())
+ rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
+}
+
+void QtWidget::deleteReplacementText(QtFrame& rFrame, int nReplacementStart, int nReplacementLength)
+{
+ // get the surrounding text
+ SolarMutexGuard aGuard;
+ SalSurroundingTextRequestEvent aSurroundingTextEvt;
+ aSurroundingTextEvt.maText.clear();
+ aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
+ rFrame.CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
+
+ // Turn nReplacementStart, nReplacementLength into a UTF-16 selection
+ const Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(
+ aSurroundingTextEvt.maText, aSurroundingTextEvt.mnStart, nReplacementStart,
+ nReplacementLength);
+
+ const Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
+ if (aSelection == aInvalid)
+ {
+ SAL_WARN("vcl.qt", "Invalid selection when deleting IM replacement text");
+ return;
+ }
+
+ SalSurroundingTextSelectionChangeEvent aEvt;
+ aEvt.mnStart = aSelection.Min();
+ aEvt.mnEnd = aSelection.Max();
+ rFrame.CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
+}
+
+bool QtWidget::handleGestureEvent(QtFrame& rFrame, QGestureEvent* pGestureEvent)
+{
+ if (QGesture* pGesture = pGestureEvent->gesture(Qt::PinchGesture))
+ {
+ if (!pGesture->hasHotSpot())
+ {
+ pGestureEvent->ignore();
+ return false;
+ }
+
+ GestureEventZoomType eType = GestureEventZoomType::Begin;
+ switch (pGesture->state())
+ {
+ case Qt::GestureStarted:
+ eType = GestureEventZoomType::Begin;
+ break;
+ case Qt::GestureUpdated:
+ eType = GestureEventZoomType::Update;
+ break;
+ case Qt::GestureFinished:
+ eType = GestureEventZoomType::End;
+ break;
+ case Qt::NoGesture:
+ case Qt::GestureCanceled:
+ default:
+ SAL_WARN("vcl.qt", "Unhandled pinch gesture state: " << pGesture->state());
+ pGestureEvent->ignore();
+ return false;
+ }
+
+ QPinchGesture* pPinchGesture = static_cast<QPinchGesture*>(pGesture);
+ const QPointF aHotspot = pGesture->hotSpot();
+ SalGestureZoomEvent aEvent;
+ aEvent.meEventType = eType;
+ aEvent.mnX = aHotspot.x();
+ aEvent.mnY = aHotspot.y();
+ aEvent.mfScaleDelta = 1 + pPinchGesture->totalScaleFactor();
+ rFrame.CallCallback(SalEvent::GestureZoom, &aEvent);
+ pGestureEvent->accept();
+ return true;
+ }
+
+ pGestureEvent->ignore();
+ return false;
+}
+
+bool QtWidget::handleKeyEvent(QtFrame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent)
+{
+ const bool bIsKeyPressed
+ = pEvent->type() == QEvent::KeyPress || pEvent->type() == QEvent::ShortcutOverride;
+ sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers());
+ if (bIsKeyPressed && nCode == 0 && pEvent->text().length() > 1
+ && rWidget.testAttribute(Qt::WA_InputMethodEnabled))
+ {
+ commitText(rFrame, pEvent->text());
+ pEvent->accept();
+ return true;
+ }
+
+ if (nCode == 0 && pEvent->text().isEmpty())
+ {
+ sal_uInt16 nModCode = GetKeyModCode(pEvent->modifiers());
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = bIsKeyPressed;
+ aModEvt.mnModKeyCode = ModKeyFlags::NONE;
+
+#if CHECK_ANY_QT_USING_X11
+ if (QGuiApplication::platformName() == "xcb")
+ {
+ // pressing just the ctrl key leads to a keysym of XK_Control but
+ // the event state does not contain ControlMask. In the release
+ // event it's the other way round: it does contain the Control mask.
+ // The modifier mode therefore has to be adapted manually.
+ ModKeyFlags nExtModMask = ModKeyFlags::NONE;
+ sal_uInt16 nModMask = 0;
+ switch (pEvent->nativeVirtualKey())
+ {
+ case XK_Control_L:
+ nExtModMask = ModKeyFlags::LeftMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case XK_Control_R:
+ nExtModMask = ModKeyFlags::RightMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case XK_Alt_L:
+ nExtModMask = ModKeyFlags::LeftMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case XK_Alt_R:
+ nExtModMask = ModKeyFlags::RightMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case XK_Shift_L:
+ nExtModMask = ModKeyFlags::LeftShift;
+ nModMask = KEY_SHIFT;
+ break;
+ case XK_Shift_R:
+ nExtModMask = ModKeyFlags::RightShift;
+ nModMask = KEY_SHIFT;
+ break;
+ // Map Meta/Super keys to MOD3 modifier on all Unix systems
+ // except macOS
+ case XK_Meta_L:
+ case XK_Super_L:
+ nExtModMask = ModKeyFlags::LeftMod3;
+ nModMask = KEY_MOD3;
+ break;
+ case XK_Meta_R:
+ case XK_Super_R:
+ nExtModMask = ModKeyFlags::RightMod3;
+ nModMask = KEY_MOD3;
+ break;
+ }
+
+ if (!bIsKeyPressed)
+ {
+ // sending the old mnModKeyCode mask on release is needed to
+ // implement the writing direction switch with Ctrl + L/R-Shift
+ aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
+ nModCode &= ~nModMask;
+ rFrame.m_nKeyModifiers &= ~nExtModMask;
+ }
+ else
+ {
+ nModCode |= nModMask;
+ rFrame.m_nKeyModifiers |= nExtModMask;
+ aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
+ }
+ }
+#endif
+ aModEvt.mnCode = nModCode;
+
+ rFrame.CallCallback(SalEvent::KeyModChange, &aModEvt);
+ return false;
+ }
+
+#if CHECK_ANY_QT_USING_X11
+ // prevent interference of writing direction switch (Ctrl + L/R-Shift) with "normal" shortcuts
+ rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
+#endif
+
+ SalKeyEvent aEvent;
+ aEvent.mnCharCode = (pEvent->text().isEmpty() ? 0 : pEvent->text().at(0).unicode());
+ aEvent.mnRepeat = 0;
+ aEvent.mnCode = nCode;
+ aEvent.mnCode |= GetKeyModCode(pEvent->modifiers());
+
+ QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
+
+ bool bStopProcessingKey;
+ if (bIsKeyPressed)
+ bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent);
+ else
+ bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent);
+ if (bStopProcessingKey)
+ pEvent->accept();
+ return bStopProcessingKey;
+}
+
+bool QtWidget::handleEvent(QtFrame& rFrame, QWidget& rWidget, QEvent* pEvent)
+{
+ if (pEvent->type() == QEvent::Gesture)
+ {
+ QGestureEvent* pGestureEvent = static_cast<QGestureEvent*>(pEvent);
+ return handleGestureEvent(rFrame, pGestureEvent);
+ }
+ else if (pEvent->type() == QEvent::ShortcutOverride)
+ {
+ // ignore non-spontaneous QEvent::ShortcutOverride events,
+ // since such an extra event is sent e.g. with Orca screen reader enabled,
+ // so that two events of that kind (the "real one" and a non-spontaneous one)
+ // would otherwise be processed, resulting in duplicate input as 'handleKeyEvent'
+ // is called below (s. tdf#122053)
+ if (!pEvent->spontaneous())
+ {
+ return false;
+ }
+
+ // Accepted event disables shortcut activation,
+ // but enables keypress event.
+ // If event is not accepted and shortcut is successfully activated,
+ // KeyPress event is omitted.
+ //
+ // Instead of processing keyPressEvent, handle ShortcutOverride event,
+ // and if it's handled - disable the shortcut, it should have been activated.
+ // Don't process keyPressEvent generated after disabling shortcut since it was handled here.
+ // If event is not handled, don't accept it and let Qt activate related shortcut.
+ if (handleKeyEvent(rFrame, rWidget, static_cast<QKeyEvent*>(pEvent)))
+ return true;
+ }
+ else if (pEvent->type() == QEvent::ToolTip)
+ {
+ // Qt's POV on the active popup is wrong due to our fake popup, so check LO's state.
+ // Otherwise Qt will continue handling ToolTip events from the "parent" window.
+ const QtFrame* pPopupFrame = GetQtInstance()->activePopup();
+ if (!rFrame.m_aTooltipText.isEmpty() && (!pPopupFrame || pPopupFrame == &rFrame))
+ QToolTip::showText(QCursor::pos(), toQString(rFrame.m_aTooltipText), &rWidget,
+ rFrame.m_aTooltipArea);
+ else
+ {
+ QToolTip::hideText();
+ pEvent->ignore();
+ }
+ return true;
+ }
+ return false;
+}
+
+bool QtWidget::event(QEvent* pEvent)
+{
+ return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent);
+}
+
+void QtWidget::keyReleaseEvent(QKeyEvent* pEvent)
+{
+ if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent))
+ QWidget::keyReleaseEvent(pEvent);
+}
+
+void QtWidget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); }
+
+void QtWidget::closePopup()
+{
+ VclPtr<FloatingWindow> pFirstFloat = ImplGetSVData()->mpWinData->mpFirstFloat;
+ if (pFirstFloat && !(pFirstFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoAppFocusClose))
+ {
+ SolarMutexGuard aGuard;
+ pFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
+ }
+}
+
+void QtWidget::focusOutEvent(QFocusEvent*)
+{
+#if CHECK_ANY_QT_USING_X11
+ m_rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
+#endif
+ endExtTextInput();
+ m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr);
+ closePopup();
+}
+
+QtWidget::QtWidget(QtFrame& rFrame, Qt::WindowFlags f)
+ // if you try to set the QWidget parent via the QtFrame, instead of using the Q_NULLPTR, at
+ // least test Wayland popups; these horribly broke last time doing this (read commits)!
+ : QWidget(Q_NULLPTR, f)
+ , m_rFrame(rFrame)
+ , m_bNonEmptyIMPreeditSeen(false)
+ , m_bInInputMethodQueryCursorRectangle(false)
+ , m_nDeltaX(0)
+ , m_nDeltaY(0)
+{
+ setAttribute(Qt::WA_TranslucentBackground);
+ setAttribute(Qt::WA_OpaquePaintEvent);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setMouseTracking(true);
+ if (!rFrame.isPopup())
+ setFocusPolicy(Qt::StrongFocus);
+ else
+ setFocusPolicy(Qt::ClickFocus);
+
+ grabGesture(Qt::PinchGesture);
+}
+
+static ExtTextInputAttr lcl_MapUnderlineStyle(QTextCharFormat::UnderlineStyle us)
+{
+ switch (us)
+ {
+ case QTextCharFormat::NoUnderline:
+ return ExtTextInputAttr::NONE;
+ case QTextCharFormat::DotLine:
+ return ExtTextInputAttr::DottedUnderline;
+ case QTextCharFormat::DashDotDotLine:
+ case QTextCharFormat::DashDotLine:
+ return ExtTextInputAttr::DashDotUnderline;
+ case QTextCharFormat::WaveUnderline:
+ return ExtTextInputAttr::GrayWaveline;
+ default:
+ return ExtTextInputAttr::Underline;
+ }
+}
+
+void QtWidget::inputMethodEvent(QInputMethodEvent* pEvent)
+{
+ const bool bHasCommitText = !pEvent->commitString().isEmpty();
+ const int nReplacementLength = pEvent->replacementLength();
+
+ if (nReplacementLength > 0 || bHasCommitText)
+ {
+ if (nReplacementLength > 0)
+ deleteReplacementText(m_rFrame, pEvent->replacementStart(), nReplacementLength);
+ if (bHasCommitText)
+ commitText(m_rFrame, pEvent->commitString());
+ }
+ else
+ {
+ SalExtTextInputEvent aInputEvent;
+ aInputEvent.mpTextAttr = nullptr;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.maText = toOUString(pEvent->preeditString());
+ aInputEvent.mnCursorPos = 0;
+
+ const sal_Int32 nLength = aInputEvent.maText.getLength();
+ const QList<QInputMethodEvent::Attribute>& rAttrList = pEvent->attributes();
+ std::vector<ExtTextInputAttr> aTextAttrs(std::max(sal_Int32(1), nLength),
+ ExtTextInputAttr::NONE);
+ aInputEvent.mpTextAttr = aTextAttrs.data();
+
+ for (const QInputMethodEvent::Attribute& rAttr : rAttrList)
+ {
+ switch (rAttr.type)
+ {
+ case QInputMethodEvent::TextFormat:
+ {
+ QTextCharFormat aCharFormat
+ = qvariant_cast<QTextFormat>(rAttr.value).toCharFormat();
+ if (aCharFormat.isValid())
+ {
+ ExtTextInputAttr aETIP
+ = lcl_MapUnderlineStyle(aCharFormat.underlineStyle());
+ if (aCharFormat.hasProperty(QTextFormat::BackgroundBrush))
+ aETIP |= ExtTextInputAttr::Highlight;
+ if (aCharFormat.fontStrikeOut())
+ aETIP |= ExtTextInputAttr::RedText;
+ for (int j = rAttr.start; j < rAttr.start + rAttr.length; j++)
+ {
+ SAL_WARN_IF(j >= static_cast<int>(aTextAttrs.size()), "vcl.qt",
+ "QInputMethodEvent::Attribute out of range. Broken range: "
+ << rAttr.start << "," << rAttr.start + rAttr.length
+ << " Legal range: 0," << aTextAttrs.size());
+ if (j >= static_cast<int>(aTextAttrs.size()))
+ break;
+ aTextAttrs[j] = aETIP;
+ }
+ }
+ break;
+ }
+ case QInputMethodEvent::Cursor:
+ {
+ aInputEvent.mnCursorPos = rAttr.start;
+ if (rAttr.length == 0)
+ aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
+ break;
+ }
+ default:
+ SAL_WARN("vcl.qt", "Unhandled QInputMethodEvent attribute: "
+ << static_cast<int>(rAttr.type));
+ break;
+ }
+ }
+
+ const bool bIsEmpty = aInputEvent.maText.isEmpty();
+ if (m_bNonEmptyIMPreeditSeen || !bIsEmpty)
+ {
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel(&m_rFrame);
+ m_rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
+ if (!aDel.isDeleted() && bIsEmpty)
+ m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
+ m_bNonEmptyIMPreeditSeen = !bIsEmpty;
+ }
+ }
+
+ pEvent->accept();
+}
+
+static bool lcl_retrieveSurrounding(sal_Int32& rPosition, sal_Int32& rAnchor, QString* pText,
+ QString* pSelection)
+{
+ SolarMutexGuard aGuard;
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if (!pFocusWin)
+ return false;
+
+ uno::Reference<accessibility::XAccessibleEditableText> xText;
+ try
+ {
+ uno::Reference<accessibility::XAccessible> xAccessible(pFocusWin->GetAccessible());
+ if (xAccessible.is())
+ xText = FindFocusedEditableText(xAccessible->getAccessibleContext());
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl.qt", "Exception in getting input method surrounding text");
+ }
+
+ if (xText.is())
+ {
+ rPosition = xText->getCaretPosition();
+ if (rPosition != -1)
+ {
+ if (pText)
+ *pText = toQString(xText->getText());
+
+ sal_Int32 nSelStart = xText->getSelectionStart();
+ sal_Int32 nSelEnd = xText->getSelectionEnd();
+ if (nSelStart == nSelEnd)
+ {
+ rAnchor = rPosition;
+ }
+ else
+ {
+ if (rPosition == nSelStart)
+ rAnchor = nSelEnd;
+ else
+ rAnchor = nSelStart;
+ if (pSelection)
+ *pSelection = toQString(xText->getSelectedText());
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QVariant QtWidget::inputMethodQuery(Qt::InputMethodQuery property) const
+{
+ switch (property)
+ {
+ case Qt::ImSurroundingText:
+ {
+ QString aText;
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, &aText, nullptr))
+ return QVariant(aText);
+ return QVariant();
+ }
+ case Qt::ImCursorPosition:
+ {
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
+ return QVariant(static_cast<int>(nCursorPos));
+ return QVariant();
+ }
+ case Qt::ImCursorRectangle:
+ {
+ if (!m_bInInputMethodQueryCursorRectangle)
+ {
+ m_bInInputMethodQueryCursorRectangle = true;
+ SalExtTextInputPosEvent aPosEvent;
+ m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent);
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ m_aImCursorRectangle.setRect(aPosEvent.mnX / fRatio, aPosEvent.mnY / fRatio,
+ aPosEvent.mnWidth / fRatio,
+ aPosEvent.mnHeight / fRatio);
+ m_bInInputMethodQueryCursorRectangle = false;
+ }
+ return QVariant(m_aImCursorRectangle);
+ }
+ case Qt::ImAnchorPosition:
+ {
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
+ return QVariant(static_cast<int>(nAnchor));
+ return QVariant();
+ }
+ case Qt::ImCurrentSelection:
+ {
+ QString aSelection;
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection))
+ return QVariant(aSelection);
+ return QVariant();
+ }
+ default:
+ return QWidget::inputMethodQuery(property);
+ }
+}
+
+void QtWidget::endExtTextInput()
+{
+ if (m_bNonEmptyIMPreeditSeen)
+ {
+ m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
+ m_bNonEmptyIMPreeditSeen = false;
+ }
+}
+
+void QtWidget::changeEvent(QEvent* pEvent)
+{
+ switch (pEvent->type())
+ {
+ case QEvent::FontChange:
+ [[fallthrough]];
+ case QEvent::PaletteChange:
+ [[fallthrough]];
+ case QEvent::StyleChange:
+ {
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ pSalInst->UpdateStyle(QEvent::FontChange == pEvent->type());
+ break;
+ }
+ default:
+ break;
+ }
+ QWidget::changeEvent(pEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtX11Support.cxx b/vcl/qt5/QtX11Support.cxx
new file mode 100644
index 0000000000..84036fb8a5
--- /dev/null
+++ b/vcl/qt5/QtX11Support.cxx
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <QtX11Support.hxx>
+
+#include <config_vclplug.h>
+
+#include <QtCore/QVersionNumber>
+
+#include <QtInstance.hxx>
+#include <QtTools.hxx>
+
+#if CHECK_QT5_USING_X11
+#include <QtX11Extras/QX11Info>
+#endif
+
+#include <unx/gensys.h>
+
+void QtX11Support::setApplicationID(const xcb_window_t nWinId, std::u16string_view rWMClass)
+{
+#if CHECK_QT5_USING_X11
+ OString aResClass = OUStringToOString(rWMClass, RTL_TEXTENCODING_ASCII_US);
+ const char* pResClass
+ = !aResClass.isEmpty() ? aResClass.getStr() : SalGenericSystem::getFrameClassName();
+ OString aResName = SalGenericSystem::getFrameResName();
+
+ // the WM_CLASS data consists of two concatenated cstrings, including the terminating '\0' chars
+ const uint32_t data_len = aResName.getLength() + 1 + strlen(pResClass) + 1;
+ char* data = new char[data_len];
+ memcpy(data, aResName.getStr(), aResName.getLength() + 1);
+ memcpy(data + aResName.getLength() + 1, pResClass, strlen(pResClass) + 1);
+
+ xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, nWinId, XCB_ATOM_WM_CLASS,
+ XCB_ATOM_STRING, 8, data_len, data);
+ delete[] data;
+#else
+ Q_UNUSED(nWinId);
+ Q_UNUSED(rWMClass);
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtXAccessible.cxx b/vcl/qt5/QtXAccessible.cxx
new file mode 100644
index 0000000000..25c0c5e9e1
--- /dev/null
+++ b/vcl/qt5/QtXAccessible.cxx
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <QtXAccessible.hxx>
+#include <QtXAccessible.moc>
+
+#include <QtFrame.hxx>
+#include <QtTools.hxx>
+#include <QtWidget.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+#include <sal/log.hxx>
+#include <utility>
+
+using namespace css::accessibility;
+using namespace css::uno;
+
+QtXAccessible::QtXAccessible(Reference<XAccessible> xAccessible)
+ : m_xAccessible(std::move(xAccessible))
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */