diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/qt5 | |
parent | Initial commit. (diff) | |
download | libreoffice-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')
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: */ |