summaryrefslogtreecommitdiffstats
path: root/vcl/qt5
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/qt5
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/qt5')
-rw-r--r--vcl/qt5/Qt5AccessibleEventListener.cxx174
-rw-r--r--vcl/qt5/Qt5AccessibleWidget.cxx1267
-rw-r--r--vcl/qt5/Qt5Bitmap.cxx301
-rw-r--r--vcl/qt5/Qt5Clipboard.cxx243
-rw-r--r--vcl/qt5/Qt5Data.cxx334
-rw-r--r--vcl/qt5/Qt5DragAndDrop.cxx252
-rw-r--r--vcl/qt5/Qt5FilePicker.cxx945
-rw-r--r--vcl/qt5/Qt5Font.cxx161
-rw-r--r--vcl/qt5/Qt5FontFace.cxx206
-rw-r--r--vcl/qt5/Qt5Frame.cxx1449
-rw-r--r--vcl/qt5/Qt5Graphics.cxx128
-rw-r--r--vcl/qt5/Qt5Graphics_Controls.cxx1111
-rw-r--r--vcl/qt5/Qt5Graphics_GDI.cxx712
-rw-r--r--vcl/qt5/Qt5Graphics_Text.cxx233
-rw-r--r--vcl/qt5/Qt5Instance.cxx651
-rw-r--r--vcl/qt5/Qt5Instance_Print.cxx132
-rw-r--r--vcl/qt5/Qt5MainWindow.cxx45
-rw-r--r--vcl/qt5/Qt5Menu.cxx704
-rw-r--r--vcl/qt5/Qt5Object.cxx156
-rw-r--r--vcl/qt5/Qt5OpenGLContext.cxx152
-rw-r--r--vcl/qt5/Qt5Painter.cxx53
-rw-r--r--vcl/qt5/Qt5Printer.cxx27
-rw-r--r--vcl/qt5/Qt5SvpGraphics.cxx114
-rw-r--r--vcl/qt5/Qt5SvpSurface.cxx91
-rw-r--r--vcl/qt5/Qt5SvpVirtualDevice.hxx37
-rw-r--r--vcl/qt5/Qt5System.cxx38
-rw-r--r--vcl/qt5/Qt5Timer.cxx52
-rw-r--r--vcl/qt5/Qt5Tools.cxx122
-rw-r--r--vcl/qt5/Qt5Transferable.cxx345
-rw-r--r--vcl/qt5/Qt5VirtualDevice.cxx93
-rw-r--r--vcl/qt5/Qt5Widget.cxx740
-rw-r--r--vcl/qt5/Qt5XAccessible.cxx29
32 files changed, 11097 insertions, 0 deletions
diff --git a/vcl/qt5/Qt5AccessibleEventListener.cxx b/vcl/qt5/Qt5AccessibleEventListener.cxx
new file mode 100644
index 000000000..621e54172
--- /dev/null
+++ b/vcl/qt5/Qt5AccessibleEventListener.cxx
@@ -0,0 +1,174 @@
+/* -*- 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 <Qt5AccessibleEventListener.hxx>
+
+#include <sal/log.hxx>
+
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+
+#include <QtGui/QAccessible>
+
+using namespace css;
+using namespace css::accessibility;
+using namespace css::lang;
+using namespace css::uno;
+
+Qt5AccessibleEventListener::Qt5AccessibleEventListener(const Reference<XAccessible> xAccessible,
+ Qt5AccessibleWidget* pAccessibleWidget)
+ : m_xAccessible(xAccessible)
+ , m_pAccessibleWidget(pAccessibleWidget)
+{
+}
+
+void Qt5AccessibleEventListener::notifyEvent(
+ const css::accessibility::AccessibleEventObject& aEvent)
+{
+ QAccessibleInterface* pQAccessibleInterface = m_pAccessibleWidget;
+
+ Reference<XAccessible> xChild;
+ 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:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::ActiveDescendantChanged));
+ return;
+ case AccessibleEventId::CHILD:
+ {
+ QAccessible::Event event = QAccessible::InvalidEvent;
+ if (aEvent.OldValue >>= xChild)
+ event = QAccessible::ObjectDestroyed;
+ if (aEvent.NewValue >>= xChild)
+ event = QAccessible::ObjectCreated;
+ if (event != QAccessible::InvalidEvent)
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, event));
+ 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:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::Selection));
+ return;
+ case AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::AttributeChanged));
+ 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_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:
+ case AccessibleEventId::CARET_CHANGED:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::TableSummaryChanged));
+ return;
+ case AccessibleEventId::SELECTION_CHANGED_ADD:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::SelectionAdd));
+ return;
+ case AccessibleEventId::SELECTION_CHANGED_REMOVE:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::SelectionRemove));
+ 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::TEXT_CHANGED:
+ 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:
+ QAccessible::updateAccessibility(
+ new QAccessibleEvent(pQAccessibleInterface, QAccessible::ForegroundChanged));
+ return;
+ case AccessibleEventId::ROLE_CHANGED:
+ case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ case AccessibleEventId::VALUE_CHANGED:
+ 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::HYPERTEXT_CHANGED:
+ case AccessibleEventId::TABLE_MODEL_CHANGED:
+ case AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
+ case AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
+ case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS:
+ default:
+ SAL_WARN("vcl.qt5", "Unmapped AccessibleEventId: " << aEvent.EventId);
+ return;
+ }
+}
+
+void Qt5AccessibleEventListener::disposing(const EventObject& /* Source */) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5AccessibleWidget.cxx b/vcl/qt5/Qt5AccessibleWidget.cxx
new file mode 100644
index 000000000..15ebdf36b
--- /dev/null
+++ b/vcl/qt5/Qt5AccessibleWidget.cxx
@@ -0,0 +1,1267 @@
+/* -*- 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 <Qt5AccessibleWidget.hxx>
+#include <Qt5AccessibleWidget.moc>
+
+#include <QtGui/QAccessibleInterface>
+
+#include <Qt5AccessibleEventListener.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Widget.hxx>
+#include <Qt5XAccessible.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/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/XAccessibleStateSet.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/awt/FontWeight.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/popupmenuwindow.hxx>
+
+using namespace css;
+using namespace css::accessibility;
+using namespace css::beans;
+using namespace css::uno;
+
+Qt5AccessibleWidget::Qt5AccessibleWidget(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 Qt5AccessibleEventListener(xAccessible, this));
+ xBroadcaster->addAccessibleEventListener(xListener);
+ }
+}
+
+Reference<XAccessibleContext> Qt5AccessibleWidget::getAccessibleContextImpl() const
+{
+ Reference<XAccessibleContext> xAc;
+
+ if (m_xAccessible.is())
+ {
+ try
+ {
+ xAc = m_xAccessible->getAccessibleContext();
+ }
+ catch (css::lang::DisposedException /*ex*/)
+ {
+ SAL_WARN("vcl.qt5", "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.qt5", "Accessible context no longer alive");
+ }
+ }
+
+ return xAc;
+}
+
+QWindow* Qt5AccessibleWidget::window() const { return nullptr; }
+
+int Qt5AccessibleWidget::childCount() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return 0;
+
+ return xAc->getAccessibleChildCount();
+}
+
+int Qt5AccessibleWidget::indexOfChild(const QAccessibleInterface* /* child */) const { return 0; }
+
+namespace
+{
+QAccessible::Relation lcl_matchUnoRelation(short relationType)
+{
+ switch (relationType)
+ {
+ case AccessibleRelationType::CONTROLLER_FOR:
+ return QAccessible::Controller;
+ case AccessibleRelationType::CONTROLLED_BY:
+ return QAccessible::Controlled;
+ case AccessibleRelationType::LABEL_FOR:
+ return QAccessible::Label;
+ case AccessibleRelationType::LABELED_BY:
+ return QAccessible::Labelled;
+ case AccessibleRelationType::INVALID:
+ case AccessibleRelationType::CONTENT_FLOWS_FROM:
+ case AccessibleRelationType::CONTENT_FLOWS_TO:
+ case AccessibleRelationType::MEMBER_OF:
+ case AccessibleRelationType::SUB_WINDOW_OF:
+ case AccessibleRelationType::NODE_CHILD_OF:
+ case AccessibleRelationType::DESCRIBED_BY:
+ default:
+ SAL_WARN("vcl.qt5", "Unmatched relation: " << relationType);
+ return nullptr;
+ }
+}
+
+short lcl_matchQtRelation(QAccessible::Relation relationType)
+{
+ switch (relationType)
+ {
+ case QAccessible::Controller:
+ return AccessibleRelationType::CONTROLLER_FOR;
+ case QAccessible::Controlled:
+ return AccessibleRelationType::CONTROLLED_BY;
+ case QAccessible::Label:
+ return AccessibleRelationType::LABEL_FOR;
+ case QAccessible::Labelled:
+ return AccessibleRelationType::LABELED_BY;
+ default:
+ SAL_WARN("vcl.qt5", "Unmatched relation: " << relationType);
+ }
+ return 0;
+}
+
+void lcl_appendRelation(QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>* relations,
+ AccessibleRelation aRelation)
+{
+ QAccessible::Relation aQRelation = lcl_matchUnoRelation(aRelation.RelationType);
+ 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(new Qt5XAccessible(xAccessible)), aQRelation });
+ }
+}
+}
+
+QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>
+Qt5AccessibleWidget::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())
+ {
+ if (match == QAccessible::AllRelations)
+ {
+ int count = xRelationSet->getRelationCount();
+ for (int i = 0; i < count; i++)
+ {
+ AccessibleRelation aRelation = xRelationSet->getRelation(i);
+ lcl_appendRelation(&relations, aRelation);
+ }
+ }
+ else
+ {
+ AccessibleRelation aRelation = xRelationSet->getRelation(lcl_matchQtRelation(match));
+ lcl_appendRelation(&relations, aRelation);
+ }
+ }
+
+ return relations;
+}
+
+QAccessibleInterface* Qt5AccessibleWidget::focusChild() const
+{
+ /* if (m_pWindow->HasChildPathFocus())
+ return QAccessible::queryAccessibleInterface(
+ new Qt5XAccessible(m_xAccessible->getAccessibleContext()->getAccessibleChild(index))); */
+ return QAccessible::queryAccessibleInterface(object());
+}
+
+QRect Qt5AccessibleWidget::rect() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QRect();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ awt::Point aPoint = xAccessibleComponent->getLocation();
+ awt::Size aSize = xAccessibleComponent->getSize();
+
+ return QRect(aPoint.X, aPoint.Y, aSize.Width, aSize.Height);
+}
+
+QAccessibleInterface* Qt5AccessibleWidget::parent() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ return QAccessible::queryAccessibleInterface(new Qt5XAccessible(xAc->getAccessibleParent()));
+}
+QAccessibleInterface* Qt5AccessibleWidget::child(int index) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ return QAccessible::queryAccessibleInterface(
+ new Qt5XAccessible(xAc->getAccessibleChild(index)));
+}
+
+QString Qt5AccessibleWidget::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 Qt5AccessibleWidget::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::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::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:
+ return QAccessible::Paragraph;
+
+ case AccessibleRole::PASSWORD_TEXT:
+ 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::Button;
+
+ case AccessibleRole::BUTTON_MENU:
+ return QAccessible::Button;
+
+ 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;
+
+ /* Ignore window objects for sub-menus, combo- and list boxes,
+ * which are exposed as children of their parents.
+ */
+ case AccessibleRole::WINDOW: // top-level window without title bar
+ {
+ return QAccessible::Window;
+ }
+ }
+
+ SAL_WARN("vcl.qt5",
+ "Unmapped role: " << m_xAccessible->getAccessibleContext()->getAccessibleRole());
+ return QAccessible::NoRole;
+}
+
+namespace
+{
+void lcl_addState(QAccessible::State* state, sal_Int16 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::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::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:
+ // No match
+ break;
+ case AccessibleStateType::MANAGES_DESCENDANTS:
+ // No match
+ break;
+ case AccessibleStateType::MODAL:
+ state->modal = 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.qt5", "Unmapped state: " << nState);
+ break;
+ }
+}
+}
+
+QAccessible::State Qt5AccessibleWidget::state() const
+{
+ QAccessible::State state;
+
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return state;
+
+ Reference<XAccessibleStateSet> xStateSet(xAc->getAccessibleStateSet());
+
+ if (!xStateSet.is())
+ return state;
+
+ Sequence<sal_Int16> aStates = xStateSet->getStates();
+
+ for (sal_Int32 n = 0; n < aStates.getLength(); n++)
+ {
+ lcl_addState(&state, n);
+ }
+
+ return state;
+}
+
+QColor Qt5AccessibleWidget::foregroundColor() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QColor();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return toQColor(xAccessibleComponent->getForeground());
+}
+
+QColor Qt5AccessibleWidget::backgroundColor() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return QColor();
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return toQColor(xAccessibleComponent->getBackground());
+}
+
+void* Qt5AccessibleWidget::interface_cast(QAccessible::InterfaceType t)
+{
+ if (t == QAccessible::ActionInterface)
+ return static_cast<QAccessibleActionInterface*>(this);
+ if (t == QAccessible::TextInterface)
+ return static_cast<QAccessibleTextInterface*>(this);
+ if (t == QAccessible::EditableTextInterface)
+ return static_cast<QAccessibleEditableTextInterface*>(this);
+ if (t == QAccessible::ValueInterface)
+ return static_cast<QAccessibleValueInterface*>(this);
+ if (t == QAccessible::TableInterface)
+ return static_cast<QAccessibleTableInterface*>(this);
+ return nullptr;
+}
+
+bool Qt5AccessibleWidget::isValid() const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ return xAc.is();
+}
+
+QObject* Qt5AccessibleWidget::object() const { return m_pObject; }
+
+void Qt5AccessibleWidget::setText(QAccessible::Text /* t */, const QString& /* text */) {}
+
+QAccessibleInterface* Qt5AccessibleWidget::childAt(int x, int y) const
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return nullptr;
+
+ Reference<XAccessibleComponent> xAccessibleComponent(xAc, UNO_QUERY);
+ return QAccessible::queryAccessibleInterface(
+ new Qt5XAccessible(xAccessibleComponent->getAccessibleAtPoint(awt::Point(x, y))));
+}
+
+QAccessibleInterface* Qt5AccessibleWidget::customFactory(const QString& classname, QObject* object)
+{
+ if (classname == QLatin1String("Qt5Widget") && object && object->isWidgetType())
+ {
+ Qt5Widget* pWidget = static_cast<Qt5Widget*>(object);
+ vcl::Window* pWindow = pWidget->frame().GetWindow();
+
+ if (pWindow)
+ return new Qt5AccessibleWidget(pWindow->GetAccessible(), object);
+ }
+ if (classname == QLatin1String("Qt5XAccessible") && object)
+ {
+ Qt5XAccessible* pXAccessible = dynamic_cast<Qt5XAccessible*>(object);
+ if (pXAccessible && pXAccessible->m_xAccessible.is())
+ return new Qt5AccessibleWidget(pXAccessible->m_xAccessible, object);
+ }
+
+ return nullptr;
+}
+
+// QAccessibleActionInterface
+QStringList Qt5AccessibleWidget::actionNames() const
+{
+ QStringList actionNames;
+ Reference<XAccessibleAction> xAccessibleAction(m_xAccessible, 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 Qt5AccessibleWidget::doAction(const QString& actionName)
+{
+ Reference<XAccessibleAction> xAccessibleAction(m_xAccessible, UNO_QUERY);
+ if (!xAccessibleAction.is())
+ return;
+
+ int index = actionNames().indexOf(actionName);
+ if (index == -1)
+ return;
+ xAccessibleAction->doAccessibleAction(index);
+}
+
+QStringList Qt5AccessibleWidget::keyBindingsForAction(const QString& actionName) const
+{
+ QStringList keyBindings;
+ Reference<XAccessibleAction> xAccessibleAction(m_xAccessible, 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;
+}
+
+QAccessibleValueInterface* Qt5AccessibleWidget::valueInterface() { return nullptr; }
+
+QAccessibleTextInterface* Qt5AccessibleWidget::textInterface() { return nullptr; }
+
+// QAccessibleTextInterface
+void Qt5AccessibleWidget::addSelection(int /* startOffset */, int /* endOffset */)
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::addSelection");
+}
+
+namespace
+{
+OUString lcl_convertFontWeight(double fontWeight)
+{
+ if (fontWeight == awt::FontWeight::THIN || fontWeight == awt::FontWeight::ULTRALIGHT)
+ return "100";
+ if (fontWeight == awt::FontWeight::LIGHT)
+ return "200";
+ if (fontWeight == awt::FontWeight::SEMILIGHT)
+ return "300";
+ if (fontWeight == awt::FontWeight::NORMAL)
+ return "normal";
+ if (fontWeight == awt::FontWeight::SEMIBOLD)
+ return "500";
+ if (fontWeight == awt::FontWeight::BOLD)
+ return "bold";
+ if (fontWeight == awt::FontWeight::ULTRABOLD)
+ return "800";
+ if (fontWeight == awt::FontWeight::BLACK)
+ return "900";
+
+ // awt::FontWeight::DONTKNOW || fontWeight == awt::FontWeight::NORMAL
+ return "normal";
+}
+}
+
+QString Qt5AccessibleWidget::attributes(int offset, int* startOffset, int* endOffset) const
+{
+ Reference<XAccessibleText> xText(m_xAccessible, 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(); // currently always returns 0
+
+ const int nTextLength = characterCount();
+ if (offset == -1 || offset == nTextLength)
+ offset = nTextLength - 1;
+
+ if (offset < 0 || offset > nTextLength)
+ {
+ *startOffset = -1;
+ *endOffset = -1;
+ return QString();
+ }
+
+ const Sequence<PropertyValue> attribs
+ = xText->getCharacterAttributes(offset, Sequence<OUString>());
+ OUString aRet;
+ for (PropertyValue const& prop : attribs)
+ {
+ if (prop.Name == "CharFontName")
+ {
+ aRet += "font-family:" + *o3tl::doAccess<OUString>(prop.Value) + ";";
+ continue;
+ }
+ if (prop.Name == "CharHeight")
+ {
+ aRet += "font-size:" + OUString::number(*o3tl::doAccess<double>(prop.Value)) + "pt;";
+ continue;
+ }
+ if (prop.Name == "CharWeight")
+ {
+ aRet += "font-weight:" + lcl_convertFontWeight(*o3tl::doAccess<double>(prop.Value))
+ + ";";
+ continue;
+ }
+ }
+ *startOffset = offset;
+ *endOffset = offset + 1;
+ return toQString(aRet);
+}
+int Qt5AccessibleWidget::characterCount() const
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ return xText->getCharacterCount();
+ return 0;
+}
+QRect Qt5AccessibleWidget::characterRect(int /* offset */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::characterRect");
+ return QRect();
+}
+int Qt5AccessibleWidget::cursorPosition() const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::cursorPosition");
+ return 0;
+}
+int Qt5AccessibleWidget::offsetAtPoint(const QPoint& /* point */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::offsetAtPoint");
+ return 0;
+}
+void Qt5AccessibleWidget::removeSelection(int /* selectionIndex */)
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::removeSelection");
+}
+void Qt5AccessibleWidget::scrollToSubstring(int startIndex, int endIndex)
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ xText->scrollSubstringTo(startIndex, endIndex, AccessibleScrollType_SCROLL_ANYWHERE);
+}
+
+void Qt5AccessibleWidget::selection(int selectionIndex, int* startOffset, int* endOffset) const
+{
+ if (!startOffset && !endOffset)
+ return;
+
+ Reference<XAccessibleText> xText;
+ if (selectionIndex == 0)
+ xText = Reference<XAccessibleText>(m_xAccessible, UNO_QUERY);
+
+ if (startOffset)
+ *startOffset = xText.is() ? xText->getSelectionStart() : 0;
+ if (endOffset)
+ *endOffset = xText.is() ? xText->getSelectionEnd() : 0;
+}
+
+int Qt5AccessibleWidget::selectionCount() const
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is() && !xText->getSelectedText().isEmpty())
+ return 1; // Only 1 selection supported atm
+ return 0;
+}
+void Qt5AccessibleWidget::setCursorPosition(int position)
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ xText->setCaretPosition(position);
+}
+void Qt5AccessibleWidget::setSelection(int /* selectionIndex */, int startOffset, int endOffset)
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ xText->setSelection(startOffset, endOffset);
+}
+QString Qt5AccessibleWidget::text(int startOffset, int endOffset) const
+{
+ Reference<XAccessibleText> xText(m_xAccessible, UNO_QUERY);
+ if (xText.is())
+ return toQString(xText->getTextRange(startOffset, endOffset));
+ return QString();
+}
+QString Qt5AccessibleWidget::textAfterOffset(int /* offset */,
+ QAccessible::TextBoundaryType /* boundaryType */,
+ int* /* startOffset */, int* /* endOffset */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::textAfterOffset");
+ return QString();
+}
+QString Qt5AccessibleWidget::textAtOffset(int /* offset */,
+ QAccessible::TextBoundaryType /* boundaryType */,
+ int* /* startOffset */, int* /* endOffset */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::textAtOffset");
+ return QString();
+}
+QString Qt5AccessibleWidget::textBeforeOffset(int /* offset */,
+ QAccessible::TextBoundaryType /* boundaryType */,
+ int* /* startOffset */, int* /* endOffset */) const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTextInterface::textBeforeOffset");
+ return QString();
+}
+
+// QAccessibleEditableTextInterface
+
+void Qt5AccessibleWidget::deleteText(int startOffset, int endOffset)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+ xEditableText->deleteText(startOffset, endOffset);
+}
+
+void Qt5AccessibleWidget::insertText(int offset, const QString& text)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleEditableText> xEditableText(xAc, UNO_QUERY);
+ if (!xEditableText.is())
+ return;
+ xEditableText->insertText(toOUString(text), offset);
+}
+
+void Qt5AccessibleWidget::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;
+ xEditableText->replaceText(startOffset, endOffset, toOUString(text));
+}
+
+// QAccessibleValueInterface
+QVariant Qt5AccessibleWidget::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 Qt5AccessibleWidget::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 Qt5AccessibleWidget::minimumStepSize() const { return QVariant(); }
+QVariant Qt5AccessibleWidget::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 Qt5AccessibleWidget::setCurrentValue(const QVariant& value)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return;
+
+ Reference<XAccessibleValue> xValue(xAc, UNO_QUERY);
+ if (!xValue.is())
+ return;
+ xValue->setCurrentValue(Any(value.toDouble()));
+}
+
+// QAccessibleTable
+QAccessibleInterface* Qt5AccessibleWidget::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(
+ new Qt5XAccessible(xTable->getAccessibleCaption()));
+}
+
+QAccessibleInterface* Qt5AccessibleWidget::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;
+ return QAccessible::queryAccessibleInterface(
+ new Qt5XAccessible(xTable->getAccessibleCellAt(row, column)));
+}
+
+int Qt5AccessibleWidget::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 Qt5AccessibleWidget::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 Qt5AccessibleWidget::isColumnSelected(int /* column */) const { return true; }
+
+bool Qt5AccessibleWidget::isRowSelected(int /* row */) const { return true; }
+
+void Qt5AccessibleWidget::modelChange(QAccessibleTableModelChangeEvent*) {}
+
+int Qt5AccessibleWidget::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 Qt5AccessibleWidget::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 Qt5AccessibleWidget::selectColumn(int column)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->selectColumn(column);
+}
+
+bool Qt5AccessibleWidget::selectRow(int row)
+{
+ Reference<XAccessibleContext> xAc = getAccessibleContextImpl();
+ if (!xAc.is())
+ return false;
+
+ Reference<XAccessibleTableSelection> xTableSelection(xAc, UNO_QUERY);
+ if (!xTableSelection.is())
+ return false;
+ return xTableSelection->selectRow(row);
+}
+
+int Qt5AccessibleWidget::selectedCellCount() const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTableInterface::selectedCellCount");
+ return 0;
+}
+
+QList<QAccessibleInterface*> Qt5AccessibleWidget::selectedCells() const
+{
+ SAL_INFO("vcl.qt5", "Unsupported QAccessibleTableInterface::selectedCells");
+ return QList<QAccessibleInterface*>();
+}
+
+int Qt5AccessibleWidget::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> Qt5AccessibleWidget::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 Qt5AccessibleWidget::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> Qt5AccessibleWidget::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* Qt5AccessibleWidget::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(
+ new Qt5XAccessible(xTable->getAccessibleSummary()));
+}
+
+bool Qt5AccessibleWidget::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 Qt5AccessibleWidget::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);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Bitmap.cxx b/vcl/qt5/Qt5Bitmap.cxx
new file mode 100644
index 000000000..01c6ebc4d
--- /dev/null
+++ b/vcl/qt5/Qt5Bitmap.cxx
@@ -0,0 +1,301 @@
+/* -*- 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 <Qt5Bitmap.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Graphics.hxx>
+
+#include <QtGui/QImage>
+#include <QtCore/QVector>
+#include <QtGui/QColor>
+
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include <tools/helpers.hxx>
+
+Qt5Bitmap::Qt5Bitmap() {}
+
+Qt5Bitmap::Qt5Bitmap(const QImage& rImage) { m_pImage.reset(new QImage(rImage)); }
+
+bool Qt5Bitmap::Create(const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal)
+{
+ assert(
+ (nBitCount == 1 || nBitCount == 4 || nBitCount == 8 || nBitCount == 24 || nBitCount == 32)
+ && "Unsupported BitCount!");
+
+ if (nBitCount == 1)
+ assert(2 >= rPal.GetEntryCount());
+ if (nBitCount == 4)
+ assert(16 >= rPal.GetEntryCount());
+ if (nBitCount == 8)
+ assert(256 >= rPal.GetEntryCount());
+
+ if (nBitCount == 4)
+ {
+ m_pImage.reset();
+ m_aSize = rSize;
+ bool bFail = o3tl::checked_multiply<sal_uInt32>(rSize.Width(), nBitCount, m_nScanline);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ return false;
+ }
+ m_nScanline = AlignedWidth4Bytes(m_nScanline);
+ sal_uInt8* pBuffer = nullptr;
+ if (0 != m_nScanline && 0 != rSize.Height())
+ pBuffer = new sal_uInt8[m_nScanline * rSize.Height()];
+ m_pBuffer.reset(pBuffer);
+ }
+ else
+ {
+ m_pImage.reset(new QImage(toQSize(rSize), getBitFormat(nBitCount)));
+ m_pImage->fill(Qt::transparent);
+ m_pBuffer.reset();
+ }
+ m_aPalette = rPal;
+
+ auto count = rPal.GetEntryCount();
+ if (nBitCount != 4 && 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(aColorTable);
+ }
+ return true;
+}
+
+bool Qt5Bitmap::Create(const SalBitmap& rSalBmp)
+{
+ const Qt5Bitmap* pBitmap = static_cast<const Qt5Bitmap*>(&rSalBmp);
+ if (pBitmap->m_pImage)
+ {
+ m_pImage.reset(new QImage(*pBitmap->m_pImage));
+ m_pBuffer.reset();
+ }
+ else
+ {
+ m_aSize = pBitmap->m_aSize;
+ m_nScanline = pBitmap->m_nScanline;
+ sal_uInt8* pBuffer = nullptr;
+ if (0 != m_nScanline && 0 != m_aSize.Height())
+ {
+ sal_uInt32 nSize = m_nScanline * m_aSize.Height();
+ pBuffer = new sal_uInt8[nSize];
+ memcpy(pBuffer, pBitmap->m_pBuffer.get(), nSize);
+ }
+ m_pBuffer.reset(pBuffer);
+ m_pImage.reset();
+ }
+ m_aPalette = pBitmap->m_aPalette;
+ return true;
+}
+
+bool Qt5Bitmap::Create(const SalBitmap& rSalBmp, SalGraphics* pSalGraphics)
+{
+ const Qt5Bitmap* pBitmap = static_cast<const Qt5Bitmap*>(&rSalBmp);
+ Qt5Graphics* pGraphics = static_cast<Qt5Graphics*>(pSalGraphics);
+ QImage* pImage = pGraphics->m_pQImage;
+ m_pImage.reset(new QImage(pBitmap->m_pImage->convertToFormat(pImage->format())));
+ m_pBuffer.reset();
+ return true;
+}
+
+bool Qt5Bitmap::Create(const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount)
+{
+ assert((nNewBitCount == 1 || nNewBitCount == 4 || nNewBitCount == 8 || nNewBitCount == 24
+ || nNewBitCount == 32)
+ && "Unsupported BitCount!");
+
+ const Qt5Bitmap* pBitmap = static_cast<const Qt5Bitmap*>(&rSalBmp);
+ if (pBitmap->m_pBuffer)
+ {
+ if (nNewBitCount != 32)
+ return false;
+
+ // convert 4bit indexed palette to 32bit ARGB
+ m_pImage.reset(new QImage(pBitmap->m_aSize.Width(), pBitmap->m_aSize.Height(),
+ getBitFormat(nNewBitCount)));
+ m_pImage->fill(Qt::transparent);
+
+ // prepare a whole palette
+ const BitmapPalette& rPal = pBitmap->m_aPalette;
+ QVector<QRgb> colorTable(16);
+ int i = 0, maxEntry = pBitmap->m_aPalette.GetEntryCount();
+ assert(maxEntry <= 16 && maxEntry >= 0);
+ for (; i < maxEntry; ++i)
+ colorTable[i] = qRgb(rPal[i].GetRed(), rPal[i].GetGreen(), rPal[i].GetBlue());
+ for (; i < 16; ++i)
+ colorTable[i] = qRgb(0, 0, 0);
+
+ sal_uInt32* image_data = reinterpret_cast<sal_uInt32*>(m_pImage->bits());
+ sal_uInt8* buffer_data_pos = pBitmap->m_pBuffer.get();
+ sal_uInt32 nWidth = pBitmap->m_aSize.Height() / 2;
+ bool isOdd(0 != pBitmap->m_aSize.Height() % 2);
+
+ for (long h = 0; h < pBitmap->m_aSize.Height(); ++h)
+ {
+ sal_uInt8* buffer_data = buffer_data_pos;
+ buffer_data_pos += pBitmap->m_nScanline;
+ for (sal_uInt32 w = 0; w < nWidth; ++w)
+ {
+ *image_data = static_cast<sal_uInt32>(colorTable.at(*buffer_data >> 4));
+ ++image_data;
+ *image_data = static_cast<sal_uInt32>(colorTable.at(*buffer_data & 0xF));
+ ++image_data;
+ ++buffer_data;
+ }
+ if (isOdd)
+ {
+ *image_data = static_cast<sal_uInt32>(colorTable.at(*buffer_data >> 4));
+ ++image_data;
+ }
+ }
+ }
+ else
+ m_pImage.reset(new QImage(pBitmap->m_pImage->convertToFormat(getBitFormat(nNewBitCount))));
+ m_pBuffer.reset();
+ return true;
+}
+
+bool Qt5Bitmap::Create(const css::uno::Reference<css::rendering::XBitmapCanvas>& /*rBitmapCanvas*/,
+ Size& /*rSize*/, bool /*bMask*/)
+{
+ return false;
+}
+
+void Qt5Bitmap::Destroy()
+{
+ m_pImage.reset();
+ m_pBuffer.reset();
+}
+
+Size Qt5Bitmap::GetSize() const
+{
+ if (m_pBuffer)
+ return m_aSize;
+ else if (m_pImage)
+ return toSize(m_pImage->size());
+ return Size();
+}
+
+sal_uInt16 Qt5Bitmap::GetBitCount() const
+{
+ if (m_pBuffer)
+ return 4;
+ else if (m_pImage)
+ return getFormatBits(m_pImage->format());
+ return 0;
+}
+
+BitmapBuffer* Qt5Bitmap::AcquireBuffer(BitmapAccessMode /*nMode*/)
+{
+ static const BitmapPalette aEmptyPalette;
+
+ if (!(m_pImage || m_pBuffer))
+ return nullptr;
+
+ BitmapBuffer* pBuffer = new BitmapBuffer;
+
+ if (m_pBuffer)
+ {
+ pBuffer->mnWidth = m_aSize.Width();
+ pBuffer->mnHeight = m_aSize.Height();
+ pBuffer->mnBitCount = 4;
+ pBuffer->mpBits = m_pBuffer.get();
+ pBuffer->mnScanlineSize = m_nScanline;
+ }
+ else
+ {
+ 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 4:
+ pBuffer->mnFormat = ScanlineFormat::N4BitMsnPal | 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 Qt5Bitmap::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(aColorTable);
+ }
+ delete pBuffer;
+ if (nMode == BitmapAccessMode::Write)
+ InvalidateChecksum();
+}
+
+bool Qt5Bitmap::GetSystemData(BitmapSystemData& /*rData*/) { return false; }
+
+bool Qt5Bitmap::ScalingSupported() const { return false; }
+
+bool Qt5Bitmap::Scale(const double& /*rScaleX*/, const double& /*rScaleY*/,
+ BmpScaleFlag /*nScaleFlag*/)
+{
+ return false;
+}
+
+bool Qt5Bitmap::Replace(const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/,
+ sal_uInt8 /*nTol*/)
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Clipboard.cxx b/vcl/qt5/Qt5Clipboard.cxx
new file mode 100644
index 000000000..8720cfe44
--- /dev/null
+++ b/vcl/qt5/Qt5Clipboard.cxx
@@ -0,0 +1,243 @@
+/* -*- 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 <Qt5Clipboard.hxx>
+#include <Qt5Clipboard.moc>
+
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+
+#include <QtWidgets/QApplication>
+
+#include <Qt5Instance.hxx>
+#include <Qt5Transferable.hxx>
+#include <Qt5Tools.hxx>
+
+#include <cassert>
+#include <map>
+
+Qt5Clipboard::Qt5Clipboard(const OUString& aModeString, const QClipboard::Mode aMode)
+ : cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::datatransfer::clipboard::XFlushableClipboard,
+ XServiceInfo>(m_aMutex)
+ , m_aClipboardName(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, &Qt5Clipboard::handleChanged,
+ Qt::DirectConnection);
+
+ // explicitly queue an event, so we can eventually ignore it
+ connect(this, &Qt5Clipboard::clearClipboard, this, &Qt5Clipboard::handleClearClipboard,
+ Qt::QueuedConnection);
+}
+
+css::uno::Reference<css::uno::XInterface> Qt5Clipboard::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 static_cast<cppu::OWeakObject*>(new Qt5Clipboard(aModeString, iter->second));
+ SAL_WARN("vcl.qt5", "Ignoring unrecognized clipboard type: '" << aModeString << "'");
+ return css::uno::Reference<css::uno::XInterface>();
+}
+
+void Qt5Clipboard::flushClipboard()
+{
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!isOwner(m_aClipboardMode))
+ return;
+
+ QClipboard* pClipboard = QApplication::clipboard();
+ const Qt5MimeData* pQt5MimeData
+ = dynamic_cast<const Qt5MimeData*>(pClipboard->mimeData(m_aClipboardMode));
+ assert(pQt5MimeData);
+
+ QMimeData* pMimeCopy = nullptr;
+ if (pQt5MimeData && pQt5MimeData->deepCopy(&pMimeCopy))
+ {
+ m_bOwnClipboardChange = true;
+ pClipboard->setMimeData(pMimeCopy, m_aClipboardMode);
+ m_bOwnClipboardChange = false;
+ }
+ });
+}
+
+css::uno::Reference<css::datatransfer::XTransferable> Qt5Clipboard::getContents()
+{
+ 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 Qt5ClipboardTransferable
+ const QMimeData* pMimeData = QApplication::clipboard()->mimeData(m_aClipboardMode);
+ if (m_aContents.is())
+ {
+ const auto* pTrans = dynamic_cast<Qt5ClipboardTransferable*>(m_aContents.get());
+ assert(pTrans);
+ if (pTrans && pTrans->mimeData() == pMimeData)
+ return m_aContents;
+ }
+
+ m_aContents = new Qt5ClipboardTransferable(m_aClipboardMode, pMimeData);
+ return m_aContents;
+}
+
+void Qt5Clipboard::handleClearClipboard()
+{
+ if (!m_bDoClear)
+ return;
+ QApplication::clipboard()->clear(m_aClipboardMode);
+}
+
+void Qt5Clipboard::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 Qt5MimeData(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 Qt5Clipboard::handleChanged(QClipboard::Mode aMode)
+{
+ if (aMode != m_aClipboardMode)
+ return;
+
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ 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 Qt5Clipboard::getImplementationName() { return "com.sun.star.datatransfer.Qt5Clipboard"; }
+
+css::uno::Sequence<OUString> Qt5Clipboard::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+}
+
+sal_Bool Qt5Clipboard::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+OUString Qt5Clipboard::getName() { return m_aClipboardName; }
+
+sal_Int8 Qt5Clipboard::getRenderingCapabilities() { return 0; }
+
+void Qt5Clipboard::addClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ m_aListeners.push_back(listener);
+}
+
+void Qt5Clipboard::removeClipboardListener(
+ const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener),
+ m_aListeners.end());
+}
+
+bool Qt5Clipboard::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 Qt5Clipboard::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/Qt5Data.cxx b/vcl/qt5/Qt5Data.cxx
new file mode 100644
index 000000000..c50f8c57d
--- /dev/null
+++ b/vcl/qt5/Qt5Data.cxx
@@ -0,0 +1,334 @@
+/* -*- 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 <Qt5Data.hxx>
+
+#include <QtGui/QBitmap>
+#include <QtGui/QCursor>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QStyle>
+
+#include <sal/log.hxx>
+
+#include <unx/x11_cursors/ase_curs.h>
+#include <unx/x11_cursors/ase_mask.h>
+#include <unx/x11_cursors/asn_curs.h>
+#include <unx/x11_cursors/asn_mask.h>
+#include <unx/x11_cursors/asne_curs.h>
+#include <unx/x11_cursors/asne_mask.h>
+#include <unx/x11_cursors/asns_curs.h>
+#include <unx/x11_cursors/asns_mask.h>
+#include <unx/x11_cursors/asnswe_curs.h>
+#include <unx/x11_cursors/asnswe_mask.h>
+#include <unx/x11_cursors/asnw_curs.h>
+#include <unx/x11_cursors/asnw_mask.h>
+#include <unx/x11_cursors/ass_curs.h>
+#include <unx/x11_cursors/ass_mask.h>
+#include <unx/x11_cursors/asse_curs.h>
+#include <unx/x11_cursors/asse_mask.h>
+#include <unx/x11_cursors/assw_curs.h>
+#include <unx/x11_cursors/assw_mask.h>
+#include <unx/x11_cursors/asw_curs.h>
+#include <unx/x11_cursors/asw_mask.h>
+#include <unx/x11_cursors/aswe_curs.h>
+#include <unx/x11_cursors/aswe_mask.h>
+#include <unx/x11_cursors/chain_curs.h>
+#include <unx/x11_cursors/chain_mask.h>
+#include <unx/x11_cursors/chainnot_curs.h>
+#include <unx/x11_cursors/chainnot_mask.h>
+#include <unx/x11_cursors/chart_curs.h>
+#include <unx/x11_cursors/chart_mask.h>
+#include <unx/x11_cursors/copydata_curs.h>
+#include <unx/x11_cursors/copydata_mask.h>
+#include <unx/x11_cursors/copydlnk_curs.h>
+#include <unx/x11_cursors/copydlnk_mask.h>
+#include <unx/x11_cursors/copyfile_curs.h>
+#include <unx/x11_cursors/copyfile_mask.h>
+#include <unx/x11_cursors/copyfiles_curs.h>
+#include <unx/x11_cursors/copyfiles_mask.h>
+#include <unx/x11_cursors/copyflnk_curs.h>
+#include <unx/x11_cursors/copyflnk_mask.h>
+#include <unx/x11_cursors/crook_curs.h>
+#include <unx/x11_cursors/crook_mask.h>
+#include <unx/x11_cursors/crop_curs.h>
+#include <unx/x11_cursors/crop_mask.h>
+#include <unx/x11_cursors/detective_curs.h>
+#include <unx/x11_cursors/detective_mask.h>
+#include <unx/x11_cursors/drawarc_curs.h>
+#include <unx/x11_cursors/drawarc_mask.h>
+#include <unx/x11_cursors/drawbezier_curs.h>
+#include <unx/x11_cursors/drawbezier_mask.h>
+#include <unx/x11_cursors/drawcaption_curs.h>
+#include <unx/x11_cursors/drawcaption_mask.h>
+#include <unx/x11_cursors/drawcirclecut_curs.h>
+#include <unx/x11_cursors/drawcirclecut_mask.h>
+#include <unx/x11_cursors/drawconnect_curs.h>
+#include <unx/x11_cursors/drawconnect_mask.h>
+#include <unx/x11_cursors/drawellipse_curs.h>
+#include <unx/x11_cursors/drawellipse_mask.h>
+#include <unx/x11_cursors/drawfreehand_curs.h>
+#include <unx/x11_cursors/drawfreehand_mask.h>
+#include <unx/x11_cursors/drawline_curs.h>
+#include <unx/x11_cursors/drawline_mask.h>
+#include <unx/x11_cursors/drawpie_curs.h>
+#include <unx/x11_cursors/drawpie_mask.h>
+#include <unx/x11_cursors/drawpolygon_curs.h>
+#include <unx/x11_cursors/drawpolygon_mask.h>
+#include <unx/x11_cursors/drawrect_curs.h>
+#include <unx/x11_cursors/drawrect_mask.h>
+#include <unx/x11_cursors/drawtext_curs.h>
+#include <unx/x11_cursors/drawtext_mask.h>
+#include <unx/x11_cursors/fill_curs.h>
+#include <unx/x11_cursors/fill_mask.h>
+#include <unx/x11_cursors/hshear_curs.h>
+#include <unx/x11_cursors/hshear_mask.h>
+#include <unx/x11_cursors/linkdata_curs.h>
+#include <unx/x11_cursors/linkdata_mask.h>
+#include <unx/x11_cursors/linkfile_curs.h>
+#include <unx/x11_cursors/linkfile_mask.h>
+#include <unx/x11_cursors/magnify_curs.h>
+#include <unx/x11_cursors/magnify_mask.h>
+#include <unx/x11_cursors/mirror_curs.h>
+#include <unx/x11_cursors/mirror_mask.h>
+#include <unx/x11_cursors/movebezierweight_curs.h>
+#include <unx/x11_cursors/movebezierweight_mask.h>
+#include <unx/x11_cursors/movedata_curs.h>
+#include <unx/x11_cursors/movedata_mask.h>
+#include <unx/x11_cursors/movedlnk_curs.h>
+#include <unx/x11_cursors/movedlnk_mask.h>
+#include <unx/x11_cursors/movefile_curs.h>
+#include <unx/x11_cursors/movefile_mask.h>
+#include <unx/x11_cursors/movefiles_curs.h>
+#include <unx/x11_cursors/movefiles_mask.h>
+#include <unx/x11_cursors/moveflnk_curs.h>
+#include <unx/x11_cursors/moveflnk_mask.h>
+#include <unx/x11_cursors/movepoint_curs.h>
+#include <unx/x11_cursors/movepoint_mask.h>
+#include <unx/x11_cursors/nodrop_curs.h>
+#include <unx/x11_cursors/nodrop_mask.h>
+#include <unx/x11_cursors/pivotcol_curs.h>
+#include <unx/x11_cursors/pivotcol_mask.h>
+#include <unx/x11_cursors/pivotdel_curs.h>
+#include <unx/x11_cursors/pivotdel_mask.h>
+#include <unx/x11_cursors/pivotfld_curs.h>
+#include <unx/x11_cursors/pivotfld_mask.h>
+#include <unx/x11_cursors/pivotrow_curs.h>
+#include <unx/x11_cursors/pivotrow_mask.h>
+#include <unx/x11_cursors/rotate_curs.h>
+#include <unx/x11_cursors/rotate_mask.h>
+#include <unx/x11_cursors/tblsele_curs.h>
+#include <unx/x11_cursors/tblsele_mask.h>
+#include <unx/x11_cursors/tblsels_curs.h>
+#include <unx/x11_cursors/tblsels_mask.h>
+#include <unx/x11_cursors/tblselse_curs.h>
+#include <unx/x11_cursors/tblselse_mask.h>
+#include <unx/x11_cursors/tblselsw_curs.h>
+#include <unx/x11_cursors/tblselsw_mask.h>
+#include <unx/x11_cursors/tblselw_curs.h>
+#include <unx/x11_cursors/tblselw_mask.h>
+#include <unx/x11_cursors/vertcurs_curs.h>
+#include <unx/x11_cursors/vertcurs_mask.h>
+#include <unx/x11_cursors/vshear_curs.h>
+#include <unx/x11_cursors/vshear_mask.h>
+#include <unx/x11_cursors/wshide_curs.h>
+#include <unx/x11_cursors/wshide_mask.h>
+#include <unx/x11_cursors/wsshow_curs.h>
+#include <unx/x11_cursors/wsshow_mask.h>
+
+#include <unx/glyphcache.hxx>
+
+Qt5Data::Qt5Data(SalInstance* pInstance)
+ : GenericUnixSalData(SAL_DATA_QT5, pInstance)
+{
+ 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
+Qt5Data::~Qt5Data() {}
+
+static QCursor* getQCursorFromXBM(const unsigned char* pBitmap, const unsigned char* pMask,
+ int nWidth, int nHeight, int nXHot, int nYHot)
+{
+ QBitmap aPixmap = QBitmap::fromData(QSize(nWidth, nHeight), pBitmap);
+ QBitmap aMask = QBitmap::fromData(QSize(nWidth, nHeight), pMask);
+ return new QCursor(aPixmap, aMask, nXHot, nYHot);
+}
+
+#define MAKE_CURSOR(vcl_name, name) \
+ case vcl_name: \
+ pCursor = getQCursorFromXBM(name##curs##_bits, name##mask##_bits, name##curs_width, \
+ name##curs_height, 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& Qt5Data::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::OpenHandCursor);
+ 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_);
+ MAKE_CURSOR(PointerStyle::Fill, fill_);
+ MAKE_CURSOR(PointerStyle::MoveData, movedata_);
+ MAKE_CURSOR(PointerStyle::CopyData, copydata_);
+ MAKE_CURSOR(PointerStyle::MoveFile, movefile_);
+ MAKE_CURSOR(PointerStyle::CopyFile, copyfile_);
+ MAKE_CURSOR(PointerStyle::MoveFiles, movefiles_);
+ MAKE_CURSOR(PointerStyle::CopyFiles, copyfiles_);
+ MAKE_CURSOR(PointerStyle::NotAllowed, nodrop_);
+ MAKE_CURSOR(PointerStyle::Rotate, rotate_);
+ MAKE_CURSOR(PointerStyle::HShear, hshear_);
+ MAKE_CURSOR(PointerStyle::VShear, vshear_);
+ MAKE_CURSOR(PointerStyle::DrawLine, drawline_);
+ MAKE_CURSOR(PointerStyle::DrawRect, drawrect_);
+ MAKE_CURSOR(PointerStyle::DrawPolygon, drawpolygon_);
+ MAKE_CURSOR(PointerStyle::DrawBezier, drawbezier_);
+ MAKE_CURSOR(PointerStyle::DrawArc, drawarc_);
+ MAKE_CURSOR(PointerStyle::DrawPie, drawpie_);
+ MAKE_CURSOR(PointerStyle::DrawCircleCut, drawcirclecut_);
+ MAKE_CURSOR(PointerStyle::DrawEllipse, drawellipse_);
+ MAKE_CURSOR(PointerStyle::DrawConnect, drawconnect_);
+ MAKE_CURSOR(PointerStyle::DrawText, drawtext_);
+ MAKE_CURSOR(PointerStyle::Mirror, mirror_);
+ MAKE_CURSOR(PointerStyle::Crook, crook_);
+ MAKE_CURSOR(PointerStyle::Crop, crop_);
+ MAKE_CURSOR(PointerStyle::MovePoint, movepoint_);
+ MAKE_CURSOR(PointerStyle::MoveBezierWeight, movebezierweight_);
+ MAKE_CURSOR(PointerStyle::DrawFreehand, drawfreehand_);
+ MAKE_CURSOR(PointerStyle::DrawCaption, drawcaption_);
+ MAKE_CURSOR(PointerStyle::LinkData, linkdata_);
+ MAKE_CURSOR(PointerStyle::MoveDataLink, movedlnk_);
+ MAKE_CURSOR(PointerStyle::CopyDataLink, copydlnk_);
+ MAKE_CURSOR(PointerStyle::LinkFile, linkfile_);
+ MAKE_CURSOR(PointerStyle::MoveFileLink, moveflnk_);
+ MAKE_CURSOR(PointerStyle::CopyFileLink, copyflnk_);
+ MAKE_CURSOR(PointerStyle::Chart, chart_);
+ MAKE_CURSOR(PointerStyle::Detective, detective_);
+ MAKE_CURSOR(PointerStyle::PivotCol, pivotcol_);
+ MAKE_CURSOR(PointerStyle::PivotRow, pivotrow_);
+ MAKE_CURSOR(PointerStyle::PivotField, pivotfld_);
+ MAKE_CURSOR(PointerStyle::PivotDelete, pivotdel_);
+ MAKE_CURSOR(PointerStyle::Chain, chain_);
+ MAKE_CURSOR(PointerStyle::ChainNotAllowed, chainnot_);
+ MAKE_CURSOR(PointerStyle::AutoScrollN, asn_);
+ MAKE_CURSOR(PointerStyle::AutoScrollS, ass_);
+ MAKE_CURSOR(PointerStyle::AutoScrollW, asw_);
+ MAKE_CURSOR(PointerStyle::AutoScrollE, ase_);
+ MAKE_CURSOR(PointerStyle::AutoScrollNW, asnw_);
+ MAKE_CURSOR(PointerStyle::AutoScrollNE, asne_);
+ MAKE_CURSOR(PointerStyle::AutoScrollSW, assw_);
+ MAKE_CURSOR(PointerStyle::AutoScrollSE, asse_);
+ MAKE_CURSOR(PointerStyle::AutoScrollNS, asns_);
+ MAKE_CURSOR(PointerStyle::AutoScrollWE, aswe_);
+ MAKE_CURSOR(PointerStyle::AutoScrollNSWE, asnswe_);
+ MAKE_CURSOR(PointerStyle::TextVertical, vertcurs_);
+
+ MAKE_CURSOR(PointerStyle::TabSelectS, tblsels_);
+ MAKE_CURSOR(PointerStyle::TabSelectE, tblsele_);
+ MAKE_CURSOR(PointerStyle::TabSelectSE, tblselse_);
+ MAKE_CURSOR(PointerStyle::TabSelectW, tblselw_);
+ MAKE_CURSOR(PointerStyle::TabSelectSW, tblselsw_);
+
+ MAKE_CURSOR(PointerStyle::HideWhitespace, hidewhitespace_);
+ MAKE_CURSOR(PointerStyle::ShowWhitespace, showwhitespace_);
+ default:
+ break;
+ }
+ if (!pCursor)
+ {
+ pCursor = new QCursor(Qt::ArrowCursor);
+ SAL_WARN("vcl.qt5",
+ "pointer " << static_cast<int>(ePointerStyle) << " not implemented");
+ }
+
+ m_aCursors[ePointerStyle].reset(pCursor);
+ }
+
+ return *m_aCursors[ePointerStyle];
+}
+
+void Qt5Data::ErrorTrapPush() {}
+
+bool Qt5Data::ErrorTrapPop(bool /*bIgnoreError*/) { return false; }
+
+bool Qt5Data::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/Qt5DragAndDrop.cxx b/vcl/qt5/Qt5DragAndDrop.cxx
new file mode 100644
index 000000000..615b5d1f7
--- /dev/null
+++ b/vcl/qt5/Qt5DragAndDrop.cxx
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#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 <Qt5DragAndDrop.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Transferable.hxx>
+#include <Qt5Widget.hxx>
+
+#include <QtGui/QDrag>
+
+using namespace com::sun::star;
+
+Qt5DragSource::~Qt5DragSource() {}
+
+void Qt5DragSource::deinitialize() { m_pFrame = nullptr; }
+
+sal_Bool Qt5DragSource::isDragImageSupported() { return true; }
+
+sal_Int32 Qt5DragSource::getDefaultCursor(sal_Int8) { return 0; }
+
+void Qt5DragSource::initialize(const css::uno::Sequence<css::uno::Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw uno::RuntimeException("DragSource::initialize: Cannot install window event handler",
+ static_cast<OWeakObject*>(this));
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw uno::RuntimeException("DragSource::initialize: missing SalFrame",
+ static_cast<OWeakObject*>(this));
+ }
+
+ m_pFrame = reinterpret_cast<Qt5Frame*>(nFrame);
+ m_pFrame->registerDragSource(this);
+}
+
+void Qt5DragSource::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 Qt5MimeData(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 Qt5DragSource::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 Qt5DragSource::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclQt5DragSource";
+}
+
+sal_Bool SAL_CALL Qt5DragSource::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL Qt5DragSource::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.Qt5DragSource" };
+}
+
+Qt5DropTarget::Qt5DropTarget()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ , m_bActive(false)
+ , m_nDefaultActions(0)
+{
+}
+
+OUString SAL_CALL Qt5DropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclQt5DropTarget";
+}
+
+sal_Bool SAL_CALL Qt5DropTarget::supportsService(OUString const& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL Qt5DropTarget::getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.Qt5DropTarget" };
+}
+
+Qt5DropTarget::~Qt5DropTarget() {}
+
+void Qt5DropTarget::deinitialize()
+{
+ m_pFrame = nullptr;
+ m_bActive = false;
+}
+
+void Qt5DropTarget::initialize(const uno::Sequence<uno::Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw uno::RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ static_cast<OWeakObject*>(this));
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw uno::RuntimeException("DropTarget::initialize: missing SalFrame",
+ static_cast<OWeakObject*>(this));
+ }
+
+ m_nDropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
+
+ m_pFrame = reinterpret_cast<Qt5Frame*>(nFrame);
+ m_pFrame->registerDropTarget(this);
+ m_bActive = true;
+}
+
+void Qt5DropTarget::addDropTargetListener(
+ const uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ ::osl::Guard<::osl::Mutex> aGuard(m_aMutex);
+
+ m_aListeners.push_back(xListener);
+}
+
+void Qt5DropTarget::removeDropTargetListener(
+ const uno::Reference<css::datatransfer::dnd::XDropTargetListener>& xListener)
+{
+ ::osl::Guard<::osl::Mutex> aGuard(m_aMutex);
+
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), xListener),
+ m_aListeners.end());
+}
+
+sal_Bool Qt5DropTarget::isActive() { return m_bActive; }
+
+void Qt5DropTarget::setActive(sal_Bool bActive) { m_bActive = bActive; }
+
+sal_Int8 Qt5DropTarget::getDefaultActions() { return m_nDefaultActions; }
+
+void Qt5DropTarget::setDefaultActions(sal_Int8 nDefaultActions)
+{
+ m_nDefaultActions = nDefaultActions;
+}
+
+void Qt5DropTarget::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 Qt5DropTarget::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 Qt5DropTarget::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 Qt5DropTarget::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 Qt5DropTarget::acceptDrag(sal_Int8 dragOperation) { m_nDropAction = dragOperation; }
+
+void Qt5DropTarget::rejectDrag() { m_nDropAction = 0; }
+
+void Qt5DropTarget::acceptDrop(sal_Int8 dropOperation) { m_nDropAction = dropOperation; }
+
+void Qt5DropTarget::rejectDrop() { m_nDropAction = 0; }
+
+void Qt5DropTarget::dropComplete(sal_Bool success)
+{
+ m_bDropSuccessful = (m_bDropSuccessful && success);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5FilePicker.cxx b/vcl/qt5/Qt5FilePicker.cxx
new file mode 100644
index 000000000..d648a5d94
--- /dev/null
+++ b/vcl/qt5/Qt5FilePicker.cxx
@@ -0,0 +1,945 @@
+/* -*- 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 <Qt5FilePicker.hxx>
+#include <Qt5FilePicker.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Widget.hxx>
+#include <Qt5Instance.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 <strings.hrc>
+
+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.Qt5FilePicker" };
+}
+}
+
+Qt5FilePicker::Qt5FilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode, bool bUseNative)
+ : Qt5FilePicker_Base(m_aHelperMutex)
+ , m_context(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(VclResId(STR_FPICKER_FOLDER_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()));
+}
+
+Qt5FilePicker::~Qt5FilePicker()
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this]() {
+ // must delete it in main thread, otherwise
+ // QSocketNotifier::setEnabled() will crash us
+ m_pFileDialog.reset();
+ });
+}
+
+void SAL_CALL
+Qt5FilePicker::addFilePickerListener(const uno::Reference<XFilePickerListener>& xListener)
+{
+ SolarMutexGuard aGuard;
+ m_xListener = xListener;
+}
+
+void SAL_CALL Qt5FilePicker::removeFilePickerListener(const uno::Reference<XFilePickerListener>&)
+{
+ SolarMutexGuard aGuard;
+ m_xListener.clear();
+}
+
+void SAL_CALL Qt5FilePicker::setTitle(const OUString& title)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [this, &title]() { m_pFileDialog->setWindowTitle(toQString(title)); });
+}
+
+sal_Int16 SAL_CALL Qt5FilePicker::execute()
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ sal_uInt16 ret;
+ pSalInst->RunInMainThread([&ret, this]() { ret = execute(); });
+ return ret;
+ }
+
+ QWidget* pTransientParent = m_pParentWidget;
+ if (!pTransientParent)
+ {
+ vcl::Window* pWindow = ::Application::GetActiveTopWindow();
+ if (pWindow)
+ {
+ Qt5Frame* pFrame = dynamic_cast<Qt5Frame*>(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);
+ int result = m_pFileDialog->exec();
+ xDesktop->removeTerminateListener(this);
+ m_pFileDialog->setParent(nullptr, m_pFileDialog->windowFlags());
+
+ if (QFileDialog::Rejected == result)
+ return ExecutableDialogResults::CANCEL;
+ return ExecutableDialogResults::OK;
+}
+
+void SAL_CALL Qt5FilePicker::setMultiSelectionMode(sal_Bool multiSelect)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5FilePicker::setDefaultName(const OUString& name)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &name]() { m_pFileDialog->selectFile(toQString(name)); });
+}
+
+void SAL_CALL Qt5FilePicker::setDisplayDirectory(const OUString& dir)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &dir]() {
+ QString qDir(toQString(dir));
+ m_pFileDialog->setDirectoryUrl(QUrl(qDir));
+ });
+}
+
+OUString SAL_CALL Qt5FilePicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+ OUString ret;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread(
+ [&ret, this]() { ret = toOUString(m_pFileDialog->directoryUrl().toString()); });
+ return ret;
+}
+
+uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getFiles()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq;
+}
+
+uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getSelectedFiles()
+{
+ SolarMutexGuard g;
+ QList<QUrl> urls;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([&urls, this]() { urls = m_pFileDialog->selectedUrls(); });
+
+ uno::Sequence<OUString> seq(urls.size());
+
+ 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.qt5", "cannot convert <" << extUrl << "> from locale encoding to UTF-8");
+ intUrl = extUrl;
+ }
+ seq[i++] = intUrl;
+ }
+
+ return seq;
+}
+
+void SAL_CALL Qt5FilePicker::appendFilter(const OUString& title, const OUString& filter)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5 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 Qt5FilePicker::setCurrentFilter(const OUString& title)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, &title]() {
+ m_aCurrentFilter = m_aTitleToFilterMap.value(toQString(title).replace("/", "\\/"));
+ });
+}
+
+OUString SAL_CALL Qt5FilePicker::getCurrentFilter()
+{
+ SolarMutexGuard g;
+ QString filter;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5FilePicker::appendFilterGroup(const OUString& rGroupTitle,
+ const uno::Sequence<beans::StringPair>& filters)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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 Qt5FilePicker::handleGetListValue(const QComboBox* pWidget, sal_Int16 nControlAction)
+{
+ uno::Any aAny;
+ switch (nControlAction)
+ {
+ case ControlActions::GET_ITEMS:
+ {
+ Sequence<OUString> aItemList(pWidget->count());
+ for (sal_Int32 i = 0; i < pWidget->count(); ++i)
+ aItemList[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.qt5",
+ "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+ return aAny;
+}
+
+void Qt5FilePicker::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.qt5",
+ "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+
+ pWidget->setEnabled(pWidget->count() > 0);
+}
+
+void SAL_CALL Qt5FilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
+ const uno::Any& value)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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.qt5", "set value on unknown control " << controlId);
+}
+
+uno::Any SAL_CALL Qt5FilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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.qt5", "get value on unknown control " << controlId);
+
+ return res;
+}
+
+void SAL_CALL Qt5FilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, controlId, enable]() {
+ if (m_aCustomWidgetsMap.contains(controlId))
+ m_aCustomWidgetsMap.value(controlId)->setEnabled(enable);
+ else
+ SAL_WARN("vcl.qt5", "enable unknown control " << controlId);
+ });
+}
+
+void SAL_CALL Qt5FilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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.qt5", "set label on unknown control " << controlId);
+}
+
+OUString SAL_CALL Qt5FilePicker::getLabel(sal_Int16 controlId)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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.qt5", "get label on unknown control " << controlId);
+
+ return toOUString(label);
+}
+
+QString Qt5FilePicker::getResString(const char* pResId)
+{
+ QString aResString;
+
+ if (pResId == nullptr)
+ return aResString;
+
+ aResString = toQString(VclResId(pResId));
+
+ return aResString.replace('~', '&');
+}
+
+void Qt5FilePicker::addCustomControl(sal_Int16 controlId)
+{
+ QWidget* widget = nullptr;
+ QLabel* label = nullptr;
+ const char* resId = nullptr;
+ QCheckBox* pCheckbox = nullptr;
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ resId = STR_FPICKER_AUTO_EXTENSION;
+ break;
+ case CHECKBOX_PASSWORD:
+ resId = STR_FPICKER_PASSWORD;
+ break;
+ case CHECKBOX_FILTEROPTIONS:
+ resId = STR_FPICKER_FILTER_OPTIONS;
+ break;
+ case CHECKBOX_READONLY:
+ resId = STR_FPICKER_READONLY;
+ break;
+ case CHECKBOX_LINK:
+ resId = STR_FPICKER_INSERT_AS_LINK;
+ break;
+ case CHECKBOX_PREVIEW:
+ resId = STR_FPICKER_SHOW_PREVIEW;
+ break;
+ case CHECKBOX_SELECTION:
+ resId = STR_FPICKER_SELECTION;
+ break;
+ case CHECKBOX_GPGENCRYPTION:
+ resId = STR_FPICKER_GPGENCRYPT;
+ break;
+ case PUSHBUTTON_PLAY:
+ resId = STR_FPICKER_PLAY;
+ break;
+ case LISTBOX_VERSION:
+ resId = STR_FPICKER_VERSION;
+ break;
+ case LISTBOX_TEMPLATE:
+ resId = STR_FPICKER_TEMPLATES;
+ break;
+ case LISTBOX_IMAGE_TEMPLATE:
+ resId = STR_FPICKER_IMAGE_TEMPLATE;
+ break;
+ case LISTBOX_IMAGE_ANCHOR:
+ resId = STR_FPICKER_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 Qt5FilePicker::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(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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);
+ }
+
+ const char* resId = nullptr;
+ switch (acceptMode)
+ {
+ case QFileDialog::AcceptOpen:
+ resId = STR_FPICKER_OPEN;
+ break;
+ case QFileDialog::AcceptSave:
+ resId = STR_FPICKER_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())
+ {
+ css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysWinPeer(xParentWindow,
+ css::uno::UNO_QUERY);
+ if (xSysWinPeer.is())
+ {
+ // 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 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 && long(pData->aWindow) == aWindowHandle;
+ });
+ if (it != pFrames.end())
+ m_pParentWidget = static_cast<Qt5Frame*>(*it)->asChild();
+ }
+ }
+}
+
+void SAL_CALL Qt5FilePicker::cancel() { m_pFileDialog->reject(); }
+
+void SAL_CALL Qt5FilePicker::disposing(const lang::EventObject& rEvent)
+{
+ uno::Reference<XFilePickerListener> xFilePickerListener(rEvent.Source, uno::UNO_QUERY);
+
+ if (xFilePickerListener.is())
+ {
+ removeFilePickerListener(xFilePickerListener);
+ }
+}
+
+void SAL_CALL Qt5FilePicker::queryTermination(const css::lang::EventObject&)
+{
+ throw css::frame::TerminationVetoException();
+}
+
+void SAL_CALL Qt5FilePicker::notifyTermination(const css::lang::EventObject&)
+{
+ SolarMutexGuard aGuard;
+ m_pFileDialog->reject();
+}
+
+OUString SAL_CALL Qt5FilePicker::getImplementationName()
+{
+ return "com.sun.star.ui.dialogs.Qt5FilePicker";
+}
+
+sal_Bool SAL_CALL Qt5FilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL Qt5FilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+void Qt5FilePicker::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.qt5",
+ "Unable to retrieve unambiguous file extension. Will not add any automatically.");
+ bSetAutoExtension = false;
+ }
+ }
+
+ if (!bSetAutoExtension)
+ m_pFileDialog->setDefaultSuffix("");
+}
+
+void Qt5FilePicker::filterSelected(const QString&)
+{
+ FilePickerEvent aEvent;
+ aEvent.ElementId = LISTBOX_FILTER;
+ SAL_INFO("vcl.qt5", "filter changed");
+ if (m_xListener.is())
+ m_xListener->controlStateChanged(aEvent);
+}
+
+void Qt5FilePicker::currentChanged(const QString&)
+{
+ FilePickerEvent aEvent;
+ SAL_INFO("vcl.qt5", "file selection changed");
+ if (m_xListener.is())
+ m_xListener->fileSelectionChanged(aEvent);
+}
+
+OUString Qt5FilePicker::getDirectory()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq[0];
+}
+
+void Qt5FilePicker::setDescription(const OUString&) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Font.cxx b/vcl/qt5/Qt5Font.cxx
new file mode 100644
index 000000000..2ab614043
--- /dev/null
+++ b/vcl/qt5/Qt5Font.cxx
@@ -0,0 +1,161 @@
+/* -*- 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 <Qt5Font.hxx>
+#include <Qt5Tools.hxx>
+
+#include <QtGui/QFont>
+#include <QtGui/QRawFont>
+
+static inline void applyWeight(Qt5Font& 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(Qt5Font& 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(Qt5Font& 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;
+ }
+}
+
+Qt5Font::Qt5Font(const PhysicalFontFace& rPFF, const FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+{
+ setFamily(toQString(rPFF.GetFamilyName()));
+ applyWeight(*this, rPFF.GetWeight());
+ setPixelSize(rFSP.mnHeight);
+ applyStretch(*this, rPFF.GetWidthType());
+ applyStyle(*this, rFSP.GetItalic());
+}
+
+static hb_blob_t* getFontTable(hb_face_t*, hb_tag_t nTableTag, void* pUserData)
+{
+ char pTagName[5];
+ LogicalFontInstance::DecodeOpenTypeTag(nTableTag, pTagName);
+
+ Qt5Font* pFont = static_cast<Qt5Font*>(pUserData);
+ QRawFont aRawFont(QRawFont::fromFont(*pFont));
+ 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;
+}
+
+hb_font_t* Qt5Font::ImplInitHbFont()
+{
+ return InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
+}
+
+bool Qt5Font::GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) const { return false; }
+
+bool Qt5Font::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool) const
+{
+ QRawFont aRawFont(QRawFont::fromFont(*this));
+ rRect = toRectangle(aRawFont.boundingRect(nId).toAlignedRect());
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5FontFace.cxx b/vcl/qt5/Qt5FontFace.cxx
new file mode 100644
index 000000000..8ac2f87d1
--- /dev/null
+++ b/vcl/qt5/Qt5FontFace.cxx
@@ -0,0 +1,206 @@
+/* -*- 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 <Qt5FontFace.hxx>
+#include <Qt5Font.hxx>
+#include <Qt5Tools.hxx>
+
+#include <sft.hxx>
+#include <impfontcharmap.hxx>
+#include <fontinstance.hxx>
+#include <fontselect.hxx>
+#include <PhysicalFontCollection.hxx>
+
+#include <QtGui/QFont>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QFontInfo>
+#include <QtGui/QRawFont>
+
+using namespace vcl;
+
+Qt5FontFace::Qt5FontFace(const Qt5FontFace& rSrc)
+ : PhysicalFontFace(rSrc)
+ , m_aFontId(rSrc.m_aFontId)
+{
+ if (rSrc.m_xCharMap.is())
+ m_xCharMap = rSrc.m_xCharMap;
+}
+
+FontWeight Qt5FontFace::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 Qt5FontFace::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 Qt5FontFace::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 Qt5FontFace::fillAttributesFromQFont(const QFont& rFont, FontAttributes& rFA)
+{
+ QFontInfo aFontInfo(rFont);
+
+ rFA.SetFamilyName(toOUString(aFontInfo.family()));
+ if (IsStarSymbol(toOUString(aFontInfo.family())))
+ rFA.SetSymbolFlag(true);
+ rFA.SetStyleName(toOUString(aFontInfo.styleName()));
+ rFA.SetPitch(aFontInfo.fixedPitch() ? PITCH_FIXED : PITCH_VARIABLE);
+ rFA.SetWeight(Qt5FontFace::toFontWeight(aFontInfo.weight()));
+ rFA.SetItalic(Qt5FontFace::toFontItalic(aFontInfo.style()));
+ rFA.SetWidthType(Qt5FontFace::toFontWidth(rFont.stretch()));
+}
+
+Qt5FontFace* Qt5FontFace::fromQFont(const QFont& rFont)
+{
+ FontAttributes aFA;
+ fillAttributesFromQFont(rFont, aFA);
+ return new Qt5FontFace(aFA, rFont.toString());
+}
+
+Qt5FontFace* Qt5FontFace::fromQFontDatabase(const QString& aFamily, const QString& aStyle)
+{
+ QFontDatabase aFDB;
+ FontAttributes aFA;
+ aFA.SetFamilyName(toOUString(aFamily));
+ if (IsStarSymbol(aFA.GetFamilyName()))
+ aFA.SetSymbolFlag(true);
+ aFA.SetStyleName(toOUString(aStyle));
+ aFA.SetPitch(aFDB.isFixedPitch(aFamily, aStyle) ? PITCH_FIXED : PITCH_VARIABLE);
+ aFA.SetWeight(Qt5FontFace::toFontWeight(aFDB.weight(aFamily, aStyle)));
+ aFA.SetItalic(aFDB.italic(aFamily, aStyle) ? ITALIC_NORMAL : ITALIC_NONE);
+ return new Qt5FontFace(aFA, aFamily + "," + aStyle);
+}
+
+Qt5FontFace::Qt5FontFace(const FontAttributes& rFA, const QString& rFontID)
+ : PhysicalFontFace(rFA)
+ , m_aFontId(rFontID)
+ , m_bFontCapabilitiesRead(false)
+{
+}
+
+sal_IntPtr Qt5FontFace::GetFontId() const { return reinterpret_cast<sal_IntPtr>(&m_aFontId); }
+
+rtl::Reference<LogicalFontInstance>
+Qt5FontFace::CreateFontInstance(const FontSelectPattern& rFSD) const
+{
+ return new Qt5Font(*this, rFSD);
+}
+
+const FontCharMapRef& Qt5FontFace::GetFontCharMap() const
+{
+ if (m_xCharMap.is())
+ return m_xCharMap;
+
+ QFont aFont;
+ aFont.fromString(m_aFontId);
+ QRawFont aRawFont(QRawFont::fromFont(aFont));
+ QByteArray aCMapTable = aRawFont.fontTable("cmap");
+ if (aCMapTable.isEmpty())
+ {
+ m_xCharMap = new FontCharMap();
+ return m_xCharMap;
+ }
+
+ CmapResult aCmapResult;
+ if (ParseCMAP(reinterpret_cast<const unsigned char*>(aCMapTable.data()), aCMapTable.size(),
+ aCmapResult))
+ m_xCharMap = new FontCharMap(aCmapResult);
+
+ return m_xCharMap;
+}
+
+bool Qt5FontFace::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const
+{
+ // read this only once per font
+ if (m_bFontCapabilitiesRead)
+ {
+ rFontCapabilities = m_aFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+ }
+ m_bFontCapabilitiesRead = true;
+
+ QFont aFont;
+ aFont.fromString(m_aFontId);
+ QRawFont aRawFont(QRawFont::fromFont(aFont));
+ QByteArray aOS2Table = aRawFont.fontTable("OS/2");
+ if (!aOS2Table.isEmpty())
+ {
+ vcl::getTTCoverage(m_aFontCapabilities.oUnicodeRange, m_aFontCapabilities.oCodePageRange,
+ reinterpret_cast<const unsigned char*>(aOS2Table.data()),
+ aOS2Table.size());
+ }
+
+ rFontCapabilities = m_aFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Frame.cxx b/vcl/qt5/Qt5Frame.cxx
new file mode 100644
index 000000000..936216b9d
--- /dev/null
+++ b/vcl/qt5/Qt5Frame.cxx
@@ -0,0 +1,1449 @@
+/* -*- 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 <Qt5Frame.hxx>
+#include <Qt5Frame.moc>
+
+#include <Qt5Data.hxx>
+#include <Qt5DragAndDrop.hxx>
+#include <Qt5Graphics.hxx>
+#include <Qt5Instance.hxx>
+#include <Qt5MainWindow.hxx>
+#include <Qt5Menu.hxx>
+#include <Qt5SvpGraphics.hxx>
+#include <Qt5System.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Transferable.hxx>
+#include <Qt5Widget.hxx>
+
+#include <QtCore/QMimeData>
+#include <QtCore/QPoint>
+#include <QtCore/QSize>
+#include <QtCore/QThread>
+#include <QtCore/QVersionNumber>
+#include <QtGui/QDragMoveEvent>
+#include <QtGui/QDropEvent>
+#include <QtGui/QIcon>
+#include <QtGui/QWindow>
+#include <QtGui/QScreen>
+#include <QtWidgets/QStyle>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QDesktopWidget>
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QMainWindow>
+
+#if QT5_USING_X11
+#include <QtX11Extras/QX11Info>
+#include <xcb/xproto.h>
+#if QT5_HAVE_XCB_ICCCM
+#include <xcb/xcb_icccm.h>
+#endif
+#endif
+
+#include <saldatabasic.hxx>
+#include <window.h>
+#include <vcl/layout.hxx>
+#include <vcl/syswin.hxx>
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include <cairo.h>
+#include <headless/svpgdi.hxx>
+
+#if QT5_USING_X11 && QT5_HAVE_XCB_ICCCM
+static bool g_bNeedsWmHintsWindowGroup = true;
+static xcb_atom_t g_aXcbClientLeaderAtom = 0;
+#endif
+
+static void SvpDamageHandler(void* handle, sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
+{
+ Qt5Frame* pThis = static_cast<Qt5Frame*>(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;
+}
+}
+
+Qt5Frame::Qt5Frame(Qt5Frame* pParent, SalFrameStyleFlags nStyle, bool bUseCairo)
+ : m_pTopLevel(nullptr)
+ , m_bUseCairo(bUseCairo)
+ , m_pSvpGraphics(nullptr)
+ , m_bNullRegion(true)
+ , m_bGraphicsInUse(false)
+ , m_bGraphicsInvalid(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)
+{
+ Qt5Instance* pInst = static_cast<Qt5Instance*>(GetSalData()->m_pInstance);
+ 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;
+ 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::FLOAT | SalFrameStyleFlags::TOOLTIP))
+ aWinFlags |= Qt::ToolTip;
+ else if ((nStyle & SalFrameStyleFlags::FLOAT)
+ && !(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION))
+ aWinFlags |= Qt::Popup;
+ 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 Qt5MainWindow(*this, aWinFlags);
+ m_pQWidget = new Qt5Widget(*this, aWinFlags);
+ m_pTopLevel->setCentralWidget(m_pQWidget);
+ m_pTopLevel->setFocusProxy(m_pQWidget);
+ }
+ else
+ m_pQWidget = new Qt5Widget(*this, aWinFlags);
+
+ if (pParent && !(pParent->m_nStyle & SalFrameStyleFlags::PLUG))
+ {
+ QWindow* pParentWindow = pParent->GetQWidget()->window()->windowHandle();
+ QWindow* pChildWindow = asChild()->window()->windowHandle();
+ if (pParentWindow && pChildWindow && (pParentWindow != pChildWindow))
+ pChildWindow->setTransientParent(pParentWindow);
+ }
+
+ // Calling 'QWidget::winId()' implicitly enables native windows to be used
+ // rather than "alien widgets" that are unknown to the windowing system,
+ // s. https://doc.qt.io/qt-5/qwidget.html#native-widgets-vs-alien-widgets
+ // Avoid this on Wayland due to problems with missing 'mouseMoveEvent's,
+ // s. tdf#122293/QTBUG-75766
+ const bool bWayland = QGuiApplication::platformName() == "wayland";
+ if (!bWayland)
+ m_aSystemData.aWindow = m_pQWidget->winId();
+ else
+ {
+ // TODO implement as needed for Wayland,
+ // s.a. commit c0d4f3ad3307c which did this for gtk3
+ // QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
+ // m_aSystemData.pDisplay = native->nativeResourceForWindow("display", nullptr);
+ // m_aSystemData.aWindow = reinterpret_cast<unsigned long>(
+ // native->nativeResourceForWindow("surface", m_pQWidget->windowHandle()));
+ }
+
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ //m_aSystemData.pSalFrame = this;
+ m_aSystemData.pWidget = m_pQWidget;
+ //m_aSystemData.nScreen = m_nXScreen.getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Qt5;
+ if (!bWayland)
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+ else
+ m_aSystemData.platform = SystemEnvData::Platform::Wayland;
+
+ SetIcon(SV_ICON_ID_OFFICE);
+
+ fixICCCMwindowGroup();
+}
+
+void Qt5Frame::fixICCCMwindowGroup()
+{
+#if QT5_USING_X11 && QT5_HAVE_XCB_ICCCM
+ // older Qt5 just sets WM_CLIENT_LEADER, but not the XCB_ICCCM_WM_HINT_WINDOW_GROUP
+ // see Qt commit 0de4b326d8 ("xcb: fix issue with dialogs hidden by other windows")
+ // or QTBUG-46626. So LO has to set this itself to help some WMs.
+ if (!g_bNeedsWmHintsWindowGroup)
+ return;
+ g_bNeedsWmHintsWindowGroup = false;
+
+ if (QGuiApplication::platformName() != "xcb")
+ return;
+ if (QVersionNumber::fromString(qVersion()) >= QVersionNumber(5, 12))
+ return;
+
+ xcb_connection_t* conn = QX11Info::connection();
+ xcb_window_t win = asChild()->winId();
+
+ xcb_icccm_wm_hints_t hints;
+
+ xcb_get_property_cookie_t prop_cookie = xcb_icccm_get_wm_hints_unchecked(conn, win);
+ if (!xcb_icccm_get_wm_hints_reply(conn, prop_cookie, &hints, nullptr))
+ return;
+
+ if (hints.flags & XCB_ICCCM_WM_HINT_WINDOW_GROUP)
+ return;
+
+ if (g_aXcbClientLeaderAtom == 0)
+ {
+ const char* const leader_name = "WM_CLIENT_LEADER\0";
+ xcb_intern_atom_cookie_t atom_cookie
+ = xcb_intern_atom(conn, 1, strlen(leader_name), leader_name);
+ xcb_intern_atom_reply_t* atom_reply = xcb_intern_atom_reply(conn, atom_cookie, nullptr);
+ if (!atom_reply)
+ return;
+ g_aXcbClientLeaderAtom = atom_reply->atom;
+ free(atom_reply);
+ }
+
+ g_bNeedsWmHintsWindowGroup = true;
+
+ prop_cookie = xcb_get_property(conn, 0, win, g_aXcbClientLeaderAtom, XCB_ATOM_WINDOW, 0, 1);
+ xcb_get_property_reply_t* prop_reply = xcb_get_property_reply(conn, prop_cookie, nullptr);
+ if (!prop_reply)
+ return;
+
+ if (xcb_get_property_value_length(prop_reply) != 4)
+ {
+ free(prop_reply);
+ return;
+ }
+
+ xcb_window_t leader = *static_cast<xcb_window_t*>(xcb_get_property_value(prop_reply));
+ free(prop_reply);
+
+ hints.flags |= XCB_ICCCM_WM_HINT_WINDOW_GROUP;
+ hints.window_group = leader;
+ xcb_icccm_set_wm_hints(conn, win, &hints);
+#else
+ (void)this; // avoid loplugin:staticmethods
+#endif
+}
+
+Qt5Frame::~Qt5Frame()
+{
+ Qt5Instance* pInst = static_cast<Qt5Instance*>(GetSalData()->m_pInstance);
+ pInst->eraseFrame(this);
+ delete asChild();
+ m_aSystemData.aShellWindow = 0;
+}
+
+void Qt5Frame::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()));
+}
+
+void Qt5Frame::InitQt5SvpGraphics(Qt5SvpGraphics* pQt5SvpGraphics)
+{
+ int width = 640;
+ int height = 480;
+ m_pSvpGraphics = pQt5SvpGraphics;
+ m_pSurface.reset(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height));
+ m_pSvpGraphics->setSurface(m_pSurface.get(), basegfx::B2IVector(width, height));
+ cairo_surface_set_user_data(m_pSurface.get(), Qt5SvpGraphics::getDamageKey(), &m_aDamageHandler,
+ nullptr);
+}
+
+SalGraphics* Qt5Frame::AcquireGraphics()
+{
+ if (m_bGraphicsInUse)
+ return nullptr;
+
+ m_bGraphicsInUse = true;
+
+ if (m_bUseCairo)
+ {
+ if (!m_pOurSvpGraphics || m_bGraphicsInvalid)
+ {
+ m_pOurSvpGraphics.reset(new Qt5SvpGraphics(this));
+ InitQt5SvpGraphics(m_pOurSvpGraphics.get());
+ m_bGraphicsInvalid = false;
+ }
+ return m_pOurSvpGraphics.get();
+ }
+ else
+ {
+ if (!m_pQt5Graphics || m_bGraphicsInvalid)
+ {
+ m_pQt5Graphics.reset(new Qt5Graphics(this));
+ m_pQImage.reset(
+ new QImage(m_pQWidget->size() * devicePixelRatioF(), Qt5_DefaultFormat32));
+ m_pQImage->fill(Qt::transparent);
+ m_pQt5Graphics->ChangeQImage(m_pQImage.get());
+ m_bGraphicsInvalid = false;
+ }
+ return m_pQt5Graphics.get();
+ }
+}
+
+void Qt5Frame::ReleaseGraphics(SalGraphics* pSalGraph)
+{
+ (void)pSalGraph;
+ if (m_bUseCairo)
+ assert(pSalGraph == m_pOurSvpGraphics.get());
+ else
+ assert(pSalGraph == m_pQt5Graphics.get());
+ m_bGraphicsInUse = false;
+}
+
+bool Qt5Frame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ Qt5Instance* pInst = static_cast<Qt5Instance*>(GetSalData()->m_pInstance);
+ pInst->PostEvent(this, pData.release(), SalEvent::UserEvent);
+ return true;
+}
+
+QWidget* Qt5Frame::asChild() const { return m_pTopLevel ? m_pTopLevel : m_pQWidget; }
+
+qreal Qt5Frame::devicePixelRatioF() const { return asChild()->devicePixelRatioF(); }
+
+bool Qt5Frame::isWindow() const { return asChild()->isWindow(); }
+
+QWindow* Qt5Frame::windowHandle() const
+{
+ // set attribute 'Qt::WA_NativeWindow' first to make sure a window handle actually exists
+ QWidget* pChild = asChild();
+ pChild->setAttribute(Qt::WA_NativeWindow);
+ return pChild->windowHandle();
+}
+
+QScreen* Qt5Frame::screen() const
+{
+ QWindow* const pWindow = windowHandle();
+ return pWindow ? pWindow->screen() : nullptr;
+}
+
+bool Qt5Frame::isMinimized() const { return asChild()->isMinimized(); }
+
+bool Qt5Frame::isMaximized() const { return asChild()->isMaximized(); }
+
+void Qt5Frame::SetWindowStateImpl(Qt::WindowStates eState)
+{
+ return asChild()->setWindowState(eState);
+}
+
+void Qt5Frame::SetTitle(const OUString& rTitle)
+{
+ m_pQWidget->window()->setWindowTitle(toQString(rTitle));
+}
+
+void Qt5Frame::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 Qt5Frame::SetMenu(SalMenu* pMenu) { m_pSalMenu = static_cast<Qt5Menu*>(pMenu); }
+
+void Qt5Frame::DrawMenuBar() { /* not needed */}
+
+void Qt5Frame::SetExtendedFrameStyle(SalExtStyle /*nExtStyle*/) { /* not needed */}
+
+void Qt5Frame::Show(bool bVisible, bool /*bNoActivate*/)
+{
+ assert(m_pQWidget);
+
+ SetDefaultSize();
+ SetDefaultPos();
+
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ pSalInst->RunInMainThread([this, bVisible]() { asChild()->setVisible(bVisible); });
+}
+
+void Qt5Frame::SetMinClientSize(long nWidth, long nHeight)
+{
+ if (!isChild())
+ {
+ const qreal fRatio = devicePixelRatioF();
+ asChild()->setMinimumSize(round(nWidth / fRatio), round(nHeight / fRatio));
+ }
+}
+
+void Qt5Frame::SetMaxClientSize(long nWidth, long nHeight)
+{
+ if (!isChild())
+ {
+ const qreal fRatio = devicePixelRatioF();
+ asChild()->setMaximumSize(round(nWidth / fRatio), round(nHeight / fRatio));
+ }
+}
+
+void Qt5Frame::SetDefaultPos()
+{
+ if (!m_bDefaultPos)
+ return;
+
+ // center on parent
+ if (m_pParent)
+ {
+ const qreal fRatio = devicePixelRatioF();
+ QWidget* const pWindow = m_pParent->GetQWidget()->window();
+ QWidget* const pWidget = asChild();
+ QPoint aPos = pWindow->rect().center() - pWidget->rect().center();
+ SetPosSize(round(aPos.x() * fRatio), round(aPos.y() * fRatio), 0, 0,
+ SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y);
+ assert(!m_bDefaultPos);
+ }
+ else
+ m_bDefaultPos = false;
+}
+
+Size Qt5Frame::CalcDefaultSize()
+{
+ assert(isWindow());
+
+ Size aSize;
+ if (!m_bFullScreen)
+ {
+ const QScreen* pScreen = screen();
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ aSize = bestmaxFrameSizeForScreenSize(
+ toSize(pScreen ? pScreen->size() : QApplication::desktop()->screenGeometry(0).size()));
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ else
+ {
+ if (!m_bFullScreenSpanAll)
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ aSize = toSize(
+ QApplication::desktop()->screenGeometry(maGeometry.nDisplayScreenNumber).size());
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ else
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ int nLeftScreen = QApplication::desktop()->screenNumber(QPoint(0, 0));
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ aSize = toSize(QApplication::screens()[nLeftScreen]->availableVirtualGeometry().size());
+ }
+ }
+
+ return aSize;
+}
+
+void Qt5Frame::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 Qt5Frame::SetPosSize(long nX, long nY, long nWidth, 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.nWidth;
+ else if (!(nFlags & SAL_FRAME_POSSIZE_HEIGHT))
+ nHeight = maGeometry.nHeight;
+
+ 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.nWidth = nWidth;
+ if (nHeight > 0)
+ maGeometry.nHeight = nHeight;
+ }
+ }
+
+ if (nFlags & (SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y))
+ {
+ if (m_pParent)
+ {
+ const SalFrameGeometry& aParentGeometry = m_pParent->maGeometry;
+ if (QGuiApplication::isRightToLeft())
+ nX = aParentGeometry.nX + aParentGeometry.nWidth - nX - maGeometry.nWidth - 1;
+ else
+ nX += aParentGeometry.nX;
+ nY += aParentGeometry.nY;
+
+ Qt5MainWindow* pTopLevel = m_pParent->GetTopLevelWindow();
+ if (pTopLevel && pTopLevel->menuBar() && pTopLevel->menuBar()->isVisible())
+ nY += round(pTopLevel->menuBar()->geometry().height() * devicePixelRatioF());
+ }
+
+ if (!(nFlags & SAL_FRAME_POSSIZE_X))
+ nX = maGeometry.nX;
+ else if (!(nFlags & SAL_FRAME_POSSIZE_Y))
+ nY = maGeometry.nY;
+
+ // assume the reposition happened
+ // needed for calculations and will eventually be corrected by events later
+ maGeometry.nX = nX;
+ maGeometry.nY = nY;
+
+ m_bDefaultPos = false;
+ asChild()->move(round(nX / devicePixelRatioF()), round(nY / devicePixelRatioF()));
+ }
+}
+
+void Qt5Frame::GetClientSize(long& rWidth, long& rHeight)
+{
+ rWidth = round(m_pQWidget->width() * devicePixelRatioF());
+ rHeight = round(m_pQWidget->height() * devicePixelRatioF());
+}
+
+void Qt5Frame::GetWorkArea(tools::Rectangle& rRect)
+{
+ if (!isWindow())
+ return;
+ QScreen* pScreen = screen();
+ if (!pScreen)
+ return;
+
+ QSize aSize = pScreen->availableVirtualSize() * devicePixelRatioF();
+ rRect = tools::Rectangle(0, 0, aSize.width(), aSize.height());
+}
+
+SalFrame* Qt5Frame::GetParent() const { return m_pParent; }
+
+void Qt5Frame::SetModal(bool bModal)
+{
+ if (isWindow())
+ {
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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();
+
+ pChild->setWindowModality(bModal ? Qt::WindowModal : Qt::NonModal);
+
+ if (bWasVisible)
+ pChild->show();
+ });
+ }
+}
+
+bool Qt5Frame::GetModal() const { return isWindow() && windowHandle()->isModal(); }
+
+void Qt5Frame::SetWindowState(const SalFrameState* pState)
+{
+ if (!isWindow() || !pState || isChild(true, false))
+ return;
+
+ const WindowStateMask nMaxGeometryMask
+ = WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width | WindowStateMask::Height
+ | WindowStateMask::MaximizedX | WindowStateMask::MaximizedY
+ | WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
+
+ if ((pState->mnMask & WindowStateMask::State) && (pState->mnState & WindowStateState::Maximized)
+ && !isMaximized() && (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask)
+ {
+ const qreal fRatio = devicePixelRatioF();
+ QWidget* const pChild = asChild();
+ pChild->resize(ceil(pState->mnWidth / fRatio), ceil(pState->mnHeight / fRatio));
+ pChild->move(ceil(pState->mnX / fRatio), ceil(pState->mnY / fRatio));
+ SetWindowStateImpl(Qt::WindowMaximized);
+ }
+ else if (pState->mnMask
+ & (WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width
+ | WindowStateMask::Height))
+ {
+ sal_uInt16 nPosSizeFlags = 0;
+ if (pState->mnMask & WindowStateMask::X)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
+ if (pState->mnMask & WindowStateMask::Y)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
+ if (pState->mnMask & WindowStateMask::Width)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if (pState->mnMask & WindowStateMask::Height)
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ SetPosSize(pState->mnX, pState->mnY, pState->mnWidth, pState->mnHeight, nPosSizeFlags);
+ }
+ else if (pState->mnMask & WindowStateMask::State && !isChild())
+ {
+ if (pState->mnState & WindowStateState::Maximized)
+ SetWindowStateImpl(Qt::WindowMaximized);
+ else if (pState->mnState & WindowStateState::Minimized)
+ SetWindowStateImpl(Qt::WindowMinimized);
+ else
+ SetWindowStateImpl(Qt::WindowNoState);
+ }
+}
+
+bool Qt5Frame::GetWindowState(SalFrameState* pState)
+{
+ pState->mnState = WindowStateState::Normal;
+ pState->mnMask = WindowStateMask::State;
+ if (isMinimized() /*|| !windowHandle()*/)
+ pState->mnState |= WindowStateState::Minimized;
+ else if (isMaximized())
+ {
+ pState->mnState |= WindowStateState::Maximized;
+ }
+ else
+ {
+ // geometry() is the drawable area, which is wanted here
+ QRect rect = scaledQRect(asChild()->geometry(), devicePixelRatioF());
+ pState->mnX = rect.x();
+ pState->mnY = rect.y();
+ pState->mnWidth = rect.width();
+ pState->mnHeight = rect.height();
+ pState->mnMask |= WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width
+ | WindowStateMask::Height;
+ }
+
+ return true;
+}
+
+void Qt5Frame::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.nDisplayScreenNumber;
+ 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 Qt5Frame::StartPresentation(bool bStart)
+{
+// meh - so there's no Qt platform independent solution
+// https://forum.qt.io/topic/38504/solved-qdialog-in-fullscreen-disable-os-screensaver
+#if QT5_USING_X11
+ std::optional<unsigned int> aRootWindow;
+ std::optional<Display*> aDisplay;
+
+ if (QX11Info::isPlatformX11())
+ {
+ aRootWindow = QX11Info::appRootWindow();
+ aDisplay = QX11Info::display();
+ }
+
+ m_ScreenSaverInhibitor.inhibit(bStart, "presentation", QX11Info::isPlatformX11(), aRootWindow,
+ aDisplay);
+#else
+ (void)bStart;
+#endif
+}
+
+void Qt5Frame::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 Qt5Frame::ToTop(SalFrameToTop nFlags)
+{
+ QWidget* const pWidget = asChild();
+ if (isWindow() && !(nFlags & SalFrameToTop::GrabFocusOnly))
+ pWidget->raise();
+ if ((nFlags & SalFrameToTop::RestoreWhenMin) || (nFlags & SalFrameToTop::ForegroundTask))
+ pWidget->activateWindow();
+ else if ((nFlags & SalFrameToTop::GrabFocus) || (nFlags & SalFrameToTop::GrabFocusOnly))
+ {
+ pWidget->activateWindow();
+ pWidget->setFocus();
+ }
+}
+
+void Qt5Frame::SetPointer(PointerStyle ePointerStyle)
+{
+ QWindow* pWindow = m_pQWidget->window()->windowHandle();
+ if (!pWindow)
+ return;
+ if (ePointerStyle == m_ePointerStyle)
+ return;
+ m_ePointerStyle = ePointerStyle;
+
+ pWindow->setCursor(static_cast<Qt5Data*>(GetSalData())->getCursor(ePointerStyle));
+}
+
+void Qt5Frame::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 Qt5Frame::SetPointerPos(long nX, 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)));
+}
+
+void Qt5Frame::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 Qt5Widget::paintEvent() is
+ // destroyed, so that state should be safely flushed.
+}
+
+bool Qt5Frame::ShowTooltip(const OUString& rText, const tools::Rectangle& rHelpArea)
+{
+ QRect aHelpArea(toQRect(rHelpArea));
+ if (QGuiApplication::isRightToLeft())
+ aHelpArea.moveLeft(maGeometry.nWidth - aHelpArea.width() - aHelpArea.left() - 1);
+ QToolTip::showText(QCursor::pos(), toQString(rText), m_pQWidget, aHelpArea);
+ return true;
+}
+
+void Qt5Frame::SetInputContext(SalInputContext* pContext)
+{
+ if (!pContext)
+ return;
+
+ if (!(pContext->mnOptions & InputContextFlags::Text))
+ return;
+
+ m_pQWidget->setAttribute(Qt::WA_InputMethodEnabled);
+}
+
+void Qt5Frame::EndExtTextInput(EndExtTextInputFlags /*nFlags*/)
+{
+ Qt5Widget* pQt5Widget = static_cast<Qt5Widget*>(m_pQWidget);
+ if (pQt5Widget)
+ pQt5Widget->endExtTextInput();
+}
+
+OUString Qt5Frame::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_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 Qt5Frame::MapUnicodeToKeyCode(sal_Unicode /*aUnicode*/, LanguageType /*aLangType*/,
+ vcl::KeyCode& /*rKeyCode*/)
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType Qt5Frame::GetInputLanguage()
+{
+ // fallback
+ return LANGUAGE_DONTKNOW;
+}
+
+static Color toColor(const QColor& rColor)
+{
+ return Color(rColor.red(), rColor.green(), rColor.blue());
+}
+
+void Qt5Frame::UpdateSettings(AllSettings& rSettings)
+{
+ if (Qt5Data::noNativeControls())
+ return;
+
+ StyleSettings style(rSettings.GetStyleSettings());
+
+ // 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.SetWindowTextColor(aText);
+ style.SetToolTextColor(aText);
+
+ // Base
+ style.SetFieldColor(aBase);
+ style.SetWindowColor(aBase);
+ style.SetActiveTabColor(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
+ style.SetHighlightColor(aHigh);
+ style.SetHighlightTextColor(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)));
+
+ const int flash_time = QApplication::cursorFlashTime();
+ style.SetCursorBlinkTime(flash_time != 0 ? flash_time / 2 : STYLE_CURSOR_NOBLINKTIME);
+
+ // 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());
+
+ // Icon theme
+ style.SetPreferredIconTheme(toOUString(QIcon::themeName()));
+
+ // 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)));
+
+ m_bGraphicsInvalid = true;
+ rSettings.SetStyleSettings(style);
+}
+
+void Qt5Frame::Beep() { QApplication::beep(); }
+
+SalFrame::SalPointerState Qt5Frame::GetPointerState()
+{
+ SalPointerState aState;
+ aState.maPos = toPoint(QCursor::pos() * devicePixelRatioF());
+ aState.maPos.Move(-maGeometry.nX, -maGeometry.nY);
+ aState.mnState = GetMouseModCode(QGuiApplication::mouseButtons())
+ | GetKeyModCode(QGuiApplication::keyboardModifiers());
+ return aState;
+}
+
+KeyIndicatorState Qt5Frame::GetIndicatorState() { return KeyIndicatorState(); }
+
+void Qt5Frame::SimulateKeyPress(sal_uInt16 nKeyCode)
+{
+ SAL_WARN("vcl.qt5", "missing simulate keypress " << nKeyCode);
+}
+
+void Qt5Frame::SetParent(SalFrame* pNewParent) { m_pParent = static_cast<Qt5Frame*>(pNewParent); }
+
+bool Qt5Frame::SetPluginParent(SystemParentData* /*pNewParent*/)
+{
+ //FIXME: no SetPluginParent impl. for qt5
+ return false;
+}
+
+void Qt5Frame::ResetClipRegion() { m_bNullRegion = true; }
+
+void Qt5Frame::BeginSetClipRegion(sal_uInt32)
+{
+ m_aRegion = QRegion(QRect(QPoint(0, 0), m_pQWidget->size()));
+}
+
+void Qt5Frame::UnionClipRegion(long nX, long nY, long nWidth, long nHeight)
+{
+ m_aRegion
+ = m_aRegion.united(scaledQRect(QRect(nX, nY, nWidth, nHeight), 1 / devicePixelRatioF()));
+}
+
+void Qt5Frame::EndSetClipRegion() { m_bNullRegion = false; }
+
+void Qt5Frame::SetScreenNumber(unsigned int nScreen)
+{
+ if (isWindow())
+ {
+ QWindow* const pWindow = windowHandle();
+ if (pWindow)
+ {
+ QList<QScreen*> screens = QApplication::screens();
+ if (static_cast<int>(nScreen) < screens.size() || m_bFullScreenSpanAll)
+ {
+ QRect screenGeo;
+
+ if (!m_bFullScreenSpanAll)
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ screenGeo = QApplication::desktop()->screenGeometry(nScreen);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ pWindow->setScreen(QApplication::screens()[nScreen]);
+ }
+ else // special case: fullscreen over all available screens
+ {
+ assert(m_bFullScreen);
+ // left-most screen
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ int nLeftScreen = QApplication::desktop()->screenNumber(QPoint(0, 0));
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ // entire virtual desktop
+ screenGeo = QApplication::screens()[nLeftScreen]->availableVirtualGeometry();
+ pWindow->setScreen(QApplication::screens()[nLeftScreen]);
+ pWindow->setGeometry(screenGeo);
+ nScreen = nLeftScreen;
+ }
+
+ // 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.nDisplayScreenNumber = nScreen;
+ }
+ }
+}
+
+void Qt5Frame::SetApplicationID(const OUString& rWMClass)
+{
+#if QT5_USING_X11
+ if (QGuiApplication::platformName() != "xcb" || !m_pTopLevel)
+ return;
+
+ 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, m_pTopLevel->winId(),
+ XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, data_len, data);
+ delete[] data;
+#else
+ (void)rWMClass;
+#endif
+}
+
+// Drag'n'drop foo
+
+void Qt5Frame::registerDragSource(Qt5DragSource* pDragSource)
+{
+ assert(!m_pDragSource);
+ m_pDragSource = pDragSource;
+}
+
+void Qt5Frame::deregisterDragSource(Qt5DragSource const* pDragSource)
+{
+ assert(m_pDragSource == pDragSource);
+ (void)pDragSource;
+ m_pDragSource = nullptr;
+}
+
+void Qt5Frame::registerDropTarget(Qt5DropTarget* pDropTarget)
+{
+ assert(!m_pDropTarget);
+ m_pDropTarget = pDropTarget;
+ m_pQWidget->setAcceptDrops(true);
+}
+
+void Qt5Frame::deregisterDropTarget(Qt5DropTarget 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 Qt5MimeData* pQt5MimeData = dynamic_cast<const Qt5MimeData*>(pMimeData);
+ if (!pQt5MimeData)
+ xTransferable = new Qt5DnDTransferable(pMimeData);
+ else
+ xTransferable = pQt5MimeData->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
+ const Qt::KeyboardModifiers eKeyMod = pEvent->keyboardModifiers();
+ 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 Qt5MimeData*>(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 Qt5Frame::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);
+ const Point aPos = toPoint(pEvent->pos() * devicePixelRatioF());
+
+ 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 Qt5Frame::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());
+ const Point aPos = toPoint(pEvent->pos() * devicePixelRatioF());
+
+ 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())
+ {
+ Qt5Widget* pWidget = dynamic_cast<Qt5Widget*>(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 Qt5Frame::handleDragLeave()
+{
+ css::datatransfer::dnd::DropTargetEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(m_pDropTarget);
+ m_pDropTarget->fire_dragExit(aEvent);
+ m_bInDrag = false;
+}
+
+cairo_t* Qt5Frame::getCairoContext() const
+{
+ cairo_t* cr = nullptr;
+ if (m_bUseCairo)
+ {
+ cr = cairo_create(m_pSurface.get());
+ assert(cr);
+ }
+ return cr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Graphics.cxx b/vcl/qt5/Qt5Graphics.cxx
new file mode 100644
index 000000000..34f610812
--- /dev/null
+++ b/vcl/qt5/Qt5Graphics.cxx
@@ -0,0 +1,128 @@
+/* -*- 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 <Qt5Graphics.hxx>
+
+#include <Qt5Data.hxx>
+#include <Qt5Font.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Graphics_Controls.hxx>
+#include <Qt5Painter.hxx>
+
+#include <QtGui/QImage>
+#include <QtGui/QPainter>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QWidget>
+
+Qt5Graphics::Qt5Graphics( Qt5Frame *pFrame, QImage *pQImage )
+ : m_pFrame( pFrame )
+ , m_pQImage( pQImage )
+ , m_aLineColor( 0x00, 0x00, 0x00 )
+ , m_aFillColor( 0xFF, 0xFF, 0XFF )
+ , m_eCompositionMode( QPainter::CompositionMode_SourceOver )
+ , m_pFontCollection( nullptr )
+ , m_pTextStyle{ nullptr, }
+ , m_aTextColor( 0x00, 0x00, 0x00 )
+{
+ ResetClipRegion();
+
+ if (!initWidgetDrawBackends(false))
+ {
+ if (!Qt5Data::noNativeControls())
+ m_pWidgetDraw.reset(new Qt5Graphics_Controls(*this));
+ }
+ if (m_pFrame)
+ setDevicePixelRatioF(m_pFrame->devicePixelRatioF());
+}
+
+Qt5Graphics::~Qt5Graphics() { ReleaseFonts(); }
+
+void Qt5Graphics::ChangeQImage(QImage* pQImage)
+{
+ m_pQImage = pQImage;
+ ResetClipRegion();
+}
+
+SalGraphicsImpl* Qt5Graphics::GetImpl() const { return nullptr; }
+
+SystemGraphicsData Qt5Graphics::GetGraphicsData() const { return SystemGraphicsData(); }
+
+bool Qt5Graphics::supportsOperation(OutDevSupportType eType) const
+{
+ switch (eType)
+ {
+ case OutDevSupportType::B2DDraw:
+ case OutDevSupportType::TransparentRect:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool Qt5Graphics::SupportsCairo() const { return false; }
+
+cairo::SurfaceSharedPtr
+Qt5Graphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
+{
+ return nullptr;
+}
+
+cairo::SurfaceSharedPtr Qt5Graphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/,
+ int /*y*/, int /*width*/, int /*height*/) const
+{
+ return nullptr;
+}
+
+cairo::SurfaceSharedPtr Qt5Graphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/,
+ const BitmapSystemData& /*rData*/,
+ const Size& /*rSize*/) const
+{
+ return nullptr;
+}
+
+css::uno::Any Qt5Graphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/,
+ const basegfx::B2ISize& /*rSize*/) const
+{
+ return css::uno::Any();
+}
+
+SystemFontData Qt5Graphics::GetSysFontData(int /*nFallbacklevel*/) const
+{
+ return SystemFontData();
+}
+
+#endif
+
+void Qt5Graphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(dynamic_cast<Qt5Graphics_Controls*>(m_pWidgetDraw.get()));
+ assert(!rDamagedRegion.IsEmpty());
+
+ QImage* pImage = static_cast<Qt5Graphics_Controls*>(m_pWidgetDraw.get())->getImage();
+ QImage blit(*pImage);
+ blit.setDevicePixelRatio(1);
+ Qt5Painter aPainter(*this);
+ aPainter.drawImage(QPoint(rDamagedRegion.getX(), rDamagedRegion.getY()), blit);
+ aPainter.update(toQRect(rDamagedRegion));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Graphics_Controls.cxx b/vcl/qt5/Qt5Graphics_Controls.cxx
new file mode 100644
index 000000000..dce9c1687
--- /dev/null
+++ b/vcl/qt5/Qt5Graphics_Controls.cxx
@@ -0,0 +1,1111 @@
+/* -*- 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 <Qt5Graphics_Controls.hxx>
+
+#include <QtGui/QPainter>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QFrame>
+#include <QtWidgets/QLabel>
+
+#include <qt5/Qt5Tools.hxx>
+#include <qt5/Qt5GraphicsBase.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_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;
+}
+
+Qt5Graphics_Controls::Qt5Graphics_Controls(const Qt5GraphicsBase& rGraphics)
+ : m_rGraphics(rGraphics)
+{
+}
+
+bool Qt5Graphics_Controls::isNativeControlSupported(ControlType type, ControlPart part)
+{
+ switch (type)
+ {
+ case ControlType::Tooltip:
+ case ControlType::Progress:
+ case ControlType::ListNode:
+ return (part == ControlPart::Entire);
+
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ return (part == ControlPart::Entire) || (part == ControlPart::Focus);
+ case ControlType::Pushbutton:
+ return (part == ControlPart::Entire);
+
+ 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 Qt5Graphics_Controls::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option)
+{
+ return QApplication::style()->pixelMetric(metric, option);
+}
+
+inline QSize Qt5Graphics_Controls::sizeFromContents(QStyle::ContentsType type,
+ const QStyleOption* option,
+ const QSize& contentsSize)
+{
+ return QApplication::style()->sizeFromContents(type, option, contentsSize);
+}
+
+inline QRect Qt5Graphics_Controls::subControlRect(QStyle::ComplexControl control,
+ const QStyleOptionComplex* option,
+ QStyle::SubControl subControl)
+{
+ return QApplication::style()->subControlRect(control, option, subControl);
+}
+
+inline QRect Qt5Graphics_Controls::subElementRect(QStyle::SubElement element,
+ const QStyleOption* option)
+{
+ return QApplication::style()->subElementRect(element, option);
+}
+
+void Qt5Graphics_Controls::draw(QStyle::ControlElement element, QStyleOption* option, QImage* image,
+ QStyle::State const state, QRect rect)
+{
+ const QRect& targetRect = !rect.isNull() ? rect : image->rect();
+
+ option->state |= state;
+ option->rect = downscale(targetRect);
+
+ QPainter painter(image);
+ QApplication::style()->drawControl(element, option, &painter);
+}
+
+void Qt5Graphics_Controls::draw(QStyle::PrimitiveElement element, QStyleOption* option,
+ QImage* image, QStyle::State const state, QRect rect)
+{
+ const QRect& targetRect = !rect.isNull() ? rect : image->rect();
+
+ option->state |= state;
+ option->rect = downscale(targetRect);
+
+ QPainter painter(image);
+ QApplication::style()->drawPrimitive(element, option, &painter);
+}
+
+void Qt5Graphics_Controls::draw(QStyle::ComplexControl element, QStyleOptionComplex* option,
+ QImage* image, QStyle::State const state)
+{
+ const QRect& targetRect = image->rect();
+
+ option->state |= state;
+ option->rect = downscale(targetRect);
+
+ QPainter painter(image);
+ QApplication::style()->drawComplexControl(element, option, &painter);
+}
+
+void Qt5Graphics_Controls::drawFrame(QStyle::PrimitiveElement element, QImage* image,
+ 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;
+
+ QPainter painter(image);
+ if (bClip)
+ painter.setClipRegion(QRegion(aRect).subtracted(aRect.adjusted(fw, fw, -fw, -fw)));
+ QApplication::style()->drawPrimitive(element, &option, &painter);
+}
+
+void Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_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)
+ {
+ assert(part == ControlPart::Entire);
+ QStyleOptionButton option;
+ draw(QStyle::CE_PushButton, &option, m_image.get(),
+ 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());
+ }
+ else if (part == ControlPart::Entire)
+ {
+ QStyleOptionMenuItem option;
+ draw(QStyle::CE_MenuBarEmptyArea, &option, m_image.get(),
+ 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(),
+ 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(),
+ 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(),
+ 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());
+ // Try hard to get any frame!
+ QStyleOptionFrame frame;
+ draw(QStyle::PE_FrameMenu, &frame, m_image.get());
+ draw(QStyle::PE_FrameWindow, &frame, m_image.get());
+ 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());
+ }
+ else if ((type == ControlType::Toolbar) && (part == ControlPart::Entire))
+ {
+ QStyleOptionToolBar option;
+ draw(QStyle::CE_ToolBar, &option, m_image.get(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value), aRect);
+ }
+ else if (type == ControlType::Editbox || type == ControlType::MultilineEditbox)
+ {
+ drawFrame(QStyle::PE_FrameLineEdit, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value), false);
+ }
+ else if (type == ControlType::Combobox)
+ {
+ QStyleOptionComboBox option;
+ option.editable = true;
+ draw(QStyle::CC_ComboBox, &option, m_image.get(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value), true,
+ QStyle::PM_ComboBoxFrameWidth);
+ break;
+ case ControlPart::SubEdit:
+ draw(QStyle::CE_ComboBoxLabel, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ case ControlPart::Entire:
+ draw(QStyle::CC_ComboBox, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ break;
+ case ControlPart::ButtonDown:
+ option.subControls = QStyle::SC_ComboBoxArrow;
+ draw(QStyle::CC_ComboBox, &option, m_image.get(),
+ 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());
+ }
+ else if (type == ControlType::ListHeader)
+ {
+ QStyleOptionHeader option;
+ draw(QStyle::CE_HeaderSection, &option, m_image.get(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (part == ControlPart::Focus)
+ {
+ QStyleOptionFocusRect option;
+ draw(QStyle::PE_FrameFocusRect, &option, m_image.get(),
+ 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 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(),
+ 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(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (part == ControlPart::Focus)
+ {
+ QStyleOptionFocusRect option;
+ draw(QStyle::PE_FrameFocusRect, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ }
+ else if (type == ControlType::Tooltip)
+ {
+ QStyleOption option;
+ draw(QStyle::PE_PanelTipLabel, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::Frame)
+ {
+ drawFrame(QStyle::PE_Frame, m_image.get(), 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());
+ }
+ 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());
+ }
+ else if (type == ControlType::Progress && part == ControlPart::Entire)
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ QStyleOptionProgressBarV2 option;
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ option.minimum = 0;
+ option.maximum = widgetRect.width();
+ option.progress = value.getNumericVal();
+
+ draw(QStyle::CE_ProgressBar, &option, m_image.get(),
+ vclStateValue2StateFlag(nControlState, value));
+ }
+ else if (type == ControlType::TabItem && part == ControlPart::Entire)
+ {
+ QStyleOptionTab sot;
+ fillQStyleOptionTab(value, sot);
+ draw(QStyle::CE_TabBarTabShape, &sot, m_image.get(),
+ 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(),
+ vclStateValue2StateFlag(nControlState, value), aRect);
+ }
+ else
+ {
+ returnVal = false;
+ }
+
+ return returnVal;
+}
+
+bool Qt5Graphics_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;
+ }
+ }
+ 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);
+ 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);
+ aContentSize.setHeight(QApplication::fontMetrics().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);
+ aContentSize.setHeight(QApplication::fontMetrics().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)
+ {
+ auto nStyle = static_cast<DrawFrameFlags>(val.getNumericVal() & 0xFFF0);
+ if (nStyle & DrawFrameFlags::NoDraw)
+ {
+ 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 Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_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 Qt5Graphics_Controls::downscale(const QSize& size, Round eRound)
+{
+ return QSize(downscale(size.width(), eRound), downscale(size.height(), eRound));
+}
+
+inline QSize Qt5Graphics_Controls::upscale(const QSize& size, Round eRound)
+{
+ return QSize(upscale(size.width(), eRound), upscale(size.height(), eRound));
+}
+
+inline QPoint Qt5Graphics_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/Qt5Graphics_GDI.cxx b/vcl/qt5/Qt5Graphics_GDI.cxx
new file mode 100644
index 000000000..cfebca7c6
--- /dev/null
+++ b/vcl/qt5/Qt5Graphics_GDI.cxx
@@ -0,0 +1,712 @@
+/* -*- 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 <Qt5Graphics.hxx>
+
+#include <Qt5Bitmap.hxx>
+#include <Qt5Painter.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>
+
+static 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;
+}
+
+bool Qt5Graphics::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, !getAntiAliasB2DDraw(), false);
+ m_aClipPath.swap(aPath);
+ if (!m_aClipRegion.isEmpty())
+ {
+ QRegion aRegion;
+ m_aClipRegion.swap(aRegion);
+ }
+ }
+ return true;
+}
+
+void Qt5Graphics::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 Qt5Graphics::drawPixel(long nX, long nY)
+{
+ Qt5Painter aPainter(*this);
+ aPainter.drawPoint(nX, nY);
+ aPainter.update(nX, nY, 1, 1);
+}
+
+void Qt5Graphics::drawPixel(long nX, long nY, Color nColor)
+{
+ Qt5Painter aPainter(*this);
+ aPainter.setPen(toQColor(nColor));
+ aPainter.setPen(Qt::SolidLine);
+ aPainter.drawPoint(nX, nY);
+ aPainter.update(nX, nY, 1, 1);
+}
+
+void Qt5Graphics::drawLine(long nX1, long nY1, long nX2, long nY2)
+{
+ Qt5Painter aPainter(*this);
+ aPainter.drawLine(nX1, nY1, nX2, nY2);
+
+ long tmp;
+ if (nX1 > nX2)
+ {
+ tmp = nX1;
+ nX1 = nX2;
+ nX2 = tmp;
+ }
+ if (nY1 > nY2)
+ {
+ tmp = nY1;
+ nY1 = nY2;
+ nY2 = tmp;
+ }
+ aPainter.update(nX1, nY1, nX2 - nX1 + 1, nY2 - nY1 + 1);
+}
+
+void Qt5Graphics::drawRect(long nX, long nY, long nWidth, long nHeight)
+{
+ if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ return;
+
+ Qt5Painter aPainter(*this, true);
+ if (SALCOLOR_NONE != m_aFillColor)
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
+ if (SALCOLOR_NONE != m_aLineColor)
+ aPainter.drawRect(nX, nY, nWidth - 1, nHeight - 1);
+ aPainter.update(nX, nY, nWidth, nHeight);
+}
+
+void Qt5Graphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ if (0 == nPoints)
+ return;
+
+ Qt5Painter aPainter(*this);
+ QPoint* pPoints = new QPoint[nPoints];
+ QPoint aTopLeft(pPtAry->mnX, pPtAry->mnY);
+ QPoint aBottomRight = aTopLeft;
+ for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
+ {
+ pPoints[i] = QPoint(pPtAry->mnX, pPtAry->mnY);
+ if (pPtAry->mnX < aTopLeft.x())
+ aTopLeft.setX(pPtAry->mnX);
+ if (pPtAry->mnY < aTopLeft.y())
+ aTopLeft.setY(pPtAry->mnY);
+ if (pPtAry->mnX > aBottomRight.x())
+ aBottomRight.setX(pPtAry->mnX);
+ if (pPtAry->mnY > aBottomRight.y())
+ aBottomRight.setY(pPtAry->mnY);
+ }
+ aPainter.drawPolyline(pPoints, nPoints);
+ delete[] pPoints;
+ aPainter.update(QRect(aTopLeft, aBottomRight));
+}
+
+void Qt5Graphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
+{
+ Qt5Painter aPainter(*this, true);
+ QPolygon aPolygon(nPoints);
+ for (sal_uInt32 i = 0; i < nPoints; ++i, ++pPtAry)
+ aPolygon.setPoint(i, pPtAry->mnX, pPtAry->mnY);
+ aPainter.drawPolygon(aPolygon);
+ aPainter.update(aPolygon.boundingRect());
+}
+
+void Qt5Graphics::drawPolyPolygon(sal_uInt32 nPolyCount, const sal_uInt32* pPoints,
+ PCONSTSALPOINT* ppPtAry)
+{
+ // ignore invisible polygons
+ if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ return;
+
+ QPainterPath aPath;
+ for (sal_uInt32 nPoly = 0; nPoly < nPolyCount; nPoly++)
+ {
+ const sal_uInt32 nPoints = pPoints[nPoly];
+ if (nPoints > 1)
+ {
+ const SalPoint* pPtAry = ppPtAry[nPoly];
+ aPath.moveTo(pPtAry->mnX, pPtAry->mnY);
+ pPtAry++;
+ for (sal_uInt32 nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++)
+ aPath.lineTo(pPtAry->mnX, pPtAry->mnY);
+ aPath.closeSubpath();
+ }
+ }
+
+ Qt5Painter aPainter(*this, true);
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+}
+
+bool Qt5Graphics::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency)
+{
+ // ignore invisible polygons
+ if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ return true;
+ if ((fTransparency >= 1.0) || (fTransparency < 0))
+ return true;
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ QPainterPath aPath;
+ // ignore empty polygons
+ if (!AddPolyPolygonToPath(aPath, aPolyPolygon, !getAntiAliasB2DDraw(),
+ m_aLineColor != SALCOLOR_NONE))
+ return true;
+
+ Qt5Painter aPainter(*this, true, 255 * (1.0 - fTransparency));
+ aPainter.drawPath(aPath);
+ aPainter.update(aPath.boundingRect());
+ return true;
+}
+
+bool Qt5Graphics::drawPolyLineBezier(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::drawPolygonBezier(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/,
+ const PolyFlags* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* /*pPoints*/,
+ const SalPoint* const* /*pPtAry*/,
+ const PolyFlags* const* /*pFlgAry*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::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 (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ {
+ 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 Qt5,
+ // 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(), !getAntiAliasB2DDraw(), true);
+ }
+
+ Qt5Painter 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 Qt5Graphics::drawGradient(const tools::PolyPolygon&, const Gradient&) { return false; }
+
+void Qt5Graphics::drawScaledImage(const SalTwoRect& rPosAry, const QImage& rImage)
+{
+ Qt5Painter 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 Qt5Graphics::copyArea(long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth,
+ long nSrcHeight, bool /*bWindowInvalidate*/)
+{
+ if (nDestX == nSrcX && nDestY == nSrcY)
+ return;
+
+ SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+ copyBits(aTR, this);
+}
+
+void Qt5Graphics::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 || this == 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<Qt5Graphics*>(pSrcGraphics)->m_pQImage;
+
+ drawScaledImage(aPosAry, *pImage);
+}
+
+void Qt5Graphics::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0)
+ return;
+
+ Qt5Bitmap aRGBABitmap;
+ if (rSalBitmap.GetBitCount() == 4)
+ aRGBABitmap.Create(rSalBitmap, 32);
+ const QImage* pImage = (rSalBitmap.GetBitCount() != 4)
+ ? static_cast<const Qt5Bitmap*>(&rSalBitmap)->GetQImage()
+ : aRGBABitmap.GetQImage();
+ assert(pImage);
+
+ drawScaledImage(rPosAry, *pImage);
+}
+
+void Qt5Graphics::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 Qt5Graphics::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> Qt5Graphics::getBitmap(long nX, long nY, long nWidth, long nHeight)
+{
+ return std::make_shared<Qt5Bitmap>(m_pQImage->copy(nX, nY, nWidth, nHeight));
+}
+
+Color Qt5Graphics::getPixel(long nX, long nY) { return m_pQImage->pixel(nX, nY); }
+
+void Qt5Graphics::invert(long nX, long nY, long nWidth, long nHeight, SalInvert nFlags)
+{
+ Qt5Painter 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 Qt5Graphics::invert(sal_uInt32 /*nPoints*/, const SalPoint* /*pPtAry*/, SalInvert /*nFlags*/)
+{
+}
+
+bool Qt5Graphics::drawEPS(long /*nX*/, long /*nY*/, long /*nWidth*/, long /*nHeight*/,
+ void* /*pPtr*/, sal_uInt32 /*nSize*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::blendBitmap(const SalTwoRect&, const SalBitmap& /*rBitmap*/) { return false; }
+
+bool Qt5Graphics::blendAlphaBitmap(const SalTwoRect&, const SalBitmap& /*rSrcBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+static bool getAlphaImage(const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap,
+ QImage& rAlphaImage)
+{
+ if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1)
+ {
+ SAL_WARN("vcl.gdi", "unsupported alpha depth case: " << rAlphaBitmap.GetBitCount());
+ return false;
+ }
+
+ Qt5Bitmap aRGBABitmap;
+ if (rSourceBitmap.GetBitCount() == 4)
+ aRGBABitmap.Create(rSourceBitmap, 32);
+ const QImage* pBitmap = (rSourceBitmap.GetBitCount() != 4)
+ ? static_cast<const Qt5Bitmap*>(&rSourceBitmap)->GetQImage()
+ : aRGBABitmap.GetQImage();
+ const QImage* pAlpha = static_cast<const Qt5Bitmap*>(&rAlphaBitmap)->GetQImage();
+ rAlphaImage = pBitmap->convertToFormat(Qt5_DefaultFormat32);
+
+ if (rAlphaBitmap.GetBitCount() == 8)
+ {
+ for (int y = 0; y < rAlphaImage.height(); ++y)
+ {
+ uchar* image_line = rAlphaImage.scanLine(y);
+ const uchar* alpha_line = pAlpha->scanLine(y);
+ for (int x = 0; x < rAlphaImage.width(); ++x, image_line += 4)
+ image_line[3] = 255 - alpha_line[x];
+ }
+ }
+ else
+ {
+ for (int y = 0; y < rAlphaImage.height(); ++y)
+ {
+ uchar* image_line = rAlphaImage.scanLine(y);
+ const uchar* alpha_line = pAlpha->scanLine(y);
+ for (int x = 0; x < rAlphaImage.width(); ++x, image_line += 4)
+ {
+ if (x && !(x % 8))
+ ++alpha_line;
+ if (0 != (*alpha_line & (1 << (7 - x % 8))))
+ image_line[3] = 0;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Qt5Graphics::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap)
+{
+ QImage aImage;
+ if (!getAlphaImage(rSourceBitmap, rAlphaBitmap, aImage))
+ return false;
+ drawScaledImage(rPosAry, aImage);
+ return true;
+}
+
+bool Qt5Graphics::drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap)
+{
+ QImage aImage;
+ if (pAlphaBitmap && !getAlphaImage(rSourceBitmap, *pAlphaBitmap, aImage))
+ return false;
+ else
+ {
+ Qt5Bitmap aRGBABitmap;
+ if (rSourceBitmap.GetBitCount() == 4)
+ aRGBABitmap.Create(rSourceBitmap, 32);
+ const QImage* pBitmap = (rSourceBitmap.GetBitCount() != 4)
+ ? static_cast<const Qt5Bitmap*>(&rSourceBitmap)->GetQImage()
+ : aRGBABitmap.GetQImage();
+ aImage = pBitmap->convertToFormat(Qt5_DefaultFormat32);
+ }
+
+ Qt5Painter aPainter(*this);
+ const basegfx::B2DVector aXRel = rX - rNull;
+ const basegfx::B2DVector aYRel = rY - rNull;
+ 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 Qt5Graphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight,
+ sal_uInt8 nTransparency)
+{
+ if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor)
+ return true;
+ assert(nTransparency <= 100);
+ if (nTransparency > 100)
+ nTransparency = 100;
+ Qt5Painter aPainter(*this, true, (100 - nTransparency) * (255.0 / 100));
+ if (SALCOLOR_NONE != m_aFillColor)
+ aPainter.fillRect(nX, nY, nWidth, nHeight, aPainter.brush());
+ if (SALCOLOR_NONE != m_aLineColor)
+ aPainter.drawRect(nX, nY, nWidth - 1, nHeight - 1);
+ aPainter.update(nX, nY, nWidth, nHeight);
+ return true;
+}
+
+void Qt5Graphics::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 || !m_pFrame->GetQWidget()->window()->windowHandle())
+ return;
+
+ QScreen* pScreen = m_pFrame->GetQWidget()->window()->windowHandle()->screen();
+ rDPIX = pScreen->logicalDotsPerInchX() * pScreen->devicePixelRatio() + 0.5;
+ rDPIY = pScreen->logicalDotsPerInchY() * pScreen->devicePixelRatio() + 0.5;
+}
+
+sal_uInt16 Qt5Graphics::GetBitCount() const { return getFormatBits(m_pQImage->format()); }
+
+long Qt5Graphics::GetGraphicsWidth() const { return m_pQImage->width(); }
+
+void Qt5Graphics::SetLineColor() { m_aLineColor = SALCOLOR_NONE; }
+
+void Qt5Graphics::SetLineColor(Color nColor) { m_aLineColor = nColor; }
+
+void Qt5Graphics::SetFillColor() { m_aFillColor = SALCOLOR_NONE; }
+
+void Qt5Graphics::SetFillColor(Color nColor) { m_aFillColor = nColor; }
+
+void Qt5Graphics::SetXORMode(bool bSet, bool)
+{
+ if (bSet)
+ m_eCompositionMode = QPainter::CompositionMode_Xor;
+ else
+ m_eCompositionMode = QPainter::CompositionMode_SourceOver;
+}
+
+void Qt5Graphics::SetROPLineColor(SalROPColor /*nROPColor*/) {}
+
+void Qt5Graphics::SetROPFillColor(SalROPColor /*nROPColor*/) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Graphics_Text.cxx b/vcl/qt5/Qt5Graphics_Text.cxx
new file mode 100644
index 000000000..ded886efd
--- /dev/null
+++ b/vcl/qt5/Qt5Graphics_Text.cxx
@@ -0,0 +1,233 @@
+/* -*- 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 <Qt5Graphics.hxx>
+#include <Qt5FontFace.hxx>
+#include <Qt5Font.hxx>
+#include <Qt5Painter.hxx>
+
+#include <vcl/fontcharmap.hxx>
+#include <unx/geninst.h>
+#include <unx/fontmanager.hxx>
+#include <unx/glyphcache.hxx>
+#include <unx/genpspgraphics.h>
+
+#include <sallayout.hxx>
+#include <PhysicalFontCollection.hxx>
+
+#include <QtGui/QGlyphRun>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QRawFont>
+#include <QtCore/QStringList>
+
+void Qt5Graphics::SetTextColor(Color nColor) { m_aTextColor = nColor; }
+
+void Qt5Graphics::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<Qt5Font*>(pReqFont);
+}
+
+void Qt5Graphics::GetFontMetric(ImplFontMetricDataRef& rFMD, int nFallbackLevel)
+{
+ QRawFont aRawFont(QRawFont::fromFont(*m_pTextStyle[nFallbackLevel]));
+ Qt5FontFace::fillAttributesFromQFont(*m_pTextStyle[nFallbackLevel], *rFMD);
+
+ rFMD->ImplCalcLineSpacing(m_pTextStyle[nFallbackLevel].get());
+
+ rFMD->SetSlant(0);
+ rFMD->SetWidth(aRawFont.averageCharWidth());
+
+ rFMD->SetMinKashida(m_pTextStyle[nFallbackLevel]->GetKashidaWidth());
+}
+
+FontCharMapRef Qt5Graphics::GetFontCharMap() const
+{
+ if (!m_pTextStyle[0])
+ return FontCharMapRef(new FontCharMap());
+ return static_cast<const Qt5FontFace*>(m_pTextStyle[0]->GetFontFace())->GetFontCharMap();
+}
+
+bool Qt5Graphics::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const
+{
+ if (!m_pTextStyle[0])
+ return false;
+ return static_cast<const Qt5FontFace*>(m_pTextStyle[0]->GetFontFace())
+ ->GetFontCapabilities(rFontCapabilities);
+}
+
+void Qt5Graphics::GetDevFontList(PhysicalFontCollection* pPFC)
+{
+ static const bool bUseFontconfig = (nullptr == getenv("SAL_VCL_QT5_NO_FONTCONFIG"));
+
+ m_pFontCollection = pPFC;
+ if (pPFC->Count())
+ return;
+
+ QFontDatabase aFDB;
+ FreetypeManager& rFontManager = FreetypeManager::get();
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ ::std::vector<psp::fontID> aList;
+ psp::FastPrintFontInfo aInfo;
+
+ rMgr.getFontList(aList);
+ for (auto const& elem : aList)
+ {
+ if (!rMgr.getFontFastInfo(elem, aInfo))
+ continue;
+
+ // normalize face number to the FreetypeManager
+ int nFaceNum = rMgr.getFontFaceNumber(aInfo.m_nID);
+ int nVariantNum = rMgr.getFontFaceVariation(aInfo.m_nID);
+
+ // inform FreetypeManager about this font provided by the PsPrint subsystem
+ FontAttributes aDFA = GenPspGraphics::Info2FontAttributes(aInfo);
+ aDFA.IncreaseQualityBy(4096);
+ const OString& rFileName = rMgr.getFontFileSysPath(aInfo.m_nID);
+ rFontManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA);
+ }
+
+ if (bUseFontconfig)
+ SalGenericInstance::RegisterFontSubstitutors(pPFC);
+
+ for (auto& family : aFDB.families())
+ for (auto& style : aFDB.styles(family))
+ pPFC->Add(Qt5FontFace::fromQFontDatabase(family, style));
+}
+
+void Qt5Graphics::ClearDevFontCache() {}
+
+bool Qt5Graphics::AddTempDevFont(PhysicalFontCollection*, const OUString& /*rFileURL*/,
+ const OUString& /*rFontName*/)
+{
+ return false;
+}
+
+bool Qt5Graphics::CreateFontSubset(const OUString& /*rToFile*/, const PhysicalFontFace* /*pFont*/,
+ const sal_GlyphId* /*pGlyphIds*/, const sal_uInt8* /*pEncoding*/,
+ sal_Int32* /*pWidths*/, int /*nGlyphs*/,
+ FontSubsetInfo& /*rInfo*/)
+{
+ return false;
+}
+
+const void* Qt5Graphics::GetEmbedFontData(const PhysicalFontFace*, long* /*pDataLen*/)
+{
+ return nullptr;
+}
+
+void Qt5Graphics::FreeEmbedFontData(const void* /*pData*/, long /*nDataLen*/) {}
+
+void Qt5Graphics::GetGlyphWidths(const PhysicalFontFace* /*pPFF*/, bool /*bVertical*/,
+ std::vector<sal_Int32>& /*rWidths*/, Ucs2UIntMap& /*rUnicodeEnc*/)
+{
+}
+
+namespace
+{
+class Qt5CommonSalLayout : public GenericSalLayout
+{
+public:
+ Qt5CommonSalLayout(LogicalFontInstance& rLFI)
+ : GenericSalLayout(rLFI)
+ {
+ }
+
+ void SetOrientation(int nOrientation) { mnOrientation = nOrientation; }
+};
+}
+
+std::unique_ptr<GenericSalLayout> Qt5Graphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(m_pTextStyle[nFallbackLevel]);
+ if (!m_pTextStyle[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<Qt5CommonSalLayout>(*m_pTextStyle[nFallbackLevel]);
+}
+
+void Qt5Graphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ const Qt5Font* pFont = static_cast<const Qt5Font*>(&rLayout.GetFont());
+ assert(pFont);
+ QRawFont aRawFont(QRawFont::fromFont(*pFont));
+
+ QVector<quint32> glyphIndexes;
+ QVector<QPointF> positions;
+
+ // prevent glyph rotation inside the SalLayout
+ // probably better to add a parameter to GetNextGlyphs?
+ Qt5CommonSalLayout* pQt5Layout
+ = static_cast<Qt5CommonSalLayout*>(const_cast<GenericSalLayout*>(&rLayout));
+ int nOrientation = rLayout.GetOrientation();
+ if (nOrientation)
+ pQt5Layout->SetOrientation(0);
+
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ glyphIndexes.push_back(pGlyph->glyphId());
+ positions.push_back(QPointF(aPos.X(), aPos.Y()));
+ }
+
+ // seems to be common to try to layout an empty string...
+ if (positions.empty())
+ return;
+
+ if (nOrientation)
+ pQt5Layout->SetOrientation(nOrientation);
+
+ QGlyphRun aGlyphRun;
+ aGlyphRun.setPositions(positions);
+ aGlyphRun.setGlyphIndexes(glyphIndexes);
+ aGlyphRun.setRawFont(aRawFont);
+
+ Qt5Painter aPainter(*this);
+ 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) / 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/Qt5Instance.cxx b/vcl/qt5/Qt5Instance.cxx
new file mode 100644
index 000000000..06b959b91
--- /dev/null
+++ b/vcl/qt5/Qt5Instance.cxx
@@ -0,0 +1,651 @@
+/* -*- 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 <Qt5Instance.hxx>
+#include <Qt5Instance.moc>
+
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <Qt5Bitmap.hxx>
+#include <Qt5Clipboard.hxx>
+#include <Qt5Data.hxx>
+#include <Qt5DragAndDrop.hxx>
+#include <Qt5FilePicker.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Menu.hxx>
+#include <Qt5Object.hxx>
+#include <Qt5OpenGLContext.hxx>
+#include "Qt5SvpVirtualDevice.hxx"
+#include <Qt5System.hxx>
+#include <Qt5Timer.hxx>
+#include <Qt5VirtualDevice.hxx>
+
+#include <headless/svpvd.hxx>
+
+#include <QtCore/QAbstractEventDispatcher>
+#include <QtCore/QThread>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+
+#include <vclpluginapi.h>
+#include <tools/debug.hxx>
+#include <comphelper/flagguard.hxx>
+#include <sal/log.hxx>
+#include <osl/process.h>
+#include <unx/gstsink.hxx>
+#include <headless/svpbmp.hxx>
+
+#include <mutex>
+#include <condition_variable>
+
+namespace
+{
+/// TODO: not much Qt5 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 Qt5; 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 Qt5YieldMutex : 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 Qt5YieldMutex::IsCurrentThread() const
+{
+ auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ if (pSalInst->IsMainThread() && m_bNoYieldLock)
+ {
+ return true; // main thread has borrowed SolarMutex
+ }
+ return SalYieldMutex::IsCurrentThread();
+}
+
+void Qt5YieldMutex::doAcquire(sal_uInt32 nLockCount)
+{
+ auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
+ 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 Qt5YieldMutex::doRelease(bool const bUnlockAll)
+{
+ auto const* pSalInst(static_cast<Qt5Instance const*>(GetSalData()->m_pInstance));
+ 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 Qt5 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 Qt5Instance::RunInMainThread(std::function<void()> func)
+{
+ DBG_TESTSOLARMUTEX();
+ if (IsMainThread())
+ {
+ func();
+ return;
+ }
+
+ Qt5YieldMutex* const pMutex(static_cast<Qt5YieldMutex*>(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();
+ }
+ // wake up main thread in case it is blocked on event queue
+ // TriggerUserEventProcessing() appears to be insufficient in case the
+ // main thread does QEventLoop::WaitForMoreEvents
+ Q_EMIT ImplRunInMainSignal();
+ {
+ std::unique_lock<std::mutex> g(pMutex->m_RunInMainMutex);
+ pMutex->m_ResultCondition.wait(g, [pMutex]() { return pMutex->m_isResultReady; });
+ pMutex->m_isResultReady = false;
+ }
+}
+
+void Qt5Instance::ImplRunInMain()
+{
+ SolarMutexGuard g; // trigger the dispatch code in Qt5YieldMutex::doAcquire
+ (void)this; // suppress unhelpful [loplugin:staticmethods]; can't be static
+}
+
+Qt5Instance::Qt5Instance(std::unique_ptr<QApplication>& pQApp, bool bUseCairo)
+ : SalGenericInstance(std::make_unique<Qt5YieldMutex>())
+ , m_postUserEventId(-1)
+ , m_bUseCairo(bUseCairo)
+ , m_pQApplication(std::move(pQApp))
+ , m_aUpdateStyleTimer("vcl::qt5 m_aUpdateStyleTimer")
+ , m_bUpdateFonts(false)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if (bUseCairo)
+ pSVData->maAppData.mxToolkitName = OUString("qt5+cairo");
+ else
+ pSVData->maAppData.mxToolkitName = OUString("qt5");
+
+ m_postUserEventId = QEvent::registerEventType();
+
+ // 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);
+ connect(this, &Qt5Instance::ImplRunInMainSignal, this, &Qt5Instance::ImplRunInMain,
+ Qt::QueuedConnection); // no Blocking!
+
+ // this one needs to be queued non-blocking
+ // in order to have this event arriving to correct event processing loop
+ connect(this, &Qt5Instance::deleteObjectLaterSignal, this,
+ [](QObject* pObject) { Qt5Instance::deleteObjectLater(pObject); },
+ Qt::QueuedConnection);
+
+ m_aUpdateStyleTimer.SetTimeout(50);
+ m_aUpdateStyleTimer.SetInvokeHandler(LINK(this, Qt5Instance, updateStyleHdl));
+}
+
+Qt5Instance::~Qt5Instance()
+{
+ // force freeing the QApplication before freeing the arguments,
+ // as it uses references to the provided arguments!
+ m_pQApplication.reset();
+}
+
+void Qt5Instance::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 Qt5Instance::deleteObjectLater(QObject* pObject) { pObject->deleteLater(); }
+
+SalFrame* Qt5Instance::CreateChildFrame(SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle)
+{
+ return new Qt5Frame(nullptr, nStyle, m_bUseCairo);
+}
+
+SalFrame* Qt5Instance::CreateFrame(SalFrame* pParent, SalFrameStyleFlags nStyle)
+{
+ assert(!pParent || dynamic_cast<Qt5Frame*>(pParent));
+ return new Qt5Frame(static_cast<Qt5Frame*>(pParent), nStyle, m_bUseCairo);
+}
+
+void Qt5Instance::DestroyFrame(SalFrame* pFrame)
+{
+ if (pFrame)
+ {
+ assert(dynamic_cast<Qt5Frame*>(pFrame));
+ Q_EMIT deleteObjectLaterSignal(static_cast<Qt5Frame*>(pFrame));
+ }
+}
+
+SalObject* Qt5Instance::CreateObject(SalFrame* pParent, SystemWindowData*, bool bShow)
+{
+ assert(!pParent || dynamic_cast<Qt5Frame*>(pParent));
+ return new Qt5Object(static_cast<Qt5Frame*>(pParent), bShow);
+}
+
+void Qt5Instance::DestroyObject(SalObject* pObject)
+{
+ if (pObject)
+ {
+ assert(dynamic_cast<Qt5Object*>(pObject));
+ Q_EMIT deleteObjectLaterSignal(static_cast<Qt5Object*>(pObject));
+ }
+}
+
+std::unique_ptr<SalVirtualDevice> Qt5Instance::CreateVirtualDevice(SalGraphics* pGraphics,
+ long& nDX, long& nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData* pGd)
+{
+ if (m_bUseCairo)
+ {
+ SvpSalGraphics* pSvpSalGraphics = dynamic_cast<Qt5SvpGraphics*>(pGraphics);
+ 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 Qt5SvpVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ pVD->SetSize(nDX, nDY);
+ return pVD;
+ }
+ else
+ {
+ std::unique_ptr<SalVirtualDevice> pVD(new Qt5VirtualDevice(eFormat, 1));
+ pVD->SetSize(nDX, nDY);
+ return pVD;
+ }
+}
+
+std::unique_ptr<SalMenu> Qt5Instance::CreateMenu(bool bMenuBar, Menu* pVCLMenu)
+{
+ std::unique_ptr<SalMenu> pRet;
+ RunInMainThread([&pRet, bMenuBar, pVCLMenu]() {
+ Qt5Menu* pSalMenu = new Qt5Menu(bMenuBar);
+ pRet.reset(pSalMenu);
+ pSalMenu->SetMenu(pVCLMenu);
+ });
+ assert(pRet);
+ return pRet;
+}
+
+std::unique_ptr<SalMenuItem> Qt5Instance::CreateMenuItem(const SalItemParams& rItemData)
+{
+ return std::unique_ptr<SalMenuItem>(new Qt5MenuItem(&rItemData));
+}
+
+SalTimer* Qt5Instance::CreateSalTimer() { return new Qt5Timer(); }
+
+SalSystem* Qt5Instance::CreateSalSystem() { return new Qt5System; }
+
+std::shared_ptr<SalBitmap> Qt5Instance::CreateSalBitmap()
+{
+ if (m_bUseCairo)
+ return std::make_shared<SvpSalBitmap>();
+ else
+ return std::make_shared<Qt5Bitmap>();
+}
+
+bool Qt5Instance::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 Qt5Instance::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 Qt5Instance::AnyInput(VclInputFlags /*nType*/) { return false; }
+
+OUString Qt5Instance::GetConnectionIdentifier() { return OUString(); }
+
+void Qt5Instance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {}
+
+OpenGLContext* Qt5Instance::CreateOpenGLContext() { return new Qt5OpenGLContext; }
+
+bool Qt5Instance::IsMainThread() const
+{
+ return !qApp || (qApp->thread() == QThread::currentThread());
+}
+
+void Qt5Instance::TriggerUserEventProcessing()
+{
+ QApplication::postEvent(this, new QEvent(QEvent::Type(m_postUserEventId)));
+}
+
+void Qt5Instance::ProcessEvent(SalUserEvent aEvent)
+{
+ aEvent.m_pFrame->CallCallback(aEvent.m_nEvent, aEvent.m_pData);
+}
+
+Qt5FilePicker*
+Qt5Instance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+{
+ if (!IsMainThread())
+ {
+ SolarMutexGuard g;
+ Qt5FilePicker* pPicker;
+ RunInMainThread([&, this]() { pPicker = createPicker(context, eMode); });
+ assert(pPicker);
+ return pPicker;
+ }
+
+ return new Qt5FilePicker(context, eMode);
+}
+
+css::uno::Reference<css::ui::dialogs::XFilePicker2>
+Qt5Instance::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>
+Qt5Instance::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>
+Qt5Instance::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 Qt5Instance::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 = Qt5Clipboard::create(sel);
+ if (xClipboard.is())
+ m_aClipboards[sel] = xClipboard;
+
+ return xClipboard;
+}
+
+css::uno::Reference<css::uno::XInterface> Qt5Instance::CreateDragSource()
+{
+ return css::uno::Reference<css::uno::XInterface>(
+ static_cast<cppu::OWeakObject*>(new Qt5DragSource()));
+}
+
+css::uno::Reference<css::uno::XInterface> Qt5Instance::CreateDropTarget()
+{
+ return css::uno::Reference<css::uno::XInterface>(
+ static_cast<cppu::OWeakObject*>(new Qt5DropTarget()));
+}
+
+IMPL_LINK_NOARG(Qt5Instance, 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 Qt5Instance::UpdateStyle(bool bFontsChanged)
+{
+ if (bFontsChanged)
+ m_bUpdateFonts = true;
+ if (!m_aUpdateStyleTimer.IsActive())
+ m_aUpdateStyleTimer.Start();
+}
+
+void* Qt5Instance::CreateGStreamerSink(const SystemChildWindow* pWindow)
+{
+#if 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.qt5", "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
+ (void*)pWindow;
+ return nullptr;
+#endif
+}
+
+void Qt5Instance::AllocFakeCmdlineArgs(std::unique_ptr<char* []>& rFakeArgv,
+ std::unique_ptr<int>& rFakeArgc,
+ std::vector<FreeableCStr>& rFakeArgvFreeable)
+{
+ OString aVersion(qVersion());
+ SAL_INFO("vcl.qt5", "qt version string is " << aVersion);
+
+ const sal_uInt32 nParams = osl_getCommandArgCount();
+ OString aDisplay;
+ 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);
+ 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 Qt5Instance::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> Qt5Instance::CreateQApplication(int& nArgc, char** pArgv)
+{
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ // for scaled icons in the native menus
+ QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+
+ 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;
+}
+
+extern "C" {
+VCLPLUG_QT5_PUBLIC SalInstance* create_SalInstance()
+{
+ static const bool bUseCairo = (nullptr != getenv("SAL_VCL_QT5_USE_CAIRO"));
+
+ std::unique_ptr<char* []> pFakeArgv;
+ std::unique_ptr<int> pFakeArgc;
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ Qt5Instance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ std::unique_ptr<QApplication> pQApp
+ = Qt5Instance::CreateQApplication(*pFakeArgc, pFakeArgv.get());
+
+ Qt5Instance* pInstance = new Qt5Instance(pQApp, bUseCairo);
+ pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ new Qt5Data(pInstance);
+
+ return pInstance;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Instance_Print.cxx b/vcl/qt5/Qt5Instance_Print.cxx
new file mode 100644
index 000000000..cab5757a3
--- /dev/null
+++ b/vcl/qt5/Qt5Instance_Print.cxx
@@ -0,0 +1,132 @@
+/* -*- 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 <Qt5Instance.hxx>
+#include <Qt5Printer.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <printerinfomanager.hxx>
+
+#include <jobset.h>
+#include <print.h>
+#include <salptype.hxx>
+#include <saldatabasic.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())
+ aDir = OStringToOUString(OString(getenv("HOME")), osl_getThreadTextEncoding());
+ break;
+ }
+ }
+ return aDir;
+}
+
+SalInfoPrinter* Qt5Instance::CreateInfoPrinter(SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pJobSetup)
+{
+ // create and initialize SalInfoPrinter
+ PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup);
+
+ return pPrinter;
+}
+
+void Qt5Instance::DestroyInfoPrinter(SalInfoPrinter* pPrinter) { delete pPrinter; }
+
+std::unique_ptr<SalPrinter> Qt5Instance::CreatePrinter(SalInfoPrinter* pInfoPrinter)
+{
+ // create and initialize SalPrinter
+ Qt5Printer* pPrinter = new Qt5Printer(pInfoPrinter);
+ pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
+
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+void Qt5Instance::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 Qt5Instance::GetPrinterQueueState(SalPrinterQueueInfo*) {}
+
+OUString Qt5Instance::GetDefaultPrinter()
+{
+ PrinterInfoManager& rManager(PrinterInfoManager::get());
+ return rManager.getDefaultPrinter();
+}
+
+void Qt5Instance::PostPrintersChanged() {}
+
+std::unique_ptr<GenPspGraphics> Qt5Instance::CreatePrintGraphics()
+{
+ return std::make_unique<GenPspGraphics>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5MainWindow.cxx b/vcl/qt5/Qt5MainWindow.cxx
new file mode 100644
index 000000000..45d726ba2
--- /dev/null
+++ b/vcl/qt5/Qt5MainWindow.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 <Qt5MainWindow.hxx>
+#include <Qt5MainWindow.moc>
+#include <Qt5AccessibleWidget.hxx>
+
+#include <QtGui/QAccessible>
+#include <QtGui/QCloseEvent>
+
+Qt5MainWindow::Qt5MainWindow(Qt5Frame& rFrame, Qt::WindowFlags f)
+ : QMainWindow(nullptr, f)
+ , m_rFrame(rFrame)
+{
+ QAccessible::installFactory(Qt5AccessibleWidget::customFactory);
+}
+
+void Qt5MainWindow::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 Qt5MainWindow::moveEvent(QMoveEvent* pEvent)
+{
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ m_rFrame.maGeometry.nX = round(pEvent->pos().x() * fRatio);
+ m_rFrame.maGeometry.nY = round(pEvent->pos().y() * fRatio);
+ m_rFrame.CallCallback(SalEvent::Move, nullptr);
+}
diff --git a/vcl/qt5/Qt5Menu.cxx b/vcl/qt5/Qt5Menu.cxx
new file mode 100644
index 000000000..986152470
--- /dev/null
+++ b/vcl/qt5/Qt5Menu.cxx
@@ -0,0 +1,704 @@
+/* -*- 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 <Qt5Menu.hxx>
+#include <Qt5Menu.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Instance.hxx>
+#include <Qt5MainWindow.hxx>
+
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QPushButton>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <strings.hrc>
+#include <bitmaps.hlst>
+
+#include <vcl/floatwin.hxx>
+#include <window.h>
+
+Qt5Menu::Qt5Menu(bool bMenuBar)
+ : mpVCLMenu(nullptr)
+ , mpParentSalMenu(nullptr)
+ , mpFrame(nullptr)
+ , mbMenuBar(bMenuBar)
+ , mpQMenuBar(nullptr)
+ , mpQMenu(nullptr)
+ , mpCloseButton(nullptr)
+{
+}
+
+bool Qt5Menu::VisibleMenuBar() { return true; }
+
+void Qt5Menu::InsertMenuItem(Qt5MenuItem* 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 (mpQMenuBar)
+ {
+ QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
+ 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();
+ }
+
+ if (pSalMenuItem->mpSubMenu)
+ {
+ // submenu
+ QMenu* pQMenu = new QMenu(toQString(aText), nullptr);
+ 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);
+
+ const Qt5Frame* pFrame = GetFrame();
+ if (pFrame)
+ pAction->setShortcut(toQString(nAccelKey.GetName(pFrame->GetWindow())));
+
+ connect(pAction, &QAction::triggered, this,
+ [pSalMenuItem] { slotMenuTriggered(pSalMenuItem); });
+ }
+ }
+ }
+
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ pAction->setEnabled(pSalMenuItem->mbEnabled);
+ pAction->setVisible(pSalMenuItem->mbVisible);
+ }
+}
+
+void Qt5Menu::ReinitializeActionGroup(unsigned nPos)
+{
+ const unsigned nCount = GetItemCount();
+
+ if (nCount == 0)
+ {
+ return;
+ }
+
+ if (nPos == MENU_APPEND)
+ {
+ nPos = nCount - 1;
+ }
+ else if (nPos >= nCount)
+ {
+ return;
+ }
+
+ Qt5MenuItem* pPrevItem = (nPos > 0) ? GetItemAtPos(nPos - 1) : nullptr;
+ Qt5MenuItem* pCurrentItem = GetItemAtPos(nPos);
+ Qt5MenuItem* 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)
+ {
+ Qt5MenuItem* 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)
+ {
+ Qt5MenuItem* pModifiedItem = GetItemAtPos(idx);
+
+ if ((!pModifiedItem) || (!pModifiedItem->mpActionGroup))
+ {
+ break;
+ }
+
+ pModifiedItem->mpActionGroup = pCurrentItem->mpActionGroup;
+ }
+ }
+ }
+}
+
+void Qt5Menu::ResetAllActionGroups()
+{
+ for (unsigned nItem = 0; nItem < GetItemCount(); ++nItem)
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nItem);
+ pSalMenuItem->mpActionGroup.reset();
+ }
+}
+
+void Qt5Menu::UpdateActionGroupItem(const Qt5MenuItem* 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 Qt5Menu::InsertItem(SalMenuItem* pSalMenuItem, unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+ Qt5MenuItem* pItem = static_cast<Qt5MenuItem*>(pSalMenuItem);
+
+ if (nPos == MENU_APPEND)
+ maItems.push_back(pItem);
+ else
+ maItems.insert(maItems.begin() + nPos, pItem);
+
+ pItem->mpParentMenu = this;
+
+ InsertMenuItem(pItem, nPos);
+}
+
+void Qt5Menu::RemoveItem(unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* 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 Qt5Menu::SetSubMenu(SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos)
+{
+ SolarMutexGuard aGuard;
+ Qt5MenuItem* pItem = static_cast<Qt5MenuItem*>(pSalMenuItem);
+ Qt5Menu* pQSubMenu = static_cast<Qt5Menu*>(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 Qt5Menu::SetFrame(const SalFrame* pFrame)
+{
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ pSalInst->RunInMainThread([this, pFrame]() { SetFrame(pFrame); });
+ return;
+ }
+
+ SolarMutexGuard aGuard;
+ assert(mbMenuBar);
+ mpFrame = const_cast<Qt5Frame*>(static_cast<const Qt5Frame*>(pFrame));
+
+ mpFrame->SetMenu(this);
+
+ Qt5MainWindow* pMainWindow = mpFrame->GetTopLevelWindow();
+ if (pMainWindow)
+ {
+ mpQMenuBar = pMainWindow->menuBar();
+ if (mpQMenuBar)
+ {
+ mpQMenuBar->clear();
+ QPushButton* pButton
+ = static_cast<QPushButton*>(mpQMenuBar->cornerWidget(Qt::TopRightCorner));
+ if (pButton && ((mpCloseButton != pButton) || !maCloseButtonConnection))
+ {
+ maCloseButtonConnection
+ = connect(pButton, &QPushButton::clicked, this, &Qt5Menu::slotCloseDocument);
+ mpCloseButton = pButton;
+ }
+ }
+
+ mpQMenu = nullptr;
+
+ DoFullMenuUpdate(mpVCLMenu);
+ }
+}
+
+void Qt5Menu::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++)
+ {
+ Qt5MenuItem* 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 = 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 Qt5Menu::ShowItem(unsigned nPos, bool bShow)
+{
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setVisible(bShow);
+ pSalMenuItem->mbVisible = bShow;
+ }
+}
+
+void Qt5Menu::SetItemBits(unsigned nPos, MenuItemBits)
+{
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ UpdateActionGroupItem(pSalMenuItem);
+ }
+}
+
+void Qt5Menu::CheckItem(unsigned nPos, bool bChecked)
+{
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ pAction->setCheckable(true);
+ pAction->setChecked(bChecked);
+ }
+ }
+}
+
+void Qt5Menu::EnableItem(unsigned nPos, bool bEnable)
+{
+ if (nPos < maItems.size())
+ {
+ Qt5MenuItem* pSalMenuItem = GetItemAtPos(nPos);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setEnabled(bEnable);
+ pSalMenuItem->mbEnabled = bEnable;
+ }
+}
+
+void Qt5Menu::SetItemText(unsigned, SalMenuItem* pItem, const OUString& rText)
+{
+ Qt5MenuItem* pSalMenuItem = static_cast<Qt5MenuItem*>(pItem);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ {
+ OUString aText(rText);
+ NativeItemText(aText);
+ pAction->setText(toQString(aText));
+ }
+}
+
+void Qt5Menu::SetItemImage(unsigned, SalMenuItem* pItem, const Image& rImage)
+{
+ Qt5MenuItem* pSalMenuItem = static_cast<Qt5MenuItem*>(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 Qt5Menu::SetAccelerator(unsigned, SalMenuItem* pItem, const vcl::KeyCode&,
+ const OUString& rText)
+{
+ Qt5MenuItem* pSalMenuItem = static_cast<Qt5MenuItem*>(pItem);
+ QAction* pAction = pSalMenuItem->getAction();
+ if (pAction)
+ pAction->setShortcut(QKeySequence(toQString(rText), QKeySequence::PortableText));
+}
+
+void Qt5Menu::GetSystemMenuData(SystemMenuData*) {}
+
+Qt5Menu* Qt5Menu::GetTopLevel()
+{
+ Qt5Menu* pMenu = this;
+ while (pMenu->mpParentSalMenu)
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu;
+}
+
+void Qt5Menu::ShowMenuBar(bool bVisible)
+{
+ if (mpQMenuBar)
+ mpQMenuBar->setVisible(bVisible);
+}
+
+const Qt5Frame* Qt5Menu::GetFrame() const
+{
+ SolarMutexGuard aGuard;
+ const Qt5Menu* pMenu = this;
+ while (pMenu && !pMenu->mpFrame)
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu ? pMenu->mpFrame : nullptr;
+}
+
+void Qt5Menu::slotMenuTriggered(Qt5MenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ Qt5Menu* pSalMenu = pQItem->mpParentMenu;
+ Qt5Menu* 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 Qt5Menu::slotMenuAboutToShow(Qt5MenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ Qt5Menu* pSalMenu = pQItem->mpSubMenu;
+ Qt5Menu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+
+ // following function may update the menu
+ pTopLevel->GetMenu()->HandleMenuActivateEvent(pMenu);
+ }
+}
+
+void Qt5Menu::slotMenuAboutToHide(Qt5MenuItem* pQItem)
+{
+ if (pQItem)
+ {
+ Qt5Menu* pSalMenu = pQItem->mpSubMenu;
+ Qt5Menu* pTopLevel = pSalMenu->GetTopLevel();
+
+ Menu* pMenu = pSalMenu->GetMenu();
+
+ pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pMenu);
+ }
+}
+
+void Qt5Menu::NativeItemText(OUString& rItemText)
+{
+ // preserve literal '&'s in menu texts
+ rItemText = rItemText.replaceAll("&", "&&");
+
+ rItemText = rItemText.replace('~', '&');
+}
+
+void Qt5Menu::slotCloseDocument()
+{
+ MenuBar* pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ if (pVclMenuBar)
+ Application::PostUserEvent(pVclMenuBar->GetCloseButtonClickHdl());
+}
+
+void Qt5Menu::ShowCloseButton(bool bShow)
+{
+ if (!mpQMenuBar)
+ return;
+
+ QPushButton* pButton = static_cast<QPushButton*>(mpQMenuBar->cornerWidget(Qt::TopRightCorner));
+ 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 = new QPushButton(mpQMenuBar);
+ pButton->setIcon(aIcon);
+ pButton->setFlat(true);
+ pButton->setFocusPolicy(Qt::NoFocus);
+ pButton->setToolTip(toQString(VclResId(SV_HELPTEXT_CLOSEDOCUMENT)));
+ mpQMenuBar->setCornerWidget(pButton, Qt::TopRightCorner);
+ maCloseButtonConnection
+ = connect(pButton, &QPushButton::clicked, this, &Qt5Menu::slotCloseDocument);
+ mpCloseButton = pButton;
+ }
+
+ if (bShow)
+ pButton->show();
+ else
+ pButton->hide();
+}
+
+bool Qt5Menu::ShowNativePopupMenu(FloatingWindow*, const tools::Rectangle&,
+ FloatWinPopupFlags nFlags)
+{
+ assert(mpQMenu);
+ DoFullMenuUpdate(mpVCLMenu);
+ mpQMenu->setTearOffEnabled(bool(nFlags & FloatWinPopupFlags::AllowTearOff));
+
+ const QPoint aPos = QCursor::pos();
+ mpQMenu->exec(aPos);
+
+ return true;
+}
+
+Qt5MenuItem::Qt5MenuItem(const SalItemParams* pItemData)
+ : mpParentMenu(nullptr)
+ , mpSubMenu(nullptr)
+ , mnId(pItemData->nId)
+ , mnType(pItemData->eType)
+ , mbVisible(true)
+ , mbEnabled(true)
+ , maImage(pItemData->aImage)
+{
+}
+
+QAction* Qt5MenuItem::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/Qt5Object.cxx b/vcl/qt5/Qt5Object.cxx
new file mode 100644
index 000000000..5bbfef5a5
--- /dev/null
+++ b/vcl/qt5/Qt5Object.cxx
@@ -0,0 +1,156 @@
+/* -*- 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 <Qt5Object.hxx>
+#include <Qt5Object.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Widget.hxx>
+
+#include <QtGui/QGuiApplication>
+
+Qt5Object::Qt5Object(Qt5Frame* pParent, bool bShow)
+ : m_pParent(pParent)
+ , m_pQWidget(nullptr)
+ , m_pQWindow(nullptr)
+{
+ if (!m_pParent || !pParent->GetQWidget())
+ return;
+
+ m_pQWindow = new Qt5ObjectWindow(*this);
+ m_pQWidget = QWidget::createWindowContainer(m_pQWindow, pParent->GetQWidget());
+ m_pQWidget->setAttribute(Qt::WA_NoSystemBackground);
+ connect(m_pQWidget, &QObject::destroyed, this, [this]() { m_pQWidget = nullptr; });
+
+ if (bShow)
+ m_pQWidget->show();
+
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ //m_aSystemData.pSalFrame = this;
+ m_aSystemData.pWidget = m_pQWidget;
+ //m_aSystemData.nScreen = m_nXScreen.getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Qt5;
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+ const bool bWayland = QGuiApplication::platformName() == "wayland";
+ if (!bWayland)
+ {
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+ m_aSystemData.aWindow = m_pQWindow->winId(); // ID of the embedded window
+ }
+ else
+ {
+ m_aSystemData.platform = SystemEnvData::Platform::Wayland;
+ // TODO implement as needed for Wayland,
+ // s.a. commit c0d4f3ad3307c which did this for gtk3
+ // QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
+ // m_aSystemData.pDisplay = native->nativeResourceForWindow("display", nullptr);
+ // m_aSystemData.aWindow = reinterpret_cast<unsigned long>(
+ // native->nativeResourceForWindow("surface", m_pQWidget->windowHandle()));
+ }
+}
+
+Qt5Object::~Qt5Object()
+{
+ if (m_pQWidget)
+ {
+ m_pQWidget->setParent(nullptr);
+ delete m_pQWidget;
+ }
+}
+
+void Qt5Object::ResetClipRegion()
+{
+ if (m_pQWidget)
+ m_pRegion = QRegion(m_pQWidget->geometry());
+ else
+ m_pRegion = QRegion();
+}
+
+void Qt5Object::BeginSetClipRegion(sal_uInt32) { m_pRegion = QRegion(); }
+
+void Qt5Object::UnionClipRegion(long nX, long nY, long nWidth, long nHeight)
+{
+ m_pRegion += QRect(nX, nY, nWidth, nHeight);
+}
+
+void Qt5Object::EndSetClipRegion()
+{
+ if (m_pQWidget)
+ m_pRegion = m_pRegion.intersected(m_pQWidget->geometry());
+}
+
+void Qt5Object::SetPosSize(long nX, long nY, long nWidth, long nHeight)
+{
+ if (m_pQWidget)
+ {
+ m_pQWidget->move(nX, nY);
+ m_pQWidget->setFixedSize(nWidth, nHeight);
+ }
+}
+
+void Qt5Object::Show(bool bVisible)
+{
+ if (m_pQWidget)
+ m_pQWidget->setVisible(bVisible);
+}
+
+void Qt5Object::SetForwardKey(bool /*bEnable*/) {}
+
+Qt5ObjectWindow::Qt5ObjectWindow(Qt5Object& rParent)
+ : m_rParent(rParent)
+{
+ assert(m_rParent.frame() && m_rParent.frame()->GetQWidget());
+}
+
+void Qt5ObjectWindow::focusInEvent(QFocusEvent* pEvent)
+{
+ m_rParent.CallCallback(SalObjEvent::GetFocus);
+ QWindow::focusInEvent(pEvent);
+}
+
+void Qt5ObjectWindow::focusOutEvent(QFocusEvent* pEvent)
+{
+ m_rParent.CallCallback(SalObjEvent::LoseFocus);
+ QWindow::focusOutEvent(pEvent);
+}
+
+void Qt5ObjectWindow::mousePressEvent(QMouseEvent* pEvent)
+{
+ m_rParent.CallCallback(SalObjEvent::ToTop);
+ Qt5Widget::handleMousePressEvent(*m_rParent.frame(), pEvent);
+}
+
+void Qt5ObjectWindow::mouseReleaseEvent(QMouseEvent* pEvent)
+{
+ Qt5Widget::handleMouseReleaseEvent(*m_rParent.frame(), pEvent);
+}
+
+bool Qt5ObjectWindow::event(QEvent* pEvent)
+{
+ return Qt5Widget::handleEvent(*m_rParent.frame(), *m_rParent.widget(), pEvent)
+ || QWindow::event(pEvent);
+}
+
+void Qt5ObjectWindow::keyReleaseEvent(QKeyEvent* pEvent)
+{
+ if (!Qt5Widget::handleKeyReleaseEvent(*m_rParent.frame(), *m_rParent.widget(), pEvent))
+ QWindow::keyReleaseEvent(pEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5OpenGLContext.cxx b/vcl/qt5/Qt5OpenGLContext.cxx
new file mode 100644
index 000000000..a33f7abde
--- /dev/null
+++ b/vcl/qt5/Qt5OpenGLContext.cxx
@@ -0,0 +1,152 @@
+/* -*- 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 <Qt5OpenGLContext.hxx>
+
+#include <vcl/sysdata.hxx>
+#include <opengl/zone.hxx>
+#include <sal/log.hxx>
+
+#include <window.h>
+
+#include <Qt5Object.hxx>
+
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QWindow>
+
+bool Qt5OpenGLContext::g_bAnyCurrent = false;
+
+void Qt5OpenGLContext::swapBuffers()
+{
+ OpenGLZone aZone;
+
+ if (m_pContext && m_pWindow && m_pWindow->isExposed())
+ {
+ m_pContext->swapBuffers(m_pWindow);
+ }
+
+ BuffersSwapped();
+}
+
+void Qt5OpenGLContext::resetCurrent()
+{
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ if (m_pContext)
+ {
+ m_pContext->doneCurrent();
+ g_bAnyCurrent = false;
+ }
+}
+
+bool Qt5OpenGLContext::isCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && (QOpenGLContext::currentContext() == m_pContext);
+}
+
+bool Qt5OpenGLContext::isAnyCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && (QOpenGLContext::currentContext() != nullptr);
+}
+
+bool Qt5OpenGLContext::ImplInit()
+{
+ if (!m_pWindow)
+ {
+ SAL_WARN("vcl.opengl.qt5", "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.qt5", "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 Qt5OpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+ if (m_pContext && m_pWindow)
+ {
+ m_pContext->makeCurrent(m_pWindow);
+ g_bAnyCurrent = true;
+ }
+
+ registerAsCurrent();
+}
+
+void Qt5OpenGLContext::destroyCurrentContext()
+{
+ OpenGLZone aZone;
+
+ if (m_pContext)
+ {
+ m_pContext->doneCurrent();
+ g_bAnyCurrent = false;
+ }
+
+ if (glGetError() != GL_NO_ERROR)
+ {
+ SAL_WARN("vcl.opengl.qt5", "glError: " << glGetError());
+ }
+}
+
+void Qt5OpenGLContext::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<Qt5Object*>(m_pChildWindow->ImplGetWindowImpl()->mpSysObj)->windowHandle();
+}
diff --git a/vcl/qt5/Qt5Painter.cxx b/vcl/qt5/Qt5Painter.cxx
new file mode 100644
index 000000000..06eeb2895
--- /dev/null
+++ b/vcl/qt5/Qt5Painter.cxx
@@ -0,0 +1,53 @@
+/* -*- 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 <Qt5Painter.hxx>
+
+#include <QtGui/QColor>
+
+Qt5Painter::Qt5Painter(Qt5Graphics& rGraphics, bool bPrepareBrush, sal_uInt8 nTransparency)
+ : m_rGraphics(rGraphics)
+{
+ if (rGraphics.m_pQImage)
+ begin(rGraphics.m_pQImage);
+ else
+ {
+ assert(rGraphics.m_pFrame);
+ begin(rGraphics.m_pFrame->GetQWidget());
+ }
+ if (!rGraphics.m_aClipPath.isEmpty())
+ setClipPath(rGraphics.m_aClipPath);
+ else
+ setClipRegion(rGraphics.m_aClipRegion);
+ if (SALCOLOR_NONE != rGraphics.m_aLineColor)
+ {
+ QColor aColor = toQColor(rGraphics.m_aLineColor);
+ aColor.setAlpha(nTransparency);
+ setPen(aColor);
+ }
+ else
+ setPen(Qt::NoPen);
+ if (bPrepareBrush && SALCOLOR_NONE != rGraphics.m_aFillColor)
+ {
+ QColor aColor = toQColor(rGraphics.m_aFillColor);
+ aColor.setAlpha(nTransparency);
+ setBrush(aColor);
+ }
+ setCompositionMode(rGraphics.m_eCompositionMode);
+}
diff --git a/vcl/qt5/Qt5Printer.cxx b/vcl/qt5/Qt5Printer.cxx
new file mode 100644
index 000000000..16a6a1115
--- /dev/null
+++ b/vcl/qt5/Qt5Printer.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 <Qt5Printer.hxx>
+
+Qt5Printer::Qt5Printer(SalInfoPrinter* pInfoPrinter)
+ : PspSalPrinter(pInfoPrinter)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5SvpGraphics.cxx b/vcl/qt5/Qt5SvpGraphics.cxx
new file mode 100644
index 000000000..8885b9cb5
--- /dev/null
+++ b/vcl/qt5/Qt5SvpGraphics.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 <Qt5Data.hxx>
+#include <Qt5Frame.hxx>
+#include <Qt5Graphics_Controls.hxx>
+#include <Qt5SvpGraphics.hxx>
+#include <Qt5SvpSurface.hxx>
+#include <Qt5Tools.hxx>
+
+#include <QtGui/QScreen>
+#include <QtGui/QWindow>
+#include <QtWidgets/QWidget>
+
+Qt5SvpGraphics::Qt5SvpGraphics(Qt5Frame* pFrame)
+ : SvpSalGraphics()
+ , m_pFrame(pFrame)
+{
+ if (!Qt5Data::noNativeControls())
+ m_pWidgetDraw.reset(new Qt5Graphics_Controls(*this));
+ if (m_pFrame)
+ setDevicePixelRatioF(m_pFrame->devicePixelRatioF());
+}
+
+Qt5SvpGraphics::~Qt5SvpGraphics() {}
+
+void Qt5SvpGraphics::updateQWidget() const
+{
+ if (!m_pFrame)
+ return;
+ QWidget* pQWidget = m_pFrame->GetQWidget();
+ if (pQWidget)
+ pQWidget->update(pQWidget->rect());
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool Qt5SvpGraphics::SupportsCairo() const { return true; }
+
+cairo::SurfaceSharedPtr
+Qt5SvpGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ return std::make_shared<cairo::Qt5SvpSurface>(rSurface);
+}
+
+cairo::SurfaceSharedPtr Qt5SvpGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int x,
+ int y, int width, int height) const
+{
+ return std::make_shared<cairo::Qt5SvpSurface>(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 Qt5SvpGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(dynamic_cast<Qt5Graphics_Controls*>(m_pWidgetDraw.get()));
+ assert(!rDamagedRegion.IsEmpty());
+
+ QImage* pImage = static_cast<Qt5Graphics_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.getX(),
+ rDamagedRegion.getY(), rDamagedRegion.GetWidth(), rDamagedRegion.GetHeight());
+ drawBitmap(aTR, &aBuffer, CAIRO_OPERATOR_OVER);
+}
+
+void Qt5SvpGraphics::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 || !m_pFrame->GetQWidget()->window()->windowHandle())
+ return;
+
+ QScreen* pScreen = m_pFrame->GetQWidget()->window()->windowHandle()->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/Qt5SvpSurface.cxx b/vcl/qt5/Qt5SvpSurface.cxx
new file mode 100644
index 000000000..4bab42997
--- /dev/null
+++ b/vcl/qt5/Qt5SvpSurface.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 <Qt5SvpSurface.hxx>
+
+#include <Qt5SvpGraphics.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
+{
+Qt5SvpSurface::Qt5SvpSurface(const CairoSurfaceSharedPtr& pSurface)
+ : m_pGraphics(nullptr)
+ , m_pCairoContext(nullptr)
+ , m_pSurface(pSurface)
+{
+}
+
+Qt5SvpSurface::Qt5SvpSurface(const Qt5SvpGraphics* pGraphics, int x, int y, int width, int height)
+ : m_pGraphics(pGraphics)
+ , m_pCairoContext(pGraphics->getCairoContext(false))
+{
+ 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);
+}
+
+Qt5SvpSurface::~Qt5SvpSurface()
+{
+ if (m_pCairoContext)
+ cairo_destroy(m_pCairoContext);
+}
+
+CairoSharedPtr Qt5SvpSurface::getCairo() const
+{
+ return CairoSharedPtr(cairo_create(m_pSurface.get()), &cairo_destroy);
+}
+
+SurfaceSharedPtr Qt5SvpSurface::getSimilar(int cairo_content_type, int width, int height) const
+{
+ return std::make_shared<Qt5SvpSurface>(CairoSurfaceSharedPtr(
+ cairo_surface_create_similar(
+ m_pSurface.get(), static_cast<cairo_content_t>(cairo_content_type), width, height),
+ &cairo_surface_destroy));
+}
+
+void Qt5SvpSurface::flush() const
+{
+ cairo_surface_flush(m_pSurface.get());
+ if (m_pGraphics)
+ m_pGraphics->updateQWidget();
+}
+
+VclPtr<VirtualDevice> Qt5SvpSurface::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::DEFAULT);
+}
+
+} // namespace cairo
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5SvpVirtualDevice.hxx b/vcl/qt5/Qt5SvpVirtualDevice.hxx
new file mode 100644
index 000000000..0eb4ed26e
--- /dev/null
+++ b/vcl/qt5/Qt5SvpVirtualDevice.hxx
@@ -0,0 +1,37 @@
+/* -*- 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 <qt5/Qt5SvpGraphics.hxx>
+
+class VCL_DLLPUBLIC Qt5SvpVirtualDevice : public SvpSalVirtualDevice
+{
+public:
+ Qt5SvpVirtualDevice(DeviceFormat eFormat, cairo_surface_t* pRefSurface,
+ cairo_surface_t* pPreExistingTarget)
+ : SvpSalVirtualDevice(eFormat, pRefSurface, pPreExistingTarget)
+ {
+ }
+
+ SalGraphics* AcquireGraphics() override { return AddGraphics(new Qt5SvpGraphics(nullptr)); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5System.cxx b/vcl/qt5/Qt5System.cxx
new file mode 100644
index 000000000..d769f7118
--- /dev/null
+++ b/vcl/qt5/Qt5System.cxx
@@ -0,0 +1,38 @@
+/* -*- 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 <QtWidgets/QApplication>
+#include <QtWidgets/QDesktopWidget>
+
+#include <string.h>
+#include <tools/gen.hxx>
+#include <Qt5System.hxx>
+#include <Qt5Tools.hxx>
+
+unsigned int Qt5System::GetDisplayScreenCount()
+{
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ return QApplication::desktop()->screenCount();
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+tools::Rectangle Qt5System::GetDisplayScreenPosSizePixel(unsigned int nScreen)
+{
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ QRect qRect = QApplication::desktop()->screenGeometry(nScreen);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ return toRectangle(scaledQRect(qRect, qApp->devicePixelRatio()));
+}
+
+int Qt5System::ShowNativeDialog(const OUString&, const OUString&, const std::vector<OUString>&)
+{
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Timer.cxx b/vcl/qt5/Qt5Timer.cxx
new file mode 100644
index 000000000..bbc2800e8
--- /dev/null
+++ b/vcl/qt5/Qt5Timer.cxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <Qt5Timer.hxx>
+#include <Qt5Timer.moc>
+
+#include <QtWidgets/QApplication>
+#include <QtCore/QThread>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+Qt5Timer::Qt5Timer()
+{
+ 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 Qt5Timer::timeoutActivated()
+{
+ SolarMutexGuard aGuard;
+ CallCallback();
+}
+
+void Qt5Timer::startTimer(int nMS) { m_aTimer.start(nMS); }
+
+void Qt5Timer::Start(sal_uInt64 nMS) { Q_EMIT startTimerSignal(nMS); }
+
+void Qt5Timer::stopTimer() { m_aTimer.stop(); }
+
+void Qt5Timer::Stop() { Q_EMIT stopTimerSignal(); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Tools.cxx b/vcl/qt5/Qt5Tools.cxx
new file mode 100644
index 000000000..667a5af6b
--- /dev/null
+++ b/vcl/qt5/Qt5Tools.cxx
@@ -0,0 +1,122 @@
+/* -*- 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 <Qt5Tools.hxx>
+
+#include <cairo.h>
+
+#include <tools/stream.hxx>
+#include <vcl/event.hxx>
+#include <vcl/image.hxx>
+#include <vcl/pngwrite.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::MidButton)
+ 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;
+ vcl::PNGWriter aWriter(rImage.GetBitmapEx());
+ aWriter.Write(aMemStm);
+ aImage.loadFromData(static_cast<const uchar*>(aMemStm.GetData()), aMemStm.TellEnd());
+ }
+
+ return aImage;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Transferable.cxx b/vcl/qt5/Qt5Transferable.cxx
new file mode 100644
index 000000000..2e0deef9b
--- /dev/null
+++ b/vcl/qt5/Qt5Transferable.cxx
@@ -0,0 +1,345 @@
+/* -*- 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 <Qt5Transferable.hxx>
+
+#include <comphelper/sequence.hxx>
+#include <sal/log.hxx>
+
+#include <QtWidgets/QApplication>
+
+#include <Qt5Instance.hxx>
+#include <Qt5Tools.hxx>
+
+#include <cassert>
+
+static bool lcl_textMimeInfo(const OUString& rMimeString, bool& bHaveNoCharset, bool& bHaveUTF16,
+ bool& bHaveUTF8)
+{
+ sal_Int32 nIndex = 0;
+ if (rMimeString.getToken(0, ';', nIndex) == "text/plain")
+ {
+ OUString aToken(rMimeString.getToken(0, ';', nIndex));
+ if (aToken == "charset=utf-16")
+ bHaveUTF16 = true;
+ else if (aToken == "charset=utf-8")
+ bHaveUTF8 = true;
+ else if (aToken.isEmpty())
+ bHaveNoCharset = true;
+ else // we just handle UTF-16 and UTF-8, everything else is "bytes"
+ return false;
+ return true;
+ }
+ return false;
+}
+
+Qt5Transferable::Qt5Transferable(const QMimeData* pMimeData)
+ : m_pMimeData(pMimeData)
+ , m_bConvertFromLocale(false)
+{
+ assert(pMimeData);
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL Qt5Transferable::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;
+ css::uno::Sequence<css::datatransfer::DataFlavor> aMimeTypeSeq(nMimeTypeSeqSize);
+
+ 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;
+ 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);
+ aMimeTypeSeq[nMimeTypeCount] = aFlavor;
+ nMimeTypeCount++;
+ }
+
+ m_bConvertFromLocale = bHaveNoCharset && !bHaveUTF16;
+ if (m_bConvertFromLocale)
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ assert(nMimeTypeCount < nMimeTypeSeqSize);
+ aMimeTypeSeq[nMimeTypeCount] = aFlavor;
+ nMimeTypeCount++;
+ }
+
+ aMimeTypeSeq.realloc(nMimeTypeCount);
+
+ m_aMimeTypeSeq = aMimeTypeSeq;
+ return m_aMimeTypeSeq;
+}
+
+sal_Bool SAL_CALL
+Qt5Transferable::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
+Qt5Transferable::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_bConvertFromLocale)
+ {
+ 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;
+}
+
+Qt5ClipboardTransferable::Qt5ClipboardTransferable(const QClipboard::Mode aMode,
+ const QMimeData* pMimeData)
+ : Qt5Transferable(pMimeData)
+ , m_aMode(aMode)
+{
+}
+
+bool Qt5ClipboardTransferable::hasInFlightChanged() const
+{
+ const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode));
+ SAL_WARN_IF(bChanged, "vcl.qt5",
+ "In flight clipboard change detected - broken clipboard read!");
+ return bChanged;
+}
+
+css::uno::Any SAL_CALL
+Qt5ClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
+{
+ css::uno::Any aAny;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ aAny = Qt5Transferable::getTransferData(rFlavor);
+ });
+ return aAny;
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor>
+ SAL_CALL Qt5ClipboardTransferable::getTransferDataFlavors()
+{
+ css::uno::Sequence<css::datatransfer::DataFlavor> aSeq;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ aSeq = Qt5Transferable::getTransferDataFlavors();
+ });
+ return aSeq;
+}
+
+sal_Bool SAL_CALL
+Qt5ClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
+{
+ bool bIsSupported = false;
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ SolarMutexGuard g;
+ pSalInst->RunInMainThread([&, this]() {
+ if (!hasInFlightChanged())
+ bIsSupported = Qt5Transferable::isDataFlavorSupported(rFlavor);
+ });
+ return bIsSupported;
+}
+
+Qt5MimeData::Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable>& xTrans)
+ : m_aContents(xTrans)
+ , m_bHaveNoCharset(false)
+ , m_bHaveUTF8(false)
+{
+ assert(xTrans.is());
+}
+
+bool Qt5MimeData::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 Qt5MimeData::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;
+}
+
+QVariant Qt5MimeData::retrieveData(const QString& mimeType, QVariant::Type) const
+{
+ 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(reinterpret_cast<const char*>(aUTF8String.getStr()),
+ aUTF8String.getLength());
+ }
+ else if (bWantNoCharset)
+ {
+ OString aLocaleString(OUStringToOString(aString, osl_getThreadTextEncoding()));
+ aByteArray = QByteArray(reinterpret_cast<const char*>(aLocaleString.getStr()),
+ aLocaleString.getLength());
+ }
+ 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 Qt5MimeData::hasFormat(const QString& mimeType) const { return formats().contains(mimeType); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5VirtualDevice.cxx b/vcl/qt5/Qt5VirtualDevice.cxx
new file mode 100644
index 000000000..f1c7d9606
--- /dev/null
+++ b/vcl/qt5/Qt5VirtualDevice.cxx
@@ -0,0 +1,93 @@
+/* -*- 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 <Qt5VirtualDevice.hxx>
+
+#include <Qt5Graphics.hxx>
+#include <Qt5Tools.hxx>
+
+#include <QtGui/QImage>
+
+Qt5VirtualDevice::Qt5VirtualDevice(DeviceFormat eFormat, double fScale)
+ : m_eFormat(eFormat)
+ , m_fScale(fScale)
+{
+}
+
+SalGraphics* Qt5VirtualDevice::AcquireGraphics()
+{
+ assert(m_pImage);
+ Qt5Graphics* pGraphics = new Qt5Graphics(m_pImage.get());
+ m_aGraphics.push_back(pGraphics);
+ return pGraphics;
+}
+
+void Qt5VirtualDevice::ReleaseGraphics(SalGraphics* pGraphics)
+{
+ m_aGraphics.remove(dynamic_cast<Qt5Graphics*>(pGraphics));
+ delete pGraphics;
+}
+
+bool Qt5VirtualDevice::SetSize(long nNewDX, long nNewDY)
+{
+ return SetSizeUsingBuffer(nNewDX, nNewDY, nullptr);
+}
+
+bool Qt5VirtualDevice::SetSizeUsingBuffer(long nNewDX, 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 (m_eFormat == DeviceFormat::BITMASK)
+ {
+ m_pImage.reset(new QImage(nNewDX, nNewDY, QImage::Format_Mono));
+ }
+ else
+ {
+ if (pBuffer)
+ m_pImage.reset(new QImage(pBuffer, nNewDX, nNewDY, Qt5_DefaultFormat32));
+ else
+ m_pImage.reset(new QImage(nNewDX, nNewDY, Qt5_DefaultFormat32));
+ }
+
+ m_pImage->fill(Qt::transparent);
+ m_pImage->setDevicePixelRatio(m_fScale);
+
+ // update device in existing graphics
+ for (auto pQt5Graph : m_aGraphics)
+ pQt5Graph->ChangeQImage(m_pImage.get());
+
+ return true;
+}
+
+long Qt5VirtualDevice::GetWidth() const { return m_pImage ? m_aFrameSize.width() : 0; }
+
+long Qt5VirtualDevice::GetHeight() const { return m_pImage ? m_aFrameSize.height() : 0; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/Qt5Widget.cxx b/vcl/qt5/Qt5Widget.cxx
new file mode 100644
index 000000000..0ef305f42
--- /dev/null
+++ b/vcl/qt5/Qt5Widget.cxx
@@ -0,0 +1,740 @@
+/* -*- 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 <Qt5Widget.hxx>
+#include <Qt5Widget.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Graphics.hxx>
+#include <Qt5Instance.hxx>
+#include <Qt5SvpGraphics.hxx>
+#include <Qt5Transferable.hxx>
+#include <Qt5Tools.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/QWidget>
+
+#include <cairo.h>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <window.h>
+#include <tools/diagnose_ex.h>
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+using namespace com::sun::star;
+
+void Qt5Widget::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), Qt5_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 Qt5Widget::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.nWidth = nWidth;
+ m_rFrame.maGeometry.nHeight = nHeight;
+
+ if (m_rFrame.m_bUseCairo)
+ {
+ if (m_rFrame.m_pSvpGraphics)
+ {
+ 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);
+
+ int min_width = qMin(cairo_image_surface_get_width(old_surface.get()), nWidth);
+ int min_height = qMin(cairo_image_surface_get_height(old_surface.get()), nHeight);
+
+ SalTwoRect rect(0, 0, min_width, min_height, 0, 0, min_width, min_height);
+
+ m_rFrame.m_pSvpGraphics->copySource(rect, old_surface.get());
+ }
+ }
+ else
+ {
+ QImage* pImage = nullptr;
+
+ if (m_rFrame.m_pQImage)
+ pImage = new QImage(m_rFrame.m_pQImage->copy(0, 0, nWidth, nHeight));
+ else
+ {
+ pImage = new QImage(nWidth, nHeight, Qt5_DefaultFormat32);
+ pImage->fill(Qt::transparent);
+ }
+
+ m_rFrame.m_pQt5Graphics->ChangeQImage(pImage);
+ m_rFrame.m_pQImage.reset(pImage);
+ }
+
+ m_rFrame.CallCallback(SalEvent::Resize, nullptr);
+}
+
+void Qt5Widget::handleMouseButtonEvent(const Qt5Frame& rFrame, const QMouseEvent* pEvent,
+ const ButtonKeyState eState)
+{
+ SalMouseEvent aEvent;
+ switch (pEvent->button())
+ {
+ case Qt::LeftButton:
+ aEvent.mnButton = MOUSE_LEFT;
+ break;
+ case Qt::MidButton:
+ aEvent.mnButton = MOUSE_MIDDLE;
+ break;
+ case Qt::RightButton:
+ aEvent.mnButton = MOUSE_RIGHT;
+ break;
+ default:
+ return;
+ }
+
+ const qreal fRatio = rFrame.devicePixelRatioF();
+ const Point aPos = toPoint(pEvent->pos() * fRatio);
+
+ aEvent.mnX = QGuiApplication::isLeftToRight()
+ ? aPos.X()
+ : round(rFrame.GetQWidget()->width() * fRatio) - aPos.X();
+ aEvent.mnY = aPos.Y();
+ aEvent.mnTime = pEvent->timestamp();
+ aEvent.mnCode = GetKeyModCode(pEvent->modifiers()) | GetMouseModCode(pEvent->buttons());
+
+ SalEvent nEventType;
+ if (eState == ButtonKeyState::Pressed)
+ nEventType = SalEvent::MouseButtonDown;
+ else
+ nEventType = SalEvent::MouseButtonUp;
+ rFrame.CallCallback(nEventType, &aEvent);
+}
+
+void Qt5Widget::mousePressEvent(QMouseEvent* pEvent) { handleMousePressEvent(m_rFrame, pEvent); }
+
+void Qt5Widget::mouseReleaseEvent(QMouseEvent* pEvent)
+{
+ handleMouseReleaseEvent(m_rFrame, pEvent);
+}
+
+void Qt5Widget::mouseMoveEvent(QMouseEvent* pEvent)
+{
+ const qreal fRatio = m_rFrame.devicePixelRatioF();
+ const Point aPos = toPoint(pEvent->pos() * fRatio);
+
+ SalMouseEvent aEvent;
+ aEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(width() * fRatio) - aPos.X();
+ aEvent.mnY = aPos.Y();
+ aEvent.mnTime = pEvent->timestamp();
+ aEvent.mnCode = GetKeyModCode(pEvent->modifiers()) | GetMouseModCode(pEvent->buttons());
+ aEvent.mnButton = 0;
+
+ m_rFrame.CallCallback(SalEvent::MouseMove, &aEvent);
+ pEvent->accept();
+}
+
+void Qt5Widget::wheelEvent(QWheelEvent* pEvent)
+{
+ const Point aPos = toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF());
+
+ SalWheelMouseEvent aEvent;
+ aEvent.mnX = QGuiApplication::isLeftToRight()
+ ? aPos.X()
+ : round(width() * m_rFrame.devicePixelRatioF()) - aPos.X();
+ aEvent.mnY = aPos.Y();
+ aEvent.mnTime = pEvent->timestamp();
+ aEvent.mnCode = GetKeyModCode(pEvent->modifiers()) | GetMouseModCode(pEvent->buttons());
+
+ // 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 Qt5Widget::dragEnterEvent(QDragEnterEvent* event)
+{
+ if (dynamic_cast<const Qt5MimeData*>(event->mimeData()))
+ event->accept();
+ else
+ event->acceptProposedAction();
+}
+
+// also called when a drop is rejected
+void Qt5Widget::dragLeaveEvent(QDragLeaveEvent*) { m_rFrame.handleDragLeave(); }
+
+void Qt5Widget::dragMoveEvent(QDragMoveEvent* pEvent) { m_rFrame.handleDragMove(pEvent); }
+
+void Qt5Widget::dropEvent(QDropEvent* pEvent) { m_rFrame.handleDrop(pEvent); }
+
+void Qt5Widget::moveEvent(QMoveEvent* pEvent)
+{
+ if (m_rFrame.m_pTopLevel)
+ return;
+
+ const Point aPos = toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF());
+ m_rFrame.maGeometry.nX = aPos.X();
+ m_rFrame.maGeometry.nY = aPos.Y();
+ m_rFrame.CallCallback(SalEvent::Move, nullptr);
+}
+
+void Qt5Widget::showEvent(QShowEvent*)
+{
+ QSize aSize(m_rFrame.GetQWidget()->size() * m_rFrame.devicePixelRatioF());
+ // forcing an immediate update somehow interferes with the hide + show
+ // sequence from Qt5Frame::SetModal, if the frame was already set visible,
+ // resulting in a hidden / unmapped window
+ SalPaintEvent aPaintEvt(0, 0, aSize.width(), aSize.height());
+ m_rFrame.CallCallback(SalEvent::Paint, &aPaintEvt);
+}
+
+void Qt5Widget::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_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 Qt5Widget::commitText(Qt5Frame& 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);
+}
+
+bool Qt5Widget::handleKeyEvent(Qt5Frame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent,
+ const ButtonKeyState eState)
+{
+ sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers());
+ if (eState == ButtonKeyState::Pressed && nCode == 0 && pEvent->text().length() > 1
+ && rWidget.testAttribute(Qt::WA_InputMethodEnabled))
+ {
+ commitText(rFrame, pEvent->text());
+ pEvent->accept();
+ return true;
+ }
+
+ 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 (eState == ButtonKeyState::Pressed)
+ bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent);
+ else
+ bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent);
+ if (bStopProcessingKey)
+ pEvent->accept();
+ return bStopProcessingKey;
+}
+
+bool Qt5Widget::handleEvent(Qt5Frame& rFrame, const QWidget& rWidget, QEvent* pEvent)
+{
+ 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),
+ ButtonKeyState::Pressed))
+ return true;
+ }
+ return false;
+}
+
+bool Qt5Widget::event(QEvent* pEvent)
+{
+ return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent);
+}
+
+void Qt5Widget::keyReleaseEvent(QKeyEvent* pEvent)
+{
+ if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent))
+ QWidget::keyReleaseEvent(pEvent);
+}
+
+void Qt5Widget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); }
+
+void Qt5Widget::focusOutEvent(QFocusEvent*)
+{
+ endExtTextInput();
+ m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr);
+}
+
+Qt5Widget::Qt5Widget(Qt5Frame& rFrame, Qt::WindowFlags f)
+ : QWidget(Q_NULLPTR, f)
+ , m_rFrame(rFrame)
+ , m_bNonEmptyIMPreeditSeen(false)
+ , m_nDeltaX(0)
+ , m_nDeltaY(0)
+{
+ create();
+ setMouseTracking(true);
+ setFocusPolicy(Qt::StrongFocus);
+}
+
+static ExtTextInputAttr lcl_MapUndrelineStyle(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 Qt5Widget::inputMethodEvent(QInputMethodEvent* pEvent)
+{
+ if (!pEvent->commitString().isEmpty())
+ 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 (int i = 0; i < rAttrList.size(); ++i)
+ {
+ const QInputMethodEvent::Attribute& rAttr = rAttrList.at(i);
+ switch (rAttr.type)
+ {
+ case QInputMethodEvent::TextFormat:
+ {
+ QTextCharFormat aCharFormat
+ = qvariant_cast<QTextFormat>(rAttr.value).toCharFormat();
+ if (aCharFormat.isValid())
+ {
+ ExtTextInputAttr aETIP
+ = lcl_MapUndrelineStyle(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++)
+ 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.qt5", "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.qt5", "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 Qt5Widget::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);
+ [[fallthrough]];
+ }
+ case Qt::ImCursorPosition:
+ {
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
+ return QVariant(static_cast<int>(nCursorPos));
+ [[fallthrough]];
+ }
+ case Qt::ImCursorRectangle:
+ {
+ SalExtTextInputPosEvent aPosEvent;
+ m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent);
+ return QVariant(
+ QRect(aPosEvent.mnX, aPosEvent.mnY, aPosEvent.mnWidth, aPosEvent.mnHeight));
+ }
+ case Qt::ImAnchorPosition:
+ {
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
+ return QVariant(static_cast<int>(nAnchor));
+ [[fallthrough]];
+ }
+ case Qt::ImCurrentSelection:
+ {
+ QString aSelection;
+ sal_Int32 nCursorPos, nAnchor;
+ if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection))
+ return QVariant(aSelection);
+ [[fallthrough]];
+ }
+ default:
+ return QWidget::inputMethodQuery(property);
+ }
+
+ return QVariant();
+}
+
+void Qt5Widget::endExtTextInput()
+{
+ if (m_bNonEmptyIMPreeditSeen)
+ {
+ m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
+ m_bNonEmptyIMPreeditSeen = false;
+ }
+}
+
+void Qt5Widget::changeEvent(QEvent* pEvent)
+{
+ switch (pEvent->type())
+ {
+ case QEvent::FontChange:
+ [[fallthrough]];
+ case QEvent::PaletteChange:
+ [[fallthrough]];
+ case QEvent::StyleChange:
+ {
+ auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
+ 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/Qt5XAccessible.cxx b/vcl/qt5/Qt5XAccessible.cxx
new file mode 100644
index 000000000..e9f0804d5
--- /dev/null
+++ b/vcl/qt5/Qt5XAccessible.cxx
@@ -0,0 +1,29 @@
+/* -*- 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 <Qt5XAccessible.hxx>
+#include <Qt5XAccessible.moc>
+
+#include <Qt5Frame.hxx>
+#include <Qt5Tools.hxx>
+#include <Qt5Widget.hxx>
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+#include <sal/log.hxx>
+
+using namespace css::accessibility;
+using namespace css::uno;
+
+Qt5XAccessible::Qt5XAccessible(Reference<XAccessible> xAccessible)
+ : m_xAccessible(xAccessible)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */