diff options
Diffstat (limited to 'src/quicktemplates2')
187 files changed, 57896 insertions, 0 deletions
diff --git a/src/quicktemplates2/CMakeLists.txt b/src/quicktemplates2/CMakeLists.txt new file mode 100644 index 0000000000..3ddb994cc1 --- /dev/null +++ b/src/quicktemplates2/CMakeLists.txt @@ -0,0 +1,169 @@ +##################################################################### +## QuickTemplates2 Module: +##################################################################### + +qt_internal_add_qml_module(QuickTemplates2 + URI "QtQuick.Templates" + VERSION "${PROJECT_VERSION}" + CLASSNAME QtQuickTemplates2Plugin + DEPENDENCIES + QtQuick/auto + PLUGIN_TARGET qtquicktemplates2plugin + NO_PLUGIN_OPTIONAL + NO_GENERATE_PLUGIN_SOURCE + SOURCES + qquickabstractbutton.cpp qquickabstractbutton_p.h + qquickabstractbutton_p_p.h + qquickaction.cpp qquickaction_p.h + qquickactiongroup.cpp qquickactiongroup_p.h + qquickapplicationwindow.cpp qquickapplicationwindow_p.h + qquickbusyindicator.cpp qquickbusyindicator_p.h + qquickbutton.cpp qquickbutton_p.h + qquickbutton_p_p.h + qquickbuttongroup.cpp qquickbuttongroup_p.h + qquickcheckbox.cpp qquickcheckbox_p.h + qquickcheckdelegate.cpp qquickcheckdelegate_p.h + qquickcombobox.cpp qquickcombobox_p.h + qquickcontainer.cpp qquickcontainer_p.h + qquickcontainer_p_p.h + qquickcontentitem.cpp qquickcontentitem_p.h + qquickcontrol.cpp qquickcontrol_p.h + qquickcontrol_p_p.h + qquickdeferredexecute.cpp + qquickdeferredexecute_p_p.h + qquickdeferredpointer_p_p.h + qquickdelaybutton.cpp qquickdelaybutton_p.h + qquickdial.cpp qquickdial_p.h + qquickdialog.cpp qquickdialog_p.h + qquickdialog_p_p.h + qquickdialogbuttonbox.cpp qquickdialogbuttonbox_p.h + qquickdialogbuttonbox_p_p.h + qquickdrawer.cpp qquickdrawer_p.h + qquickdrawer_p_p.h + qquickframe.cpp qquickframe_p.h + qquickframe_p_p.h + qquickgroupbox.cpp qquickgroupbox_p.h + qquickicon.cpp qquickicon_p.h + qquickindicatorbutton_p.cpp qquickindicatorbutton_p.h + qquickitemdelegate.cpp qquickitemdelegate_p.h + qquickitemdelegate_p_p.h + qquicklabel.cpp qquicklabel_p.h + qquicklabel_p_p.h + qquickmenu.cpp qquickmenu_p.h + qquickmenu_p_p.h + qquickmenubar.cpp qquickmenubar_p.h + qquickmenubar_p_p.h + qquickmenubaritem.cpp qquickmenubaritem_p.h + qquickmenubaritem_p_p.h + qquickmenuitem.cpp qquickmenuitem_p.h + qquickmenuitem_p_p.h + qquickmenuseparator.cpp qquickmenuseparator_p.h + qquickoverlay.cpp qquickoverlay_p.h + qquickoverlay_p_p.h + qquickpage.cpp qquickpage_p.h + qquickpage_p_p.h + qquickpageindicator.cpp qquickpageindicator_p.h + qquickpane.cpp qquickpane_p.h + qquickpane_p_p.h + qquickpopup.cpp qquickpopup_p.h + qquickpopup_p_p.h + qquickpopupanchors.cpp qquickpopupanchors_p.h + qquickpopupanchors_p_p.h + qquickpopupitem.cpp + qquickpopupitem_p_p.h + qquickpopuppositioner.cpp + qquickpopuppositioner_p_p.h + qquickpresshandler.cpp + qquickpresshandler_p_p.h + qquickprogressbar.cpp qquickprogressbar_p.h + qquickradiobutton.cpp qquickradiobutton_p.h + qquickradiodelegate.cpp qquickradiodelegate_p.h + qquickrangeslider.cpp qquickrangeslider_p.h + qquickroundbutton.cpp qquickroundbutton_p.h + qquickscrollbar.cpp qquickscrollbar_p.h + qquickscrollbar_p_p.h + qquickscrollindicator.cpp qquickscrollindicator_p.h + qquickscrollview.cpp qquickscrollview_p.h + qquickselectionrectangle.cpp qquickselectionrectangle_p.h + qquickselectionrectangle_p_p.h + qquickshortcutcontext.cpp + qquickshortcutcontext_p_p.h + qquickslider.cpp qquickslider_p.h + qquickspinbox.cpp qquickspinbox_p.h + qquicksplitview.cpp qquicksplitview_p.h + qquickstackelement.cpp + qquickstackelement_p_p.h + qquickstacktransition.cpp + qquickstacktransition_p_p.h + qquickstackview.cpp qquickstackview_p.cpp qquickstackview_p.h + qquickstackview_p_p.h + qquickswipe_p.h + qquickswipedelegate.cpp qquickswipedelegate_p.h + qquickswipedelegate_p_p.h + qquickswipeview.cpp qquickswipeview_p.h + qquickswitch.cpp qquickswitch_p.h + qquickswitchdelegate.cpp qquickswitchdelegate_p.h + qquicktabbar.cpp qquicktabbar_p.h + qquicktabbutton.cpp qquicktabbutton_p.h + qquicktextarea.cpp qquicktextarea_p.h + qquicktextarea_p_p.h + qquicktextfield.cpp qquicktextfield_p.h + qquicktextfield_p_p.h + qquicktheme.cpp qquicktheme_p.h + qquicktheme_p_p.h + qquicktoolbar.cpp qquicktoolbar_p.h + qquicktoolbutton.cpp qquicktoolbutton_p.h + qquicktoolseparator.cpp qquicktoolseparator_p.h + qquicktooltip.cpp qquicktooltip_p.h + qquickvelocitycalculator.cpp + qquickvelocitycalculator_p_p.h + qtquicktemplates2global.cpp qtquicktemplates2global_p.h + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR} + LIBRARIES + Qt::CorePrivate + Qt::GuiPrivate + Qt::QmlPrivate + Qt::QuickPrivate + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui + Qt::Quick +) + +qt_internal_extend_target(QuickTemplates2 CONDITION TARGET Qt::QmlModels + LIBRARIES + Qt::QmlModelsPrivate + PUBLIC_LIBRARIES + Qt::QmlModels + PRIVATE_MODULE_INTERFACE + Qt::QmlModelsPrivate +) + +qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_accessibility + SOURCES + accessible/qaccessiblequickpage.cpp accessible/qaccessiblequickpage_p.h +) + +qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_quick_tableview + SOURCES + qquickheaderview.cpp qquickheaderview_p.h + qquickheaderview_p_p.h +) + +qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_quick_listview AND QT_FEATURE_quick_pathview + SOURCES + qquicktumbler.cpp qquicktumbler_p.h + qquicktumbler_p_p.h +) + +qt_internal_extend_Target(qtquicktemplates2plugin + SOURCES + qtquicktemplates2plugin.cpp + LIBRARIES + Qt::Quick + Qt::QuickTemplates2Private +) diff --git a/src/quicktemplates2/accessible/accessible.pri b/src/quicktemplates2/accessible/accessible.pri new file mode 100644 index 0000000000..0c855d34c6 --- /dev/null +++ b/src/quicktemplates2/accessible/accessible.pri @@ -0,0 +1,4 @@ +HEADERS += \ + $$PWD/qaccessiblequickpage_p.h \ +SOURCES += \ + $$PWD/qaccessiblequickpage.cpp \ diff --git a/src/quicktemplates2/accessible/qaccessiblequickpage.cpp b/src/quicktemplates2/accessible/qaccessiblequickpage.cpp new file mode 100644 index 0000000000..90ac49f9da --- /dev/null +++ b/src/quicktemplates2/accessible/qaccessiblequickpage.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessiblequickpage_p.h" +#include "qquickpage_p.h" + +QT_BEGIN_NAMESPACE + +QAccessibleQuickPage::QAccessibleQuickPage(QQuickPage *page) + : QAccessibleQuickItem(page) +{ +} + +QAccessibleInterface *QAccessibleQuickPage::child(int index) const +{ + const QList<QQuickItem*> kids = orderedChildItems(); + if (QQuickItem *item = kids.value(index)) + return QAccessible::queryAccessibleInterface(item); + return nullptr; +} + +int QAccessibleQuickPage::indexOfChild(const QAccessibleInterface *iface) const +{ + const QList<QQuickItem*> kids = orderedChildItems(); + return (int)kids.indexOf(static_cast<QQuickItem*>(iface->object())); +} + +QList<QQuickItem *> QAccessibleQuickPage::orderedChildItems() const +{ + // Just ensures that the header is first, and footer is last. Other existing order is kept. + const QQuickPage *p = page(); + QList<QQuickItem*> kids = childItems(); + const qsizetype hidx = kids.indexOf(p->header()); + if (hidx != -1) + kids.move(hidx, 0); + const qsizetype fidx = kids.indexOf(p->footer()); + if (fidx != -1) + kids.move(fidx, kids.count() - 1); + return kids; +} + +QQuickPage *QAccessibleQuickPage::page() const +{ + return static_cast<QQuickPage*>(object()); +} + +QT_END_NAMESPACE + diff --git a/src/quicktemplates2/accessible/qaccessiblequickpage_p.h b/src/quicktemplates2/accessible/qaccessiblequickpage_p.h new file mode 100644 index 0000000000..9b208c1415 --- /dev/null +++ b/src/quicktemplates2/accessible/qaccessiblequickpage_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLEQUICKPAGE_H +#define QACCESSIBLEQUICKPAGE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qaccessiblequickitem_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickPage; + +class QAccessibleQuickPage : public QAccessibleQuickItem +{ +public: + QAccessibleQuickPage(QQuickPage *page); + QAccessibleInterface *child(int index) const override; + int indexOfChild(const QAccessibleInterface *iface) const override; +private: + QQuickPage *page() const; + QList<QQuickItem *> orderedChildItems() const; +}; + +QT_END_NAMESPACE + +#endif // QACCESSIBLEQUICKPAGE_H diff --git a/src/quicktemplates2/configure.cmake b/src/quicktemplates2/configure.cmake new file mode 100644 index 0000000000..4e09756939 --- /dev/null +++ b/src/quicktemplates2/configure.cmake @@ -0,0 +1,30 @@ + + +#### Inputs + + + +#### Libraries + + + +#### Tests + + + +#### Features + +qt_feature("quicktemplates2-hover" PRIVATE + SECTION "Quick Templates 2" + LABEL "Hover support" + PURPOSE "Provides support for hover effects." +) +qt_feature("quicktemplates2-multitouch" PRIVATE + SECTION "Quick Templates 2" + LABEL "Multi-touch support" + PURPOSE "Provides support for multi-touch." +) +qt_configure_add_summary_section(NAME "Qt Quick Templates 2") +qt_configure_add_summary_entry(ARGS "quicktemplates2-hover") +qt_configure_add_summary_entry(ARGS "quicktemplates2-multitouch") +qt_configure_end_summary_section() # end of "Qt Quick Templates 2" section diff --git a/src/quicktemplates2/doc/src/qtquicktemplates2-index.qdoc b/src/quicktemplates2/doc/src/qtquicktemplates2-index.qdoc new file mode 100644 index 0000000000..c7e2554821 --- /dev/null +++ b/src/quicktemplates2/doc/src/qtquicktemplates2-index.qdoc @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page qtquicktemplates2-index.html + \title Qt Quick Templates 2 + \brief A set of templates to create user interface controls in Qt Quick + + Qt Quick Templates are the foundations of \l {Qt Quick Controls}. Templates + are non-visual implementations of controls' logic and behavior. They offer + an interface to visualize the controls in QML using \l {Qt Quick}. + + Even though the templates aim to be as style-agnostic as possible, in some + cases they have to make certain assumptions about the visual structure of + a control. For example, a spinbox has buttons that increment and decrement + the value of the spinbox. In order to implement the behavior of a spinbox, + the spinbox template needs to know if the user is interacting with the up + or down button. A visual implementation of the spinbox template merely needs + to position the up and down buttons and visualize them in normal, pressed, + and disabled states. Any input event handling and state processing is taken + care of by the underlying template. + + \section1 Module Evolution + \l{Changes to Qt Quick Controls} lists important changes in the + module API and functionality that were done for the Qt 6 series of Qt. + + \section1 Related Information + + \list + \li \l{Qt Quick} + \li \l{Qt Quick Controls} + \li \l{Qt Quick Templates 2 QML Types} + \endlist +*/ diff --git a/src/quicktemplates2/doc/src/qtquicktemplates2-qmltypes.qdoc b/src/quicktemplates2/doc/src/qtquicktemplates2-qmltypes.qdoc new file mode 100644 index 0000000000..feb770bb54 --- /dev/null +++ b/src/quicktemplates2/doc/src/qtquicktemplates2-qmltypes.qdoc @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \qmlmodule QtQuick.Templates + \title Qt Quick Templates 2 QML Types + \ingroup qmlmodules + \brief Provides QML types for templates (Qt Quick Templates). + + The \l {Qt Quick Templates 2} module provides a set of non-visual templates + that can be used to build user interface controls in QML using \l {Qt Quick}. + + The QML types can be imported using the following import statement in your + \c .qml file: + + \qml + import QtQuick.Templates as T + \endqml + + For the sake of clarity, there is a one-to-one mapping between the types + provided by the \c QtQuick.Templates and \c QtQuick.Controls imports. For + every type available in the \c QtQuick.Controls import, a non-visual template + type by the same name exists in the \c QtQuick.Templates import. + + \note It is recommended to use a namespace for the templates import to avoid + overlap with the types provided by the \c QtQuick.Controls import. + + \section1 QML Types + + \generatelist {qmltypesbymodule QtQuick.Controls} + + \section1 Related Information + + \list + \li \l {Qt Quick Controls QML Types} + \li \l {Using Qt Quick Controls types in property declarations} + \endlist + + \noautolist +*/ diff --git a/src/quicktemplates2/qquickabstractbutton.cpp b/src/quicktemplates2/qquickabstractbutton.cpp new file mode 100644 index 0000000000..ffda109dfa --- /dev/null +++ b/src/quicktemplates2/qquickabstractbutton.cpp @@ -0,0 +1,1220 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickabstractbutton_p.h" +#include "qquickabstractbutton_p_p.h" +#include "qquickbuttongroup_p.h" +#include "qquickaction_p.h" +#include "qquickaction_p_p.h" +#include "qquickshortcutcontext_p_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtGui/qstylehints.h> +#include <QtGui/qguiapplication.h> +#if QT_CONFIG(shortcut) +# include <QtGui/private/qshortcutmap_p.h> +#endif +#include <QtGui/private/qguiapplication_p.h> +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQml/qqmllist.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype AbstractButton + \inherits Control +//! \instantiates QQuickAbstractButton + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-buttons + \brief Abstract base type providing functionality common to buttons. + + AbstractButton provides the interface for controls with button-like + behavior; for example, push buttons and checkable controls like + radio buttons and check boxes. As an abstract control, it has no delegate + implementations, leaving them to the types that derive from it. + + \sa ButtonGroup, {Button Controls} +*/ + +/*! + \qmlsignal QtQuick.Controls::AbstractButton::pressed() + + This signal is emitted when the button is interactively pressed by the user via touch, mouse, or keyboard. +*/ + +/*! + \qmlsignal QtQuick.Controls::AbstractButton::released() + + This signal is emitted when the button is interactively released by the user via touch, mouse, or keyboard. +*/ + +/*! + \qmlsignal QtQuick.Controls::AbstractButton::canceled() + + This signal is emitted when the button loses mouse grab + while being pressed, or when it would emit the \l released + signal but the mouse cursor is not inside the button. +*/ + +/*! + \qmlsignal QtQuick.Controls::AbstractButton::clicked() + + This signal is emitted when the button is interactively clicked by the user via touch, mouse, or keyboard. +*/ + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlsignal QtQuick.Controls::AbstractButton::toggled() + + This signal is emitted when a checkable button is interactively toggled by the user via touch, mouse, or keyboard. +*/ + +/*! + \qmlsignal QtQuick.Controls::AbstractButton::pressAndHold() + + This signal is emitted when the button is interactively pressed and held down by the user via touch or mouse. + It is not emitted when \l autoRepeat is enabled. +*/ + +/*! + \qmlsignal QtQuick.Controls::AbstractButton::doubleClicked() + + This signal is emitted when the button is interactively double clicked by the user via touch or mouse. +*/ + +void QQuickAbstractButtonPrivate::setPressPoint(const QPointF &point) +{ + pressPoint = point; + setMovePoint(point); +} + +void QQuickAbstractButtonPrivate::setMovePoint(const QPointF &point) +{ + Q_Q(QQuickAbstractButton); + bool xChange = !qFuzzyCompare(point.x(), movePoint.x()); + bool yChange = !qFuzzyCompare(point.y(), movePoint.y()); + movePoint = point; + if (xChange) + emit q->pressXChanged(); + if (yChange) + emit q->pressYChanged(); +} + +void QQuickAbstractButtonPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickAbstractButton); + QQuickControlPrivate::handlePress(point); + setPressPoint(point); + q->setPressed(true); + + emit q->pressed(); + + if (autoRepeat) + startRepeatDelay(); + else if (touchId != -1 || Qt::LeftButton == (pressButtons & Qt::LeftButton)) + startPressAndHold(); + else + stopPressAndHold(); +} + +void QQuickAbstractButtonPrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickAbstractButton); + QQuickControlPrivate::handleMove(point); + setMovePoint(point); + q->setPressed(keepPressed || q->contains(point)); + + if (!pressed && autoRepeat) + stopPressRepeat(); + else if (holdTimer > 0 && (!pressed || QLineF(pressPoint, point).length() > QGuiApplication::styleHints()->startDragDistance())) + stopPressAndHold(); +} + +void QQuickAbstractButtonPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickAbstractButton); + QQuickControlPrivate::handleRelease(point); + bool wasPressed = pressed; + setPressPoint(point); + q->setPressed(false); + pressButtons = Qt::NoButton; + + if (!wasHeld && (keepPressed || q->contains(point))) + q->nextCheckState(); + + if (wasPressed) { + emit q->released(); + if (!wasHeld && !wasDoubleClick) + trigger(); + } else { + emit q->canceled(); + } + + if (autoRepeat) + stopPressRepeat(); + else + stopPressAndHold(); + + wasDoubleClick = false; +} + +void QQuickAbstractButtonPrivate::handleUngrab() +{ + Q_Q(QQuickAbstractButton); + QQuickControlPrivate::handleUngrab(); + pressButtons = Qt::NoButton; + if (!pressed) + return; + + q->setPressed(false); + stopPressRepeat(); + stopPressAndHold(); + wasDoubleClick = false; + emit q->canceled(); +} + +bool QQuickAbstractButtonPrivate::acceptKeyClick(Qt::Key key) const +{ + return key == Qt::Key_Space; +} + +bool QQuickAbstractButtonPrivate::isPressAndHoldConnected() +{ + Q_Q(QQuickAbstractButton); + static const QMetaMethod method = [&]() { + const auto signal = &QQuickAbstractButton::pressAndHold; + return QMetaMethod::fromSignal(signal); + }(); + return q->isSignalConnected(method); +} + +bool QQuickAbstractButtonPrivate::isDoubleClickConnected() +{ + Q_Q(QQuickAbstractButton); + static const QMetaMethod method = [&]() { + const auto signal = &QQuickAbstractButton::doubleClicked; + return QMetaMethod::fromSignal(signal); + }(); + return q->isSignalConnected(method); +} + +void QQuickAbstractButtonPrivate::startPressAndHold() +{ + Q_Q(QQuickAbstractButton); + wasHeld = false; + stopPressAndHold(); + if (isPressAndHoldConnected()) + holdTimer = q->startTimer(QGuiApplication::styleHints()->mousePressAndHoldInterval()); +} + +void QQuickAbstractButtonPrivate::stopPressAndHold() +{ + Q_Q(QQuickAbstractButton); + if (holdTimer > 0) { + q->killTimer(holdTimer); + holdTimer = 0; + } +} + +void QQuickAbstractButtonPrivate::startRepeatDelay() +{ + Q_Q(QQuickAbstractButton); + stopPressRepeat(); + delayTimer = q->startTimer(repeatDelay); +} + +void QQuickAbstractButtonPrivate::startPressRepeat() +{ + Q_Q(QQuickAbstractButton); + stopPressRepeat(); + repeatTimer = q->startTimer(repeatInterval); +} + +void QQuickAbstractButtonPrivate::stopPressRepeat() +{ + Q_Q(QQuickAbstractButton); + if (delayTimer > 0) { + q->killTimer(delayTimer); + delayTimer = 0; + } + if (repeatTimer > 0) { + q->killTimer(repeatTimer); + repeatTimer = 0; + } +} + +#if QT_CONFIG(shortcut) +void QQuickAbstractButtonPrivate::grabShortcut() +{ + Q_Q(QQuickAbstractButton); + if (shortcut.isEmpty()) + return; + + shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(q, shortcut, Qt::WindowShortcut, QQuickShortcutContext::matcher); + + if (!q->isEnabled()) + QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(false, shortcutId, q); +} + +void QQuickAbstractButtonPrivate::ungrabShortcut() +{ + Q_Q(QQuickAbstractButton); + if (!shortcutId) + return; + + QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(shortcutId, q); + shortcutId = 0; +} +#endif + +void QQuickAbstractButtonPrivate::actionTextChange() +{ + Q_Q(QQuickAbstractButton); + if (explicitText) + return; + + q->buttonChange(QQuickAbstractButton::ButtonTextChange); +} + +void QQuickAbstractButtonPrivate::setText(const QString &newText, bool isExplicit) +{ + Q_Q(QQuickAbstractButton); + const QString oldText = q->text(); + explicitText = isExplicit; + text = newText; + if (oldText == q->text()) + return; + + q->buttonChange(QQuickAbstractButton::ButtonTextChange); +} + +void QQuickAbstractButtonPrivate::updateEffectiveIcon() +{ + Q_Q(QQuickAbstractButton); + // We store effectiveIcon because we need to be able to tell if the icon has actually changed. + // If we only stored our icon and the action's icon, and resolved in the getter, we'd have + // no way of knowing what the old value was here. As an added benefit, we only resolve when + // something has changed, as opposed to doing it unconditionally in the icon() getter. + const QQuickIcon newEffectiveIcon = action ? icon.resolve(action->icon()) : icon; + if (newEffectiveIcon == effectiveIcon) + return; + + effectiveIcon = newEffectiveIcon; + emit q->iconChanged(); +} + +void QQuickAbstractButtonPrivate::click() +{ + Q_Q(QQuickAbstractButton); + if (effectiveEnable) + emit q->clicked(); +} + +void QQuickAbstractButtonPrivate::trigger() +{ + Q_Q(QQuickAbstractButton); + const bool wasEnabled = effectiveEnable; + if (action && action->isEnabled()) + QQuickActionPrivate::get(action)->trigger(q, false); + if (wasEnabled && (!action || !action->isEnabled())) + emit q->clicked(); +} + +void QQuickAbstractButtonPrivate::toggle(bool value) +{ + Q_Q(QQuickAbstractButton); + const bool wasChecked = checked; + q->setChecked(value); + if (wasChecked != checked) + emit q->toggled(); +} + +static inline QString indicatorName() { return QStringLiteral("indicator"); } + +void QQuickAbstractButtonPrivate::cancelIndicator() +{ + Q_Q(QQuickAbstractButton); + quickCancelDeferred(q, indicatorName()); +} + +void QQuickAbstractButtonPrivate::executeIndicator(bool complete) +{ + Q_Q(QQuickAbstractButton); + if (indicator.wasExecuted()) + return; + + if (!indicator || complete) + quickBeginDeferred(q, indicatorName(), indicator); + if (complete) + quickCompleteDeferred(q, indicatorName(), indicator); +} + +void QQuickAbstractButtonPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickAbstractButton); + QQuickControlPrivate::itemImplicitWidthChanged(item); + if (item == indicator) + emit q->implicitIndicatorWidthChanged(); +} + +void QQuickAbstractButtonPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickAbstractButton); + QQuickControlPrivate::itemImplicitHeightChanged(item); + if (item == indicator) + emit q->implicitIndicatorHeightChanged(); +} + +void QQuickAbstractButtonPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickAbstractButton); + QQuickControlPrivate::itemDestroyed(item); + if (item == indicator) { + indicator = nullptr; + emit q->implicitIndicatorWidthChanged(); + emit q->implicitIndicatorHeightChanged(); + } +} + +QQuickAbstractButton *QQuickAbstractButtonPrivate::findCheckedButton() const +{ + Q_Q(const QQuickAbstractButton); + if (group) + return qobject_cast<QQuickAbstractButton *>(group->checkedButton()); + + const QList<QQuickAbstractButton *> buttons = findExclusiveButtons(); + // TODO: A singular QRadioButton can be unchecked, which seems logical, + // because there's nothing to be exclusive with. However, a RadioButton + // from QtQuick.Controls 1.x can never be unchecked, which is the behavior + // that QQuickRadioButton adopted. Uncommenting the following count check + // gives the QRadioButton behavior. Notice that tst_radiobutton.qml needs + // to be updated. + if (!autoExclusive /*|| buttons.count() == 1*/) + return nullptr; + + for (QQuickAbstractButton *button : buttons) { + if (button->isChecked() && button != q) + return button; + } + return checked ? const_cast<QQuickAbstractButton *>(q) : nullptr; +} + +QList<QQuickAbstractButton *> QQuickAbstractButtonPrivate::findExclusiveButtons() const +{ + QList<QQuickAbstractButton *> buttons; + if (group) { + QQmlListProperty<QQuickAbstractButton> groupButtons = group->buttons(); + int count = groupButtons.count(&groupButtons); + for (int i = 0; i < count; ++i) { + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(groupButtons.at(&groupButtons, i)); + if (button) + buttons += button; + } + } else if (parentItem) { + const auto childItems = parentItem->childItems(); + for (QQuickItem *child : childItems) { + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(child); + if (button && button->autoExclusive() && !QQuickAbstractButtonPrivate::get(button)->group) + buttons += button; + } + } + return buttons; +} + +QQuickAbstractButton::QQuickAbstractButton(QQuickItem *parent) + : QQuickControl(*(new QQuickAbstractButtonPrivate), parent) +{ + setActiveFocusOnTab(true); +#ifdef Q_OS_MACOS + setFocusPolicy(Qt::TabFocus); +#else + setFocusPolicy(Qt::StrongFocus); +#endif + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif +} + +QQuickAbstractButton::QQuickAbstractButton(QQuickAbstractButtonPrivate &dd, QQuickItem *parent) + : QQuickControl(dd, parent) +{ + setActiveFocusOnTab(true); +#ifdef Q_OS_MACOS + setFocusPolicy(Qt::TabFocus); +#else + setFocusPolicy(Qt::StrongFocus); +#endif + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif +} + +QQuickAbstractButton::~QQuickAbstractButton() +{ + Q_D(QQuickAbstractButton); + d->removeImplicitSizeListener(d->indicator); + if (d->group) + d->group->removeButton(this); +#if QT_CONFIG(shortcut) + d->ungrabShortcut(); +#endif +} + +/*! + \qmlproperty string QtQuick.Controls::AbstractButton::text + + This property holds a textual description of the button. + + \note The text is used for accessibility purposes, so it makes sense to + set a textual description even if the content item is an image. + + \sa icon, display, {Control::contentItem}{contentItem} +*/ +QString QQuickAbstractButton::text() const +{ + Q_D(const QQuickAbstractButton); + return d->explicitText || !d->action ? d->text : d->action->text(); +} + +void QQuickAbstractButton::setText(const QString &text) +{ + Q_D(QQuickAbstractButton); + d->setText(text, true); +} + +void QQuickAbstractButton::resetText() +{ + Q_D(QQuickAbstractButton); + d->setText(QString(), false); +} + +/*! + \qmlproperty bool QtQuick.Controls::AbstractButton::down + + This property holds whether the button is visually down. + + Unless explicitly set, this property follows the value of \l pressed. To + return to the default value, set this property to \c undefined. + + \sa pressed +*/ +bool QQuickAbstractButton::isDown() const +{ + Q_D(const QQuickAbstractButton); + return d->down; +} + +void QQuickAbstractButton::setDown(bool down) +{ + Q_D(QQuickAbstractButton); + d->explicitDown = true; + + if (d->down == down) + return; + + d->down = down; + emit downChanged(); +} + +void QQuickAbstractButton::resetDown() +{ + Q_D(QQuickAbstractButton); + if (!d->explicitDown) + return; + + setDown(d->pressed); + d->explicitDown = false; +} + +/*! + \qmlproperty bool QtQuick.Controls::AbstractButton::pressed + \readonly + + This property holds whether the button is physically pressed. A button can + be pressed by either touch or key events. + + \sa down +*/ +bool QQuickAbstractButton::isPressed() const +{ + Q_D(const QQuickAbstractButton); + return d->pressed; +} + +void QQuickAbstractButton::setPressed(bool isPressed) +{ + Q_D(QQuickAbstractButton); + if (d->pressed == isPressed) + return; + + d->pressed = isPressed; + setAccessibleProperty("pressed", isPressed); + emit pressedChanged(); + buttonChange(ButtonPressedChanged); + + if (!d->explicitDown) { + setDown(d->pressed); + d->explicitDown = false; + } +} + +/*! + \qmlproperty bool QtQuick.Controls::AbstractButton::checked + + This property holds whether the button is checked. + + Since Qt 6.2, setting this property no longer affects the + \l {AbstractButton::}{checkable} property. Explicitly set the + \c checkable property if needed. + + \sa checkable +*/ +bool QQuickAbstractButton::isChecked() const +{ + Q_D(const QQuickAbstractButton); + return d->checked; +} + +void QQuickAbstractButton::setChecked(bool checked) +{ + Q_D(QQuickAbstractButton); + if (d->checked == checked) + return; + + d->checked = checked; + if (d->action) + d->action->setChecked(checked); + setAccessibleProperty("checked", checked); + buttonChange(ButtonCheckedChange); + emit checkedChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::AbstractButton::checkable + + This property holds whether the button is checkable. + + A checkable button toggles between checked (on) and unchecked (off) when + the user clicks on it or presses the space bar while the button has active + focus. + + The default value is \c false. + + \sa checked +*/ +bool QQuickAbstractButton::isCheckable() const +{ + Q_D(const QQuickAbstractButton); + return d->checkable; +} + +void QQuickAbstractButton::setCheckable(bool checkable) +{ + Q_D(QQuickAbstractButton); + if (d->checkable == checkable) + return; + + d->checkable = checkable; + if (d->action) + d->action->setCheckable(checkable); + setAccessibleProperty("checkable", checkable); + buttonChange(ButtonCheckableChange); + emit checkableChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::AbstractButton::autoExclusive + + This property holds whether auto-exclusivity is enabled. + + If auto-exclusivity is enabled, checkable buttons that belong to the same + parent item behave as if they were part of the same ButtonGroup. Only + one button can be checked at any time; checking another button automatically + unchecks the previously checked one. + + \note The property has no effect on buttons that belong to a ButtonGroup. + + RadioButton and TabButton are auto-exclusive by default. +*/ +bool QQuickAbstractButton::autoExclusive() const +{ + Q_D(const QQuickAbstractButton); + return d->autoExclusive; +} + +void QQuickAbstractButton::setAutoExclusive(bool exclusive) +{ + Q_D(QQuickAbstractButton); + if (d->autoExclusive == exclusive) + return; + + d->autoExclusive = exclusive; + emit autoExclusiveChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::AbstractButton::autoRepeat + + This property holds whether the button repeats \l pressed(), \l released() + and \l clicked() signals while the button is pressed and held down. + + If this property is set to \c true, the \l pressAndHold() signal will not + be emitted. + + The default value is \c false. + + The initial delay and the repetition interval are defined in milliseconds + by \l autoRepeatDelay and \l autoRepeatInterval. +*/ +bool QQuickAbstractButton::autoRepeat() const +{ + Q_D(const QQuickAbstractButton); + return d->autoRepeat; +} + +void QQuickAbstractButton::setAutoRepeat(bool repeat) +{ + Q_D(QQuickAbstractButton); + if (d->autoRepeat == repeat) + return; + + d->stopPressRepeat(); + d->autoRepeat = repeat; + emit autoRepeatChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::AbstractButton::indicator + + This property holds the indicator item. +*/ +QQuickItem *QQuickAbstractButton::indicator() const +{ + QQuickAbstractButtonPrivate *d = const_cast<QQuickAbstractButtonPrivate *>(d_func()); + if (!d->indicator) + d->executeIndicator(); + return d->indicator; +} + +void QQuickAbstractButton::setIndicator(QQuickItem *indicator) +{ + Q_D(QQuickAbstractButton); + if (d->indicator == indicator) + return; + + if (!d->indicator.isExecuting()) + d->cancelIndicator(); + + const qreal oldImplicitIndicatorWidth = implicitIndicatorWidth(); + const qreal oldImplicitIndicatorHeight = implicitIndicatorHeight(); + + d->removeImplicitSizeListener(d->indicator); + QQuickControlPrivate::hideOldItem(d->indicator); + d->indicator = indicator; + + if (indicator) { + if (!indicator->parentItem()) + indicator->setParentItem(this); + indicator->setAcceptedMouseButtons(Qt::LeftButton); + d->addImplicitSizeListener(indicator); + } + + if (!qFuzzyCompare(oldImplicitIndicatorWidth, implicitIndicatorWidth())) + emit implicitIndicatorWidthChanged(); + if (!qFuzzyCompare(oldImplicitIndicatorHeight, implicitIndicatorHeight())) + emit implicitIndicatorHeightChanged(); + if (!d->indicator.isExecuting()) + emit indicatorChanged(); +} + +/*! + \qmlproperty string QtQuick.Controls::AbstractButton::icon.name + \qmlproperty url QtQuick.Controls::AbstractButton::icon.source + \qmlproperty int QtQuick.Controls::AbstractButton::icon.width + \qmlproperty int QtQuick.Controls::AbstractButton::icon.height + \qmlproperty color QtQuick.Controls::AbstractButton::icon.color + \qmlproperty bool QtQuick.Controls::AbstractButton::icon.cache + + This property group was added in QtQuick.Controls 2.3. + + \include qquickicon.qdocinc grouped-properties + + \sa text, display, {Icons in Qt Quick Controls} +*/ + +QQuickIcon QQuickAbstractButton::icon() const +{ + Q_D(const QQuickAbstractButton); + return d->effectiveIcon; +} + +void QQuickAbstractButton::setIcon(const QQuickIcon &icon) +{ + Q_D(QQuickAbstractButton); + d->icon = icon; + d->icon.ensureRelativeSourceResolved(this); + d->updateEffectiveIcon(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty enumeration QtQuick.Controls::AbstractButton::display + + This property determines how the \l icon and \l text are displayed within + the button. + + \table + \header \li Display \li Result + \row \li \c AbstractButton.IconOnly \li \image qtquickcontrols2-button-icononly.png + \row \li \c AbstractButton.TextOnly \li \image qtquickcontrols2-button-textonly.png + \row \li \c AbstractButton.TextBesideIcon \li \image qtquickcontrols2-button-textbesideicon.png + \row \li \c AbstractButton.TextUnderIcon \li \image qtquickcontrols2-button-textundericon.png + \endtable + + \sa {Control::}{spacing}, {Control::}{padding} +*/ +QQuickAbstractButton::Display QQuickAbstractButton::display() const +{ + Q_D(const QQuickAbstractButton); + return d->display; +} + +void QQuickAbstractButton::setDisplay(Display display) +{ + Q_D(QQuickAbstractButton); + if (display == d->display) + return; + + d->display = display; + emit displayChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty Action QtQuick.Controls::AbstractButton::action + + This property holds the button action. + + \sa Action +*/ +QQuickAction *QQuickAbstractButton::action() const +{ + Q_D(const QQuickAbstractButton); + return d->action; +} + +void QQuickAbstractButton::setAction(QQuickAction *action) +{ + Q_D(QQuickAbstractButton); + if (d->action == action) + return; + + const QString oldText = text(); + + if (QQuickAction *oldAction = d->action.data()) { + QQuickActionPrivate::get(oldAction)->unregisterItem(this); + QObjectPrivate::disconnect(oldAction, &QQuickAction::triggered, d, &QQuickAbstractButtonPrivate::click); + QObjectPrivate::disconnect(oldAction, &QQuickAction::textChanged, d, &QQuickAbstractButtonPrivate::actionTextChange); + + QObjectPrivate::disconnect(oldAction, &QQuickAction::iconChanged, d, &QQuickAbstractButtonPrivate::updateEffectiveIcon); + disconnect(oldAction, &QQuickAction::checkedChanged, this, &QQuickAbstractButton::setChecked); + disconnect(oldAction, &QQuickAction::checkableChanged, this, &QQuickAbstractButton::setCheckable); + disconnect(oldAction, &QQuickAction::enabledChanged, this, &QQuickItem::setEnabled); + } + + if (action) { + QQuickActionPrivate::get(action)->registerItem(this); + QObjectPrivate::connect(action, &QQuickAction::triggered, d, &QQuickAbstractButtonPrivate::click); + QObjectPrivate::connect(action, &QQuickAction::textChanged, d, &QQuickAbstractButtonPrivate::actionTextChange); + + QObjectPrivate::connect(action, &QQuickAction::iconChanged, d, &QQuickAbstractButtonPrivate::updateEffectiveIcon); + connect(action, &QQuickAction::checkedChanged, this, &QQuickAbstractButton::setChecked); + connect(action, &QQuickAction::checkableChanged, this, &QQuickAbstractButton::setCheckable); + connect(action, &QQuickAction::enabledChanged, this, &QQuickItem::setEnabled); + + setChecked(action->isChecked()); + setCheckable(action->isCheckable()); + setEnabled(action->isEnabled()); + } + + d->action = action; + + if (oldText != text()) + buttonChange(ButtonTextChange); + + d->updateEffectiveIcon(); + + emit actionChanged(); +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty int QtQuick.Controls::AbstractButton::autoRepeatDelay + + This property holds the initial delay of auto-repetition in milliseconds. + The default value is \c 300 ms. + + \sa autoRepeat, autoRepeatInterval +*/ +int QQuickAbstractButton::autoRepeatDelay() const +{ + Q_D(const QQuickAbstractButton); + return d->repeatDelay; +} + +void QQuickAbstractButton::setAutoRepeatDelay(int delay) +{ + Q_D(QQuickAbstractButton); + if (d->repeatDelay == delay) + return; + + d->repeatDelay = delay; + emit autoRepeatDelayChanged(); +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty int QtQuick.Controls::AbstractButton::autoRepeatInterval + + This property holds the interval of auto-repetition in milliseconds. + The default value is \c 100 ms. + + \sa autoRepeat, autoRepeatDelay +*/ +int QQuickAbstractButton::autoRepeatInterval() const +{ + Q_D(const QQuickAbstractButton); + return d->repeatInterval; +} + +void QQuickAbstractButton::setAutoRepeatInterval(int interval) +{ + Q_D(QQuickAbstractButton); + if (d->repeatInterval == interval) + return; + + d->repeatInterval = interval; + emit autoRepeatIntervalChanged(); +} + +#if QT_CONFIG(shortcut) +QKeySequence QQuickAbstractButton::shortcut() const +{ + Q_D(const QQuickAbstractButton); + return d->shortcut; +} + +void QQuickAbstractButton::setShortcut(const QKeySequence &shortcut) +{ + Q_D(QQuickAbstractButton); + if (d->shortcut == shortcut) + return; + + d->ungrabShortcut(); + d->shortcut = shortcut; + if (isVisible()) + d->grabShortcut(); +} +#endif + +/*! + \readonly + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty real QtQuick.Controls::AbstractButton::pressX + + This property holds the x-coordinate of the last press. + + \note The value is updated on touch moves, but left intact after touch release. + + \sa pressY +*/ +qreal QQuickAbstractButton::pressX() const +{ + Q_D(const QQuickAbstractButton); + return d->movePoint.x(); +} + +/*! + \readonly + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty real QtQuick.Controls::AbstractButton::pressY + + This property holds the y-coordinate of the last press. + + \note The value is updated on touch moves, but left intact after touch release. + + \sa pressX +*/ +qreal QQuickAbstractButton::pressY() const +{ + Q_D(const QQuickAbstractButton); + return d->movePoint.y(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::AbstractButton::implicitIndicatorWidth + \readonly + + This property holds the implicit indicator width. + + The value is equal to \c {indicator ? indicator.implicitWidth : 0}. + + This is typically used, together with \l {Control::}{implicitContentWidth} and + \l {Control::}{implicitBackgroundWidth}, to calculate the \l {Item::}{implicitWidth}. + + \sa implicitIndicatorHeight +*/ +qreal QQuickAbstractButton::implicitIndicatorWidth() const +{ + Q_D(const QQuickAbstractButton); + if (!d->indicator) + return 0; + return d->indicator->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::AbstractButton::implicitIndicatorHeight + \readonly + + This property holds the implicit indicator height. + + The value is equal to \c {indicator ? indicator.implicitHeight : 0}. + + This is typically used, together with \l {Control::}{implicitContentHeight} and + \l {Control::}{implicitBackgroundHeight}, to calculate the \l {Item::}{implicitHeight}. + + \sa implicitIndicatorWidth +*/ +qreal QQuickAbstractButton::implicitIndicatorHeight() const +{ + Q_D(const QQuickAbstractButton); + if (!d->indicator) + return 0; + return d->indicator->implicitHeight(); +} + +/*! + \qmlmethod void QtQuick.Controls::AbstractButton::toggle() + + Toggles the checked state of the button. +*/ +void QQuickAbstractButton::toggle() +{ + Q_D(QQuickAbstractButton); + setChecked(!d->checked); +} + +void QQuickAbstractButton::componentComplete() +{ + Q_D(QQuickAbstractButton); + d->executeIndicator(true); + QQuickControl::componentComplete(); +} + +bool QQuickAbstractButton::event(QEvent *event) +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickAbstractButton); + if (event->type() == QEvent::Shortcut) { + QShortcutEvent *se = static_cast<QShortcutEvent *>(event); + if (se->shortcutId() == d->shortcutId) { + d->trigger(); + return true; + } + } +#endif + return QQuickControl::event(event); +} + +void QQuickAbstractButton::focusOutEvent(QFocusEvent *event) +{ + Q_D(QQuickAbstractButton); + QQuickControl::focusOutEvent(event); + if (d->touchId == -1) // don't ungrab on multi-touch if another control gets focused + d->handleUngrab(); +} + +void QQuickAbstractButton::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickAbstractButton); + QQuickControl::keyPressEvent(event); + if (d->acceptKeyClick(static_cast<Qt::Key>(event->key()))) { + d->setPressPoint(QPoint(qRound(width() / 2), qRound(height() / 2))); + setPressed(true); + + if (d->autoRepeat) + d->startRepeatDelay(); + + emit pressed(); + event->accept(); + } +} + +void QQuickAbstractButton::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QQuickAbstractButton); + QQuickControl::keyReleaseEvent(event); + if (d->pressed && d->acceptKeyClick(static_cast<Qt::Key>(event->key()))) { + setPressed(false); + + nextCheckState(); + emit released(); + d->trigger(); + + if (d->autoRepeat) + d->stopPressRepeat(); + event->accept(); + } +} + +void QQuickAbstractButton::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickAbstractButton); + d->pressButtons = event->buttons(); + QQuickControl::mousePressEvent(event); +} + +void QQuickAbstractButton::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QQuickAbstractButton); + if (d->isDoubleClickConnected()) { + QQuickControl::mouseDoubleClickEvent(event); + emit doubleClicked(); + d->wasDoubleClick = true; + } +} + +void QQuickAbstractButton::timerEvent(QTimerEvent *event) +{ + Q_D(QQuickAbstractButton); + QQuickControl::timerEvent(event); + if (event->timerId() == d->holdTimer) { + d->stopPressAndHold(); + d->wasHeld = true; + emit pressAndHold(); + } else if (event->timerId() == d->delayTimer) { + d->startPressRepeat(); + } else if (event->timerId() == d->repeatTimer) { + emit released(); + d->trigger(); + emit pressed(); + } +} + +void QQuickAbstractButton::itemChange(ItemChange change, const ItemChangeData &value) +{ + QQuickControl::itemChange(change, value); +#if QT_CONFIG(shortcut) + Q_D(QQuickAbstractButton); + if (change == ItemVisibleHasChanged) { + if (value.boolValue) + d->grabShortcut(); + else + d->ungrabShortcut(); + } +#endif +} + +void QQuickAbstractButton::buttonChange(ButtonChange change) +{ + Q_D(QQuickAbstractButton); + switch (change) { + case ButtonCheckedChange: + if (d->checked) { + QQuickAbstractButton *button = d->findCheckedButton(); + if (button && button != this) + button->setChecked(false); + } + break; + case ButtonTextChange: { + const QString txt = text(); + maybeSetAccessibleName(txt); +#if QT_CONFIG(shortcut) + setShortcut(QKeySequence::mnemonic(txt)); +#endif + emit textChanged(); + break; + } + default: + break; + } +} + +void QQuickAbstractButton::nextCheckState() +{ + Q_D(QQuickAbstractButton); + if (d->checkable && (!d->checked || d->findCheckedButton() != this)) + d->toggle(!d->checked); +} + +#if QT_CONFIG(accessibility) +void QQuickAbstractButton::accessibilityActiveChanged(bool active) +{ + QQuickControl::accessibilityActiveChanged(active); + + Q_D(QQuickAbstractButton); + if (active) { + maybeSetAccessibleName(text()); + setAccessibleProperty("pressed", d->pressed); + setAccessibleProperty("checked", d->checked); + setAccessibleProperty("checkable", d->checkable); + } +} + +QAccessible::Role QQuickAbstractButton::accessibleRole() const +{ + Q_D(const QQuickAbstractButton); + if (d->checkable) { + return QAccessible::CheckBox; + } + return QAccessible::Button; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickabstractbutton_p.cpp" diff --git a/src/quicktemplates2/qquickabstractbutton_p.h b/src/quicktemplates2/qquickabstractbutton_p.h new file mode 100644 index 0000000000..a5dbd733d0 --- /dev/null +++ b/src/quicktemplates2/qquickabstractbutton_p.h @@ -0,0 +1,225 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKABSTRACTBUTTON_P_H +#define QQUICKABSTRACTBUTTON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQuickTemplates2/private/qquickicon_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickAction; +class QQuickAbstractButtonPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickAbstractButton : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText RESET resetText NOTIFY textChanged FINAL) + Q_PROPERTY(bool down READ isDown WRITE setDown NOTIFY downChanged RESET resetDown FINAL) + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) + Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY checkedChanged FINAL) + Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable NOTIFY checkableChanged FINAL) + Q_PROPERTY(bool autoExclusive READ autoExclusive WRITE setAutoExclusive NOTIFY autoExclusiveChanged FINAL) + Q_PROPERTY(bool autoRepeat READ autoRepeat WRITE setAutoRepeat NOTIFY autoRepeatChanged FINAL) + Q_PROPERTY(QQuickItem *indicator READ indicator WRITE setIndicator NOTIFY indicatorChanged FINAL) + // 2.3 (Qt 5.10) + Q_PROPERTY(QQuickIcon icon READ icon WRITE setIcon NOTIFY iconChanged FINAL REVISION(2, 3)) + Q_PROPERTY(Display display READ display WRITE setDisplay NOTIFY displayChanged FINAL REVISION(2, 3)) + Q_PROPERTY(QQuickAction *action READ action WRITE setAction NOTIFY actionChanged FINAL REVISION(2, 3)) + // 2.4 (Qt 5.11) + Q_PROPERTY(int autoRepeatDelay READ autoRepeatDelay WRITE setAutoRepeatDelay NOTIFY autoRepeatDelayChanged FINAL REVISION(2, 4)) + Q_PROPERTY(int autoRepeatInterval READ autoRepeatInterval WRITE setAutoRepeatInterval NOTIFY autoRepeatIntervalChanged FINAL REVISION(2, 4)) + Q_PROPERTY(qreal pressX READ pressX NOTIFY pressXChanged FINAL REVISION(2, 4)) + Q_PROPERTY(qreal pressY READ pressY NOTIFY pressYChanged FINAL REVISION(2, 4)) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal implicitIndicatorWidth READ implicitIndicatorWidth NOTIFY implicitIndicatorWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitIndicatorHeight READ implicitIndicatorHeight NOTIFY implicitIndicatorHeightChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background,contentItem,indicator") + QML_NAMED_ELEMENT(AbstractButton) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickAbstractButton(QQuickItem *parent = nullptr); + ~QQuickAbstractButton(); + + QString text() const; + void setText(const QString &text); + void resetText(); + + bool isDown() const; + void setDown(bool down); + void resetDown(); + + bool isPressed() const; + void setPressed(bool pressed); + + bool isChecked() const; + void setChecked(bool checked); + + bool isCheckable() const; + void setCheckable(bool checkable); + + bool autoExclusive() const; + void setAutoExclusive(bool exclusive); + + bool autoRepeat() const; + void setAutoRepeat(bool repeat); + + QQuickItem *indicator() const; + void setIndicator(QQuickItem *indicator); + + // 2.3 (Qt 5.10) + QQuickIcon icon() const; + void setIcon(const QQuickIcon &icon); + + enum Display { + IconOnly, + TextOnly, + TextBesideIcon, + TextUnderIcon + }; + Q_ENUM(Display) + + Display display() const; + void setDisplay(Display display); + + QQuickAction *action() const; + void setAction(QQuickAction *action); + +#if QT_CONFIG(shortcut) + QKeySequence shortcut() const; + void setShortcut(const QKeySequence &shortcut); +#endif + + // 2.4 (Qt 5.11) + int autoRepeatDelay() const; + void setAutoRepeatDelay(int delay); + + int autoRepeatInterval() const; + void setAutoRepeatInterval(int interval); + + qreal pressX() const; + qreal pressY() const; + + // 2.5 (Qt 5.12) + qreal implicitIndicatorWidth() const; + qreal implicitIndicatorHeight() const; + +public Q_SLOTS: + void toggle(); + +Q_SIGNALS: + void pressed(); + void released(); + void canceled(); + void clicked(); + void pressAndHold(); + void doubleClicked(); + void textChanged(); + void downChanged(); + void pressedChanged(); + void checkedChanged(); + void checkableChanged(); + void autoExclusiveChanged(); + void autoRepeatChanged(); + void indicatorChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void toggled(); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void iconChanged(); + Q_REVISION(2, 3) void displayChanged(); + Q_REVISION(2, 3) void actionChanged(); + // 2.4 (Qt 5.11) + Q_REVISION(2, 4) void autoRepeatDelayChanged(); + Q_REVISION(2, 4) void autoRepeatIntervalChanged(); + Q_REVISION(2, 4) void pressXChanged(); + Q_REVISION(2, 4) void pressYChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void implicitIndicatorWidthChanged(); + Q_REVISION(2, 5) void implicitIndicatorHeightChanged(); + +protected: + QQuickAbstractButton(QQuickAbstractButtonPrivate &dd, QQuickItem *parent); + + void componentComplete() override; + + bool event(QEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void timerEvent(QTimerEvent *event) override; + + void itemChange(ItemChange change, const ItemChangeData &value) override; + + enum ButtonChange { + ButtonCheckedChange, + ButtonCheckableChange, + ButtonPressedChanged, + ButtonTextChange + }; + virtual void buttonChange(ButtonChange change); + + virtual void nextCheckState(); + +#if QT_CONFIG(accessibility) + void accessibilityActiveChanged(bool active) override; + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickAbstractButton) + Q_DECLARE_PRIVATE(QQuickAbstractButton) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickAbstractButton) + +#endif // QQUICKABSTRACTBUTTON_P_H diff --git a/src/quicktemplates2/qquickabstractbutton_p_p.h b/src/quicktemplates2/qquickabstractbutton_p_p.h new file mode 100644 index 0000000000..907790dcc8 --- /dev/null +++ b/src/quicktemplates2/qquickabstractbutton_p_p.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKABSTRACTBUTTON_P_P_H +#define QQUICKABSTRACTBUTTON_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#if QT_CONFIG(shortcut) +# include <QtGui/qkeysequence.h> +#endif + +QT_BEGIN_NAMESPACE + +class QQuickAction; +class QQuickButtonGroup; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickAbstractButtonPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickAbstractButton) + +public: + static QQuickAbstractButtonPrivate *get(QQuickAbstractButton *button) + { + return button->d_func(); + } + + void setPressPoint(const QPointF &point); + void setMovePoint(const QPointF &point); + + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + void handleUngrab() override; + + virtual bool acceptKeyClick(Qt::Key key) const; + + bool isPressAndHoldConnected(); + bool isDoubleClickConnected(); + void startPressAndHold(); + void stopPressAndHold(); + + void startRepeatDelay(); + void startPressRepeat(); + void stopPressRepeat(); + +#if QT_CONFIG(shortcut) + void grabShortcut(); + void ungrabShortcut(); +#endif + + QQuickAbstractButton *findCheckedButton() const; + QList<QQuickAbstractButton *> findExclusiveButtons() const; + + void actionTextChange(); + void setText(const QString &text, bool isExplicit); + + void updateEffectiveIcon(); + + void click(); + void trigger(); + void toggle(bool value); + + void cancelIndicator(); + void executeIndicator(bool complete = false); + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; + + // copied from qabstractbutton.cpp + static const int AUTO_REPEAT_DELAY = 300; + static const int AUTO_REPEAT_INTERVAL = 100; + + bool explicitText = false; + bool down = false; + bool explicitDown = false; + bool pressed = false; + bool keepPressed = false; + bool checked = false; + bool checkable = false; + bool autoExclusive = false; + bool autoRepeat = false; + bool wasHeld = false; + bool wasDoubleClick = false; + int holdTimer = 0; + int delayTimer = 0; + int repeatTimer = 0; + int repeatDelay = AUTO_REPEAT_DELAY; + int repeatInterval = AUTO_REPEAT_INTERVAL; +#if QT_CONFIG(shortcut) + int shortcutId = 0; + QKeySequence shortcut; +#endif + QString text; + QQuickIcon icon; + QQuickIcon effectiveIcon; + QPointF pressPoint; + QPointF movePoint; + Qt::MouseButtons pressButtons = Qt::NoButton; + QQuickAbstractButton::Display display = QQuickAbstractButton::TextBesideIcon; + QQuickDeferredPointer<QQuickItem> indicator; + QQuickButtonGroup *group = nullptr; + QPointer<QQuickAction> action; +}; + +QT_END_NAMESPACE + +#endif // QQUICKABSTRACTBUTTON_P_P_H diff --git a/src/quicktemplates2/qquickaction.cpp b/src/quicktemplates2/qquickaction.cpp new file mode 100644 index 0000000000..193169162d --- /dev/null +++ b/src/quicktemplates2/qquickaction.cpp @@ -0,0 +1,587 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickaction_p.h" +#include "qquickaction_p_p.h" +#include "qquickactiongroup_p.h" +#include "qquickshortcutcontext_p_p.h" + +#include <QtGui/qevent.h> +#if QT_CONFIG(shortcut) +# include <QtGui/private/qshortcutmap_p.h> +#endif +#include <QtGui/private/qguiapplication_p.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Action + \inherits QtObject +//! \instantiates QQuickAction + \inqmlmodule QtQuick.Controls + \since 5.10 + \ingroup utilities + \brief Abstract user interface action. + + Action represents an abstract user interface action that can have shortcuts + and can be assigned to menu items and toolbar buttons. + + Actions may contain \l text, an \l icon, and a \l shortcut. Actions are normally + \l triggered by the user via menu items, toolbar buttons, or keyboard shortcuts. + A \l checkable Action toggles its \l checked state when triggered. + + \snippet qtquickcontrols2-action.qml action + + Action is commonly used to implement application commands that can be invoked + via menu items, toolbar buttons, and keyboard shortcuts. Since the user expects + the commands to be performed in the same way, regardless of the user interface + used, it is useful to represent the commands as shareable actions. + + Action can be also used to separate the logic and the visual presentation. For + example, when declaring buttons and menu items in \c .ui.qml files, actions can + be declared elsewhere and assigned from the outside. + + \snippet qtquickcontrols2-action.qml toolbutton + + When an action is paired with buttons and menu items, the \c enabled, \c checkable, + and \c checked states are synced automatically. For example, in a word processor, + if the user clicks a "Bold" toolbar button, the "Bold" menu item will automatically + be checked. Buttons and menu items get their \c text and \c icon from the action by + default. An action-specific \c text or \c icon can be overridden for a specific + control by specifying \c text or \c icon directly on the control. + + \snippet qtquickcontrols2-action.qml menuitem + + Since Action presents a user interface action, it is intended to be assigned to + a \l MenuItem, \l ToolButton, or any other control that inherits \l AbstractButton. + For keyboard shortcuts, the simpler \l Shortcut type is more appropriate. + + \sa MenuItem, ToolButton, Shortcut +*/ + +/*! + \qmlsignal QtQuick.Controls::Action::toggled(QtObject source) + + This signal is emitted when the action is toggled. The \a source argument + identifies the object that toggled the action. + + For example, if the action is assigned to a menu item and a toolbar button, the + action is toggled when the control is toggled, the shortcut is activated, or + when \l toggle() is called directly. +*/ + +/*! + \qmlsignal QtQuick.Controls::Action::triggered(QtObject source) + + This signal is emitted when the action is triggered. The \a source argument + identifies the object that triggered the action. + + For example, if the action is assigned to a menu item and a toolbar button, the + action is triggered when the control is clicked, the shortcut is activated, or + when \l trigger() is called directly. +*/ + +#if QT_CONFIG(shortcut) +static QKeySequence variantToKeySequence(const QVariant &var) +{ + if (var.metaType().id() == QMetaType::Int) + return QKeySequence(static_cast<QKeySequence::StandardKey>(var.toInt())); + return QKeySequence::fromString(var.toString()); +} + +QQuickActionPrivate::ShortcutEntry::ShortcutEntry(QObject *target) + : m_target(target) +{ +} + +QQuickActionPrivate::ShortcutEntry::~ShortcutEntry() +{ + ungrab(); +} + +QObject *QQuickActionPrivate::ShortcutEntry::target() const +{ + return m_target; +} + +int QQuickActionPrivate::ShortcutEntry::shortcutId() const +{ + return m_shortcutId; +} + +void QQuickActionPrivate::ShortcutEntry::grab(const QKeySequence &shortcut, bool enabled) +{ + if (shortcut.isEmpty() || m_shortcutId) + return; + + Qt::ShortcutContext context = Qt::WindowShortcut; // TODO + m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(m_target, shortcut, context, QQuickShortcutContext::matcher); + + if (!enabled) + QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(false, m_shortcutId, m_target); +} + +void QQuickActionPrivate::ShortcutEntry::ungrab() +{ + if (!m_shortcutId) + return; + + QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_shortcutId, m_target); + m_shortcutId = 0; +} + +void QQuickActionPrivate::ShortcutEntry::setEnabled(bool enabled) +{ + if (!m_shortcutId) + return; + + QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, m_shortcutId, m_target); +} + +QVariant QQuickActionPrivate::shortcut() const +{ + return vshortcut; +} + +void QQuickActionPrivate::setShortcut(const QVariant &var) +{ + Q_Q(QQuickAction); + if (vshortcut == var) + return; + + defaultShortcutEntry->ungrab(); + for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(shortcutEntries)) + entry->ungrab(); + + vshortcut = var; + keySequence = variantToKeySequence(var); + + defaultShortcutEntry->grab(keySequence, enabled); + for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(shortcutEntries)) + entry->grab(keySequence, enabled); + + emit q->shortcutChanged(keySequence); +} +#endif // QT_CONFIG(shortcut) + +void QQuickActionPrivate::setEnabled(bool enable) +{ + Q_Q(QQuickAction); + if (enabled == enable) + return; + + enabled = enable; + +#if QT_CONFIG(shortcut) + defaultShortcutEntry->setEnabled(enable); + for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(shortcutEntries)) + entry->setEnabled(enable); +#endif + + emit q->enabledChanged(enable); +} + +bool QQuickActionPrivate::watchItem(QQuickItem *item) +{ + Q_Q(QQuickAction); + if (!item) + return false; + + item->installEventFilter(q); + QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed); + return true; +} + +bool QQuickActionPrivate::unwatchItem(QQuickItem *item) +{ + Q_Q(QQuickAction); + if (!item) + return false; + + item->removeEventFilter(q); + QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed); + return true; +} + +void QQuickActionPrivate::registerItem(QQuickItem *item) +{ + if (!watchItem(item)) + return; + +#if QT_CONFIG(shortcut) + QQuickActionPrivate::ShortcutEntry *entry = new QQuickActionPrivate::ShortcutEntry(item); + if (item->isVisible()) + entry->grab(keySequence, enabled); + shortcutEntries += entry; + + updateDefaultShortcutEntry(); +#endif +} + +void QQuickActionPrivate::unregisterItem(QQuickItem *item) +{ +#if QT_CONFIG(shortcut) + QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(item); + if (!entry || !unwatchItem(item)) + return; + + shortcutEntries.removeOne(entry); + delete entry; + + updateDefaultShortcutEntry(); +#else + Q_UNUSED(item); +#endif +} + +void QQuickActionPrivate::itemVisibilityChanged(QQuickItem *item) +{ +#if QT_CONFIG(shortcut) + QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(item); + if (!entry) + return; + + if (item->isVisible()) + entry->grab(keySequence, enabled); + else + entry->ungrab(); + + updateDefaultShortcutEntry(); +#else + Q_UNUSED(item); +#endif +} + +void QQuickActionPrivate::itemDestroyed(QQuickItem *item) +{ + unregisterItem(item); +} + +#if QT_CONFIG(shortcut) +bool QQuickActionPrivate::handleShortcutEvent(QObject *object, QShortcutEvent *event) +{ + Q_Q(QQuickAction); + if (event->key() != keySequence) + return false; + + QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(object); + if (!entry || event->shortcutId() != entry->shortcutId()) + return false; + + q->trigger(entry->target()); + return true; +} + +QQuickActionPrivate::ShortcutEntry *QQuickActionPrivate::findShortcutEntry(QObject *target) const +{ + Q_Q(const QQuickAction); + if (target == q) + return defaultShortcutEntry; + for (QQuickActionPrivate::ShortcutEntry *entry : shortcutEntries) { + if (entry->target() == target) + return entry; + } + return nullptr; +} + +void QQuickActionPrivate::updateDefaultShortcutEntry() +{ + bool hasActiveShortcutEntries = false; + for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(shortcutEntries)) { + if (entry->shortcutId()) { + hasActiveShortcutEntries = true; + break; + } + } + + if (hasActiveShortcutEntries) + defaultShortcutEntry->ungrab(); + else if (!defaultShortcutEntry->shortcutId()) + defaultShortcutEntry->grab(keySequence, enabled); +} +#endif // QT_CONFIG(shortcut) + +QQuickAction::QQuickAction(QObject *parent) + : QObject(*(new QQuickActionPrivate), parent) +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickAction); + d->defaultShortcutEntry = new QQuickActionPrivate::ShortcutEntry(this); +#endif +} + +QQuickAction::~QQuickAction() +{ + Q_D(QQuickAction); + if (d->group) + d->group->removeAction(this); + +#if QT_CONFIG(shortcut) + for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(d->shortcutEntries)) + d->unwatchItem(qobject_cast<QQuickItem *>(entry->target())); + + qDeleteAll(d->shortcutEntries); + delete d->defaultShortcutEntry; +#endif +} + +/*! + \qmlproperty string QtQuick.Controls::Action::text + + This property holds a textual description of the action. +*/ +QString QQuickAction::text() const +{ + Q_D(const QQuickAction); + return d->text; +} + +void QQuickAction::setText(const QString &text) +{ + Q_D(QQuickAction); + if (d->text == text) + return; + + d->text = text; + emit textChanged(text); +} + +/*! + \qmlproperty string QtQuick.Controls::Action::icon.name + \qmlproperty url QtQuick.Controls::Action::icon.source + \qmlproperty int QtQuick.Controls::Action::icon.width + \qmlproperty int QtQuick.Controls::Action::icon.height + \qmlproperty color QtQuick.Controls::Action::icon.color + \qmlproperty bool QtQuick.Controls::Action::icon.cache + + \include qquickicon.qdocinc grouped-properties +*/ +QQuickIcon QQuickAction::icon() const +{ + Q_D(const QQuickAction); + return d->icon; +} + +void QQuickAction::setIcon(const QQuickIcon &icon) +{ + Q_D(QQuickAction); + if (d->icon == icon) + return; + + d->icon = icon; + d->icon.ensureRelativeSourceResolved(this); + emit iconChanged(icon); +} + +/*! + \qmlproperty bool QtQuick.Controls::Action::enabled + + This property holds whether the action is enabled. The default value is \c true. +*/ +bool QQuickAction::isEnabled() const +{ + Q_D(const QQuickAction); + return d->enabled && (!d->group || d->group->isEnabled()); +} + +void QQuickAction::setEnabled(bool enabled) +{ + Q_D(QQuickAction); + d->explicitEnabled = true; + d->setEnabled(enabled); +} + +void QQuickAction::resetEnabled() +{ + Q_D(QQuickAction); + if (!d->explicitEnabled) + return; + + d->explicitEnabled = false; + d->setEnabled(true); +} + +/*! + \qmlproperty bool QtQuick.Controls::Action::checked + + This property holds whether the action is checked. + + \sa checkable +*/ +bool QQuickAction::isChecked() const +{ + Q_D(const QQuickAction); + return d->checked; +} + +void QQuickAction::setChecked(bool checked) +{ + Q_D(QQuickAction); + if (d->checked == checked) + return; + + d->checked = checked; + emit checkedChanged(checked); +} + +/*! + \qmlproperty bool QtQuick.Controls::Action::checkable + + This property holds whether the action is checkable. The default value is \c false. + + A checkable action toggles between checked (on) and unchecked (off) when triggered. + + \sa checked +*/ +bool QQuickAction::isCheckable() const +{ + Q_D(const QQuickAction); + return d->checkable; +} + +void QQuickAction::setCheckable(bool checkable) +{ + Q_D(QQuickAction); + if (d->checkable == checkable) + return; + + d->checkable = checkable; + emit checkableChanged(checkable); +} + +#if QT_CONFIG(shortcut) +/*! + \qmlproperty keysequence QtQuick.Controls::Action::shortcut + + This property holds the action's shortcut. The key sequence can be set + to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, + or it can be described with a string containing a sequence of up to four + key presses that are needed to trigger the shortcut. + + \code + Action { + shortcut: "Ctrl+E,Ctrl+W" + onTriggered: edit.wrapMode = TextEdit.Wrap + } + \endcode +*/ +QKeySequence QQuickAction::shortcut() const +{ + Q_D(const QQuickAction); + return d->keySequence; +} + +void QQuickAction::setShortcut(const QKeySequence &shortcut) +{ + Q_D(QQuickAction); + d->setShortcut(shortcut.toString()); +} +#endif // QT_CONFIG(shortcut) + +/*! + \qmlmethod void QtQuick.Controls::Action::toggle(QtObject source) + + Toggles the action and emits \l toggled() if enabled, with an optional \a source object defined. +*/ +void QQuickAction::toggle(QObject *source) +{ + Q_D(QQuickAction); + if (!d->enabled) + return; + + if (d->checkable) + setChecked(!d->checked); + + emit toggled(source); +} + +/*! + \qmlmethod void QtQuick.Controls::Action::trigger(QtObject source) + + Triggers the action and emits \l triggered() if enabled, with an optional \a source object defined. +*/ +void QQuickAction::trigger(QObject *source) +{ + Q_D(QQuickAction); + d->trigger(source, true); +} + +void QQuickActionPrivate::trigger(QObject* source, bool doToggle) +{ + Q_Q(QQuickAction); + if (!enabled) + return; + + QPointer<QObject> guard = q; + // the checked action of an exclusive group cannot be unchecked + if (checkable && (!checked || !group || !group->isExclusive() || group->checkedAction() != q)) { + if (doToggle) + q->toggle(source); + else + emit q->toggled(source); + } + + if (!guard.isNull()) + emit q->triggered(source); +} + +bool QQuickAction::event(QEvent *event) +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickAction); + if (event->type() == QEvent::Shortcut) + return d->handleShortcutEvent(this, static_cast<QShortcutEvent *>(event)); +#endif + return QObject::event(event); +} + +bool QQuickAction::eventFilter(QObject *object, QEvent *event) +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickAction); + if (event->type() == QEvent::Shortcut) + return d->handleShortcutEvent(object, static_cast<QShortcutEvent *>(event)); +#else + Q_UNUSED(object); + Q_UNUSED(event); +#endif + return false; +} + +QT_END_NAMESPACE + +#include "moc_qquickaction_p.cpp" diff --git a/src/quicktemplates2/qquickaction_p.h b/src/quicktemplates2/qquickaction_p.h new file mode 100644 index 0000000000..f600d89ed7 --- /dev/null +++ b/src/quicktemplates2/qquickaction_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKACTION_P_H +#define QQUICKACTION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtQuickTemplates2/private/qquickicon_p.h> +#include <QtCore/qobject.h> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickIcon; +class QQuickActionPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickAction : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL) + Q_PROPERTY(QQuickIcon icon READ icon WRITE setIcon NOTIFY iconChanged FINAL) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged RESET resetEnabled FINAL) + Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY checkedChanged FINAL) + Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable NOTIFY checkableChanged FINAL) +#if QT_CONFIG(shortcut) + Q_PRIVATE_PROPERTY(QQuickAction::d_func(), QVariant shortcut READ shortcut WRITE setShortcut NOTIFY shortcutChanged FINAL) +#endif + QML_NAMED_ELEMENT(Action) + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickAction(QObject *parent = nullptr); + ~QQuickAction(); + + QString text() const; + void setText(const QString &text); + + QQuickIcon icon() const; + void setIcon(const QQuickIcon &icon); + + bool isEnabled() const; + void setEnabled(bool enabled); + void resetEnabled(); + + bool isChecked() const; + void setChecked(bool checked); + + bool isCheckable() const; + void setCheckable(bool checkable); + +#if QT_CONFIG(shortcut) + QKeySequence shortcut() const; + void setShortcut(const QKeySequence &shortcut); +#endif + +public Q_SLOTS: + void toggle(QObject *source = nullptr); + void trigger(QObject *source = nullptr); + +Q_SIGNALS: + void textChanged(const QString &text); + void iconChanged(const QQuickIcon &icon); + void enabledChanged(bool enabled); + void checkedChanged(bool checked); + void checkableChanged(bool checkable); +#if QT_CONFIG(shortcut) + void shortcutChanged(const QKeySequence &shortcut); +#endif + + void toggled(QObject *source = nullptr); + void triggered(QObject *source = nullptr); + +protected: + bool event(QEvent *event) override; + bool eventFilter(QObject *object, QEvent *event) override; + +private: + Q_DISABLE_COPY(QQuickAction) + Q_DECLARE_PRIVATE(QQuickAction) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickAction) + +#endif // QQUICKACTION_P_H diff --git a/src/quicktemplates2/qquickaction_p_p.h b/src/quicktemplates2/qquickaction_p_p.h new file mode 100644 index 0000000000..d9b83548c9 --- /dev/null +++ b/src/quicktemplates2/qquickaction_p_p.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKACTION_P_P_H +#define QQUICKACTION_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qobject_p.h> +#include <QtCore/qvariant.h> +#include <QtCore/qstring.h> +#if QT_CONFIG(shortcut) +# include <QtGui/qkeysequence.h> +#endif +#include <QtQuick/private/qquickitemchangelistener_p.h> + +QT_BEGIN_NAMESPACE + +class QShortcutEvent; +class QQuickActionGroup; + +class QQuickActionPrivate : public QObjectPrivate, public QQuickItemChangeListener +{ + Q_DECLARE_PUBLIC(QQuickAction) + +public: + static QQuickActionPrivate *get(QQuickAction *action) + { + return action->d_func(); + } + +#if QT_CONFIG(shortcut) + QVariant shortcut() const; + void setShortcut(const QVariant &shortcut); +#endif + + void setEnabled(bool enable); + + bool watchItem(QQuickItem *item); + bool unwatchItem(QQuickItem *item); + + void registerItem(QQuickItem *item); + void unregisterItem(QQuickItem *item); + + void itemVisibilityChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; + + bool handleShortcutEvent(QObject *object, QShortcutEvent *event); + + void trigger(QObject*, bool doToggle); + +#if QT_CONFIG(shortcut) + class ShortcutEntry + { + public: + explicit ShortcutEntry(QObject *target); + ~ShortcutEntry(); + + QObject *target() const; + int shortcutId() const; + + void grab(const QKeySequence &vshortcut, bool enabled); + void ungrab(); + + void setEnabled(bool enabled); + + private: + int m_shortcutId = 0; + QObject *m_target = nullptr; + }; + + ShortcutEntry *findShortcutEntry(QObject *target) const; + void updateDefaultShortcutEntry(); +#endif // QT_CONFIG(shortcut) + + bool explicitEnabled = false; + bool enabled = true; + bool checked = false; + bool checkable = false; + QString text; + QQuickIcon icon; +#if QT_CONFIG(shortcut) + QKeySequence keySequence; + QVariant vshortcut; + ShortcutEntry *defaultShortcutEntry = nullptr; + QList<ShortcutEntry *> shortcutEntries; +#endif + QQuickActionGroup *group = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKACTION_P_P_H diff --git a/src/quicktemplates2/qquickactiongroup.cpp b/src/quicktemplates2/qquickactiongroup.cpp new file mode 100644 index 0000000000..1656ecf51e --- /dev/null +++ b/src/quicktemplates2/qquickactiongroup.cpp @@ -0,0 +1,471 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickactiongroup_p.h" + +#include <QtCore/private/qobject_p.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qvariant.h> +#include <QtQml/qqmlinfo.h> + +#include "qquickaction_p.h" +#include "qquickaction_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ActionGroup + \inherits QtObject +//! \instantiates QQuickActionGroup + \inqmlmodule QtQuick.Controls + \since 5.10 + \ingroup utilities + \brief Groups actions together. + + ActionGroup is a non-visual group of actions. A mutually \l exclusive + action group is used with actions where only one of the options can be + selected at a time. + + The most straight-forward way to use ActionGroup is to declare actions + as children of the group. + + \code + ActionGroup { + id: alignmentGroup + + Action { + checked: true + checkable: true + text: qsTr("Left") + } + + Action { + checkable: true + text: qsTr("Center") + } + + Action { + checkable: true + text: qsTr("Right") + } + } + \endcode + + Alternatively, the \l group attached property allows declaring the actions + elsewhere and assigning them to a specific group. + + \code + ActionGroup { id: alignmentGroup } + + Action { + checked: true + checkable: true + text: qsTr("Left") + ActionGroup.group: alignmentGroup + } + + Action { + checkable: true + text: qsTr("Center") + ActionGroup.group: alignmentGroup + } + + Action { + checkable: true + text: qsTr("Right") + ActionGroup.group: alignmentGroup + } + \endcode + + More advanced use cases can be handled using the \c addAction() and + \c removeAction() methods. + + \sa Action, ButtonGroup +*/ + +/*! + \qmlsignal QtQuick.Controls::ActionGroup::triggered(Action action) + + This signal is emitted when an \a action in the group has been triggered. + + This signal is convenient for implementing a common signal handler for + all actions in the same group. + + \code + ActionGroup { + onTriggered: console.log("triggered:", action.text) + + Action { text: "First" } + Action { text: "Second" } + Action { text: "Third" } + } + \endcode + + \sa Action::triggered() +*/ + +class QQuickActionGroupPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickActionGroup) + +public: + void clear(); + void actionTriggered(); + void _q_updateCurrent(); + + static bool changeEnabled(QQuickAction *action, bool enabled); + + static void actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj); + static qsizetype actions_count(QQmlListProperty<QQuickAction> *prop); + static QQuickAction *actions_at(QQmlListProperty<QQuickAction> *prop, qsizetype index); + static void actions_clear(QQmlListProperty<QQuickAction> *prop); + + bool enabled = true; + bool exclusive = true; + QPointer<QQuickAction> checkedAction; + QList<QQuickAction*> actions; +}; + +void QQuickActionGroupPrivate::clear() +{ + for (QQuickAction *action : qAsConst(actions)) { + QQuickActionPrivate::get(action)->group = nullptr; + QObjectPrivate::disconnect(action, &QQuickAction::triggered, this, &QQuickActionGroupPrivate::actionTriggered); + QObjectPrivate::disconnect(action, &QQuickAction::checkedChanged, this, &QQuickActionGroupPrivate::_q_updateCurrent); + } + actions.clear(); +} + +void QQuickActionGroupPrivate::actionTriggered() +{ + Q_Q(QQuickActionGroup); + QQuickAction *action = qobject_cast<QQuickAction*>(q->sender()); + if (action) + emit q->triggered(action); +} + +void QQuickActionGroupPrivate::_q_updateCurrent() +{ + Q_Q(QQuickActionGroup); + if (!exclusive) + return; + QQuickAction *action = qobject_cast<QQuickAction*>(q->sender()); + if (action && action->isChecked()) + q->setCheckedAction(action); + else if (!actions.contains(checkedAction)) + q->setCheckedAction(nullptr); +} + +bool QQuickActionGroupPrivate::changeEnabled(QQuickAction *action, bool enabled) +{ + return action->isEnabled() != enabled && (!enabled || !QQuickActionPrivate::get(action)->explicitEnabled); +} + +void QQuickActionGroupPrivate::actions_append(QQmlListProperty<QQuickAction> *prop, QQuickAction *obj) +{ + QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object); + q->addAction(obj); +} + +qsizetype QQuickActionGroupPrivate::actions_count(QQmlListProperty<QQuickAction> *prop) +{ + QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data); + return p->actions.count(); +} + +QQuickAction *QQuickActionGroupPrivate::actions_at(QQmlListProperty<QQuickAction> *prop, qsizetype index) +{ + QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data); + return p->actions.value(index); +} + +void QQuickActionGroupPrivate::actions_clear(QQmlListProperty<QQuickAction> *prop) +{ + QQuickActionGroupPrivate *p = static_cast<QQuickActionGroupPrivate *>(prop->data); + if (!p->actions.isEmpty()) { + p->clear(); + QQuickActionGroup *q = static_cast<QQuickActionGroup *>(prop->object); + // QTBUG-52358: don't clear the checked action immediately + QMetaObject::invokeMethod(q, "_q_updateCurrent", Qt::QueuedConnection); + emit q->actionsChanged(); + } +} + +QQuickActionGroup::QQuickActionGroup(QObject *parent) + : QObject(*(new QQuickActionGroupPrivate), parent) +{ +} + +QQuickActionGroup::~QQuickActionGroup() +{ + Q_D(QQuickActionGroup); + d->clear(); +} + +QQuickActionGroupAttached *QQuickActionGroup::qmlAttachedProperties(QObject *object) +{ + return new QQuickActionGroupAttached(object); +} + +/*! + \qmlproperty Action QtQuick.Controls::ActionGroup::checkedAction + + This property holds the currently selected action in an exclusive group, + or \c null if there is none or the group is non-exclusive. + + By default, it is the first checked action added to an exclusive action group. + + \sa exclusive +*/ +QQuickAction *QQuickActionGroup::checkedAction() const +{ + Q_D(const QQuickActionGroup); + return d->checkedAction; +} + +void QQuickActionGroup::setCheckedAction(QQuickAction *checkedAction) +{ + Q_D(QQuickActionGroup); + if (d->checkedAction == checkedAction) + return; + + if (d->checkedAction) + d->checkedAction->setChecked(false); + d->checkedAction = checkedAction; + if (checkedAction) + checkedAction->setChecked(true); + emit checkedActionChanged(); +} + +/*! + \qmlproperty list<Action> QtQuick.Controls::ActionGroup::actions + \qmldefault + + This property holds the list of actions in the group. + + \sa group +*/ +QQmlListProperty<QQuickAction> QQuickActionGroup::actions() +{ + Q_D(QQuickActionGroup); + return QQmlListProperty<QQuickAction>(this, d, + QQuickActionGroupPrivate::actions_append, + QQuickActionGroupPrivate::actions_count, + QQuickActionGroupPrivate::actions_at, + QQuickActionGroupPrivate::actions_clear); +} + +/*! + \qmlproperty bool QtQuick.Controls::ActionGroup::exclusive + + This property holds whether the action group is exclusive. The default value is \c true. + + If this property is \c true, then only one action in the group can be checked at any given time. + The user can trigger any action to check it, and that action will replace the existing one as + the checked action in the group. + + In an exclusive group, the user cannot uncheck the currently checked action by triggering it; + instead, another action in the group must be triggered to set the new checked action for that + group. + + In a non-exclusive group, checking and unchecking actions does not affect the other actions in + the group. Furthermore, the value of the \l checkedAction property is \c null. +*/ +bool QQuickActionGroup::isExclusive() const +{ + Q_D(const QQuickActionGroup); + return d->exclusive; +} + +void QQuickActionGroup::setExclusive(bool exclusive) +{ + Q_D(QQuickActionGroup); + if (d->exclusive == exclusive) + return; + + d->exclusive = exclusive; + emit exclusiveChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::ActionGroup::enabled + + This property holds whether the action group is enabled. The default value is \c true. + + If this property is \c false, then all actions in the group are disabled. If this property + is \c true, all actions in the group are enabled, unless explicitly disabled. +*/ +bool QQuickActionGroup::isEnabled() const +{ + Q_D(const QQuickActionGroup); + return d->enabled; +} + +void QQuickActionGroup::setEnabled(bool enabled) +{ + Q_D(QQuickActionGroup); + if (d->enabled == enabled) + return; + + for (QQuickAction *action : qAsConst(d->actions)) { + if (d->changeEnabled(action, enabled)) + emit action->enabledChanged(enabled); + } + + d->enabled = enabled; + emit enabledChanged(); +} + +/*! + \qmlmethod void QtQuick.Controls::ActionGroup::addAction(Action action) + + Adds an \a action to the action group. + + \note Manually adding objects to a action group is typically unnecessary. + The \l actions property and the \l group attached property provide a + convenient and declarative syntax. + + \sa actions, group +*/ +void QQuickActionGroup::addAction(QQuickAction *action) +{ + Q_D(QQuickActionGroup); + if (!action || d->actions.contains(action)) + return; + + const bool enabledChange = d->changeEnabled(action, d->enabled); + + QQuickActionPrivate::get(action)->group = this; + QObjectPrivate::connect(action, &QQuickAction::triggered, d, &QQuickActionGroupPrivate::actionTriggered); + QObjectPrivate::connect(action, &QQuickAction::checkedChanged, d, &QQuickActionGroupPrivate::_q_updateCurrent); + + if (d->exclusive && action->isChecked()) + setCheckedAction(action); + if (enabledChange) + emit action->enabledChanged(action->isEnabled()); + + d->actions.append(action); + emit actionsChanged(); +} + +/*! + \qmlmethod void QtQuick.Controls::ActionGroup::removeAction(Action action) + + Removes an \a action from the action group. + + \note Manually removing objects from a action group is typically unnecessary. + The \l actions property and the \l group attached property provide a + convenient and declarative syntax. + + \sa actions, group +*/ +void QQuickActionGroup::removeAction(QQuickAction *action) +{ + Q_D(QQuickActionGroup); + if (!action || !d->actions.contains(action)) + return; + + const bool enabledChange = d->changeEnabled(action, d->enabled); + + QQuickActionPrivate::get(action)->group = nullptr; + QObjectPrivate::disconnect(action, &QQuickAction::triggered, d, &QQuickActionGroupPrivate::actionTriggered); + QObjectPrivate::disconnect(action, &QQuickAction::checkedChanged, d, &QQuickActionGroupPrivate::_q_updateCurrent); + + if (d->checkedAction == action) + setCheckedAction(nullptr); + if (enabledChange) + emit action->enabledChanged(action->isEnabled()); + + d->actions.removeOne(action); + emit actionsChanged(); +} + +class QQuickActionGroupAttachedPrivate : public QObjectPrivate +{ +public: + QQuickActionGroup *group = nullptr; +}; + +QQuickActionGroupAttached::QQuickActionGroupAttached(QObject *parent) + : QObject(*(new QQuickActionGroupAttachedPrivate), parent) +{ +} + +/*! + \qmlattachedproperty ActionGroup QtQuick.Controls::ActionGroup::group + + This property attaches an action to an action group. + + \code + ActionGroup { id: group } + + Action { + checked: true + text: qsTr("Option A") + ActionGroup.group: group + } + + Action { + text: qsTr("Option B") + ActionGroup.group: group + } + \endcode + + \sa actions +*/ +QQuickActionGroup *QQuickActionGroupAttached::group() const +{ + Q_D(const QQuickActionGroupAttached); + return d->group; +} + +void QQuickActionGroupAttached::setGroup(QQuickActionGroup *group) +{ + Q_D(QQuickActionGroupAttached); + if (d->group == group) + return; + + if (d->group) + d->group->removeAction(qobject_cast<QQuickAction*>(parent())); + d->group = group; + if (group) + group->addAction(qobject_cast<QQuickAction*>(parent())); + emit groupChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquickactiongroup_p.cpp" diff --git a/src/quicktemplates2/qquickactiongroup_p.h b/src/quicktemplates2/qquickactiongroup_p.h new file mode 100644 index 0000000000..d905f59598 --- /dev/null +++ b/src/quicktemplates2/qquickactiongroup_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKACTIONGROUP_P_H +#define QQUICKACTIONGROUP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickAction; +class QQuickActionGroupPrivate; +class QQuickActionGroupAttached; +class QQuickActionGroupAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickActionGroup : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickAction *checkedAction READ checkedAction WRITE setCheckedAction NOTIFY checkedActionChanged FINAL) + Q_PROPERTY(QQmlListProperty<QQuickAction> actions READ actions NOTIFY actionsChanged FINAL) + Q_PROPERTY(bool exclusive READ isExclusive WRITE setExclusive NOTIFY exclusiveChanged FINAL) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged FINAL) + Q_CLASSINFO("DefaultProperty", "actions") + QML_NAMED_ELEMENT(ActionGroup) + QML_ATTACHED(QQuickActionGroupAttached) + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickActionGroup(QObject *parent = nullptr); + ~QQuickActionGroup(); + + static QQuickActionGroupAttached *qmlAttachedProperties(QObject *object); + + QQuickAction *checkedAction() const; + void setCheckedAction(QQuickAction *checkedAction); + + QQmlListProperty<QQuickAction> actions(); + + bool isExclusive() const; + void setExclusive(bool exclusive); + + bool isEnabled() const; + void setEnabled(bool enabled); + +public Q_SLOTS: + void addAction(QQuickAction *action); + void removeAction(QQuickAction *action); + +Q_SIGNALS: + void checkedActionChanged(); + void actionsChanged(); + void exclusiveChanged(); + void enabledChanged(); + void triggered(QQuickAction *action); + +private: + Q_DISABLE_COPY(QQuickActionGroup) + Q_DECLARE_PRIVATE(QQuickActionGroup) + + Q_PRIVATE_SLOT(d_func(), void _q_updateCurrent()) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickActionGroupAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickActionGroup *group READ group WRITE setGroup NOTIFY groupChanged FINAL) + +public: + explicit QQuickActionGroupAttached(QObject *parent = nullptr); + + QQuickActionGroup *group() const; + void setGroup(QQuickActionGroup *group); + +Q_SIGNALS: + void groupChanged(); + +private: + Q_DISABLE_COPY(QQuickActionGroupAttached) + Q_DECLARE_PRIVATE(QQuickActionGroupAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickActionGroup) +QML_DECLARE_TYPEINFO(QQuickActionGroup, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKACTIONGROUP_P_H diff --git a/src/quicktemplates2/qquickapplicationwindow.cpp b/src/quicktemplates2/qquickapplicationwindow.cpp new file mode 100644 index 0000000000..eb1d12c930 --- /dev/null +++ b/src/quicktemplates2/qquickapplicationwindow.cpp @@ -0,0 +1,955 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickapplicationwindow_p.h" +#include "qquickcontentitem_p.h" +#include "qquickpopup_p_p.h" +#include "qquickcontrol_p_p.h" +#include "qquicktextarea_p.h" +#include "qquicktextfield_p.h" +#include "qquicktoolbar_p.h" +#include "qquicktabbar_p.h" +#include "qquickdialogbuttonbox_p.h" +#include "qquickdeferredexecute_p_p.h" +#include "qquickdeferredpointer_p_p.h" + +#include <QtCore/private/qobject_p.h> +#include <QtCore/qscopedvaluerollback.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuick/private/qquickwindowmodule_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ApplicationWindow + \inherits Window +//! \instantiates QQuickApplicationWindow + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Styled top-level window with support for a header and footer. + + ApplicationWindow is a \l Window which makes it convenient to add + a \l {menuBar}{menu bar}, \l header and \l footer item to the window. + + You can declare ApplicationWindow as the root item of your application, + and run it by using \l QQmlApplicationEngine. In this way you can control + the window's properties, appearance and layout from QML. + + \image qtquickcontrols2-applicationwindow-wireframe.png + + \qml + import QtQuick.Controls 2.12 + + ApplicationWindow { + visible: true + + menuBar: MenuBar { + // ... + } + + header: ToolBar { + // ... + } + + footer: TabBar { + // ... + } + + StackView { + anchors.fill: parent + } + } + \endqml + + \note By default, an ApplicationWindow is not visible. + + \section2 Attached ApplicationWindow Properties + + Due to how \l {Scope and Naming Resolution} works in QML, it is possible + to reference the \c id of the application root element anywhere in its + child QML objects. Even though this approach is fine for many applications + and use cases, for a generic QML component it may not be acceptable as it + creates a dependency to the surrounding environment. + + ApplicationWindow provides a set of attached properties that can be used + to access the window and its building blocks from places where no direct + access to the window is available, without creating a dependency to a + certain window \c id. A QML component that uses the ApplicationWindow + attached properties works in any window regardless of its \c id. + + \sa {Customizing ApplicationWindow}, Overlay, Page, {Container Controls}, + {Focus Management in Qt Quick Controls} +*/ + +static const QQuickItemPrivate::ChangeTypes ItemChanges = QQuickItemPrivate::Visibility + | QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickApplicationWindowPrivate + : public QQuickWindowQmlImplPrivate + , public QQuickItemChangeListener +{ + Q_DECLARE_PUBLIC(QQuickApplicationWindow) + +public: + QQuickApplicationWindowPrivate() + { + complete = true; + } + + static QQuickApplicationWindowPrivate *get(QQuickApplicationWindow *window) + { + return window->d_func(); + } + + QQmlListProperty<QObject> contentData(); + + void relayout(); + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemVisibilityChanged(QQuickItem *item) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + void updateFont(const QFont &f); + inline void setFont_helper(const QFont &f) { + if (font.resolveMask() == f.resolveMask() && font == f) + return; + updateFont(f); + } + void resolveFont(); + + void _q_updateActiveFocus(); + void setActiveFocusControl(QQuickItem *item); + + static void contentData_append(QQmlListProperty<QObject> *prop, QObject *obj); + + void cancelBackground(); + void executeBackground(bool complete = false); + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::System); } + void updateChildrenPalettes(const QPalette &parentPalette) override + { + // Update regular children + QQuickWindowPrivate::updateChildrenPalettes(parentPalette); + + // And cover one special case + for (auto &&popup : q_func()->findChildren<QQuickPopup *>()) + QQuickPopupPrivate::get(popup)->inheritPalette(parentPalette); + } + + QQuickDeferredPointer<QQuickItem> background; + QQuickItem *appWindowContentItem = nullptr; + QQuickItem *menuBar = nullptr; + QQuickItem *header = nullptr; + QQuickItem *footer = nullptr; + QFont font; + QLocale locale; + QQuickItem *activeFocusControl = nullptr; + bool insideRelayout = false; +}; + +static void layoutItem(QQuickItem *item, qreal y, qreal width) +{ + if (!item) + return; + + item->setY(y); + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!p->widthValid()) { + item->setWidth(width); + p->widthValidFlag = false; + } +} + +void QQuickApplicationWindowPrivate::relayout() +{ + Q_Q(QQuickApplicationWindow); + if (!complete || insideRelayout) + return; + + QScopedValueRollback<bool> guard(insideRelayout, true); + QQuickItem *content = q->contentItem(); + qreal hh = header && header->isVisible() ? header->height() : 0; + qreal fh = footer && footer->isVisible() ? footer->height() : 0; + qreal mbh = menuBar && menuBar->isVisible() ? menuBar->height() : 0; + + content->setY(mbh + hh); + content->setWidth(q->width()); + content->setHeight(q->height() - mbh - hh - fh); + + layoutItem(menuBar, -mbh - hh, q->width()); + layoutItem(header, -hh, q->width()); + layoutItem(footer, content->height(), q->width()); + + if (background) { + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + if (!p->widthValid() && qFuzzyIsNull(background->x())) { + background->setWidth(q->width()); + p->widthValidFlag = false; + } + if (!p->heightValid() && qFuzzyIsNull(background->y())) { + background->setHeight(q->height()); + p->heightValidFlag = false; + } + } +} + +void QQuickApplicationWindowPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + Q_UNUSED(item); + Q_UNUSED(change); + Q_UNUSED(diff); + relayout(); +} + +void QQuickApplicationWindowPrivate::itemVisibilityChanged(QQuickItem *item) +{ + Q_UNUSED(item); + relayout(); +} + +void QQuickApplicationWindowPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_UNUSED(item); + relayout(); +} + +void QQuickApplicationWindowPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_UNUSED(item); + relayout(); +} + +void QQuickApplicationWindowPrivate::updateFont(const QFont &f) +{ + Q_Q(QQuickApplicationWindow); + const bool changed = font != f; + font = f; + + QQuickControlPrivate::updateFontRecur(q->QQuickWindow::contentItem(), f); + + const QList<QQuickPopup *> popups = q->findChildren<QQuickPopup *>(); + for (QQuickPopup *popup : popups) + QQuickControlPrivate::get(static_cast<QQuickControl *>(popup->popupItem()))->inheritFont(f); + + if (changed) + emit q->fontChanged(); +} + +void QQuickApplicationWindowPrivate::resolveFont() +{ + QFont resolvedFont = font.resolve(QQuickTheme::font(QQuickTheme::System)); + setFont_helper(resolvedFont); +} + +static QQuickItem *findActiveFocusControl(QQuickWindow *window) +{ + QQuickItem *item = window->activeFocusItem(); + while (item) { + if (qobject_cast<QQuickControl *>(item) || qobject_cast<QQuickTextField *>(item) || qobject_cast<QQuickTextArea *>(item)) + return item; + item = item->parentItem(); + } + return item; +} + +void QQuickApplicationWindowPrivate::_q_updateActiveFocus() +{ + Q_Q(QQuickApplicationWindow); + setActiveFocusControl(findActiveFocusControl(q)); +} + +void QQuickApplicationWindowPrivate::setActiveFocusControl(QQuickItem *control) +{ + Q_Q(QQuickApplicationWindow); + if (activeFocusControl != control) { + activeFocusControl = control; + emit q->activeFocusControlChanged(); + } +} + +void QQuickApplicationWindowPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) +{ + QQuickItemPrivate::data_append(prop, obj); + + // associate "top-level" popups with the window as soon as they are added to the default property + if (QQuickPopup *popup = qobject_cast<QQuickPopup *>(obj)) + QQuickPopupPrivate::get(popup)->setWindow(static_cast<QQuickApplicationWindow *>(prop->data)); +} + +static inline QString backgroundName() { return QStringLiteral("background"); } + +void QQuickApplicationWindowPrivate::cancelBackground() +{ + Q_Q(QQuickApplicationWindow); + quickCancelDeferred(q, backgroundName()); +} + +void QQuickApplicationWindowPrivate::executeBackground(bool complete) +{ + Q_Q(QQuickApplicationWindow); + if (background.wasExecuted()) + return; + + if (!background || complete) + quickBeginDeferred(q, backgroundName(), background); + if (complete) + quickCompleteDeferred(q, backgroundName(), background); +} + +QQuickApplicationWindow::QQuickApplicationWindow(QWindow *parent) + : QQuickWindowQmlImpl(*(new QQuickApplicationWindowPrivate), parent) +{ + connect(this, SIGNAL(activeFocusItemChanged()), this, SLOT(_q_updateActiveFocus())); +} + +QQuickApplicationWindow::~QQuickApplicationWindow() +{ + Q_D(QQuickApplicationWindow); + d->setActiveFocusControl(nullptr); + disconnect(this, SIGNAL(activeFocusItemChanged()), this, SLOT(_q_updateActiveFocus())); + if (d->menuBar) + QQuickItemPrivate::get(d->menuBar)->removeItemChangeListener(d, ItemChanges); + if (d->header) + QQuickItemPrivate::get(d->header)->removeItemChangeListener(d, ItemChanges); + if (d->footer) + QQuickItemPrivate::get(d->footer)->removeItemChangeListener(d, ItemChanges); +} + +QQuickApplicationWindowAttached *QQuickApplicationWindow::qmlAttachedProperties(QObject *object) +{ + return new QQuickApplicationWindowAttached(object); +} + +/*! + \qmlproperty Item QtQuick.Controls::ApplicationWindow::background + + This property holds the background item. + + The background item is stacked under the \l {contentItem}{content item}, + but above the \l {Window::color}{background color} of the window. + + The background item is useful for images and gradients, for example, + but the \l {Window::}{color} property is preferable for solid colors, + as it doesn't need to create an item. + + \note If the background item has no explicit size specified, it automatically + follows the control's size. In most cases, there is no need to specify + width or height for a background item. + + \sa {Customizing ApplicationWindow}, contentItem, header, footer +*/ +QQuickItem *QQuickApplicationWindow::background() const +{ + QQuickApplicationWindowPrivate *d = const_cast<QQuickApplicationWindowPrivate *>(d_func()); + if (!d->background) + d->executeBackground(); + return d->background; +} + +void QQuickApplicationWindow::setBackground(QQuickItem *background) +{ + Q_D(QQuickApplicationWindow); + if (d->background == background) + return; + + if (!d->background.isExecuting()) + d->cancelBackground(); + + QQuickControlPrivate::hideOldItem(d->background); + d->background = background; + if (background) { + background->setParentItem(QQuickWindow::contentItem()); + if (qFuzzyIsNull(background->z())) + background->setZ(-1); + if (isComponentComplete()) + d->relayout(); + } + if (!d->background.isExecuting()) + emit backgroundChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::ApplicationWindow::header + + This property holds the window header item. The header item is positioned at the + top of the window, below the menu bar, and resized to the width of the window. + The default value is \c null. + + \code + ApplicationWindow { + header: TabBar { + // ... + } + } + \endcode + + \note Assigning a ToolBar, TabBar, or DialogButtonBox as a window header + automatically sets the respective \l ToolBar::position, \l TabBar::position, + or \l DialogButtonBox::position property to \c Header. + + \sa menuBar, footer, Page::header +*/ +QQuickItem *QQuickApplicationWindow::header() const +{ + Q_D(const QQuickApplicationWindow); + return d->header; +} + +void QQuickApplicationWindow::setHeader(QQuickItem *header) +{ + Q_D(QQuickApplicationWindow); + if (d->header == header) + return; + + if (d->header) { + QQuickItemPrivate::get(d->header)->removeItemChangeListener(d, ItemChanges); + d->header->setParentItem(nullptr); + } + d->header = header; + if (header) { + header->setParentItem(contentItem()); + QQuickItemPrivate *p = QQuickItemPrivate::get(header); + p->addItemChangeListener(d, ItemChanges); + if (qFuzzyIsNull(header->z())) + header->setZ(1); + if (QQuickToolBar *toolBar = qobject_cast<QQuickToolBar *>(header)) + toolBar->setPosition(QQuickToolBar::Header); + else if (QQuickTabBar *tabBar = qobject_cast<QQuickTabBar *>(header)) + tabBar->setPosition(QQuickTabBar::Header); + else if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(header)) + buttonBox->setPosition(QQuickDialogButtonBox::Header); + } + if (isComponentComplete()) + d->relayout(); + emit headerChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::ApplicationWindow::footer + + This property holds the window footer item. The footer item is positioned to + the bottom, and resized to the width of the window. The default value is \c null. + + \code + ApplicationWindow { + footer: ToolBar { + // ... + } + } + \endcode + + \note Assigning a ToolBar, TabBar, or DialogButtonBox as a window footer + automatically sets the respective \l ToolBar::position, \l TabBar::position, + or \l DialogButtonBox::position property to \c Footer. + + \sa menuBar, header, Page::footer +*/ +QQuickItem *QQuickApplicationWindow::footer() const +{ + Q_D(const QQuickApplicationWindow); + return d->footer; +} + +void QQuickApplicationWindow::setFooter(QQuickItem *footer) +{ + Q_D(QQuickApplicationWindow); + if (d->footer == footer) + return; + + if (d->footer) { + QQuickItemPrivate::get(d->footer)->removeItemChangeListener(d, ItemChanges); + d->footer->setParentItem(nullptr); + } + d->footer = footer; + if (footer) { + footer->setParentItem(contentItem()); + QQuickItemPrivate *p = QQuickItemPrivate::get(footer); + p->addItemChangeListener(d, ItemChanges); + if (qFuzzyIsNull(footer->z())) + footer->setZ(1); + if (QQuickToolBar *toolBar = qobject_cast<QQuickToolBar *>(footer)) + toolBar->setPosition(QQuickToolBar::Footer); + else if (QQuickTabBar *tabBar = qobject_cast<QQuickTabBar *>(footer)) + tabBar->setPosition(QQuickTabBar::Footer); + else if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(footer)) + buttonBox->setPosition(QQuickDialogButtonBox::Footer); + } + if (isComponentComplete()) + d->relayout(); + emit footerChanged(); +} + +/*! + \qmlproperty list<Object> QtQuick.Controls::ApplicationWindow::contentData + \qmldefault + + This default property holds the list of all objects declared as children of + the window. + + The data property allows you to freely mix visual children, resources and + other windows in an ApplicationWindow. + + If you assign an Item to the contentData list, it becomes a child of the + window's contentItem, so that it appears inside the window. The item's + parent will be the window's \l contentItem. + + It should not generally be necessary to refer to the contentData property, + as it is the default property for ApplicationWindow and thus all child + items are automatically assigned to this property. + + \sa contentItem +*/ +QQmlListProperty<QObject> QQuickApplicationWindowPrivate::contentData() +{ + Q_Q(QQuickApplicationWindow); + return QQmlListProperty<QObject>(q->contentItem(), q, + QQuickApplicationWindowPrivate::contentData_append, + QQuickItemPrivate::data_count, + QQuickItemPrivate::data_at, + QQuickItemPrivate::data_clear); +} + +/*! + \qmlproperty Item QtQuick.Controls::ApplicationWindow::contentItem + \readonly + + This property holds the window content item. + + The content item is stacked above the \l background item, and under the + \l menuBar, \l header, and \l footer items. + + \sa background, menuBar, header, footer +*/ +QQuickItem *QQuickApplicationWindow::contentItem() const +{ + QQuickApplicationWindowPrivate *d = const_cast<QQuickApplicationWindowPrivate *>(d_func()); + if (!d->appWindowContentItem) { + d->appWindowContentItem = new QQuickContentItem(this, QQuickWindow::contentItem()); + d->appWindowContentItem->setFlag(QQuickItem::ItemIsFocusScope); + d->appWindowContentItem->setFocus(true); + d->relayout(); + } + return d->appWindowContentItem; +} + +/*! + \qmlproperty Control QtQuick.Controls::ApplicationWindow::activeFocusControl + \readonly + + This property holds the control that currently has active focus, or \c null if there is + no control with active focus. + + The difference between \l Window::activeFocusItem and ApplicationWindow::activeFocusControl + is that the former may point to a building block of a control, whereas the latter points + to the enclosing control. For example, when SpinBox has focus, activeFocusItem points to + the editor and activeFocusControl to the SpinBox itself. + + \sa Window::activeFocusItem +*/ +QQuickItem *QQuickApplicationWindow::activeFocusControl() const +{ + Q_D(const QQuickApplicationWindow); + return d->activeFocusControl; +} + +/*! + \qmlproperty font QtQuick.Controls::ApplicationWindow::font + + This property holds the font currently set for the window. + + The default font depends on the system environment. QGuiApplication maintains a system/theme + font which serves as a default for all application windows. You can also set the default font + for windows by passing a custom font to QGuiApplication::setFont(), before loading any QML. + Finally, the font is matched against Qt's font database to find the best match. + + ApplicationWindow propagates explicit font properties to child controls. If you change a specific + property on the window's font, that property propagates to all child controls in the window, + overriding any system defaults for that property. + + \sa Control::font +*/ +QFont QQuickApplicationWindow::font() const +{ + Q_D(const QQuickApplicationWindow); + return d->font; +} + +void QQuickApplicationWindow::setFont(const QFont &font) +{ + Q_D(QQuickApplicationWindow); + if (d->font.resolveMask() == font.resolveMask() && d->font == font) + return; + + QFont resolvedFont = font.resolve(QQuickTheme::font(QQuickTheme::System)); + d->setFont_helper(resolvedFont); +} + +void QQuickApplicationWindow::resetFont() +{ + setFont(QFont()); +} + +/*! + \qmlproperty Locale QtQuick.Controls::ApplicationWindow::locale + + This property holds the locale of the window. + + The default locale depends on the system environment. You can set the + default locale by calling QLocale::setDefault(), before loading any QML. + + ApplicationWindow propagates the locale to child controls. If you change + the window's locale, that locale propagates to all child controls in the + window, overriding the system default locale. + + \sa Control::locale +*/ +QLocale QQuickApplicationWindow::locale() const +{ + Q_D(const QQuickApplicationWindow); + return d->locale; +} + +void QQuickApplicationWindow::setLocale(const QLocale &locale) +{ + Q_D(QQuickApplicationWindow); + if (d->locale == locale) + return; + + d->locale = locale; + QQuickControlPrivate::updateLocaleRecur(QQuickWindow::contentItem(), locale); + + // TODO: internal QQuickPopupManager that provides reliable access to all QQuickPopup instances + const QList<QQuickPopup *> popups = QQuickWindow::contentItem()->findChildren<QQuickPopup *>(); + for (QQuickPopup *popup : popups) + QQuickControlPrivate::get(static_cast<QQuickControl *>(popup->popupItem()))->updateLocale(locale, false); // explicit=false + + emit localeChanged(); +} + +void QQuickApplicationWindow::resetLocale() +{ + setLocale(QLocale()); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty Item QtQuick.Controls::ApplicationWindow::menuBar + + This property holds the window menu bar. The menu bar is positioned at the + top of the window, above the header, and resized to the width of the window. + The default value is \c null. + + \code + ApplicationWindow { + menuBar: MenuBar { + // ... + } + } + \endcode + + \sa header, footer, MenuBar +*/ +QQuickItem *QQuickApplicationWindow::menuBar() const +{ + Q_D(const QQuickApplicationWindow); + return d->menuBar; +} + +void QQuickApplicationWindow::setMenuBar(QQuickItem *menuBar) +{ + Q_D(QQuickApplicationWindow); + if (d->menuBar == menuBar) + return; + + if (d->menuBar) { + QQuickItemPrivate::get(d->menuBar)->removeItemChangeListener(d, ItemChanges); + d->menuBar->setParentItem(nullptr); + } + d->menuBar = menuBar; + if (menuBar) { + menuBar->setParentItem(contentItem()); + QQuickItemPrivate *p = QQuickItemPrivate::get(menuBar); + p->addItemChangeListener(d, ItemChanges); + if (qFuzzyIsNull(menuBar->z())) + menuBar->setZ(2); + } + if (isComponentComplete()) + d->relayout(); + emit menuBarChanged(); +} + +bool QQuickApplicationWindow::isComponentComplete() const +{ + Q_D(const QQuickApplicationWindow); + return d->complete; +} + +void QQuickApplicationWindow::classBegin() +{ + Q_D(QQuickApplicationWindow); + d->complete = false; + QQuickWindowQmlImpl::classBegin(); + d->resolveFont(); +} + +void QQuickApplicationWindow::componentComplete() +{ + Q_D(QQuickApplicationWindow); + d->complete = true; + d->executeBackground(true); + QQuickWindowQmlImpl::componentComplete(); + d->relayout(); +} + +void QQuickApplicationWindow::resizeEvent(QResizeEvent *event) +{ + Q_D(QQuickApplicationWindow); + QQuickWindowQmlImpl::resizeEvent(event); + d->relayout(); +} + +class QQuickApplicationWindowAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickApplicationWindowAttached) + +public: + void windowChange(QQuickWindow *wnd); + void activeFocusChange(); + + QQuickWindow *window = nullptr; + QQuickItem *activeFocusControl = nullptr; +}; + +void QQuickApplicationWindowAttachedPrivate::windowChange(QQuickWindow *wnd) +{ + Q_Q(QQuickApplicationWindowAttached); + if (window == wnd) + return; + + QQuickApplicationWindow *oldWindow = qobject_cast<QQuickApplicationWindow *>(window); + if (oldWindow && !QQuickApplicationWindowPrivate::get(oldWindow)) + oldWindow = nullptr; // being deleted (QTBUG-52731) + + if (oldWindow) { + disconnect(oldWindow, &QQuickApplicationWindow::activeFocusControlChanged, + this, &QQuickApplicationWindowAttachedPrivate::activeFocusChange); + QObject::disconnect(oldWindow, &QQuickApplicationWindow::menuBarChanged, + q, &QQuickApplicationWindowAttached::menuBarChanged); + QObject::disconnect(oldWindow, &QQuickApplicationWindow::headerChanged, + q, &QQuickApplicationWindowAttached::headerChanged); + QObject::disconnect(oldWindow, &QQuickApplicationWindow::footerChanged, + q, &QQuickApplicationWindowAttached::footerChanged); + } else if (window) { + disconnect(window, &QQuickWindow::activeFocusItemChanged, + this, &QQuickApplicationWindowAttachedPrivate::activeFocusChange); + } + + QQuickApplicationWindow *newWindow = qobject_cast<QQuickApplicationWindow *>(wnd); + if (newWindow) { + connect(newWindow, &QQuickApplicationWindow::activeFocusControlChanged, + this, &QQuickApplicationWindowAttachedPrivate::activeFocusChange); + QObject::connect(newWindow, &QQuickApplicationWindow::menuBarChanged, + q, &QQuickApplicationWindowAttached::menuBarChanged); + QObject::connect(newWindow, &QQuickApplicationWindow::headerChanged, + q, &QQuickApplicationWindowAttached::headerChanged); + QObject::connect(newWindow, &QQuickApplicationWindow::footerChanged, + q, &QQuickApplicationWindowAttached::footerChanged); + } else if (wnd) { + connect(wnd, &QQuickWindow::activeFocusItemChanged, + this, &QQuickApplicationWindowAttachedPrivate::activeFocusChange); + } + + window = wnd; + emit q->windowChanged(); + emit q->contentItemChanged(); + + activeFocusChange(); + if ((oldWindow && oldWindow->menuBar()) || (newWindow && newWindow->menuBar())) + emit q->menuBarChanged(); + if ((oldWindow && oldWindow->header()) || (newWindow && newWindow->header())) + emit q->headerChanged(); + if ((oldWindow && oldWindow->footer()) || (newWindow && newWindow->footer())) + emit q->footerChanged(); +} + +void QQuickApplicationWindowAttachedPrivate::activeFocusChange() +{ + Q_Q(QQuickApplicationWindowAttached); + QQuickItem *control = nullptr; + if (QQuickApplicationWindow *appWindow = qobject_cast<QQuickApplicationWindow *>(window)) + control = appWindow->activeFocusControl(); + else if (window) + control = findActiveFocusControl(window); + if (activeFocusControl == control) + return; + + activeFocusControl = control; + emit q->activeFocusControlChanged(); +} + +QQuickApplicationWindowAttached::QQuickApplicationWindowAttached(QObject *parent) + : QObject(*(new QQuickApplicationWindowAttachedPrivate), parent) +{ + Q_D(QQuickApplicationWindowAttached); + if (QQuickItem *item = qobject_cast<QQuickItem *>(parent)) { + d->windowChange(item->window()); + QObjectPrivate::connect(item, &QQuickItem::windowChanged, d, &QQuickApplicationWindowAttachedPrivate::windowChange); + if (!d->window) { + QQuickItem *p = item; + while (p) { + if (QQuickPopup *popup = qobject_cast<QQuickPopup *>(p->parent())) { + d->windowChange(popup->window()); + QObjectPrivate::connect(popup, &QQuickPopup::windowChanged, d, &QQuickApplicationWindowAttachedPrivate::windowChange); + } + p = p->parentItem(); + } + } + } else if (QQuickPopup *popup = qobject_cast<QQuickPopup *>(parent)) { + d->windowChange(popup->window()); + QObjectPrivate::connect(popup, &QQuickPopup::windowChanged, d, &QQuickApplicationWindowAttachedPrivate::windowChange); + } +} + +/*! + \qmlattachedproperty ApplicationWindow QtQuick.Controls::ApplicationWindow::window + \readonly + + This attached property holds the application window. The property can be attached + to any item. The value is \c null if the item is not in an ApplicationWindow. + + \sa {Attached ApplicationWindow Properties} +*/ +QQuickApplicationWindow *QQuickApplicationWindowAttached::window() const +{ + Q_D(const QQuickApplicationWindowAttached); + return qobject_cast<QQuickApplicationWindow *>(d->window); +} + +/*! + \qmlattachedproperty Item QtQuick.Controls::ApplicationWindow::contentItem + \readonly + + This attached property holds the window content item. The property can be attached + to any item. The value is \c null if the item is not in an ApplicationWindow. + + \sa {Attached ApplicationWindow Properties} +*/ +QQuickItem *QQuickApplicationWindowAttached::contentItem() const +{ + Q_D(const QQuickApplicationWindowAttached); + if (QQuickApplicationWindow *window = qobject_cast<QQuickApplicationWindow *>(d->window)) + return window->contentItem(); + return nullptr; +} + +/*! + \qmlattachedproperty Control QtQuick.Controls::ApplicationWindow::activeFocusControl + \readonly + + This attached property holds the control that currently has active focus, or \c null + if there is no control with active focus. The property can be attached to any item. + The value is \c null if the item is not in a window, or the window has no active focus. + + \sa Window::activeFocusItem, {Attached ApplicationWindow Properties} +*/ +QQuickItem *QQuickApplicationWindowAttached::activeFocusControl() const +{ + Q_D(const QQuickApplicationWindowAttached); + return d->activeFocusControl; +} + +/*! + \qmlattachedproperty Item QtQuick.Controls::ApplicationWindow::header + \readonly + + This attached property holds the window header item. The property can be attached + to any item. The value is \c null if the item is not in an ApplicationWindow, or + the window has no header item. + + \sa {Attached ApplicationWindow Properties} +*/ +QQuickItem *QQuickApplicationWindowAttached::header() const +{ + Q_D(const QQuickApplicationWindowAttached); + if (QQuickApplicationWindow *window = qobject_cast<QQuickApplicationWindow *>(d->window)) + return window->header(); + return nullptr; +} + +/*! + \qmlattachedproperty Item QtQuick.Controls::ApplicationWindow::footer + \readonly + + This attached property holds the window footer item. The property can be attached + to any item. The value is \c null if the item is not in an ApplicationWindow, or + the window has no footer item. + + \sa {Attached ApplicationWindow Properties} +*/ +QQuickItem *QQuickApplicationWindowAttached::footer() const +{ + Q_D(const QQuickApplicationWindowAttached); + if (QQuickApplicationWindow *window = qobject_cast<QQuickApplicationWindow *>(d->window)) + return window->footer(); + return nullptr; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlattachedproperty Item QtQuick.Controls::ApplicationWindow::menuBar + \readonly + + This attached property holds the window menu bar. The property can be attached + to any item. The value is \c null if the item is not in an ApplicationWindow, or + the window has no menu bar. + + \sa {Attached ApplicationWindow Properties} +*/ +QQuickItem *QQuickApplicationWindowAttached::menuBar() const +{ + Q_D(const QQuickApplicationWindowAttached); + if (QQuickApplicationWindow *window = qobject_cast<QQuickApplicationWindow *>(d->window)) + return window->menuBar(); + return nullptr; +} + +QT_END_NAMESPACE + +#include "moc_qquickapplicationwindow_p.cpp" diff --git a/src/quicktemplates2/qquickapplicationwindow_p.h b/src/quicktemplates2/qquickapplicationwindow_p.h new file mode 100644 index 0000000000..f5aa8b8b1a --- /dev/null +++ b/src/quicktemplates2/qquickapplicationwindow_p.h @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKAPPLICATIONWINDOW_P_H +#define QQUICKAPPLICATIONWINDOW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qquickwindowmodule_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtGui/qfont.h> +#include <QtGui/qpalette.h> +#include <QtCore/qlocale.h> + +QT_BEGIN_NAMESPACE + +class QQuickApplicationWindowPrivate; +class QQuickApplicationWindowAttached; +class QQuickApplicationWindowAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickApplicationWindow : public QQuickWindowQmlImpl +{ + Q_OBJECT + Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) + Q_PROPERTY(QQuickItem *contentItem READ contentItem CONSTANT FINAL) + Q_PRIVATE_PROPERTY(QQuickApplicationWindow::d_func(), QQmlListProperty<QObject> contentData READ contentData FINAL) + Q_PROPERTY(QQuickItem *activeFocusControl READ activeFocusControl NOTIFY activeFocusControlChanged FINAL) + Q_PROPERTY(QQuickItem *header READ header WRITE setHeader NOTIFY headerChanged FINAL) + Q_PROPERTY(QQuickItem *footer READ footer WRITE setFooter NOTIFY footerChanged FINAL) + Q_PROPERTY(QFont font READ font WRITE setFont RESET resetFont NOTIFY fontChanged FINAL) + Q_PROPERTY(QLocale locale READ locale WRITE setLocale RESET resetLocale NOTIFY localeChanged FINAL) + // 2.3 (Qt 5.10) + Q_PROPERTY(QQuickItem *menuBar READ menuBar WRITE setMenuBar NOTIFY menuBarChanged FINAL REVISION(2, 3)) + // 2.14 (Qt 6) + Q_PRIVATE_PROPERTY(QQuickApplicationWindow::d_func(), QQuickPalette *palette READ palette WRITE setPalette RESET resetPalette NOTIFY paletteChanged REVISION(2, 3)) + Q_CLASSINFO("DeferredPropertyNames", "background") + Q_CLASSINFO("DefaultProperty", "contentData") + QML_NAMED_ELEMENT(ApplicationWindow) + QML_ADDED_IN_VERSION(2, 0) + QML_ATTACHED(QQuickApplicationWindowAttached) + +public: + explicit QQuickApplicationWindow(QWindow *parent = nullptr); + ~QQuickApplicationWindow(); + + static QQuickApplicationWindowAttached *qmlAttachedProperties(QObject *object); + + QQuickItem *background() const; + void setBackground(QQuickItem *background); + + QQuickItem *contentItem() const; + + QQuickItem *activeFocusControl() const; + + QQuickItem *header() const; + void setHeader(QQuickItem *header); + + QQuickItem *footer() const; + void setFooter(QQuickItem *footer); + + QFont font() const; + void setFont(const QFont &font); + void resetFont(); + + QLocale locale() const; + void setLocale(const QLocale &locale); + void resetLocale(); + + QQuickItem *menuBar() const; + void setMenuBar(QQuickItem *menuBar); + +Q_SIGNALS: + void backgroundChanged(); + void activeFocusControlChanged(); + void headerChanged(); + void footerChanged(); + void fontChanged(); + void localeChanged(); + Q_REVISION(2, 3) void menuBarChanged(); + +protected: + bool isComponentComplete() const; + void classBegin() override; + void componentComplete() override; + void resizeEvent(QResizeEvent *event) override; + +private: + Q_DISABLE_COPY(QQuickApplicationWindow) + Q_DECLARE_PRIVATE(QQuickApplicationWindow) + Q_PRIVATE_SLOT(d_func(), void _q_updateActiveFocus()) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickApplicationWindowAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickApplicationWindow *window READ window NOTIFY windowChanged FINAL) + Q_PROPERTY(QQuickItem *contentItem READ contentItem NOTIFY contentItemChanged FINAL) + Q_PROPERTY(QQuickItem *activeFocusControl READ activeFocusControl NOTIFY activeFocusControlChanged FINAL) + Q_PROPERTY(QQuickItem *header READ header NOTIFY headerChanged FINAL) + Q_PROPERTY(QQuickItem *footer READ footer NOTIFY footerChanged FINAL) + Q_PROPERTY(QQuickItem *menuBar READ menuBar NOTIFY menuBarChanged FINAL) // REVISION(2, 3) + +public: + explicit QQuickApplicationWindowAttached(QObject *parent = nullptr); + + QQuickApplicationWindow *window() const; + QQuickItem *contentItem() const; + QQuickItem *activeFocusControl() const; + QQuickItem *header() const; + QQuickItem *footer() const; + QQuickItem *menuBar() const; + +Q_SIGNALS: + void windowChanged(); + void contentItemChanged(); + void activeFocusControlChanged(); + void headerChanged(); + void footerChanged(); + // 2.3 (Qt 5.10) + /*Q_REVISION(2, 3)*/ void menuBarChanged(); + +private: + Q_DISABLE_COPY(QQuickApplicationWindowAttached) + Q_DECLARE_PRIVATE(QQuickApplicationWindowAttached) +}; + +struct QWindowForeign2 +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QWindow) + QML_ADDED_IN_VERSION(2, 0) +}; + +struct QQuickWindowForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickWindow) + QML_ADDED_IN_VERSION(2, 0) +}; + +struct QQuickWindowQmlImplForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickWindowQmlImpl) + QML_ADDED_IN_VERSION(2, 2) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickApplicationWindow) +QML_DECLARE_TYPEINFO(QQuickApplicationWindow, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKAPPLICATIONWINDOW_P_H diff --git a/src/quicktemplates2/qquickbusyindicator.cpp b/src/quicktemplates2/qquickbusyindicator.cpp new file mode 100644 index 0000000000..1b4f590f63 --- /dev/null +++ b/src/quicktemplates2/qquickbusyindicator.cpp @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickbusyindicator_p.h" +#include "qquickcontrol_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype BusyIndicator + \inherits Control +//! \instantiates QQuickBusyIndicator + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-indicators + \brief Indicates background activity, for example, while content is being loaded. + + \image qtquickcontrols2-busyindicator.gif + + The busy indicator should be used to indicate activity while content is + being loaded or the UI is blocked waiting for a resource to become available. + + The following snippet shows how to use the BusyIndicator: + + \qml + BusyIndicator { + running: image.status === Image.Loading + } + \endqml + + BusyIndicator is similar to an indeterminate \l ProgressBar. Both can be + used to indicate background activity. The main difference is visual, and + that ProgressBar can also present a concrete amount of progress (when it + can be determined). Due to the visual difference, busy indicators and + indeterminate progress bars fit different places in user interfaces. + Typical places for a busy indicator: + \list + \li in the corner of a \l ToolBar + \li as an overlay on top of a \l Page + \li on the side of an \l ItemDelegate + \endlist + + \sa {Customizing BusyIndicator}, {Indicator Controls}, ProgressBar +*/ + +class QQuickBusyIndicatorPrivate : public QQuickControlPrivate +{ +public: + bool running = true; +}; + +QQuickBusyIndicator::QQuickBusyIndicator(QQuickItem *parent) + : QQuickControl(*(new QQuickBusyIndicatorPrivate), parent) +{ +} + +/*! + \qmlproperty bool QtQuick.Controls::BusyIndicator::running + + This property holds whether the busy indicator is currently indicating + activity. + + \note The indicator is only visible when this property is set to \c true. + + The default value is \c true. +*/ +bool QQuickBusyIndicator::isRunning() const +{ + Q_D(const QQuickBusyIndicator); + return d->running; +} + +void QQuickBusyIndicator::setRunning(bool running) +{ + Q_D(QQuickBusyIndicator); + if (d->running == running) + return; + + d->running = running; + emit runningChanged(); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickBusyIndicator::touchEvent(QTouchEvent *event) +{ + event->ignore(); // QTBUG-61785 +} +#endif + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickBusyIndicator::accessibleRole() const +{ + return QAccessible::Indicator; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickbusyindicator_p.cpp" diff --git a/src/quicktemplates2/qquickbusyindicator_p.h b/src/quicktemplates2/qquickbusyindicator_p.h new file mode 100644 index 0000000000..bdb2eb240b --- /dev/null +++ b/src/quicktemplates2/qquickbusyindicator_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKBUSYINDICATOR_P_H +#define QQUICKBUSYINDICATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickBusyIndicatorPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickBusyIndicator : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged FINAL) + QML_NAMED_ELEMENT(BusyIndicator) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickBusyIndicator(QQuickItem *parent = nullptr); + + bool isRunning() const; + void setRunning(bool running); + +Q_SIGNALS: + void runningChanged(); + +protected: +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickBusyIndicator) + Q_DECLARE_PRIVATE(QQuickBusyIndicator) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickBusyIndicator) + +#endif // QQUICKBUSYINDICATOR_P_H diff --git a/src/quicktemplates2/qquickbutton.cpp b/src/quicktemplates2/qquickbutton.cpp new file mode 100644 index 0000000000..beae5287a4 --- /dev/null +++ b/src/quicktemplates2/qquickbutton.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickbutton_p.h" +#include "qquickbutton_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Button + \inherits AbstractButton +//! \instantiates QQuickButton + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-buttons + \brief Push-button that can be clicked to perform a command or answer a question. + + \image qtquickcontrols2-button.gif + + Button presents a push-button control that can be pushed or clicked by + the user. Buttons are normally used to perform an action, or to answer + a question. Typical buttons are \e OK, \e Apply, \e Cancel, \e Close, + \e Yes, \e No, and \e Help. + + Button inherits its API from AbstractButton. For instance, you can set + \l {AbstractButton::text}{text}, display an \l {Icons in Qt Quick Controls}{icon}, + and react to \l {AbstractButton::clicked}{clicks} using the AbstractButton API. + + A button emits the signal \l {AbstractButton::}{clicked()} when it is activated by the user. + Connect to this signal to perform the button's action. Buttons also + provide the signals \l {AbstractButton::}{canceled()}, \l {AbstractButton::}{doubleClicked()}, \l {AbstractButton::}{pressed()}, + \l {AbstractButton::}{released()} and \l {AbstractButton::}{pressAndHold()} for long presses. + + See the snippet below on how to connect to the button's signals. + + \code + RowLayout { + Button { + text: "Ok" + onClicked: model.submit() + } + Button { + text: "Cancel" + onClicked: model.revert() + } + } + \endcode + + \sa {Customizing Button}, {Button Controls} +*/ + +QQuickButton::QQuickButton(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickButtonPrivate), parent) +{ +} + +QQuickButton::QQuickButton(QQuickButtonPrivate &dd, QQuickItem *parent) + : QQuickAbstractButton(dd, parent) +{ +} + +QFont QQuickButton::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::Button); +} + +/*! + \qmlproperty bool QtQuick.Controls::Button::highlighted + + This property holds whether the button is highlighted. + + \image qtquickcontrols2-button-highlighted.gif + + A button can be highlighted in order to draw the user's attention towards + it. It has no effect on keyboard interaction. + + The default value is \c false. +*/ +bool QQuickButton::isHighlighted() const +{ + Q_D(const QQuickButton); + return d->highlighted; +} + +void QQuickButton::setHighlighted(bool highlighted) +{ + Q_D(QQuickButton); + if (highlighted == d->highlighted) + return; + + d->highlighted = highlighted; + emit highlightedChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Button::flat + + This property holds whether the button is flat. + + \image qtquickcontrols2-button-flat.gif + + A flat button typically does not draw a background unless it is pressed or checked. + + The default value is \c false. +*/ +bool QQuickButton::isFlat() const +{ + Q_D(const QQuickButton); + return d->flat; +} + +void QQuickButton::setFlat(bool flat) +{ + Q_D(QQuickButton); + if (flat == d->flat) + return; + + d->flat = flat; + emit flatChanged(); +} + +QPalette QQuickButtonPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::Button); +} + +QT_END_NAMESPACE + +#include "moc_qquickbutton_p.cpp" diff --git a/src/quicktemplates2/qquickbutton_p.h b/src/quicktemplates2/qquickbutton_p.h new file mode 100644 index 0000000000..bfac5663b7 --- /dev/null +++ b/src/quicktemplates2/qquickbutton_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKBUTTON_P_H +#define QQUICKBUTTON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickButtonPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickButton : public QQuickAbstractButton +{ + Q_OBJECT + Q_PROPERTY(bool highlighted READ isHighlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL) + Q_PROPERTY(bool flat READ isFlat WRITE setFlat NOTIFY flatChanged FINAL) + QML_NAMED_ELEMENT(Button) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickButton(QQuickItem *parent = nullptr); + + bool isHighlighted() const; + void setHighlighted(bool highlighted); + + bool isFlat() const; + void setFlat(bool flat); + +Q_SIGNALS: + void highlightedChanged(); + void flatChanged(); + +protected: + QQuickButton(QQuickButtonPrivate &dd, QQuickItem *parent); + + QFont defaultFont() const override; + +private: + Q_DISABLE_COPY(QQuickButton) + Q_DECLARE_PRIVATE(QQuickButton) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickButton) + +#endif // QQUICKBUTTON_P_H diff --git a/src/quicktemplates2/qquickbutton_p_p.h b/src/quicktemplates2/qquickbutton_p_p.h new file mode 100644 index 0000000000..e83e320934 --- /dev/null +++ b/src/quicktemplates2/qquickbutton_p_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKBUTTON_P_P_H +#define QQUICKBUTTON_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickButtonPrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickButton) + +public: + QPalette defaultPalette() const override; + + bool flat = false; + bool highlighted = false; +}; + +QT_END_NAMESPACE + +#endif // QQUICKBUTTON_P_P_H diff --git a/src/quicktemplates2/qquickbuttongroup.cpp b/src/quicktemplates2/qquickbuttongroup.cpp new file mode 100644 index 0000000000..fd8e822c5f --- /dev/null +++ b/src/quicktemplates2/qquickbuttongroup.cpp @@ -0,0 +1,546 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickbuttongroup_p.h" + +#include <QtCore/private/qobject_p.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qvariant.h> +#include <QtQml/qqmlinfo.h> + +#include "qquickabstractbutton_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ButtonGroup + \inherits QtObject +//! \instantiates QQuickButtonGroup + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup utilities + \brief Mutually-exclusive group of checkable buttons. + + ButtonGroup is a non-visual, mutually exclusive group of buttons. + It is used with controls such as RadioButton, where only one of the options + can be selected at a time. + + The most straight-forward way to use ButtonGroup is to assign + a list of buttons. For example, the list of children of a + \l{Item Positioners}{positioner} or a \l{Qt Quick Layouts}{layout} + that manages a group of mutually exclusive buttons. + + \code + ButtonGroup { + buttons: column.children + } + + Column { + id: column + + RadioButton { + checked: true + text: qsTr("DAB") + } + + RadioButton { + text: qsTr("FM") + } + + RadioButton { + text: qsTr("AM") + } + } + \endcode + + Mutually exclusive buttons do not always share the same parent item, + or the parent layout may sometimes contain items that should not be + included in the button group. Such cases are best handled using + the \l group attached property. + + \code + ButtonGroup { id: radioGroup } + + Column { + Label { + text: qsTr("Radio:") + } + + RadioButton { + checked: true + text: qsTr("DAB") + ButtonGroup.group: radioGroup + } + + RadioButton { + text: qsTr("FM") + ButtonGroup.group: radioGroup + } + + RadioButton { + text: qsTr("AM") + ButtonGroup.group: radioGroup + } + } + \endcode + + More advanced use cases can be handled using the \c addButton() and + \c removeButton() methods. + + \sa RadioButton, {Button Controls} +*/ + +/*! + \qmlsignal QtQuick.Controls::ButtonGroup::clicked(AbstractButton button) + \since QtQuick.Controls 2.1 (Qt 5.8) + + This signal is emitted when a \a button in the group has been clicked. + + This signal is convenient for implementing a common signal handler for + all buttons in the same group. + + \code + ButtonGroup { + buttons: column.children + onClicked: console.log("clicked:", button.text) + } + + Column { + id: column + Button { text: "First" } + Button { text: "Second" } + Button { text: "Third" } + } + \endcode + + \sa AbstractButton::clicked() +*/ + +class QQuickButtonGroupPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickButtonGroup) + +public: + void clear(); + void buttonClicked(); + void _q_updateCurrent(); + void updateCheckState(); + void setCheckState(Qt::CheckState state); + + static void buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj); + static qsizetype buttons_count(QQmlListProperty<QQuickAbstractButton> *prop); + static QQuickAbstractButton *buttons_at(QQmlListProperty<QQuickAbstractButton> *prop, qsizetype index); + static void buttons_clear(QQmlListProperty<QQuickAbstractButton> *prop); + + bool complete = true; + bool exclusive = true; + bool settingCheckState = false; + Qt::CheckState checkState = Qt::Unchecked; + QPointer<QQuickAbstractButton> checkedButton; + QList<QQuickAbstractButton*> buttons; +}; + +void QQuickButtonGroupPrivate::clear() +{ + for (QQuickAbstractButton *button : qAsConst(buttons)) { + QQuickAbstractButtonPrivate::get(button)->group = nullptr; + QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, this, &QQuickButtonGroupPrivate::buttonClicked); + QObjectPrivate::disconnect(button, &QQuickAbstractButton::checkedChanged, this, &QQuickButtonGroupPrivate::_q_updateCurrent); + } + buttons.clear(); +} + +void QQuickButtonGroupPrivate::buttonClicked() +{ + Q_Q(QQuickButtonGroup); + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(q->sender()); + if (button) + emit q->clicked(button); +} + +void QQuickButtonGroupPrivate::_q_updateCurrent() +{ + Q_Q(QQuickButtonGroup); + if (exclusive) { + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(q->sender()); + if (button && button->isChecked()) + q->setCheckedButton(button); + else if (!buttons.contains(checkedButton)) + q->setCheckedButton(nullptr); + } + updateCheckState(); +} + +void QQuickButtonGroupPrivate::updateCheckState() +{ + if (!complete || settingCheckState) + return; + + bool anyChecked = false; + bool allChecked = !buttons.isEmpty(); + for (QQuickAbstractButton *button : qAsConst(buttons)) { + const bool isChecked = button->isChecked(); + anyChecked |= isChecked; + allChecked &= isChecked; + } + setCheckState(Qt::CheckState(anyChecked + allChecked)); +} + +void QQuickButtonGroupPrivate::setCheckState(Qt::CheckState state) +{ + Q_Q(QQuickButtonGroup); + if (checkState == state) + return; + + checkState = state; + emit q->checkStateChanged(); +} + +void QQuickButtonGroupPrivate::buttons_append(QQmlListProperty<QQuickAbstractButton> *prop, QQuickAbstractButton *obj) +{ + QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object); + q->addButton(obj); +} + +qsizetype QQuickButtonGroupPrivate::buttons_count(QQmlListProperty<QQuickAbstractButton> *prop) +{ + QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data); + return p->buttons.count(); +} + +QQuickAbstractButton *QQuickButtonGroupPrivate::buttons_at(QQmlListProperty<QQuickAbstractButton> *prop, qsizetype index) +{ + QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data); + return p->buttons.value(index); +} + +void QQuickButtonGroupPrivate::buttons_clear(QQmlListProperty<QQuickAbstractButton> *prop) +{ + QQuickButtonGroupPrivate *p = static_cast<QQuickButtonGroupPrivate *>(prop->data); + if (!p->buttons.isEmpty()) { + p->clear(); + QQuickButtonGroup *q = static_cast<QQuickButtonGroup *>(prop->object); + // QTBUG-52358: don't clear the checked button immediately + QMetaObject::invokeMethod(q, "_q_updateCurrent", Qt::QueuedConnection); + emit q->buttonsChanged(); + } +} + +QQuickButtonGroup::QQuickButtonGroup(QObject *parent) + : QObject(*(new QQuickButtonGroupPrivate), parent) +{ +} + +QQuickButtonGroup::~QQuickButtonGroup() +{ + Q_D(QQuickButtonGroup); + d->clear(); +} + +QQuickButtonGroupAttached *QQuickButtonGroup::qmlAttachedProperties(QObject *object) +{ + return new QQuickButtonGroupAttached(object); +} + +/*! + \qmlproperty AbstractButton QtQuick.Controls::ButtonGroup::checkedButton + + This property holds the currently selected button in an exclusive group, + or \c null if there is none or the group is non-exclusive. + + By default, it is the first checked button added to an exclusive button group. + + \sa exclusive +*/ +QQuickAbstractButton *QQuickButtonGroup::checkedButton() const +{ + Q_D(const QQuickButtonGroup); + return d->checkedButton; +} + +void QQuickButtonGroup::setCheckedButton(QQuickAbstractButton *checkedButton) +{ + Q_D(QQuickButtonGroup); + if (d->checkedButton == checkedButton) + return; + + if (d->checkedButton) + d->checkedButton->setChecked(false); + d->checkedButton = checkedButton; + if (checkedButton) + checkedButton->setChecked(true); + emit checkedButtonChanged(); +} + +/*! + \qmlproperty list<AbstractButton> QtQuick.Controls::ButtonGroup::buttons + + This property holds the list of buttons. + + \code + ButtonGroup { + buttons: column.children + } + + Column { + id: column + + RadioButton { + checked: true + text: qsTr("Option A") + } + + RadioButton { + text: qsTr("Option B") + } + } + \endcode + + \sa group +*/ +QQmlListProperty<QQuickAbstractButton> QQuickButtonGroup::buttons() +{ + Q_D(QQuickButtonGroup); + return QQmlListProperty<QQuickAbstractButton>(this, d, + QQuickButtonGroupPrivate::buttons_append, + QQuickButtonGroupPrivate::buttons_count, + QQuickButtonGroupPrivate::buttons_at, + QQuickButtonGroupPrivate::buttons_clear); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::ButtonGroup::exclusive + + This property holds whether the button group is exclusive. The default value is \c true. + + If this property is \c true, then only one button in the group can be checked at any given time. + The user can click on any button to check it, and that button will replace the existing one as + the checked button in the group. + + In an exclusive group, the user cannot uncheck the currently checked button by clicking on it; + instead, another button in the group must be clicked to set the new checked button for that group. + + In a non-exclusive group, checking and unchecking buttons does not affect the other buttons in + the group. Furthermore, the value of the \l checkedButton property is \c null. +*/ +bool QQuickButtonGroup::isExclusive() const +{ + Q_D(const QQuickButtonGroup); + return d->exclusive; +} + +void QQuickButtonGroup::setExclusive(bool exclusive) +{ + Q_D(QQuickButtonGroup); + if (d->exclusive == exclusive) + return; + + d->exclusive = exclusive; + emit exclusiveChanged(); +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty enumeration QtQuick.Controls::ButtonGroup::checkState + + This property holds the combined check state of the button group. + + Available states: + \value Qt.Unchecked None of the buttons are checked. + \value Qt.PartiallyChecked Some of the buttons are checked. + \value Qt.Checked All of the buttons are checked. + + Setting the check state of a non-exclusive button group to \c Qt.Unchecked + or \c Qt.Checked unchecks or checks all buttons in the group, respectively. + \c Qt.PartiallyChecked is ignored. + + Setting the check state of an exclusive button group to \c Qt.Unchecked + unchecks the \l checkedButton. \c Qt.Checked and \c Qt.PartiallyChecked + are ignored. +*/ +Qt::CheckState QQuickButtonGroup::checkState() const +{ + Q_D(const QQuickButtonGroup); + return d->checkState; +} + +void QQuickButtonGroup::setCheckState(Qt::CheckState state) +{ + Q_D(QQuickButtonGroup); + if (d->checkState == state || state == Qt::PartiallyChecked) + return; + + d->settingCheckState = true; + if (d->exclusive) { + if (d->checkedButton && state == Qt::Unchecked) + setCheckedButton(nullptr); + } else { + for (QQuickAbstractButton *button : qAsConst(d->buttons)) + button->setChecked(state == Qt::Checked); + } + d->settingCheckState = false; + d->setCheckState(state); +} + +/*! + \qmlmethod void QtQuick.Controls::ButtonGroup::addButton(AbstractButton button) + + Adds a \a button to the button group. + + \note Manually adding objects to a button group is typically unnecessary. + The \l buttons property and the \l group attached property provide a + convenient and declarative syntax. + + \sa buttons, group +*/ +void QQuickButtonGroup::addButton(QQuickAbstractButton *button) +{ + Q_D(QQuickButtonGroup); + if (!button || d->buttons.contains(button)) + return; + + QQuickAbstractButtonPrivate::get(button)->group = this; + QObjectPrivate::connect(button, &QQuickAbstractButton::clicked, d, &QQuickButtonGroupPrivate::buttonClicked); + QObjectPrivate::connect(button, &QQuickAbstractButton::checkedChanged, d, &QQuickButtonGroupPrivate::_q_updateCurrent); + + if (d->exclusive && button->isChecked()) + setCheckedButton(button); + + d->buttons.append(button); + d->updateCheckState(); + emit buttonsChanged(); +} + +/*! + \qmlmethod void QtQuick.Controls::ButtonGroup::removeButton(AbstractButton button) + + Removes a \a button from the button group. + + \note Manually removing objects from a button group is typically unnecessary. + The \l buttons property and the \l group attached property provide a + convenient and declarative syntax. + + \sa buttons, group +*/ +void QQuickButtonGroup::removeButton(QQuickAbstractButton *button) +{ + Q_D(QQuickButtonGroup); + if (!button || !d->buttons.contains(button)) + return; + + QQuickAbstractButtonPrivate::get(button)->group = nullptr; + QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, d, &QQuickButtonGroupPrivate::buttonClicked); + QObjectPrivate::disconnect(button, &QQuickAbstractButton::checkedChanged, d, &QQuickButtonGroupPrivate::_q_updateCurrent); + + if (d->checkedButton == button) + setCheckedButton(nullptr); + + d->buttons.removeOne(button); + d->updateCheckState(); + emit buttonsChanged(); +} + +void QQuickButtonGroup::classBegin() +{ + Q_D(QQuickButtonGroup); + d->complete = false; +} + +void QQuickButtonGroup::componentComplete() +{ + Q_D(QQuickButtonGroup); + d->complete = true; + if (!d->buttons.isEmpty()) + d->updateCheckState(); +} + +class QQuickButtonGroupAttachedPrivate : public QObjectPrivate +{ +public: + QQuickButtonGroup *group = nullptr; +}; + +QQuickButtonGroupAttached::QQuickButtonGroupAttached(QObject *parent) + : QObject(*(new QQuickButtonGroupAttachedPrivate), parent) +{ +} + +/*! + \qmlattachedproperty ButtonGroup QtQuick.Controls::ButtonGroup::group + + This property attaches a button to a button group. + + \code + ButtonGroup { id: group } + + RadioButton { + checked: true + text: qsTr("Option A") + ButtonGroup.group: group + } + + RadioButton { + text: qsTr("Option B") + ButtonGroup.group: group + } + \endcode + + \sa buttons +*/ +QQuickButtonGroup *QQuickButtonGroupAttached::group() const +{ + Q_D(const QQuickButtonGroupAttached); + return d->group; +} + +void QQuickButtonGroupAttached::setGroup(QQuickButtonGroup *group) +{ + Q_D(QQuickButtonGroupAttached); + if (d->group == group) + return; + + if (d->group) + d->group->removeButton(qobject_cast<QQuickAbstractButton*>(parent())); + d->group = group; + if (group) + group->addButton(qobject_cast<QQuickAbstractButton*>(parent())); + emit groupChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquickbuttongroup_p.cpp" diff --git a/src/quicktemplates2/qquickbuttongroup_p.h b/src/quicktemplates2/qquickbuttongroup_p.h new file mode 100644 index 0000000000..daff84f2a0 --- /dev/null +++ b/src/quicktemplates2/qquickbuttongroup_p.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKBUTTONGROUP_P_H +#define QQUICKBUTTONGROUP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtQml/qqml.h> +#include <QtQml/qqmlparserstatus.h> + +QT_BEGIN_NAMESPACE + +class QQuickAbstractButton; +class QQuickButtonGroupPrivate; +class QQuickButtonGroupAttached; +class QQuickButtonGroupAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickButtonGroup : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QQuickAbstractButton *checkedButton READ checkedButton WRITE setCheckedButton NOTIFY checkedButtonChanged FINAL) + Q_PROPERTY(QQmlListProperty<QQuickAbstractButton> buttons READ buttons NOTIFY buttonsChanged FINAL) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool exclusive READ isExclusive WRITE setExclusive NOTIFY exclusiveChanged FINAL REVISION(2, 3)) + // 2.4 (Qt 5.11) + Q_PROPERTY(Qt::CheckState checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL REVISION(2, 4)) + Q_INTERFACES(QQmlParserStatus) + QML_NAMED_ELEMENT(ButtonGroup) + QML_ATTACHED(QQuickButtonGroupAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickButtonGroup(QObject *parent = nullptr); + ~QQuickButtonGroup(); + + static QQuickButtonGroupAttached *qmlAttachedProperties(QObject *object); + + QQuickAbstractButton *checkedButton() const; + void setCheckedButton(QQuickAbstractButton *checkedButton); + + QQmlListProperty<QQuickAbstractButton> buttons(); + + bool isExclusive() const; + void setExclusive(bool exclusive); + + // 2.4 (Qt 5.11) + Qt::CheckState checkState() const; + void setCheckState(Qt::CheckState state); + +public Q_SLOTS: + void addButton(QQuickAbstractButton *button); + void removeButton(QQuickAbstractButton *button); + +Q_SIGNALS: + void checkedButtonChanged(); + void buttonsChanged(); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void clicked(QQuickAbstractButton *button); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void exclusiveChanged(); + // 2.4 (Qt 5.11) + Q_REVISION(2, 4) void checkStateChanged(); + +protected: + void classBegin() override; + void componentComplete() override; + +private: + Q_DISABLE_COPY(QQuickButtonGroup) + Q_DECLARE_PRIVATE(QQuickButtonGroup) + + Q_PRIVATE_SLOT(d_func(), void _q_updateCurrent()) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickButtonGroupAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickButtonGroup *group READ group WRITE setGroup NOTIFY groupChanged FINAL) + +public: + explicit QQuickButtonGroupAttached(QObject *parent = nullptr); + + QQuickButtonGroup *group() const; + void setGroup(QQuickButtonGroup *group); + +Q_SIGNALS: + void groupChanged(); + +private: + Q_DISABLE_COPY(QQuickButtonGroupAttached) + Q_DECLARE_PRIVATE(QQuickButtonGroupAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickButtonGroup) +QML_DECLARE_TYPEINFO(QQuickButtonGroup, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKBUTTONGROUP_P_H diff --git a/src/quicktemplates2/qquickcheckbox.cpp b/src/quicktemplates2/qquickcheckbox.cpp new file mode 100644 index 0000000000..e3184ceb52 --- /dev/null +++ b/src/quicktemplates2/qquickcheckbox.cpp @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcheckbox_p.h" +#include "qquickabstractbutton_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQml/qjsvalue.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype CheckBox + \inherits AbstractButton +//! \instantiates QQuickCheckBox + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-buttons + \brief Check button that can be toggled on or off. + + \image qtquickcontrols2-checkbox.gif + + CheckBox presents an option button that can be toggled on (checked) or + off (unchecked). Check boxes are typically used to select one or more + options from a set of options. For larger sets of options, such as those + in a list, consider using \l CheckDelegate instead. + + CheckBox inherits its API from \l AbstractButton. For instance, the + state of the checkbox can be set with the \l {AbstractButton::}{checked} property. + + In addition to the checked and unchecked states, there is a third state: + partially checked. The partially checked state can be enabled using the + \l tristate property. This state indicates that the regular checked/unchecked + state can not be determined; generally because of other states that affect + the checkbox. This state is useful when several child nodes are selected + in a treeview, for example. + + \code + ColumnLayout { + CheckBox { + checked: true + text: qsTr("First") + } + CheckBox { + text: qsTr("Second") + } + CheckBox { + checked: true + text: qsTr("Third") + } + } + \endcode + + Hierarchical checkbox groups can be managed with a non-exclusive + \l ButtonGroup. + + \image qtquickcontrols2-checkbox-group.png + + The following example illustrates how the combined check state of + children can be bound to the check state of the parent checkbox: + + \snippet qtquickcontrols2-checkbox-group.qml 1 + + \sa {Customizing CheckBox}, ButtonGroup, {Button Controls} +*/ + +class QQuickCheckBoxPrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickCheckBox) + +public: + void setNextCheckState(const QJSValue &callback); + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::CheckBox); } + + bool tristate = false; + Qt::CheckState checkState = Qt::Unchecked; + QJSValue nextCheckState; +}; + +void QQuickCheckBoxPrivate::setNextCheckState(const QJSValue &callback) +{ + Q_Q(QQuickCheckBox); + nextCheckState = callback; + emit q->nextCheckStateChanged(); +} + +QQuickCheckBox::QQuickCheckBox(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickCheckBoxPrivate), parent) +{ + setCheckable(true); +} + +/*! + \qmlproperty bool QtQuick.Controls::CheckBox::tristate + + This property holds whether the checkbox is a tri-state checkbox. + + In the animation below, the first checkbox is tri-state: + + \image qtquickcontrols2-checkbox-tristate.gif + + The default is \c false, i.e., the checkbox has only two states. +*/ +bool QQuickCheckBox::isTristate() const +{ + Q_D(const QQuickCheckBox); + return d->tristate; +} + +void QQuickCheckBox::setTristate(bool tristate) +{ + Q_D(QQuickCheckBox); + if (d->tristate == tristate) + return; + + d->tristate = tristate; + emit tristateChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::CheckBox::checkState + + This property holds the check state of the checkbox. + + Available states: + \value Qt.Unchecked The checkbox is unchecked. + \value Qt.PartiallyChecked The checkbox is partially checked. This state is only used when \l tristate is enabled. + \value Qt.Checked The checkbox is checked. + + \sa tristate, {AbstractButton::checked}{checked} +*/ +Qt::CheckState QQuickCheckBox::checkState() const +{ + Q_D(const QQuickCheckBox); + return d->checkState; +} + +void QQuickCheckBox::setCheckState(Qt::CheckState state) +{ + Q_D(QQuickCheckBox); + if (d->checkState == state) + return; + + bool wasChecked = isChecked(); + d->checked = state == Qt::Checked; + d->checkState = state; + emit checkStateChanged(); + if (d->checked != wasChecked) + emit checkedChanged(); +} + +QFont QQuickCheckBox::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::CheckBox); +} + +void QQuickCheckBox::buttonChange(ButtonChange change) +{ + if (change == ButtonCheckedChange) + setCheckState(isChecked() ? Qt::Checked : Qt::Unchecked); + else + QQuickAbstractButton::buttonChange(change); +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty function QtQuick.Controls::CheckBox::nextCheckState + + This property holds a callback function that is called to determine + the next check state whenever the checkbox is interactively toggled + by the user via touch, mouse, or keyboard. + + By default, a normal checkbox cycles between \c Qt.Unchecked and + \c Qt.Checked states, and a tri-state checkbox cycles between + \c Qt.Unchecked, \c Qt.PartiallyChecked, and \c Qt.Checked states. + + The \c nextCheckState callback function can override the default behavior. + The following example implements a tri-state checkbox that can present + a partially checked state depending on external conditions, but never + cycles to the partially checked state when interactively toggled by + the user. + + \code + CheckBox { + tristate: true + checkState: allChildrenChecked ? Qt.Checked : + anyChildChecked ? Qt.PartiallyChecked : Qt.Unchecked + + nextCheckState: function() { + if (checkState === Qt.Checked) + return Qt.Unchecked + else + return Qt.Checked + } + } + \endcode +*/ +void QQuickCheckBox::nextCheckState() +{ + Q_D(QQuickCheckBox); + if (d->nextCheckState.isCallable()) + setCheckState(static_cast<Qt::CheckState>(d->nextCheckState.call().toInt())); + else if (d->tristate) + setCheckState(static_cast<Qt::CheckState>((d->checkState + 1) % 3)); + else + QQuickAbstractButton::nextCheckState(); +} + +QT_END_NAMESPACE + +#include "moc_qquickcheckbox_p.cpp" diff --git a/src/quicktemplates2/qquickcheckbox_p.h b/src/quicktemplates2/qquickcheckbox_p.h new file mode 100644 index 0000000000..8e53b41dd7 --- /dev/null +++ b/src/quicktemplates2/qquickcheckbox_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCHECKBOX_P_H +#define QQUICKCHECKBOX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickCheckBoxPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickCheckBox : public QQuickAbstractButton +{ + Q_OBJECT + Q_PROPERTY(bool tristate READ isTristate WRITE setTristate NOTIFY tristateChanged FINAL) + Q_PROPERTY(Qt::CheckState checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL) + // 2.4 (Qt 5.11) + Q_PRIVATE_PROPERTY(QQuickCheckBox::d_func(), QJSValue nextCheckState MEMBER nextCheckState WRITE setNextCheckState NOTIFY nextCheckStateChanged FINAL REVISION(2, 4)) + QML_NAMED_ELEMENT(CheckBox) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickCheckBox(QQuickItem *parent = nullptr); + + bool isTristate() const; + void setTristate(bool tristate); + + Qt::CheckState checkState() const; + void setCheckState(Qt::CheckState state); + +Q_SIGNALS: + void tristateChanged(); + void checkStateChanged(); + // 2.4 (Qt 5.11) + Q_REVISION(2, 4) void nextCheckStateChanged(); + +protected: + QFont defaultFont() const override; + + void buttonChange(ButtonChange change) override; + void nextCheckState() override; + +private: + Q_DISABLE_COPY(QQuickCheckBox) + Q_DECLARE_PRIVATE(QQuickCheckBox) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickCheckBox) + +#endif // QQUICKCHECKBOX_P_H diff --git a/src/quicktemplates2/qquickcheckdelegate.cpp b/src/quicktemplates2/qquickcheckdelegate.cpp new file mode 100644 index 0000000000..7596e104b9 --- /dev/null +++ b/src/quicktemplates2/qquickcheckdelegate.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcheckdelegate_p.h" +#include "qquickitemdelegate_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQml/qjsvalue.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype CheckDelegate + \inherits ItemDelegate +//! \instantiates QQuickCheckDelegate + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-delegates + \brief Item delegate with a check indicator that can be toggled on or off. + + \image qtquickcontrols2-checkdelegate.gif + + CheckDelegate presents an item delegate that can be toggled on (checked) or + off (unchecked). Check delegates are typically used to select one or more + options from a set of options in a list. For smaller sets of options, or + for options that need to be uniquely identifiable, consider using + \l CheckBox instead. + + CheckDelegate inherits its API from \l ItemDelegate, which is inherited + from AbstractButton. For instance, you can set \l {AbstractButton::text}{text}, + and react to \l {AbstractButton::clicked}{clicks} using the AbstractButton + API. The state of the check delegate can be set with the + \l {AbstractButton::}{checked} property. + + In addition to the checked and unchecked states, there is a third state: + partially checked. The partially checked state can be enabled using the + \l tristate property. This state indicates that the regular checked/unchecked + state can not be determined; generally because of other states that affect + the check delegate. This state is useful when several child nodes are selected + in a treeview, for example. + + \code + ListView { + model: ["Option 1", "Option 2", "Option 3"] + delegate: CheckDelegate { + text: modelData + } + } + \endcode + + \sa {Customizing CheckDelegate}, {Delegate Controls}, CheckBox +*/ + +class QQuickCheckDelegatePrivate : public QQuickItemDelegatePrivate +{ + Q_DECLARE_PUBLIC(QQuickCheckDelegate) + +public: + void setNextCheckState(const QJSValue &callback); + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ListView); } + + bool tristate = false; + Qt::CheckState checkState = Qt::Unchecked; + QJSValue nextCheckState; +}; + +void QQuickCheckDelegatePrivate::setNextCheckState(const QJSValue &callback) +{ + Q_Q(QQuickCheckDelegate); + nextCheckState = callback; + emit q->nextCheckStateChanged(); +} + +QQuickCheckDelegate::QQuickCheckDelegate(QQuickItem *parent) + : QQuickItemDelegate(*(new QQuickCheckDelegatePrivate), parent) +{ + setCheckable(true); +} + +/*! + \qmlproperty bool QtQuick.Controls::CheckDelegate::tristate + + This property determines whether the check delegate has three states. + + In the animation below, the first checkdelegate is tri-state: + + \image qtquickcontrols2-checkdelegate-tristate.gif + + The default is \c false, i.e., the delegate has only two states. +*/ +bool QQuickCheckDelegate::isTristate() const +{ + Q_D(const QQuickCheckDelegate); + return d->tristate; +} + +void QQuickCheckDelegate::setTristate(bool tristate) +{ + Q_D(QQuickCheckDelegate); + if (d->tristate == tristate) + return; + + d->tristate = tristate; + emit tristateChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::CheckDelegate::checkState + + This property determines the check state of the check delegate. + + Available states: + \value Qt.Unchecked The delegate is unchecked. + \value Qt.PartiallyChecked The delegate is partially checked. This state is only used when \l tristate is enabled. + \value Qt.Checked The delegate is checked. + + \sa tristate, {AbstractButton::checked}{checked} +*/ +Qt::CheckState QQuickCheckDelegate::checkState() const +{ + Q_D(const QQuickCheckDelegate); + return d->checkState; +} + +void QQuickCheckDelegate::setCheckState(Qt::CheckState state) +{ + Q_D(QQuickCheckDelegate); + if (d->checkState == state) + return; + + bool wasChecked = isChecked(); + d->checked = state == Qt::Checked; + d->checkState = state; + emit checkStateChanged(); + if (d->checked != wasChecked) + emit checkedChanged(); +} + +QFont QQuickCheckDelegate::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ListView); +} + +void QQuickCheckDelegate::buttonChange(ButtonChange change) +{ + if (change == ButtonCheckedChange) + setCheckState(isChecked() ? Qt::Checked : Qt::Unchecked); + else + QQuickAbstractButton::buttonChange(change); +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty function QtQuick.Controls::CheckDelegate::nextCheckState + + This property holds a callback function that is called to determine + the next check state whenever the check delegate is interactively toggled + by the user via touch, mouse, or keyboard. + + By default, a normal check delegate cycles between \c Qt.Unchecked and + \c Qt.Checked states, and a tri-state check delegate cycles between + \c Qt.Unchecked, \c Qt.PartiallyChecked, and \c Qt.Checked states. + + The \c nextCheckState callback function can override the default behavior. + The following example implements a tri-state check delegate that can present + a partially checked state depending on external conditions, but never + cycles to the partially checked state when interactively toggled by + the user. + + \code + CheckDelegate { + tristate: true + checkState: allChildrenChecked ? Qt.Checked : + anyChildChecked ? Qt.PartiallyChecked : Qt.Unchecked + + nextCheckState: function() { + if (checkState === Qt.Checked) + return Qt.Unchecked + else + return Qt.Checked + } + } + \endcode +*/ +void QQuickCheckDelegate::nextCheckState() +{ + Q_D(QQuickCheckDelegate); + if (d->nextCheckState.isCallable()) + setCheckState(static_cast<Qt::CheckState>(d->nextCheckState.call().toInt())); + else if (d->tristate) + setCheckState(static_cast<Qt::CheckState>((d->checkState + 1) % 3)); + else + QQuickItemDelegate::nextCheckState(); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickCheckDelegate::accessibleRole() const +{ + return QAccessible::CheckBox; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickcheckdelegate_p.cpp" diff --git a/src/quicktemplates2/qquickcheckdelegate_p.h b/src/quicktemplates2/qquickcheckdelegate_p.h new file mode 100644 index 0000000000..9b69943fd6 --- /dev/null +++ b/src/quicktemplates2/qquickcheckdelegate_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCHECKDELEGATE_P_H +#define QQUICKCHECKDELEGATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickitemdelegate_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickCheckDelegatePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickCheckDelegate : public QQuickItemDelegate +{ + Q_OBJECT + Q_PROPERTY(bool tristate READ isTristate WRITE setTristate NOTIFY tristateChanged FINAL) + Q_PROPERTY(Qt::CheckState checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL) + // 2.4 (Qt 5.11) + Q_PRIVATE_PROPERTY(QQuickCheckDelegate::d_func(), QJSValue nextCheckState MEMBER nextCheckState WRITE setNextCheckState NOTIFY nextCheckStateChanged FINAL REVISION(2, 4)) + QML_NAMED_ELEMENT(CheckDelegate) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickCheckDelegate(QQuickItem *parent = nullptr); + + bool isTristate() const; + void setTristate(bool tristate); + + Qt::CheckState checkState() const; + void setCheckState(Qt::CheckState state); + +Q_SIGNALS: + void tristateChanged(); + void checkStateChanged(); + // 2.4 (Qt 5.11) + Q_REVISION(2, 4) void nextCheckStateChanged(); + +protected: + QFont defaultFont() const override; + + void buttonChange(ButtonChange change) override; + void nextCheckState() override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickCheckDelegate) + Q_DECLARE_PRIVATE(QQuickCheckDelegate) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickCheckDelegate) + +#endif // QQUICKCHECKDELEGATE_P_H diff --git a/src/quicktemplates2/qquickcombobox.cpp b/src/quicktemplates2/qquickcombobox.cpp new file mode 100644 index 0000000000..5e2f2383fb --- /dev/null +++ b/src/quicktemplates2/qquickcombobox.cpp @@ -0,0 +1,2221 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcombobox_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickabstractbutton_p.h" +#include "qquickabstractbutton_p_p.h" +#include "qquickpopup_p_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtCore/qregularexpression.h> +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qglobal.h> +#include <QtGui/qinputmethod.h> +#include <QtGui/qguiapplication.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQml/qjsvalue.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/private/qlazilyallocated_p.h> +#include <private/qqmldelegatemodel_p.h> +#include <QtQuick/private/qquickaccessibleattached_p.h> +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquicktextinput_p.h> +#include <QtQuick/private/qquicktextinput_p_p.h> +#include <QtQuick/private/qquickitemview_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcCalculateWidestTextWidth, "qt.quick.controls.combobox.calculatewidesttextwidth") + +/*! + \qmltype ComboBox + \inherits Control +//! \instantiates QQuickComboBox + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-input + \ingroup qtquickcontrols2-focusscopes + \brief Combined button and popup list for selecting options. + + \image qtquickcontrols2-combobox.gif + + ComboBox is a combined button and popup list. It provides a means of + presenting a list of options to the user in a way that takes up the + minimum amount of screen space. + + ComboBox is populated with a data model. The data model is commonly + a JavaScript array, a \l ListModel or an integer, but other types + of \l {qml-data-models}{data models} are also supported. + + \code + ComboBox { + model: ["First", "Second", "Third"] + } + \endcode + + \section1 Editable ComboBox + + ComboBox can be made \l editable. An editable combo box auto-completes + its text based on what is available in the model. + + The following example demonstrates appending content to an editable + combo box by reacting to the \l accepted signal. + + \snippet qtquickcontrols2-combobox-accepted.qml combobox + + \section1 ComboBox's Popup + + By default, clicking outside of ComboBox's popup will close it, and the + event is propagated to items lower in the stacking order. To prevent the + popup from closing, set its \l {Popup::}{closePolicy}: + + \snippet qtquickcontrols2-combobox-popup.qml closePolicy + + To prevent event propagation, set its \l {Popup::}{modal} property to + \c true: + + \snippet qtquickcontrols2-combobox-popup.qml modal + + \section1 ComboBox Model Roles + + ComboBox is able to visualize standard \l {qml-data-models}{data models} + that provide the \c modelData role: + \list + \li models that have only one role + \li models that do not have named roles (JavaScript array, integer) + \endlist + + When using models that have multiple named roles, ComboBox must be configured + to use a specific \l {textRole}{text role} for its \l {displayText}{display text} + and \l delegate instances. If you want to use a role of the model item + that corresponds to the text role, set \l valueRole. The \l currentValue + property and \l indexOfValue() method can then be used to get information + about those values. + + For example: + + \snippet qtquickcontrols2-combobox-valuerole.qml file + + \note If ComboBox is assigned a data model that has multiple named roles, but + \l textRole is not defined, ComboBox is unable to visualize it and throws a + \c {ReferenceError: modelData is not defined}. + + \sa {Customizing ComboBox}, {Input Controls}, {Focus Management in Qt Quick Controls} +*/ + +/*! + \qmlsignal void QtQuick.Controls::ComboBox::activated(int index) + + This signal is emitted when the item at \a index is activated by the user. + + An item is activated when it is selected while the popup is open, + causing the popup to close (and \l currentIndex to change), + or while the popup is closed and the combo box is navigated via + keyboard, causing the \l currentIndex to change. + The \l currentIndex property is set to \a index. + + \sa currentIndex +*/ + +/*! + \qmlsignal void QtQuick.Controls::ComboBox::highlighted(int index) + + This signal is emitted when the item at \a index in the popup list is highlighted by the user. + + The highlighted signal is only emitted when the popup is open and an item + is highlighted, but not necessarily \l activated. + + \sa highlightedIndex +*/ + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlsignal void QtQuick.Controls::ComboBox::accepted() + + This signal is emitted when the \uicontrol Return or \uicontrol Enter key is pressed + on an \l editable combo box. + + You can handle this signal in order to add the newly entered + item to the model, for example: + + \snippet qtquickcontrols2-combobox-accepted.qml combobox + + Before the signal is emitted, a check is done to see if the string + exists in the model. If it does, \l currentIndex will be set to its index, + and \l currentText to the string itself. + + After the signal has been emitted, and if the first check failed (that is, + the item did not exist), another check will be done to see if the item was + added by the signal handler. If it was, the \l currentIndex and + \l currentText are updated accordingly. Otherwise, they will be set to + \c -1 and \c "", respectively. + + \note If there is a \l validator set on the combo box, the signal will only be + emitted if the input is in an acceptable state. +*/ + +namespace { + enum Activation { NoActivate, Activate }; + enum Highlighting { NoHighlight, Highlight }; +} + +class QQuickComboBoxDelegateModel : public QQmlDelegateModel +{ +public: + explicit QQuickComboBoxDelegateModel(QQuickComboBox *combo); + QVariant variantValue(int index, const QString &role) override; + +private: + QQuickComboBox *combo = nullptr; +}; + +QQuickComboBoxDelegateModel::QQuickComboBoxDelegateModel(QQuickComboBox *combo) + : QQmlDelegateModel(qmlContext(combo), combo), + combo(combo) +{ +} + +QVariant QQuickComboBoxDelegateModel::variantValue(int index, const QString &role) +{ + const QVariant model = combo->model(); + if (model.userType() == QMetaType::QVariantList) { + QVariant object = model.toList().value(index); + if (object.userType() == QMetaType::QVariantMap) { + const QVariantMap data = object.toMap(); + if (data.count() == 1 && role == QLatin1String("modelData")) + return data.first(); + return data.value(role); + } else if (object.userType() == QMetaType::QObjectStar) { + const QObject *data = object.value<QObject *>(); + if (data && role != QLatin1String("modelData")) + return data->property(role.toUtf8()); + } + } + return QQmlDelegateModel::variantValue(index, role); +} + +class QQuickComboBoxPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickComboBox) + +public: + bool isPopupVisible() const; + void showPopup(); + void hidePopup(bool accept); + void togglePopup(bool accept); + void popupVisibleChanged(); + + void itemClicked(); + void itemHovered(); + + void createdItem(int index, QObject *object); + void modelUpdated(); + void countChanged(); + + QString effectiveTextRole() const; + void updateEditText(); + void updateCurrentText(); + void updateCurrentValue(); + void updateCurrentTextAndValue(); + void updateAcceptableInput(); + + bool isValidIndex(int index) const; + + void acceptInput(); + QString tryComplete(const QString &inputText); + + void incrementCurrentIndex(); + void decrementCurrentIndex(); + void setCurrentIndex(int index, Activation activate); + void updateHighlightedIndex(); + void setHighlightedIndex(int index, Highlighting highlight); + + void keySearch(const QString &text); + int match(int start, const QString &text, Qt::MatchFlags flags) const; + + void createDelegateModel(); + + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + void handleUngrab() override; + + void cancelIndicator(); + void executeIndicator(bool complete = false); + + void cancelPopup(); + void executePopup(bool complete = false); + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + void setInputMethodHints(Qt::InputMethodHints hints, bool force = false); + + virtual qreal getContentWidth() const override; + qreal calculateWidestTextWidth() const; + void maybeUpdateImplicitContentWidth(); + + static void hideOldPopup(QQuickPopup *popup); + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ComboBox); } + + bool flat = false; + bool down = false; + bool hasDown = false; + bool pressed = false; + bool ownModel = false; + bool keyNavigating = false; + bool hasDisplayText = false; + bool hasCurrentIndex = false; + bool hasCalculatedWidestText = false; + int highlightedIndex = -1; + int currentIndex = -1; + QQuickComboBox::ImplicitContentWidthPolicy implicitContentWidthPolicy = QQuickComboBox::ContentItemImplicitWidth; + QVariant model; + QString textRole; + QString currentText; + QString displayText; + QString valueRole; + QVariant currentValue; + QQuickItem *pressedItem = nullptr; + QQmlInstanceModel *delegateModel = nullptr; + QQmlComponent *delegate = nullptr; + QQuickDeferredPointer<QQuickItem> indicator; + QQuickDeferredPointer<QQuickPopup> popup; + bool m_acceptableInput = true; + + struct ExtraData { + bool editable = false; + bool accepting = false; + bool allowComplete = false; + bool selectTextByMouse = false; + Qt::InputMethodHints inputMethodHints = Qt::ImhNone; + QString editText; + QValidator *validator = nullptr; + }; + QLazilyAllocated<ExtraData> extra; +}; + +bool QQuickComboBoxPrivate::isPopupVisible() const +{ + return popup && popup->isVisible(); +} + +void QQuickComboBoxPrivate::showPopup() +{ + if (!popup) + executePopup(true); + + if (popup && !popup->isVisible()) + popup->open(); +} + +void QQuickComboBoxPrivate::hidePopup(bool accept) +{ + Q_Q(QQuickComboBox); + if (accept) { + q->setCurrentIndex(highlightedIndex); + emit q->activated(currentIndex); + } + if (popup && popup->isVisible()) + popup->close(); +} + +void QQuickComboBoxPrivate::togglePopup(bool accept) +{ + if (!popup || !popup->isVisible()) + showPopup(); + else + hidePopup(accept); +} + +void QQuickComboBoxPrivate::popupVisibleChanged() +{ + Q_Q(QQuickComboBox); + if (isPopupVisible()) + QGuiApplication::inputMethod()->reset(); + + QQuickItemView *itemView = popup->findChild<QQuickItemView *>(); + if (itemView) + itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange); + + updateHighlightedIndex(); + + if (itemView) + itemView->positionViewAtIndex(highlightedIndex, QQuickItemView::Beginning); + + if (!hasDown) { + q->setDown(pressed || isPopupVisible()); + hasDown = false; + } +} + +void QQuickComboBoxPrivate::itemClicked() +{ + Q_Q(QQuickComboBox); + int index = delegateModel->indexOf(q->sender(), nullptr); + if (index != -1) { + setHighlightedIndex(index, Highlight); + hidePopup(true); + } +} + +void QQuickComboBoxPrivate::itemHovered() +{ + Q_Q(QQuickComboBox); + if (keyNavigating) + return; + + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender()); + if (!button || !button->isHovered() || !button->isEnabled() || QQuickAbstractButtonPrivate::get(button)->touchId != -1) + return; + + int index = delegateModel->indexOf(button, nullptr); + if (index != -1) { + setHighlightedIndex(index, Highlight); + + if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>()) + itemView->positionViewAtIndex(index, QQuickItemView::Contain); + } +} + +void QQuickComboBoxPrivate::createdItem(int index, QObject *object) +{ + Q_Q(QQuickComboBox); + QQuickItem *item = qobject_cast<QQuickItem *>(object); + if (item && !item->parentItem()) { + if (popup) + item->setParentItem(popup->contentItem()); + else + item->setParentItem(q); + QQuickItemPrivate::get(item)->setCulled(true); + } + + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(object); + if (button) { + button->setFocusPolicy(Qt::NoFocus); + connect(button, &QQuickAbstractButton::clicked, this, &QQuickComboBoxPrivate::itemClicked); + connect(button, &QQuickAbstractButton::hoveredChanged, this, &QQuickComboBoxPrivate::itemHovered); + } + + if (index == currentIndex && !q->isEditable()) + updateCurrentTextAndValue(); +} + +void QQuickComboBoxPrivate::modelUpdated() +{ + if (componentComplete && (!extra.isAllocated() || !extra->accepting)) { + updateCurrentTextAndValue(); + + if (implicitContentWidthPolicy == QQuickComboBox::WidestText) + updateImplicitContentSize(); + } +} + +void QQuickComboBoxPrivate::countChanged() +{ + Q_Q(QQuickComboBox); + if (q->count() == 0) + q->setCurrentIndex(-1); + emit q->countChanged(); +} + +QString QQuickComboBoxPrivate::effectiveTextRole() const +{ + return textRole.isEmpty() ? QStringLiteral("modelData") : textRole; +} + +void QQuickComboBoxPrivate::updateEditText() +{ + Q_Q(QQuickComboBox); + QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem); + if (!input) + return; + + const QString text = input->text(); + + if (extra.isAllocated() && extra->allowComplete && !text.isEmpty()) { + const QString completed = tryComplete(text); + if (completed.length() > text.length()) { + input->setText(completed); + // This will select the text backwards. + input->select(completed.length(), text.length()); + return; + } + } + q->setEditText(text); +} + +void QQuickComboBoxPrivate::updateCurrentText() +{ + Q_Q(QQuickComboBox); + const QString text = q->textAt(currentIndex); + if (currentText != text) { + currentText = text; + if (!hasDisplayText) + q->maybeSetAccessibleName(text); + emit q->currentTextChanged(); + } + if (!hasDisplayText && displayText != text) { + displayText = text; + emit q->displayTextChanged(); + } + if (!extra.isAllocated() || !extra->accepting) + q->setEditText(currentText); +} + +void QQuickComboBoxPrivate::updateCurrentValue() +{ + Q_Q(QQuickComboBox); + const QVariant value = q->valueAt(currentIndex); + if (currentValue == value) + return; + + currentValue = value; + emit q->currentValueChanged(); +} + +void QQuickComboBoxPrivate::updateCurrentTextAndValue() +{ + updateCurrentText(); + updateCurrentValue(); +} + +void QQuickComboBoxPrivate::updateAcceptableInput() +{ + Q_Q(QQuickComboBox); + + if (!contentItem) + return; + + const QQuickTextInput *textInputContentItem = qobject_cast<QQuickTextInput *>(contentItem); + + if (!textInputContentItem) + return; + + const bool newValue = textInputContentItem->hasAcceptableInput(); + + if (m_acceptableInput != newValue) { + m_acceptableInput = newValue; + emit q->acceptableInputChanged(); + } +} + +bool QQuickComboBoxPrivate::isValidIndex(int index) const +{ + return delegateModel && index >= 0 && index < delegateModel->count(); +} + +void QQuickComboBoxPrivate::acceptInput() +{ + Q_Q(QQuickComboBox); + int idx = q->find(extra.value().editText, Qt::MatchFixedString); + if (idx > -1) { + // The item that was accepted already exists, so make it the current item. + q->setCurrentIndex(idx); + // After accepting text that matches an existing entry, the selection should be cleared. + QQuickTextInput *input = qobject_cast<QQuickTextInput *>(contentItem); + if (input) { + const auto text = input->text(); + input->select(text.size(), text.size()); + } + } + + extra.value().accepting = true; + emit q->accepted(); + + // The user might have added the item since it didn't exist, so check again + // to see if we can select that new item. + if (idx == -1) + q->setCurrentIndex(q->find(extra.value().editText, Qt::MatchFixedString)); + extra.value().accepting = false; +} + +QString QQuickComboBoxPrivate::tryComplete(const QString &input) +{ + Q_Q(QQuickComboBox); + QString match; + + const int itemCount = q->count(); + for (int idx = 0; idx < itemCount; ++idx) { + const QString text = q->textAt(idx); + if (!text.startsWith(input, Qt::CaseInsensitive)) + continue; + + // either the first or the shortest match + if (match.isEmpty() || text.length() < match.length()) + match = text; + } + + if (match.isEmpty()) + return input; + + return input + match.mid(input.length()); +} + +void QQuickComboBoxPrivate::setCurrentIndex(int index, Activation activate) +{ + Q_Q(QQuickComboBox); + if (currentIndex == index) + return; + + currentIndex = index; + emit q->currentIndexChanged(); + + if (componentComplete) + updateCurrentTextAndValue(); + + if (activate) + emit q->activated(index); +} + +void QQuickComboBoxPrivate::incrementCurrentIndex() +{ + Q_Q(QQuickComboBox); + if (extra.isAllocated()) + extra->allowComplete = false; + if (isPopupVisible()) { + if (highlightedIndex < q->count() - 1) + setHighlightedIndex(highlightedIndex + 1, Highlight); + } else { + if (currentIndex < q->count() - 1) + setCurrentIndex(currentIndex + 1, Activate); + } + if (extra.isAllocated()) + extra->allowComplete = true; +} + +void QQuickComboBoxPrivate::decrementCurrentIndex() +{ + if (extra.isAllocated()) + extra->allowComplete = false; + if (isPopupVisible()) { + if (highlightedIndex > 0) + setHighlightedIndex(highlightedIndex - 1, Highlight); + } else { + if (currentIndex > 0) + setCurrentIndex(currentIndex - 1, Activate); + } + if (extra.isAllocated()) + extra->allowComplete = true; +} + +void QQuickComboBoxPrivate::updateHighlightedIndex() +{ + setHighlightedIndex(popup->isVisible() ? currentIndex : -1, NoHighlight); +} + +void QQuickComboBoxPrivate::setHighlightedIndex(int index, Highlighting highlight) +{ + Q_Q(QQuickComboBox); + if (highlightedIndex == index) + return; + + highlightedIndex = index; + emit q->highlightedIndexChanged(); + + if (highlight) + emit q->highlighted(index); +} + +void QQuickComboBoxPrivate::keySearch(const QString &text) +{ + const int startIndex = isPopupVisible() ? highlightedIndex : currentIndex; + const int index = match(startIndex + 1, text, Qt::MatchStartsWith | Qt::MatchWrap); + if (index != -1) { + if (isPopupVisible()) + setHighlightedIndex(index, Highlight); + else + setCurrentIndex(index, Activate); + } +} + +int QQuickComboBoxPrivate::match(int start, const QString &text, Qt::MatchFlags flags) const +{ + Q_Q(const QQuickComboBox); + uint matchType = flags & 0x0F; + bool wrap = flags & Qt::MatchWrap; + Qt::CaseSensitivity cs = flags & Qt::MatchCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; + QRegularExpression::PatternOptions options = flags & Qt::MatchCaseSensitive ? QRegularExpression::NoPatternOption + : QRegularExpression::CaseInsensitiveOption; + int from = start; + int to = q->count(); + + // iterates twice if wrapping + for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) { + for (int idx = from; idx < to; ++idx) { + QString t = q->textAt(idx); + switch (matchType) { + case Qt::MatchExactly: + if (t == text) + return idx; + break; + case Qt::MatchRegularExpression: { + QRegularExpression rx(QRegularExpression::anchoredPattern(text), options); + if (rx.match(t).hasMatch()) + return idx; + break; + } + case Qt::MatchWildcard: { + QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(text), + options); + if (rx.match(t).hasMatch()) + return idx; + break; + } + case Qt::MatchStartsWith: + if (t.startsWith(text, cs)) + return idx; + break; + case Qt::MatchEndsWith: + if (t.endsWith(text, cs)) + return idx; + break; + case Qt::MatchFixedString: + if (t.compare(text, cs) == 0) + return idx; + break; + case Qt::MatchContains: + default: + if (t.contains(text, cs)) + return idx; + break; + } + } + // prepare for the next iteration + from = 0; + to = start; + } + return -1; +} + +void QQuickComboBoxPrivate::createDelegateModel() +{ + Q_Q(QQuickComboBox); + bool ownedOldModel = ownModel; + QQmlInstanceModel* oldModel = delegateModel; + if (oldModel) { + disconnect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged); + disconnect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::modelUpdated); + disconnect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickComboBoxPrivate::createdItem); + } + + ownModel = false; + delegateModel = model.value<QQmlInstanceModel *>(); + + if (!delegateModel && model.isValid()) { + QQmlDelegateModel *dataModel = new QQuickComboBoxDelegateModel(q); + dataModel->setModel(model); + dataModel->setDelegate(delegate); + if (q->isComponentComplete()) + dataModel->componentComplete(); + + ownModel = true; + delegateModel = dataModel; + } + + if (delegateModel) { + connect(delegateModel, &QQmlInstanceModel::countChanged, this, &QQuickComboBoxPrivate::countChanged); + connect(delegateModel, &QQmlInstanceModel::modelUpdated, this, &QQuickComboBoxPrivate::modelUpdated); + connect(delegateModel, &QQmlInstanceModel::createdItem, this, &QQuickComboBoxPrivate::createdItem); + } + + emit q->delegateModelChanged(); + + if (ownedOldModel) + delete oldModel; +} + +void QQuickComboBoxPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickComboBox); + QQuickControlPrivate::handlePress(point); + q->setPressed(true); +} + +void QQuickComboBoxPrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickComboBox); + QQuickControlPrivate::handleMove(point); + q->setPressed(q->contains(point)); +} + +void QQuickComboBoxPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickComboBox); + QQuickControlPrivate::handleRelease(point); + if (pressed) { + q->setPressed(false); + togglePopup(false); + } +} + +void QQuickComboBoxPrivate::handleUngrab() +{ + Q_Q(QQuickComboBox); + QQuickControlPrivate::handleUngrab(); + q->setPressed(false); +} + +static inline QString indicatorName() { return QStringLiteral("indicator"); } + +void QQuickComboBoxPrivate::cancelIndicator() +{ + Q_Q(QQuickComboBox); + quickCancelDeferred(q, indicatorName()); +} + +void QQuickComboBoxPrivate::executeIndicator(bool complete) +{ + Q_Q(QQuickComboBox); + if (indicator.wasExecuted()) + return; + + if (!indicator || complete) + quickBeginDeferred(q, indicatorName(), indicator); + if (complete) + quickCompleteDeferred(q, indicatorName(), indicator); +} + +static inline QString popupName() { return QStringLiteral("popup"); } + +void QQuickComboBoxPrivate::cancelPopup() +{ + Q_Q(QQuickComboBox); + quickCancelDeferred(q, popupName()); +} + +void QQuickComboBoxPrivate::executePopup(bool complete) +{ + Q_Q(QQuickComboBox); + if (popup.wasExecuted()) + return; + + if (!popup || complete) + quickBeginDeferred(q, popupName(), popup); + if (complete) + quickCompleteDeferred(q, popupName(), popup); +} + +void QQuickComboBoxPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickComboBox); + QQuickControlPrivate::itemImplicitWidthChanged(item); + if (item == indicator) + emit q->implicitIndicatorWidthChanged(); +} + +void QQuickComboBoxPrivate::setInputMethodHints(Qt::InputMethodHints hints, bool force) +{ + Q_Q(QQuickComboBox); + if (!force && hints == q->inputMethodHints()) + return; + + extra.value().inputMethodHints = hints; + emit q->inputMethodHintsChanged(); +} + +void QQuickComboBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickComboBox); + QQuickControlPrivate::itemImplicitHeightChanged(item); + if (item == indicator) + emit q->implicitIndicatorHeightChanged(); +} + +qreal QQuickComboBoxPrivate::getContentWidth() const +{ + if (componentComplete) { + switch (implicitContentWidthPolicy) { + case QQuickComboBox::WidestText: + return calculateWidestTextWidth(); + case QQuickComboBox::WidestTextWhenCompleted: + if (!hasCalculatedWidestText) + return calculateWidestTextWidth(); + break; + default: + break; + } + } + + return QQuickControlPrivate::getContentWidth(); +} + +qreal QQuickComboBoxPrivate::calculateWidestTextWidth() const +{ + Q_Q(const QQuickComboBox); + if (!componentComplete) + return 0; + + const int count = q->count(); + if (count == 0) + return 0; + + auto textInput = qobject_cast<QQuickTextInput*>(contentItem); + if (!textInput) + return 0; + + qCDebug(lcCalculateWidestTextWidth) << "calculating widest text from" << count << "items..."; + + // Avoid the index check and repeated calls to effectiveTextRole() + // that would result from calling textAt() in a loop. + const QString textRole = effectiveTextRole(); + auto textInputPrivate = QQuickTextInputPrivate::get(textInput); + qreal widest = 0; + for (int i = 0; i < count; ++i) { + const QString text = delegateModel->stringValue(i, textRole); + const qreal textImplicitWidth = textInputPrivate->calculateImplicitWidthForText(text); + widest = qMax(widest, textImplicitWidth); + } + + qCDebug(lcCalculateWidestTextWidth) << "... widest text is" << widest; + return widest; +} + +/*! + \internal + + If the user requested it (and we haven't already done it, depending on the policy), + update the implicit content width to the largest text in the model. +*/ +void QQuickComboBoxPrivate::maybeUpdateImplicitContentWidth() +{ + if (!componentComplete) + return; + + if (implicitContentWidthPolicy == QQuickComboBox::ContentItemImplicitWidth + || (implicitContentWidthPolicy == QQuickComboBox::WidestTextWhenCompleted && hasCalculatedWidestText)) + return; + + updateImplicitContentWidth(); + hasCalculatedWidestText = true; +} + +void QQuickComboBoxPrivate::hideOldPopup(QQuickPopup *popup) +{ + if (!popup) + return; + + qCDebug(lcItemManagement) << "hiding old popup" << popup; + + popup->setVisible(false); + popup->setParentItem(nullptr); +#if QT_CONFIG(accessibility) + // Remove the item from the accessibility tree. + QQuickAccessibleAttached *accessible = accessibleAttached(popup); + if (accessible) + accessible->setIgnored(true); +#endif +} + +QQuickComboBox::QQuickComboBox(QQuickItem *parent) + : QQuickControl(*(new QQuickComboBoxPrivate), parent) +{ + setFocusPolicy(Qt::StrongFocus); + setFlag(QQuickItem::ItemIsFocusScope); + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif + Q_D(QQuickComboBox); + d->setInputMethodHints(Qt::ImhNoPredictiveText, true); +} + +QQuickComboBox::~QQuickComboBox() +{ + Q_D(QQuickComboBox); + d->removeImplicitSizeListener(d->indicator); + if (d->popup) { + // Disconnect visibleChanged() to avoid a spurious highlightedIndexChanged() signal + // emission during the destruction of the (visible) popup. (QTBUG-57650) + QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); + QQuickComboBoxPrivate::hideOldPopup(d->popup); + d->popup = nullptr; + } +} + +/*! + \readonly + \qmlproperty int QtQuick.Controls::ComboBox::count + + This property holds the number of items in the combo box. +*/ +int QQuickComboBox::count() const +{ + Q_D(const QQuickComboBox); + return d->delegateModel ? d->delegateModel->count() : 0; +} + +/*! + \qmlproperty model QtQuick.Controls::ComboBox::model + + This property holds the model providing data for the combo box. + + \code + ComboBox { + textRole: "key" + model: ListModel { + ListElement { key: "First"; value: 123 } + ListElement { key: "Second"; value: 456 } + ListElement { key: "Third"; value: 789 } + } + } + \endcode + + \sa textRole, {qml-data-models}{Data Models} +*/ +QVariant QQuickComboBox::model() const +{ + Q_D(const QQuickComboBox); + return d->model; +} + +void QQuickComboBox::setModel(const QVariant& m) +{ + Q_D(QQuickComboBox); + QVariant model = m; + if (model.userType() == qMetaTypeId<QJSValue>()) + model = model.value<QJSValue>().toVariant(); + + if (d->model == model) + return; + + if (QAbstractItemModel* aim = qvariant_cast<QAbstractItemModel *>(d->model)) { + QObjectPrivate::disconnect(aim, &QAbstractItemModel::dataChanged, + d, QOverload<>::of(&QQuickComboBoxPrivate::updateCurrentTextAndValue)); + } + if (QAbstractItemModel* aim = qvariant_cast<QAbstractItemModel *>(model)) { + QObjectPrivate::connect(aim, &QAbstractItemModel::dataChanged, + d, QOverload<>::of(&QQuickComboBoxPrivate::updateCurrentTextAndValue)); + } + + d->model = model; + d->createDelegateModel(); + emit countChanged(); + if (isComponentComplete()) { + setCurrentIndex(count() > 0 ? 0 : -1); + d->updateCurrentTextAndValue(); + } + emit modelChanged(); + + d->maybeUpdateImplicitContentWidth(); +} + +/*! + \internal + \qmlproperty model QtQuick.Controls::ComboBox::delegateModel + + This property holds the model providing delegate instances for the combo box. +*/ +QQmlInstanceModel *QQuickComboBox::delegateModel() const +{ + Q_D(const QQuickComboBox); + return d->delegateModel; +} + + +/*! + \readonly + \qmlproperty bool QtQuick.Controls::ComboBox::pressed + + This property holds whether the combo box button is physically pressed. + A button can be pressed by either touch or key events. + + \sa down +*/ +bool QQuickComboBox::isPressed() const +{ + Q_D(const QQuickComboBox); + return d->pressed; +} + +void QQuickComboBox::setPressed(bool pressed) +{ + Q_D(QQuickComboBox); + if (d->pressed == pressed) + return; + + d->pressed = pressed; + emit pressedChanged(); + + if (!d->hasDown) { + setDown(d->pressed || d->isPopupVisible()); + d->hasDown = false; + } +} + +/*! + \readonly + \qmlproperty int QtQuick.Controls::ComboBox::highlightedIndex + + This property holds the index of the highlighted item in the combo box popup list. + + When a highlighted item is activated, the popup is closed, \l currentIndex + is set to \c highlightedIndex, and the value of this property is reset to + \c -1, as there is no longer a highlighted item. + + \sa highlighted(), currentIndex +*/ +int QQuickComboBox::highlightedIndex() const +{ + Q_D(const QQuickComboBox); + return d->highlightedIndex; +} + +/*! + \qmlproperty int QtQuick.Controls::ComboBox::currentIndex + + This property holds the index of the current item in the combo box. + + The default value is \c -1 when \l count is \c 0, and \c 0 otherwise. + + \sa activated(), currentText, highlightedIndex +*/ +int QQuickComboBox::currentIndex() const +{ + Q_D(const QQuickComboBox); + return d->currentIndex; +} + +void QQuickComboBox::setCurrentIndex(int index) +{ + Q_D(QQuickComboBox); + d->hasCurrentIndex = true; + d->setCurrentIndex(index, NoActivate); +} + +/*! + \readonly + \qmlproperty string QtQuick.Controls::ComboBox::currentText + + This property holds the text of the current item in the combo box. + + \sa currentIndex, displayText, textRole, editText +*/ +QString QQuickComboBox::currentText() const +{ + Q_D(const QQuickComboBox); + return d->currentText; +} + +/*! + \qmlproperty string QtQuick.Controls::ComboBox::displayText + + This property holds the text that is displayed on the combo box button. + + By default, the display text presents the current selection. That is, + it follows the text of the current item. However, the default display + text can be overridden with a custom value. + + \code + ComboBox { + currentIndex: 1 + displayText: "Size: " + currentText + model: ["S", "M", "L"] + } + \endcode + + \sa currentText, textRole +*/ +QString QQuickComboBox::displayText() const +{ + Q_D(const QQuickComboBox); + return d->displayText; +} + +void QQuickComboBox::setDisplayText(const QString &text) +{ + Q_D(QQuickComboBox); + d->hasDisplayText = true; + if (d->displayText == text) + return; + + d->displayText = text; + maybeSetAccessibleName(text); + emit displayTextChanged(); +} + +void QQuickComboBox::resetDisplayText() +{ + Q_D(QQuickComboBox); + if (!d->hasDisplayText) + return; + + d->hasDisplayText = false; + d->updateCurrentText(); +} + + +/*! + \qmlproperty string QtQuick.Controls::ComboBox::textRole + + This property holds the model role used for populating the combo box. + + When the model has multiple roles, \c textRole can be set to determine + which role should be displayed. + + \sa model, currentText, displayText, {ComboBox Model Roles} +*/ +QString QQuickComboBox::textRole() const +{ + Q_D(const QQuickComboBox); + return d->textRole; +} + +void QQuickComboBox::setTextRole(const QString &role) +{ + Q_D(QQuickComboBox); + if (d->textRole == role) + return; + + d->textRole = role; + if (isComponentComplete()) + d->updateCurrentText(); + emit textRoleChanged(); +} + +/*! + \since QtQuick.Controls 2.14 (Qt 5.14) + \qmlproperty string QtQuick.Controls::ComboBox::valueRole + + This property holds the model role used for storing the value associated + with each item in the model. + + For an example of how to use this property, see \l {ComboBox Model Roles}. + + \sa model, currentValue +*/ +QString QQuickComboBox::valueRole() const +{ + Q_D(const QQuickComboBox); + return d->valueRole; +} + +void QQuickComboBox::setValueRole(const QString &role) +{ + Q_D(QQuickComboBox); + if (d->valueRole == role) + return; + + d->valueRole = role; + if (isComponentComplete()) + d->updateCurrentValue(); + emit valueRoleChanged(); +} + +/*! + \qmlproperty Component QtQuick.Controls::ComboBox::delegate + + This property holds a delegate that presents an item in the combo box popup. + + It is recommended to use \l ItemDelegate (or any other \l AbstractButton + derivatives) as the delegate. This ensures that the interaction works as + expected, and the popup will automatically close when appropriate. When + other types are used as the delegate, the popup must be closed manually. + For example, if \l MouseArea is used: + + \code + delegate: Rectangle { + // ... + MouseArea { + // ... + onClicked: comboBox.popup.close() + } + } + \endcode + + \sa ItemDelegate, {Customizing ComboBox} +*/ +QQmlComponent *QQuickComboBox::delegate() const +{ + Q_D(const QQuickComboBox); + return d->delegate; +} + +void QQuickComboBox::setDelegate(QQmlComponent* delegate) +{ + Q_D(QQuickComboBox); + if (d->delegate == delegate) + return; + + delete d->delegate; + d->delegate = delegate; + QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(d->delegateModel); + if (delegateModel) + delegateModel->setDelegate(d->delegate); + emit delegateChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::ComboBox::indicator + + This property holds the drop indicator item. + + \sa {Customizing ComboBox} +*/ +QQuickItem *QQuickComboBox::indicator() const +{ + QQuickComboBoxPrivate *d = const_cast<QQuickComboBoxPrivate *>(d_func()); + if (!d->indicator) + d->executeIndicator(); + return d->indicator; +} + +void QQuickComboBox::setIndicator(QQuickItem *indicator) +{ + Q_D(QQuickComboBox); + if (d->indicator == indicator) + return; + + if (!d->indicator.isExecuting()) + d->cancelIndicator(); + + const qreal oldImplicitIndicatorWidth = implicitIndicatorWidth(); + const qreal oldImplicitIndicatorHeight = implicitIndicatorHeight(); + + d->removeImplicitSizeListener(d->indicator); + QQuickControlPrivate::hideOldItem(d->indicator); + d->indicator = indicator; + if (indicator) { + if (!indicator->parentItem()) + indicator->setParentItem(this); + d->addImplicitSizeListener(indicator); + } + + if (!qFuzzyCompare(oldImplicitIndicatorWidth, implicitIndicatorWidth())) + emit implicitIndicatorWidthChanged(); + if (!qFuzzyCompare(oldImplicitIndicatorHeight, implicitIndicatorHeight())) + emit implicitIndicatorHeightChanged(); + if (!d->indicator.isExecuting()) + emit indicatorChanged(); +} + +/*! + \qmlproperty Popup QtQuick.Controls::ComboBox::popup + + This property holds the popup. + + The popup can be opened or closed manually, if necessary: + + \code + onSpecialEvent: comboBox.popup.close() + \endcode + + \sa {Customizing ComboBox} +*/ +QQuickPopup *QQuickComboBox::popup() const +{ + QQuickComboBoxPrivate *d = const_cast<QQuickComboBoxPrivate *>(d_func()); + if (!d->popup) + d->executePopup(isComponentComplete()); + return d->popup; +} + +void QQuickComboBox::setPopup(QQuickPopup *popup) +{ + Q_D(QQuickComboBox); + if (d->popup == popup) + return; + + if (!d->popup.isExecuting()) + d->cancelPopup(); + + if (d->popup) { + QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); + QQuickComboBoxPrivate::hideOldPopup(d->popup); + } + if (popup) { + QQuickPopupPrivate::get(popup)->allowVerticalFlip = true; + popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent); + QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); + + if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>()) + itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange); + } + d->popup = popup; + if (!d->popup.isExecuting()) + emit popupChanged(); +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlproperty bool QtQuick.Controls::ComboBox::flat + + This property holds whether the combo box button is flat. + + A flat combo box button does not draw a background unless it is interacted + with. In comparison to normal combo boxes, flat combo boxes provide looks + that make them stand out less from the rest of the UI. For instance, when + placing a combo box into a tool bar, it may be desirable to make the combo + box flat so it matches better with the flat looks of tool buttons. + + The default value is \c false. +*/ +bool QQuickComboBox::isFlat() const +{ + Q_D(const QQuickComboBox); + return d->flat; +} + +void QQuickComboBox::setFlat(bool flat) +{ + Q_D(QQuickComboBox); + if (d->flat == flat) + return; + + d->flat = flat; + emit flatChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::ComboBox::down + + This property holds whether the combo box button is visually down. + + Unless explicitly set, this property is \c true when either \c pressed + or \c popup.visible is \c true. To return to the default value, set this + property to \c undefined. + + \sa pressed, popup +*/ +bool QQuickComboBox::isDown() const +{ + Q_D(const QQuickComboBox); + return d->down; +} + +void QQuickComboBox::setDown(bool down) +{ + Q_D(QQuickComboBox); + d->hasDown = true; + + if (d->down == down) + return; + + d->down = down; + emit downChanged(); +} + +void QQuickComboBox::resetDown() +{ + Q_D(QQuickComboBox); + if (!d->hasDown) + return; + + setDown(d->pressed || d->isPopupVisible()); + d->hasDown = false; +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::ComboBox::editable + + This property holds whether the combo box is editable. + + The default value is \c false. + + \sa validator +*/ +bool QQuickComboBox::isEditable() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() && d->extra->editable; +} + +void QQuickComboBox::setEditable(bool editable) +{ + Q_D(QQuickComboBox); + if (editable == isEditable()) + return; + + if (d->contentItem) { + if (editable) { + d->contentItem->installEventFilter(this); + if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem)) { + QObjectPrivate::connect(input, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); + QObjectPrivate::connect(input, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); + } +#if QT_CONFIG(cursor) + d->contentItem->setCursor(Qt::IBeamCursor); +#endif + } else { + d->contentItem->removeEventFilter(this); + if (QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem)) { + QObjectPrivate::disconnect(input, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); + QObjectPrivate::disconnect(input, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); + } +#if QT_CONFIG(cursor) + d->contentItem->unsetCursor(); +#endif + } + } + + d->extra.value().editable = editable; + setAccessibleProperty("editable", editable); + emit editableChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty string QtQuick.Controls::ComboBox::editText + + This property holds the text in the text field of an editable combo box. + + \sa editable, currentText, displayText +*/ +QString QQuickComboBox::editText() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() ? d->extra->editText : QString(); +} + +void QQuickComboBox::setEditText(const QString &text) +{ + Q_D(QQuickComboBox); + if (text == editText()) + return; + + d->extra.value().editText = text; + emit editTextChanged(); +} + +void QQuickComboBox::resetEditText() +{ + setEditText(QString()); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty Validator QtQuick.Controls::ComboBox::validator + + This property holds an input text validator for an editable combo box. + + When a validator is set, the text field will only accept input which + leaves the text property in an intermediate state. The \l accepted signal + will only be emitted if the text is in an acceptable state when the + \uicontrol Return or \uicontrol Enter key is pressed. + + The currently supported validators are \l[QtQuick]{IntValidator}, + \l[QtQuick]{DoubleValidator}, and \l[QtQuick]{RegularExpressionValidator}. An + example of using validators is shown below, which allows input of + integers between \c 0 and \c 10 into the text field: + + \code + ComboBox { + model: 10 + editable: true + validator: IntValidator { + top: 9 + bottom: 0 + } + } + \endcode + + \sa acceptableInput, accepted, editable +*/ +QValidator *QQuickComboBox::validator() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() ? d->extra->validator : nullptr; +} + +void QQuickComboBox::setValidator(QValidator *validator) +{ + Q_D(QQuickComboBox); + if (validator == QQuickComboBox::validator()) + return; + + d->extra.value().validator = validator; +#if QT_CONFIG(validator) + if (validator) + validator->setLocale(d->locale); +#endif + emit validatorChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty flags QtQuick.Controls::ComboBox::inputMethodHints + + Provides hints to the input method about the expected content of the combo box and how it + should operate. + + The default value is \c Qt.ImhNoPredictiveText. + + \include inputmethodhints.qdocinc +*/ +Qt::InputMethodHints QQuickComboBox::inputMethodHints() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() ? d->extra->inputMethodHints : Qt::ImhNoPredictiveText; +} + +void QQuickComboBox::setInputMethodHints(Qt::InputMethodHints hints) +{ + Q_D(QQuickComboBox); + d->setInputMethodHints(hints); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::ComboBox::inputMethodComposing + \readonly + + This property holds whether an editable combo box has partial text input from an input method. + + While it is composing, an input method may rely on mouse or key events from the combo box to + edit or commit the partial text. This property can be used to determine when to disable event + handlers that may interfere with the correct operation of an input method. +*/ +bool QQuickComboBox::isInputMethodComposing() const +{ + Q_D(const QQuickComboBox); + return d->contentItem && d->contentItem->property("inputMethodComposing").toBool(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::ComboBox::acceptableInput + \readonly + + This property holds whether the combo box contains acceptable text in the editable text field. + + If a validator has been set, the value is \c true only if the current text is acceptable + to the validator as a final string (not as an intermediate string). + + \sa validator, accepted +*/ +bool QQuickComboBox::hasAcceptableInput() const +{ + Q_D(const QQuickComboBox); + return d->m_acceptableInput; +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::ComboBox::implicitIndicatorWidth + \readonly + + This property holds the implicit indicator width. + + The value is equal to \c {indicator ? indicator.implicitWidth : 0}. + + This is typically used, together with \l {Control::}{implicitContentWidth} and + \l {Control::}{implicitBackgroundWidth}, to calculate the \l {Item::}{implicitWidth}. + + \sa implicitIndicatorHeight +*/ +qreal QQuickComboBox::implicitIndicatorWidth() const +{ + Q_D(const QQuickComboBox); + if (!d->indicator) + return 0; + return d->indicator->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::ComboBox::implicitIndicatorHeight + \readonly + + This property holds the implicit indicator height. + + The value is equal to \c {indicator ? indicator.implicitHeight : 0}. + + This is typically used, together with \l {Control::}{implicitContentHeight} and + \l {Control::}{implicitBackgroundHeight}, to calculate the \l {Item::}{implicitHeight}. + + \sa implicitIndicatorWidth +*/ +qreal QQuickComboBox::implicitIndicatorHeight() const +{ + Q_D(const QQuickComboBox); + if (!d->indicator) + return 0; + return d->indicator->implicitHeight(); +} + +/*! + \readonly + \since QtQuick.Controls 2.14 (Qt 5.14) + \qmlproperty var QtQuick.Controls::ComboBox::currentValue + + This property holds the value of the current item in the combo box. + + For an example of how to use this property, see \l {ComboBox Model Roles}. + + \sa currentIndex, currentText, valueRole +*/ +QVariant QQuickComboBox::currentValue() const +{ + Q_D(const QQuickComboBox); + return d->currentValue; +} + +/*! + \readonly + \since QtQuick.Controls 2.14 (Qt 5.14) + \qmlmethod var QtQuick.Controls::ComboBox::valueAt(int index) + + Returns the value at position \a index in the combo box. + + \sa indexOfValue +*/ +QVariant QQuickComboBox::valueAt(int index) const +{ + Q_D(const QQuickComboBox); + if (!d->isValidIndex(index)) + return QVariant(); + + const QString effectiveValueRole = d->valueRole.isEmpty() ? QStringLiteral("modelData") : d->valueRole; + return d->delegateModel->variantValue(index, effectiveValueRole); +} + +/*! + \since QtQuick.Controls 2.14 (Qt 5.14) + \qmlmethod int QtQuick.Controls::ComboBox::indexOfValue(object value) + + Returns the index of the specified \a value, or \c -1 if no match is found. + + For an example of how to use this method, see \l {ComboBox Model Roles}. + + \include qquickcombobox.qdocinc functions-after-component-completion + + \sa find(), currentValue, currentIndex, valueRole, valueAt +*/ +int QQuickComboBox::indexOfValue(const QVariant &value) const +{ + for (int i = 0; i < count(); ++i) { + const QVariant ourValue = valueAt(i); + if (value == ourValue) + return i; + } + return -1; +} + +/*! + \since QtQuick.Controls 2.15 (Qt 5.15) + \qmlproperty bool QtQuick.Controls::ComboBox::selectTextByMouse + + This property holds whether the text field for an editable ComboBox + can be selected with the mouse. + + The default value is \c false. +*/ +bool QQuickComboBox::selectTextByMouse() const +{ + Q_D(const QQuickComboBox); + return d->extra.isAllocated() ? d->extra->selectTextByMouse : false; +} + +void QQuickComboBox::setSelectTextByMouse(bool canSelect) +{ + Q_D(QQuickComboBox); + if (canSelect == selectTextByMouse()) + return; + + d->extra.value().selectTextByMouse = canSelect; + emit selectTextByMouseChanged(); +} + +/*! + \since QtQuick.Controls 6.0 (Qt 6.0) + \qmlproperty enumeration QtQuick.Controls::ComboBox::implicitContentWidthPolicy + + This property controls how the \l{Control::}{implicitContentWidth} of the ComboBox is + calculated. + + When the width of a ComboBox is not large enough to display text, that text + is elided. Depending on which parts of the text are elided, this can make + selecting an item difficult for the end user. An efficient way of ensuring + that a ComboBox is wide enough to avoid text being elided is to set a width + that is known to be large enough: + + \code + width: 300 + implicitContentWidthPolicy: ComboBox.ContentItemImplicitWidth + \endcode + + However, it is often not possible to know whether or not a hard-coded value + will be large enough, as the size of text depends on many factors, such as + font family, font size, translations, and so on. + + implicitContentWidthPolicy provides an easy way to control how the + implicitContentWidth is calculated, which in turn affects the + \l{Item::}{implicitWidth} of the ComboBox and ensures that text will not be elided. + + The available values are: + + \value ContentItemImplicitWidth + The implicitContentWidth will default to that of the \l{Control::}{contentItem}. + This is the most efficient option, as no extra text layout is done. + \value WidestText + The implicitContentWidth will be set to the implicit width of the + the largest text for the given \l textRole every time the model + changes. + This option should be used with smaller models, as it can be expensive. + \value WidestTextWhenCompleted + The implicitContentWidth will be set to the implicit width of the + the largest text for the given \l textRole once after + \l {QQmlParserStatus::componentComplete()}{component completion}. + This option should be used with smaller models, as it can be expensive. + + The default value is \c ContentItemImplicitWidth. + + As this property only affects the \c implicitWidth of the ComboBox, setting + an explicit \l{Item::}{width} can still result in eliding. + + \note This feature requires the contentItem to be a type derived from + \l TextInput. + + \note This feature requires text to be laid out, and can therefore be + expensive for large models or models whose contents are updated + frequently. +*/ +QQuickComboBox::ImplicitContentWidthPolicy QQuickComboBox::implicitContentWidthPolicy() const +{ + Q_D(const QQuickComboBox); + return d->implicitContentWidthPolicy; +} + +void QQuickComboBox::setImplicitContentWidthPolicy(QQuickComboBox::ImplicitContentWidthPolicy policy) +{ + Q_D(QQuickComboBox); + if (policy == d->implicitContentWidthPolicy) + return; + + d->implicitContentWidthPolicy = policy; + d->maybeUpdateImplicitContentWidth(); + emit implicitContentWidthPolicyChanged(); +} +/*! + \qmlmethod string QtQuick.Controls::ComboBox::textAt(int index) + + Returns the text for the specified \a index, or an empty string + if the index is out of bounds. + + \include qquickcombobox.qdocinc functions-after-component-completion + For example: + \snippet qtquickcontrols2-combobox-textat.qml textat + + \sa textRole +*/ +QString QQuickComboBox::textAt(int index) const +{ + Q_D(const QQuickComboBox); + if (!d->isValidIndex(index)) + return QString(); + + return d->delegateModel->stringValue(index, d->effectiveTextRole()); +} + +/*! + \qmlmethod int QtQuick.Controls::ComboBox::find(string text, enumeration flags) + + Returns the index of the specified \a text, or \c -1 if no match is found. + + The way the search is performed is defined by the specified match \a flags. By default, + combo box performs case sensitive exact matching (\c Qt.MatchExactly). All other match + types are case-insensitive unless the \c Qt.MatchCaseSensitive flag is also specified. + + \value Qt.MatchExactly The search term matches exactly (default). + \value Qt.MatchRegularExpression The search term matches as a regular expression. + \value Qt.MatchWildcard The search term matches using wildcards. + \value Qt.MatchFixedString The search term matches as a fixed string. + \value Qt.MatchStartsWith The search term matches the start of the item. + \value Qt.MatchEndsWidth The search term matches the end of the item. + \value Qt.MatchContains The search term is contained in the item. + \value Qt.MatchCaseSensitive The search is case sensitive. + + \include qquickcombobox.qdocinc functions-after-component-completion + For example: + \snippet qtquickcontrols2-combobox-find.qml find + + \sa textRole +*/ +int QQuickComboBox::find(const QString &text, Qt::MatchFlags flags) const +{ + Q_D(const QQuickComboBox); + return d->match(0, text, flags); +} + +/*! + \qmlmethod void QtQuick.Controls::ComboBox::incrementCurrentIndex() + + Increments the current index of the combo box, or the highlighted + index if the popup list is visible. + + \sa currentIndex, highlightedIndex +*/ +void QQuickComboBox::incrementCurrentIndex() +{ + Q_D(QQuickComboBox); + d->incrementCurrentIndex(); +} + +/*! + \qmlmethod void QtQuick.Controls::ComboBox::decrementCurrentIndex() + + Decrements the current index of the combo box, or the highlighted + index if the popup list is visible. + + \sa currentIndex, highlightedIndex +*/ +void QQuickComboBox::decrementCurrentIndex() +{ + Q_D(QQuickComboBox); + d->decrementCurrentIndex(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlmethod void QtQuick.Controls::ComboBox::selectAll() + + Selects all the text in the editable text field of the combo box. + + \sa editText +*/ +void QQuickComboBox::selectAll() +{ + Q_D(QQuickComboBox); + QQuickTextInput *input = qobject_cast<QQuickTextInput *>(d->contentItem); + if (!input) + return; + input->selectAll(); +} + +bool QQuickComboBox::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QQuickComboBox); + switch (event->type()) { + case QEvent::MouseButtonRelease: + if (d->isPopupVisible()) + d->hidePopup(false); + break; + case QEvent::KeyPress: { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if (d->filterKeyEvent(ke, false)) + return true; + event->accept(); + if (d->extra.isAllocated()) + d->extra->allowComplete = ke->key() != Qt::Key_Backspace && ke->key() != Qt::Key_Delete; + break; + } + case QEvent::FocusOut: + if (qGuiApp->focusObject() != this && (!d->popup || !d->popup->hasActiveFocus())) { + // Only close the popup if focus was transferred somewhere else + // than to the popup or the popup button (which normally means that + // the user clicked on the popup button to open it, not close it). + d->hidePopup(false); + setPressed(false); + + // The focus left the text field, so if the edit text matches an item in the model, + // change our currentIndex to that. This matches widgets' behavior. + const int indexForEditText = find(d->extra.value().editText, Qt::MatchFixedString); + if (indexForEditText > -1) + setCurrentIndex(indexForEditText); + } + break; +#if QT_CONFIG(im) + case QEvent::InputMethod: + if (d->extra.isAllocated()) + d->extra->allowComplete = !static_cast<QInputMethodEvent*>(event)->commitString().isEmpty(); + break; +#endif + default: + break; + } + return QQuickControl::eventFilter(object, event); +} + +void QQuickComboBox::focusInEvent(QFocusEvent *event) +{ + Q_D(QQuickComboBox); + QQuickControl::focusInEvent(event); + // Setting focus on TextField should not be done when drop down indicator was clicked + // That is why, if focus is not set with key reason, it should not be passed to textEdit by default. + // Focus on Edit Text should be set only intentionally by user. + if ((event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason || + event->reason() == Qt::ShortcutFocusReason) && d->contentItem && isEditable()) + d->contentItem->forceActiveFocus(event->reason()); +} + +void QQuickComboBox::focusOutEvent(QFocusEvent *event) +{ + Q_D(QQuickComboBox); + QQuickControl::focusOutEvent(event); + + if (qGuiApp->focusObject() != d->contentItem && (!d->popup || !d->popup->hasActiveFocus())) { + // Only close the popup if focus was transferred + // somewhere else than to the popup or the inner line edit (which is + // normally done from QQuickComboBox::focusInEvent). + d->hidePopup(false); + setPressed(false); + } +} + +#if QT_CONFIG(im) +void QQuickComboBox::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QQuickComboBox); + QQuickControl::inputMethodEvent(event); + if (!isEditable() && !event->commitString().isEmpty()) + d->keySearch(event->commitString()); + else + event->ignore(); +} +#endif + +void QQuickComboBox::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickComboBox); + QQuickControl::keyPressEvent(event); + + switch (event->key()) { + case Qt::Key_Escape: + case Qt::Key_Back: + if (d->isPopupVisible()) + event->accept(); + break; + case Qt::Key_Space: + if (!event->isAutoRepeat()) + setPressed(true); + event->accept(); + break; + case Qt::Key_Enter: + case Qt::Key_Return: + if (d->isPopupVisible()) + setPressed(true); + event->accept(); + break; + case Qt::Key_Up: + d->keyNavigating = true; + d->decrementCurrentIndex(); + event->accept(); + break; + case Qt::Key_Down: + d->keyNavigating = true; + d->incrementCurrentIndex(); + event->accept(); + break; + case Qt::Key_Home: + d->keyNavigating = true; + if (d->isPopupVisible()) + d->setHighlightedIndex(0, Highlight); + else + d->setCurrentIndex(0, Activate); + event->accept(); + break; + case Qt::Key_End: + d->keyNavigating = true; + if (d->isPopupVisible()) + d->setHighlightedIndex(count() - 1, Highlight); + else + d->setCurrentIndex(count() - 1, Activate); + event->accept(); + break; + default: + if (!isEditable() && !event->text().isEmpty()) + d->keySearch(event->text()); + else + event->ignore(); + break; + } +} + +void QQuickComboBox::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QQuickComboBox); + QQuickControl::keyReleaseEvent(event); + d->keyNavigating = false; + if (event->isAutoRepeat()) + return; + + switch (event->key()) { + case Qt::Key_Space: + if (!isEditable()) + d->togglePopup(true); + setPressed(false); + event->accept(); + break; + case Qt::Key_Enter: + case Qt::Key_Return: + if (!isEditable() || d->isPopupVisible()) + d->hidePopup(d->isPopupVisible()); + setPressed(false); + event->accept(); + break; + case Qt::Key_Escape: + case Qt::Key_Back: + if (d->isPopupVisible()) { + d->hidePopup(false); + setPressed(false); + event->accept(); + } + break; + default: + break; + } +} + +#if QT_CONFIG(wheelevent) +void QQuickComboBox::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickComboBox); + QQuickControl::wheelEvent(event); + if (d->wheelEnabled && !d->isPopupVisible()) { + if (event->angleDelta().y() > 0) + d->decrementCurrentIndex(); + else + d->incrementCurrentIndex(); + } +} +#endif + +bool QQuickComboBox::event(QEvent *e) +{ + Q_D(QQuickComboBox); + if (e->type() == QEvent::LanguageChange) + d->updateCurrentTextAndValue(); + return QQuickControl::event(e); +} + +void QQuickComboBox::componentComplete() +{ + Q_D(QQuickComboBox); + d->executeIndicator(true); + QQuickControl::componentComplete(); + if (d->popup) + d->executePopup(true); + + if (d->delegateModel && d->ownModel) + static_cast<QQmlDelegateModel *>(d->delegateModel)->componentComplete(); + + if (count() > 0) { + if (!d->hasCurrentIndex && d->currentIndex == -1) + setCurrentIndex(0); + else + d->updateCurrentTextAndValue(); + + // If the widest text was already calculated in the call to + // QQmlDelegateModel::componentComplete() above, then we shouldn't do it here too. + if (!d->hasCalculatedWidestText) + d->maybeUpdateImplicitContentWidth(); + } +} + +void QQuickComboBox::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +{ + Q_D(QQuickComboBox); + QQuickControl::itemChange(change, value); + if (change == ItemVisibleHasChanged && !value.boolValue) { + d->hidePopup(false); + setPressed(false); + } +} + +void QQuickComboBox::fontChange(const QFont &newFont, const QFont &oldFont) +{ + Q_D(QQuickComboBox); + QQuickControl::fontChange(newFont, oldFont); + d->maybeUpdateImplicitContentWidth(); +} + +void QQuickComboBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickComboBox); + if (oldItem) { + oldItem->removeEventFilter(this); + if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) { + QObjectPrivate::disconnect(oldInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); + QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); + disconnect(oldInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged); + QObjectPrivate::disconnect(oldInput, &QQuickTextInput::acceptableInputChanged, d, &QQuickComboBoxPrivate::updateAcceptableInput); + } + } + if (newItem && isEditable()) { + newItem->installEventFilter(this); + if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) { + QObjectPrivate::connect(newInput, &QQuickTextInput::accepted, d, &QQuickComboBoxPrivate::acceptInput); + QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d, &QQuickComboBoxPrivate::updateEditText); + connect(newInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickComboBox::inputMethodComposingChanged); + QObjectPrivate::connect(newInput, &QQuickTextInput::acceptableInputChanged, d, &QQuickComboBoxPrivate::updateAcceptableInput); + } +#if QT_CONFIG(cursor) + newItem->setCursor(Qt::IBeamCursor); +#endif + } + + d->updateAcceptableInput(); +} + +void QQuickComboBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale) +{ + QQuickControl::localeChange(newLocale, oldLocale); +#if QT_CONFIG(validator) + if (QValidator *v = validator()) + v->setLocale(newLocale); +#endif +} + +QFont QQuickComboBox::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ComboBox); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickComboBox::accessibleRole() const +{ + return QAccessible::ComboBox; +} + +void QQuickComboBox::accessibilityActiveChanged(bool active) +{ + Q_D(QQuickComboBox); + QQuickControl::accessibilityActiveChanged(active); + + if (active) { + maybeSetAccessibleName(d->hasDisplayText ? d->displayText : d->currentText); + setAccessibleProperty("editable", isEditable()); + } +} +#endif // + +QT_END_NAMESPACE + +#include "moc_qquickcombobox_p.cpp" diff --git a/src/quicktemplates2/qquickcombobox_p.h b/src/quicktemplates2/qquickcombobox_p.h new file mode 100644 index 0000000000..71dd836603 --- /dev/null +++ b/src/quicktemplates2/qquickcombobox_p.h @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCOMBOBOX_P_H +#define QQUICKCOMBOBOX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qloggingcategory.h> +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcItemManagement) + +class QValidator; +class QQuickPopup; +class QQmlInstanceModel; +class QQuickComboBoxPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickComboBox : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged FINAL) + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged FINAL) + Q_PROPERTY(QQmlInstanceModel *delegateModel READ delegateModel NOTIFY delegateModelChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) + Q_PROPERTY(int highlightedIndex READ highlightedIndex NOTIFY highlightedIndexChanged FINAL) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL) + Q_PROPERTY(QString currentText READ currentText NOTIFY currentTextChanged FINAL) + Q_PROPERTY(QString displayText READ displayText WRITE setDisplayText RESET resetDisplayText NOTIFY displayTextChanged FINAL) + Q_PROPERTY(QString textRole READ textRole WRITE setTextRole NOTIFY textRoleChanged FINAL) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) + Q_PROPERTY(QQuickItem *indicator READ indicator WRITE setIndicator NOTIFY indicatorChanged FINAL) + Q_PROPERTY(QQuickPopup *popup READ popup WRITE setPopup NOTIFY popupChanged FINAL) + // 2.1 (Qt 5.8) + Q_PROPERTY(bool flat READ isFlat WRITE setFlat NOTIFY flatChanged FINAL REVISION(2, 1)) + // 2.2 (Qt 5.9) + Q_PROPERTY(bool down READ isDown WRITE setDown RESET resetDown NOTIFY downChanged FINAL REVISION(2, 2)) + Q_PROPERTY(bool editable READ isEditable WRITE setEditable NOTIFY editableChanged FINAL REVISION(2, 2)) + Q_PROPERTY(QString editText READ editText WRITE setEditText RESET resetEditText NOTIFY editTextChanged FINAL REVISION(2, 2)) + Q_PROPERTY(QValidator *validator READ validator WRITE setValidator NOTIFY validatorChanged FINAL REVISION(2, 2)) + Q_PROPERTY(Qt::InputMethodHints inputMethodHints READ inputMethodHints WRITE setInputMethodHints NOTIFY inputMethodHintsChanged FINAL REVISION(2, 2)) + Q_PROPERTY(bool inputMethodComposing READ isInputMethodComposing NOTIFY inputMethodComposingChanged FINAL REVISION(2, 2)) + Q_PROPERTY(bool acceptableInput READ hasAcceptableInput NOTIFY acceptableInputChanged FINAL REVISION(2, 2)) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal implicitIndicatorWidth READ implicitIndicatorWidth NOTIFY implicitIndicatorWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitIndicatorHeight READ implicitIndicatorHeight NOTIFY implicitIndicatorHeightChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background,contentItem,indicator,popup") + // 2.14 (Qt 5.14) + Q_PROPERTY(QVariant currentValue READ currentValue NOTIFY currentValueChanged FINAL REVISION(2, 14)) + Q_PROPERTY(QString valueRole READ valueRole WRITE setValueRole NOTIFY valueRoleChanged FINAL REVISION(2, 14)) + // 2.15 (Qt 5.15) + Q_PROPERTY(bool selectTextByMouse READ selectTextByMouse WRITE setSelectTextByMouse NOTIFY selectTextByMouseChanged FINAL REVISION(2, 15)) + // 6.0 (Qt 6.0) + Q_PROPERTY(ImplicitContentWidthPolicy implicitContentWidthPolicy READ implicitContentWidthPolicy + WRITE setImplicitContentWidthPolicy NOTIFY implicitContentWidthPolicyChanged FINAL REVISION(6, 0)) + QML_NAMED_ELEMENT(ComboBox) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickComboBox(QQuickItem *parent = nullptr); + ~QQuickComboBox(); + + int count() const; + + QVariant model() const; + void setModel(const QVariant &model); + QQmlInstanceModel *delegateModel() const; + + bool isPressed() const; + void setPressed(bool pressed); + + int highlightedIndex() const; + + int currentIndex() const; + void setCurrentIndex(int index); + + QString currentText() const; + + QString displayText() const; + void setDisplayText(const QString &text); + void resetDisplayText(); + + QString textRole() const; + void setTextRole(const QString &role); + + QString valueRole() const; + void setValueRole(const QString &role); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + QQuickItem *indicator() const; + void setIndicator(QQuickItem *indicator); + + QQuickPopup *popup() const; + void setPopup(QQuickPopup *popup); + + Q_INVOKABLE QString textAt(int index) const; + Q_INVOKABLE int find(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly) const; + + // 2.1 (Qt 5.8) + bool isFlat() const; + void setFlat(bool flat); + + // 2.2 (Qt 5.9) + bool isDown() const; + void setDown(bool down); + void resetDown(); + + bool isEditable() const; + void setEditable(bool editable); + + QString editText() const; + void setEditText(const QString &text); + void resetEditText(); + + QValidator *validator() const; + void setValidator(QValidator *validator); + + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints(Qt::InputMethodHints hints); + + bool isInputMethodComposing() const; + bool hasAcceptableInput() const; + + // 2.5 (Qt 5.12) + qreal implicitIndicatorWidth() const; + qreal implicitIndicatorHeight() const; + + // 2.14 (Qt 5.14) + QVariant currentValue() const; + Q_REVISION(2, 14) Q_INVOKABLE QVariant valueAt(int index) const; + Q_REVISION(2, 14) Q_INVOKABLE int indexOfValue(const QVariant &value) const; + + // 2.15 (Qt 5.15) + bool selectTextByMouse() const; + void setSelectTextByMouse(bool canSelect); + + // 6.0 (Qt 6.0) + enum ImplicitContentWidthPolicy { + ContentItemImplicitWidth, + WidestText, + WidestTextWhenCompleted + }; + Q_ENUM(ImplicitContentWidthPolicy) + + ImplicitContentWidthPolicy implicitContentWidthPolicy() const; + void setImplicitContentWidthPolicy(ImplicitContentWidthPolicy policy); + +public Q_SLOTS: + void incrementCurrentIndex(); + void decrementCurrentIndex(); + Q_REVISION(2, 2) void selectAll(); + +Q_SIGNALS: + void activated(int index); + void highlighted(int index); + void countChanged(); + void modelChanged(); + void delegateModelChanged(); + void pressedChanged(); + void highlightedIndexChanged(); + void currentIndexChanged(); + void currentTextChanged(); + void displayTextChanged(); + void textRoleChanged(); + void delegateChanged(); + void indicatorChanged(); + void popupChanged(); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void flatChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void accepted(); + Q_REVISION(2, 2) void downChanged(); + Q_REVISION(2, 2) void editableChanged(); + Q_REVISION(2, 2) void editTextChanged(); + Q_REVISION(2, 2) void validatorChanged(); + Q_REVISION(2, 2) void inputMethodHintsChanged(); + Q_REVISION(2, 2) void inputMethodComposingChanged(); + Q_REVISION(2, 2) void acceptableInputChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void implicitIndicatorWidthChanged(); + Q_REVISION(2, 5) void implicitIndicatorHeightChanged(); + // 2.14 (Qt 5.14) + Q_REVISION(2, 14) void valueRoleChanged(); + Q_REVISION(2, 14) void currentValueChanged(); + // 2.15 (Qt 5.15) + Q_REVISION(2, 15) void selectTextByMouseChanged(); + // 6.0 (Qt 6.0) + Q_REVISION(6, 0) void implicitContentWidthPolicyChanged(); + +protected: + bool eventFilter(QObject *object, QEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; +#if QT_CONFIG(im) + void inputMethodEvent(QInputMethodEvent *event) override; +#endif + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + bool event(QEvent *e) override; + + void componentComplete() override; + void itemChange(ItemChange change, const ItemChangeData &value) override; + void fontChange(const QFont &newFont, const QFont &oldFont) override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + void localeChange(const QLocale &newLocale, const QLocale &oldLocale) override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; + void accessibilityActiveChanged(bool active) override; +#endif + +private: + Q_DISABLE_COPY(QQuickComboBox) + Q_DECLARE_PRIVATE(QQuickComboBox) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickComboBox) + +#endif // QQUICKCOMBOBOX_P_H diff --git a/src/quicktemplates2/qquickcontainer.cpp b/src/quicktemplates2/qquickcontainer.cpp new file mode 100644 index 0000000000..e190c49470 --- /dev/null +++ b/src/quicktemplates2/qquickcontainer.cpp @@ -0,0 +1,913 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcontainer_p.h" +#include "qquickcontainer_p_p.h" + +#include <QtQuick/private/qquickflickable_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Container + \inherits Control +//! \instantiates QQuickContainer + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-containers + \brief Abstract base type providing functionality common to containers. + + Container is the base type of container-like user interface controls that + allow dynamic insertion and removal of items. + + \section2 Using Containers + + Typically, items are statically declared as children of Container, but it + is also possible to \l {addItem}{add}, \l {insertItem}{insert}, + \l {moveItem}{move} and \l {removeItem}{remove} items dynamically. The + items in a container can be accessed using \l itemAt() or + \l contentChildren. + + Most containers have the concept of a "current" item. The current item is + specified via the \l currentIndex property, and can be accessed using the + read-only \l currentItem property. + + The following example illustrates dynamic insertion of items to a \l TabBar, + which is one of the concrete implementations of Container. + + \code + Row { + TabBar { + id: tabBar + + currentIndex: 0 + width: parent.width - addButton.width + + TabButton { text: "TabButton" } + } + + Component { + id: tabButton + TabButton { text: "TabButton" } + } + + Button { + id: addButton + text: "+" + flat: true + onClicked: { + tabBar.addItem(tabButton.createObject(tabBar)) + console.log("added:", tabBar.itemAt(tabBar.count - 1)) + } + } + } + \endcode + + \section2 Managing the Current Index + + When using multiple containers, such as \l TabBar and \l SwipeView, together, + their \l currentIndex properties can be bound to each other to keep them in + sync. When the user interacts with either container, its current index changes + automatically propagate to the other container. + + Notice, however, that assigning a \c currentIndex value in JavaScript removes + the respective binding. In order to retain the bindings, use the following + methods to alter the current index: + + \list + \li \l incrementCurrentIndex() + \li \l decrementCurrentIndex() + \li \l setCurrentIndex() + \endlist + + \code + TabBar { + id: tabBar + currentIndex: swipeView.currentIndex + } + + SwipeView { + id: swipeView + currentIndex: tabBar.currentIndex + } + + Button { + text: qsTr("Home") + onClicked: swipeView.setCurrentIndex(0) + enabled: swipeView.currentIndex != 0 + } + + Button { + text: qsTr("Previous") + onClicked: swipeView.decrementCurrentIndex() + enabled: swipeView.currentIndex > 0 + } + + Button { + text: qsTr("Next") + onClicked: swipeView.incrementCurrentIndex() + enabled: swipeView.currentIndex < swipeView.count - 1 + } + \endcode + + + \section2 Implementing Containers + + Container does not provide any default visualization. It is used to implement + such containers as \l SwipeView and \l TabBar. When implementing a custom + container, the most important part of the API is \l contentModel, which provides + the contained items in a way that it can be used as a delegate model for item + views and repeaters. + + \code + Container { + id: container + + contentItem: ListView { + model: container.contentModel + snapMode: ListView.SnapOneItem + orientation: ListView.Horizontal + } + + Text { + text: "Page 1" + width: container.width + height: container.height + } + + Text { + text: "Page 2" + width: container.width + height: container.height + } + } + \endcode + + Notice how the sizes of the page items are set by hand. This is because the + example uses a plain Container, which does not make any assumptions on the + visual layout. It is typically not necessary to specify sizes for items in + concrete Container implementations, such as \l SwipeView and \l TabBar. + + \sa {Container Controls} +*/ + +static QQuickItem *effectiveContentItem(QQuickItem *item) +{ + QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(item); + if (flickable) + return flickable->contentItem(); + return item; +} + +void QQuickContainerPrivate::init() +{ + Q_Q(QQuickContainer); + contentModel = new QQmlObjectModel(q); + QObject::connect(contentModel, &QQmlObjectModel::countChanged, q, &QQuickContainer::countChanged); + QObject::connect(contentModel, &QQmlObjectModel::childrenChanged, q, &QQuickContainer::contentChildrenChanged); + connect(q, &QQuickControl::implicitContentWidthChanged, this, &QQuickContainerPrivate::updateContentWidth); + connect(q, &QQuickControl::implicitContentHeightChanged, this, &QQuickContainerPrivate::updateContentHeight); +} + +void QQuickContainerPrivate::cleanup() +{ + Q_Q(QQuickContainer); + // ensure correct destruction order (QTBUG-46798) + const int count = contentModel->count(); + for (int i = 0; i < count; ++i) { + QQuickItem *item = itemAt(i); + if (item) + QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes); + } + + if (contentItem) { + QQuickItem *focusItem = QQuickItemPrivate::get(contentItem)->subFocusItem; + if (focusItem && window) + QQuickWindowPrivate::get(window)->clearFocusInScope(contentItem, focusItem, Qt::OtherFocusReason); + + q->contentItemChange(nullptr, contentItem); + QQuickControlPrivate::hideOldItem(contentItem); + } + + QObject::disconnect(contentModel, &QQmlObjectModel::countChanged, q, &QQuickContainer::countChanged); + QObject::disconnect(contentModel, &QQmlObjectModel::childrenChanged, q, &QQuickContainer::contentChildrenChanged); + delete contentModel; + contentModel = nullptr; +} + +QQuickItem *QQuickContainerPrivate::itemAt(int index) const +{ + return qobject_cast<QQuickItem *>(contentModel->get(index)); +} + +void QQuickContainerPrivate::insertItem(int index, QQuickItem *item) +{ + Q_Q(QQuickContainer); + if (!q->isContent(item)) + return; + contentData.append(item); + + updatingCurrent = true; + + item->setParentItem(effectiveContentItem(q->contentItem())); + QQuickItemPrivate::get(item)->addItemChangeListener(this, changeTypes); + contentModel->insert(index, item); + + q->itemAdded(index, item); + + int count = contentModel->count(); + for (int i = index + 1; i < count; ++i) + q->itemMoved(i, itemAt(i)); + + if (count == 1 && currentIndex == -1) + q->setCurrentIndex(index); + + updatingCurrent = false; +} + +void QQuickContainerPrivate::moveItem(int from, int to, QQuickItem *item) +{ + Q_Q(QQuickContainer); + int oldCurrent = currentIndex; + contentModel->move(from, to); + + updatingCurrent = true; + + q->itemMoved(to, item); + + if (from < to) { + for (int i = from; i < to; ++i) + q->itemMoved(i, itemAt(i)); + } else { + for (int i = from; i > to; --i) + q->itemMoved(i, itemAt(i)); + } + + if (from == oldCurrent) + q->setCurrentIndex(to); + else if (from < oldCurrent && to >= oldCurrent) + q->setCurrentIndex(oldCurrent - 1); + else if (from > oldCurrent && to <= oldCurrent) + q->setCurrentIndex(oldCurrent + 1); + + updatingCurrent = false; +} + +void QQuickContainerPrivate::removeItem(int index, QQuickItem *item) +{ + Q_Q(QQuickContainer); + if (!q->isContent(item)) + return; + contentData.removeOne(item); + + updatingCurrent = true; + + int count = contentModel->count(); + bool currentChanged = false; + if (index == currentIndex && (index != 0 || count == 1)) { + q->setCurrentIndex(currentIndex - 1); + } else if (index < currentIndex) { + --currentIndex; + currentChanged = true; + } + + QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes); + item->setParentItem(nullptr); + contentModel->remove(index); + --count; + + q->itemRemoved(index, item); + + for (int i = index; i < count; ++i) + q->itemMoved(i, itemAt(i)); + + if (currentChanged) + emit q->currentIndexChanged(); + + updatingCurrent = false; +} + +void QQuickContainerPrivate::reorderItems() +{ + Q_Q(QQuickContainer); + if (!contentItem) + return; + + QList<QQuickItem *> siblings = effectiveContentItem(contentItem)->childItems(); + + int to = 0; + for (int i = 0; i < siblings.count(); ++i) { + QQuickItem* sibling = siblings.at(i); + if (QQuickItemPrivate::get(sibling)->isTransparentForPositioner()) + continue; + int index = contentModel->indexOf(sibling, nullptr); + q->moveItem(index, to++); + } +} + +void QQuickContainerPrivate::_q_currentIndexChanged() +{ + Q_Q(QQuickContainer); + if (!updatingCurrent) + q->setCurrentIndex(contentItem ? contentItem->property("currentIndex").toInt() : -1); +} + +void QQuickContainerPrivate::itemChildAdded(QQuickItem *, QQuickItem *child) +{ + // add dynamically reparented items (eg. by a Repeater) + if (!QQuickItemPrivate::get(child)->isTransparentForPositioner() && !contentData.contains(child)) + insertItem(contentModel->count(), child); +} + +void QQuickContainerPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent) +{ + // remove dynamically unparented items (eg. by a Repeater) + if (!parent) + removeItem(contentModel->indexOf(item, nullptr), item); +} + +void QQuickContainerPrivate::itemSiblingOrderChanged(QQuickItem *) +{ + if (!componentComplete) + return; + + // reorder the restacked items (eg. by a Repeater) + reorderItems(); +} + +void QQuickContainerPrivate::itemDestroyed(QQuickItem *item) +{ + int index = contentModel->indexOf(item, nullptr); + if (index != -1) + removeItem(index, item); + else + QQuickControlPrivate::itemDestroyed(item); +} + +void QQuickContainerPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) +{ + QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); + QQuickContainerPrivate *p = QQuickContainerPrivate::get(q); + QQuickItem *item = qobject_cast<QQuickItem *>(obj); + if (item) { + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + item->setParentItem(effectiveContentItem(q->contentItem())); + else if (p->contentModel->indexOf(item, nullptr) == -1) + q->addItem(item); + } else { + p->contentData.append(obj); + } +} + +qsizetype QQuickContainerPrivate::contentData_count(QQmlListProperty<QObject> *prop) +{ + QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); + return QQuickContainerPrivate::get(q)->contentData.count(); +} + +QObject *QQuickContainerPrivate::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index) +{ + QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); + return QQuickContainerPrivate::get(q)->contentData.value(index); +} + +void QQuickContainerPrivate::contentData_clear(QQmlListProperty<QObject> *prop) +{ + QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); + return QQuickContainerPrivate::get(q)->contentData.clear(); +} + +void QQuickContainerPrivate::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item) +{ + QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); + q->addItem(item); +} + +qsizetype QQuickContainerPrivate::contentChildren_count(QQmlListProperty<QQuickItem> *prop) +{ + QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); + return QQuickContainerPrivate::get(q)->contentModel->count(); +} + +QQuickItem *QQuickContainerPrivate::contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index) +{ + QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); + return q->itemAt(index); +} + +void QQuickContainerPrivate::contentChildren_clear(QQmlListProperty<QQuickItem> *prop) +{ + QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); + return QQuickContainerPrivate::get(q)->contentModel->clear(); +} + +void QQuickContainerPrivate::updateContentWidth() +{ + Q_Q(QQuickContainer); + if (hasContentWidth || qFuzzyCompare(contentWidth, implicitContentWidth) || !contentModel) + return; + + contentWidth = implicitContentWidth; + emit q->contentWidthChanged(); +} + +void QQuickContainerPrivate::updateContentHeight() +{ + Q_Q(QQuickContainer); + if (hasContentHeight || qFuzzyCompare(contentHeight, implicitContentHeight) || !contentModel) + return; + + contentHeight = implicitContentHeight; + emit q->contentHeightChanged(); +} + +QQuickContainer::QQuickContainer(QQuickItem *parent) + : QQuickControl(*(new QQuickContainerPrivate), parent) +{ + Q_D(QQuickContainer); + d->init(); +} + +QQuickContainer::QQuickContainer(QQuickContainerPrivate &dd, QQuickItem *parent) + : QQuickControl(dd, parent) +{ + Q_D(QQuickContainer); + d->init(); +} + +QQuickContainer::~QQuickContainer() +{ + Q_D(QQuickContainer); + d->cleanup(); +} + +/*! + \qmlproperty int QtQuick.Controls::Container::count + \readonly + + This property holds the number of items. +*/ +int QQuickContainer::count() const +{ + Q_D(const QQuickContainer); + return d->contentModel->count(); +} + +/*! + \qmlmethod Item QtQuick.Controls::Container::itemAt(int index) + + Returns the item at \a index, or \c null if it does not exist. +*/ +QQuickItem *QQuickContainer::itemAt(int index) const +{ + Q_D(const QQuickContainer); + return d->itemAt(index); +} + +/*! + \qmlmethod void QtQuick.Controls::Container::addItem(Item item) + + Adds an \a item. +*/ +void QQuickContainer::addItem(QQuickItem *item) +{ + Q_D(QQuickContainer); + insertItem(d->contentModel->count(), item); +} + +/*! + \qmlmethod void QtQuick.Controls::Container::insertItem(int index, Item item) + + Inserts an \a item at \a index. +*/ +void QQuickContainer::insertItem(int index, QQuickItem *item) +{ + Q_D(QQuickContainer); + if (!item) + return; + const int count = d->contentModel->count(); + if (index < 0 || index > count) + index = count; + + int oldIndex = d->contentModel->indexOf(item, nullptr); + if (oldIndex != -1) { + if (oldIndex < index) + --index; + if (oldIndex != index) + d->moveItem(oldIndex, index, item); + } else { + d->insertItem(index, item); + } +} + +/*! + \qmlmethod void QtQuick.Controls::Container::moveItem(int from, int to) + + Moves an item \a from one index \a to another. +*/ +void QQuickContainer::moveItem(int from, int to) +{ + Q_D(QQuickContainer); + const int count = d->contentModel->count(); + if (from < 0 || from > count - 1) + return; + if (to < 0 || to > count - 1) + to = count - 1; + + if (from != to) + d->moveItem(from, to, d->itemAt(from)); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Container::removeItem(Item item) + + Removes and destroys the specified \a item. +*/ +void QQuickContainer::removeItem(QQuickItem *item) +{ + Q_D(QQuickContainer); + if (!item) + return; + + const int index = d->contentModel->indexOf(item, nullptr); + if (index == -1) + return; + + d->removeItem(index, item); + item->deleteLater(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod Item QtQuick.Controls::Container::takeItem(int index) + + Removes and returns the item at \a index. + + \note The ownership of the item is transferred to the caller. +*/ +QQuickItem *QQuickContainer::takeItem(int index) +{ + Q_D(QQuickContainer); + const int count = d->contentModel->count(); + if (index < 0 || index >= count) + return nullptr; + + QQuickItem *item = itemAt(index); + if (item) + d->removeItem(index, item); + return item; +} + +/*! + \qmlproperty model QtQuick.Controls::Container::contentModel + \readonly + + This property holds the content model of items. + + The content model is provided for visualization purposes. It can be assigned + as a model to a content item that presents the contents of the container. + + \code + Container { + id: container + contentItem: ListView { + model: container.contentModel + } + } + \endcode + + \sa contentData, contentChildren +*/ +QVariant QQuickContainer::contentModel() const +{ + Q_D(const QQuickContainer); + return QVariant::fromValue(d->contentModel); +} + +/*! + \qmlproperty list<Object> QtQuick.Controls::Container::contentData + \qmldefault + + This property holds the list of content data. + + The list contains all objects that have been declared in QML as children + of the container, and also items that have been dynamically added or + inserted using the \l addItem() and \l insertItem() methods, respectively. + + \note Unlike \c contentChildren, \c contentData does include non-visual QML + objects. It is not re-ordered when items are inserted or moved. + + \sa Item::data, contentChildren +*/ +QQmlListProperty<QObject> QQuickContainer::contentData() +{ + Q_D(QQuickContainer); + if (!d->contentItem) + d->executeContentItem(); + return QQmlListProperty<QObject>(this, nullptr, + QQuickContainerPrivate::contentData_append, + QQuickContainerPrivate::contentData_count, + QQuickContainerPrivate::contentData_at, + QQuickContainerPrivate::contentData_clear); +} + +/*! + \qmlproperty list<Item> QtQuick.Controls::Container::contentChildren + + This property holds the list of content children. + + The list contains all items that have been declared in QML as children + of the container, and also items that have been dynamically added or + inserted using the \l addItem() and \l insertItem() methods, respectively. + + \note Unlike \c contentData, \c contentChildren does not include non-visual + QML objects. It is re-ordered when items are inserted or moved. + + \sa Item::children, contentData +*/ +QQmlListProperty<QQuickItem> QQuickContainer::contentChildren() +{ + return QQmlListProperty<QQuickItem>(this, nullptr, + QQuickContainerPrivate::contentChildren_append, + QQuickContainerPrivate::contentChildren_count, + QQuickContainerPrivate::contentChildren_at, + QQuickContainerPrivate::contentChildren_clear); +} + +/*! + \qmlproperty int QtQuick.Controls::Container::currentIndex + + This property holds the index of the current item. + + \sa currentItem, {Managing the Current Index} +*/ +int QQuickContainer::currentIndex() const +{ + Q_D(const QQuickContainer); + return d->currentIndex; +} + +/*! + \qmlmethod void QtQuick.Controls::Container::setCurrentIndex(int index) + + Sets the current \a index of the container. + + This method can be called to set a specific current index without breaking + existing \c currentIndex bindings. + + \sa currentIndex, {Managing the Current Index} +*/ +void QQuickContainer::setCurrentIndex(int index) +{ + Q_D(QQuickContainer); + if (d->currentIndex == index) + return; + + d->currentIndex = index; + emit currentIndexChanged(); + emit currentItemChanged(); +} + +/*! + \qmlmethod void QtQuick.Controls::Container::incrementCurrentIndex() + \since QtQuick.Controls 2.1 (Qt 5.8) + + Increments the current index of the container. + + This method can be called to alter the current index without breaking + existing \c currentIndex bindings. + + \sa currentIndex, {Managing the Current Index} +*/ +void QQuickContainer::incrementCurrentIndex() +{ + Q_D(QQuickContainer); + if (d->currentIndex < count() - 1) + setCurrentIndex(d->currentIndex + 1); +} + +/*! + \qmlmethod void QtQuick.Controls::Container::decrementCurrentIndex() + \since QtQuick.Controls 2.1 (Qt 5.8) + + Decrements the current index of the container. + + This method can be called to alter the current index without breaking + existing \c currentIndex bindings. + + \sa currentIndex, {Managing the Current Index} +*/ +void QQuickContainer::decrementCurrentIndex() +{ + Q_D(QQuickContainer); + if (d->currentIndex > 0) + setCurrentIndex(d->currentIndex - 1); +} + +/*! + \qmlproperty Item QtQuick.Controls::Container::currentItem + \readonly + + This property holds the current item. + + \sa currentIndex +*/ +QQuickItem *QQuickContainer::currentItem() const +{ + Q_D(const QQuickContainer); + return itemAt(d->currentIndex); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Container::contentWidth + + This property holds the content width. It is used for calculating the total + implicit width of the container. + + Unless explicitly overridden, the content width is automatically calculated + based on the implicit width of the items in the container. + + \sa contentHeight +*/ +qreal QQuickContainer::contentWidth() const +{ + Q_D(const QQuickContainer); + return d->contentWidth; +} + +void QQuickContainer::setContentWidth(qreal width) +{ + Q_D(QQuickContainer); + d->hasContentWidth = true; + if (qFuzzyCompare(d->contentWidth, width)) + return; + + d->contentWidth = width; + d->resizeContent(); + emit contentWidthChanged(); +} + +void QQuickContainer::resetContentWidth() +{ + Q_D(QQuickContainer); + if (!d->hasContentWidth) + return; + + d->hasContentWidth = false; + d->updateContentWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Container::contentHeight + + This property holds the content height. It is used for calculating the total + implicit height of the container. + + Unless explicitly overridden, the content height is automatically calculated + based on the implicit height of the items in the container. + + \sa contentWidth +*/ +qreal QQuickContainer::contentHeight() const +{ + Q_D(const QQuickContainer); + return d->contentHeight; +} + +void QQuickContainer::setContentHeight(qreal height) +{ + Q_D(QQuickContainer); + d->hasContentHeight = true; + if (qFuzzyCompare(d->contentHeight, height)) + return; + + d->contentHeight = height; + d->resizeContent(); + emit contentHeightChanged(); +} + +void QQuickContainer::resetContentHeight() +{ + Q_D(QQuickContainer); + if (!d->hasContentHeight) + return; + + d->hasContentHeight = false; + d->updateContentHeight(); +} + +void QQuickContainer::componentComplete() +{ + Q_D(QQuickContainer); + QQuickControl::componentComplete(); + d->reorderItems(); +} + +void QQuickContainer::itemChange(ItemChange change, const ItemChangeData &data) +{ + Q_D(QQuickContainer); + QQuickControl::itemChange(change, data); + if (change == QQuickItem::ItemChildAddedChange && isComponentComplete() && data.item != d->background && data.item != d->contentItem) { + if (!QQuickItemPrivate::get(data.item)->isTransparentForPositioner() && d->contentModel->indexOf(data.item, nullptr) == -1) + addItem(data.item); + } +} + +void QQuickContainer::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickContainer); + QQuickControl::contentItemChange(newItem, oldItem); + + static const int slotIndex = metaObject()->indexOfSlot("_q_currentIndexChanged()"); + + if (oldItem) { + QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Children | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight); + QQuickItem *oldContentItem = effectiveContentItem(oldItem); + if (oldContentItem != oldItem) + QQuickItemPrivate::get(oldContentItem)->removeItemChangeListener(d, QQuickItemPrivate::Children); + + int signalIndex = oldItem->metaObject()->indexOfSignal("currentIndexChanged()"); + if (signalIndex != -1) + QMetaObject::disconnect(oldItem, signalIndex, this, slotIndex); + } + + if (newItem) { + QQuickItemPrivate::get(newItem)->addItemChangeListener(d, QQuickItemPrivate::Children | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight); + QQuickItem *newContentItem = effectiveContentItem(newItem); + if (newContentItem != newItem) + QQuickItemPrivate::get(newContentItem)->addItemChangeListener(d, QQuickItemPrivate::Children); + + int signalIndex = newItem->metaObject()->indexOfSignal("currentIndexChanged()"); + if (signalIndex != -1) + QMetaObject::connect(newItem, signalIndex, this, slotIndex); + } +} + +bool QQuickContainer::isContent(QQuickItem *item) const +{ + // If the item has a QML context associated to it (it was created in QML), + // we add it to the content model. Otherwise, it's probably the default + // highlight item that is always created by the item views, which we need + // to exclude. + // + // TODO: Find a better way to identify/exclude the highlight item... + return qmlContext(item); +} + +void QQuickContainer::itemAdded(int index, QQuickItem *item) +{ + Q_UNUSED(index); + Q_UNUSED(item); +} + +void QQuickContainer::itemMoved(int index, QQuickItem *item) +{ + Q_UNUSED(index); + Q_UNUSED(item); +} + +void QQuickContainer::itemRemoved(int index, QQuickItem *item) +{ + Q_UNUSED(index); + Q_UNUSED(item); +} + +QT_END_NAMESPACE + +#include "moc_qquickcontainer_p.cpp" diff --git a/src/quicktemplates2/qquickcontainer_p.h b/src/quicktemplates2/qquickcontainer_p.h new file mode 100644 index 0000000000..ccfe4c5618 --- /dev/null +++ b/src/quicktemplates2/qquickcontainer_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCONTAINER_P_H +#define QQUICKCONTAINER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQml/qqmllist.h> + +QT_BEGIN_NAMESPACE + +class QQuickContainerPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickContainer : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged FINAL) + Q_PROPERTY(QVariant contentModel READ contentModel CONSTANT FINAL) + Q_PROPERTY(QQmlListProperty<QObject> contentData READ contentData) + Q_PROPERTY(QQmlListProperty<QQuickItem> contentChildren READ contentChildren NOTIFY contentChildrenChanged FINAL) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL) + Q_PROPERTY(QQuickItem *currentItem READ currentItem NOTIFY currentItemChanged FINAL) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth RESET resetContentWidth NOTIFY contentWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight RESET resetContentHeight NOTIFY contentHeightChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DefaultProperty", "contentData") + QML_NAMED_ELEMENT(Container) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickContainer(QQuickItem *parent = nullptr); + ~QQuickContainer(); + + int count() const; + Q_INVOKABLE QQuickItem *itemAt(int index) const; + Q_INVOKABLE void addItem(QQuickItem *item); + Q_INVOKABLE void insertItem(int index, QQuickItem *item); + Q_INVOKABLE void moveItem(int from, int to); + Q_INVOKABLE void removeItem(QQuickItem *item); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) Q_INVOKABLE QQuickItem *takeItem(int index); + + QVariant contentModel() const; + QQmlListProperty<QObject> contentData(); + QQmlListProperty<QQuickItem> contentChildren(); + + int currentIndex() const; + QQuickItem *currentItem() const; + + // 2.5 (Qt 5.12) + qreal contentWidth() const; + void setContentWidth(qreal width); + void resetContentWidth(); + + qreal contentHeight() const; + void setContentHeight(qreal height); + void resetContentHeight(); + +public Q_SLOTS: + void setCurrentIndex(int index); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void incrementCurrentIndex(); + Q_REVISION(2, 1) void decrementCurrentIndex(); + +Q_SIGNALS: + void countChanged(); + void contentChildrenChanged(); + void currentIndexChanged(); + void currentItemChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void contentWidthChanged(); + Q_REVISION(2, 5) void contentHeightChanged(); + +protected: + QQuickContainer(QQuickContainerPrivate &dd, QQuickItem *parent); + + void componentComplete() override; + + void itemChange(ItemChange change, const ItemChangeData &data) override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + + virtual bool isContent(QQuickItem *item) const; + virtual void itemAdded(int index, QQuickItem *item); + virtual void itemMoved(int index, QQuickItem *item); + virtual void itemRemoved(int index, QQuickItem *item); + +private: + Q_DISABLE_COPY(QQuickContainer) + Q_DECLARE_PRIVATE(QQuickContainer) + Q_PRIVATE_SLOT(d_func(), void _q_currentIndexChanged()) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickContainer) + +#endif // QQUICKCONTAINER_P_H diff --git a/src/quicktemplates2/qquickcontainer_p_p.h b/src/quicktemplates2/qquickcontainer_p_p.h new file mode 100644 index 0000000000..b9f8eb0f01 --- /dev/null +++ b/src/quicktemplates2/qquickcontainer_p_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCONTAINER_P_P_H +#define QQUICKCONTAINER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#include <private/qqmlobjectmodel_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickContainerPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickContainer) + +public: + static QQuickContainerPrivate *get(QQuickContainer *container) + { + return container->d_func(); + } + + void init(); + void cleanup(); + + QQuickItem *itemAt(int index) const; + void insertItem(int index, QQuickItem *item); + void moveItem(int from, int to, QQuickItem *item); + void removeItem(int index, QQuickItem *item); + void reorderItems(); + + void _q_currentIndexChanged(); + + void itemChildAdded(QQuickItem *item, QQuickItem *child) override; + void itemSiblingOrderChanged(QQuickItem *item) override; + void itemParentChanged(QQuickItem *item, QQuickItem *parent) override; + void itemDestroyed(QQuickItem *item) override; + + static void contentData_append(QQmlListProperty<QObject> *prop, QObject *obj); + static qsizetype contentData_count(QQmlListProperty<QObject> *prop); + static QObject *contentData_at(QQmlListProperty<QObject> *prop, qsizetype index); + static void contentData_clear(QQmlListProperty<QObject> *prop); + + static void contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *obj); + static qsizetype contentChildren_count(QQmlListProperty<QQuickItem> *prop); + static QQuickItem *contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index); + static void contentChildren_clear(QQmlListProperty<QQuickItem> *prop); + + void updateContentWidth(); + void updateContentHeight(); + + bool hasContentWidth = false; + bool hasContentHeight = false; + qreal contentWidth = 0; + qreal contentHeight = 0; + QObjectList contentData; + QQmlObjectModel *contentModel = nullptr; + qsizetype currentIndex = -1; + bool updatingCurrent = false; + QQuickItemPrivate::ChangeTypes changeTypes = Destroyed | Parent | SiblingOrder; +}; + +QT_END_NAMESPACE + +#endif // QQUICKCONTAINER_P_P_H diff --git a/src/quicktemplates2/qquickcontentitem.cpp b/src/quicktemplates2/qquickcontentitem.cpp new file mode 100644 index 0000000000..325eb1fb45 --- /dev/null +++ b/src/quicktemplates2/qquickcontentitem.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcontentitem_p.h" + +#include <QtQml/private/qqmlmetatype_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + + Helper class that aids debugging by producing more useful debugging output. +*/ + +QQuickContentItem::QQuickContentItem(QQuickItem *parent) + : QQuickItem(parent) +{ + setObjectName(QQmlMetaType::prettyTypeName(parent)); +} + +QQuickContentItem::QQuickContentItem(const QObject *scope, QQuickItem *parent) + : QQuickItem(parent) +{ + setObjectName(QQmlMetaType::prettyTypeName(scope)); +} + +QT_END_NAMESPACE + +#include "moc_qquickcontentitem_p.cpp" diff --git a/src/quicktemplates2/qquickcontentitem_p.h b/src/quicktemplates2/qquickcontentitem_p.h new file mode 100644 index 0000000000..df0f0b243a --- /dev/null +++ b/src/quicktemplates2/qquickcontentitem_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCONTENTITEM_P_H +#define QQUICKCONTENTITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/qquickitem.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickContentItem : public QQuickItem +{ + Q_OBJECT + +public: + explicit QQuickContentItem(QQuickItem *parent = nullptr); + explicit QQuickContentItem(const QObject *scope, QQuickItem *parent); + +private: + Q_DISABLE_COPY(QQuickContentItem) +}; + +QT_END_NAMESPACE + +#endif // QQUICKCONTENTITEM_P_H diff --git a/src/quicktemplates2/qquickcontrol.cpp b/src/quicktemplates2/qquickcontrol.cpp new file mode 100644 index 0000000000..140b2a7f17 --- /dev/null +++ b/src/quicktemplates2/qquickcontrol.cpp @@ -0,0 +1,2231 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcontrol_p.h" +#include "qquickcontrol_p_p.h" + +#include <QtGui/qstylehints.h> +#include <QtGui/qguiapplication.h> +#include "qquicklabel_p.h" +#include "qquicklabel_p_p.h" +#include "qquicktextarea_p.h" +#include "qquicktextarea_p_p.h" +#include "qquicktextfield_p.h" +#include "qquicktextfield_p_p.h" +#include "qquickpopup_p.h" +#include "qquickpopupitem_p_p.h" +#include "qquickapplicationwindow_p.h" +#include "qquickdeferredexecute_p_p.h" +#include "qquickcontentitem_p.h" + +#if QT_CONFIG(accessibility) +#include <QtQuick/private/qquickaccessibleattached_p.h> +#endif + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcItemManagement, "qt.quick.controls.control.itemmanagement") + +/*! + \qmltype Control + \inherits Item +//! \instantiates QQuickControl + \inqmlmodule QtQuick.Controls + \since 5.7 + \brief Abstract base type providing functionality common to all controls. + + Control is the base type of user interface controls. It receives input + events from the window system, and paints a representation of itself on + the screen. + + \section1 Control Layout + + The following diagram illustrates the layout of a typical control: + + \image qtquickcontrols2-control.png + + The \l {Item::}{implicitWidth} and \l {Item::}{implicitHeight} of a control + are typically based on the implicit sizes of the background and the content + item plus any insets and paddings. These properties determine how large + the control will be when no explicit \l {Item::}{width} or + \l {Item::}{height} is specified. + + The geometry of the \l {Control::}{contentItem} is determined by the padding. + The following example reserves 10px padding between the boundaries of the + control and its content: + + \code + Control { + padding: 10 + + contentItem: Text { + text: "Content" + } + } + \endcode + + The \l {Control::}{background} item fills the entire width and height of the + control, unless insets or an explicit size have been given for it. Background + insets are useful for extending the touchable/interactive area of a control + without affecting its visual size. This is often used on touch devices to + ensure that a control is not too small to be interacted with by the user. + Insets affect the size of the control, and hence will affect how much space + they take up in a layout, for example. + + Negative insets can be used to make the background larger than the control. + The following example uses negative insets to place a shadow outside the + control's boundaries: + + \code + Control { + topInset: -2 + leftInset: -2 + rightInset: -6 + bottomInset: -6 + + background: BorderImage { + source: ":/images/shadowed-background.png" + } + } + \endcode + + \section1 Event Handling + + All controls, except non-interactive indicators, do not let clicks and + touches through to items below them. For example, the \c console.log() + call in the example below will never be executed when clicking on the + Pane, because the \l MouseArea is below it in the scene: + + \code + MouseArea { + anchors.fill: parent + onClicked: console.log("MouseArea was clicked") + + Pane { + anchors.fill: parent + } + } + \endcode + + \sa ApplicationWindow, Container, {Using Qt Quick Controls types in + property declarations} +*/ + +const QQuickItemPrivate::ChangeTypes QQuickControlPrivate::ImplicitSizeChanges = QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight | QQuickItemPrivate::Destroyed; + +static bool isKeyFocusReason(Qt::FocusReason reason) +{ + return reason == Qt::TabFocusReason || reason == Qt::BacktabFocusReason || reason == Qt::ShortcutFocusReason; +} + +QQuickControlPrivate::QQuickControlPrivate() +{ +#if QT_CONFIG(accessibility) + QAccessible::installActivationObserver(this); +#endif +} + +QQuickControlPrivate::~QQuickControlPrivate() +{ +} + +void QQuickControlPrivate::init() +{ + Q_Q(QQuickControl); + QObject::connect(q, &QQuickItem::baselineOffsetChanged, q, &QQuickControl::baselineOffsetChanged); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +bool QQuickControlPrivate::acceptTouch(const QTouchEvent::TouchPoint &point) +{ + if (point.id() == touchId) + return true; + + if (touchId == -1 && point.state() == QEventPoint::Pressed) { + touchId = point.id(); + return true; + } + + // If the control is on a Flickable that has a pressDelay, then the press is never + // sent as a touch event, therefore we need to check for this case. + if (touchId == -1 && pressWasTouch && point.state() == QEventPoint::Released && + point.position() == previousPressPos) { + return true; + } + return false; +} +#endif + +static void setActiveFocus(QQuickControl *control, Qt::FocusReason reason) +{ + QQuickControlPrivate *d = QQuickControlPrivate::get(control); + if (d->subFocusItem && d->window && d->flags & QQuickItem::ItemIsFocusScope) + QQuickWindowPrivate::get(d->window)->clearFocusInScope(control, d->subFocusItem, reason); + control->forceActiveFocus(reason); +} + +void QQuickControlPrivate::handlePress(const QPointF &) +{ + Q_Q(QQuickControl); + if ((focusPolicy & Qt::ClickFocus) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease()) + setActiveFocus(q, Qt::MouseFocusReason); +} + +void QQuickControlPrivate::handleMove(const QPointF &point) +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_Q(QQuickControl); + q->setHovered(hoverEnabled && q->contains(point)); +#else + Q_UNUSED(point); +#endif +} + +void QQuickControlPrivate::handleRelease(const QPointF &) +{ + Q_Q(QQuickControl); + if ((focusPolicy & Qt::ClickFocus) == Qt::ClickFocus && QGuiApplication::styleHints()->setFocusOnTouchRelease()) + setActiveFocus(q, Qt::MouseFocusReason); + touchId = -1; + pressWasTouch = false; + previousPressPos = QPointF(); +} + +void QQuickControlPrivate::handleUngrab() +{ + touchId = -1; +} + +void QQuickControlPrivate::mirrorChange() +{ + Q_Q(QQuickControl); + q->mirrorChange(); +} + +void QQuickControlPrivate::setTopPadding(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldPadding = getPadding(); + extra.value().topPadding = value; + extra.value().hasTopPadding = !reset; + if ((!reset && !qFuzzyCompare(oldPadding.top(), value)) || (reset && !qFuzzyCompare(oldPadding.top(), getVerticalPadding()))) { + emit q->topPaddingChanged(); + emit q->availableHeightChanged(); + q->paddingChange(getPadding(), oldPadding); + } +} + +void QQuickControlPrivate::setLeftPadding(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldPadding = getPadding(); + extra.value().leftPadding = value; + extra.value().hasLeftPadding = !reset; + if ((!reset && !qFuzzyCompare(oldPadding.left(), value)) || (reset && !qFuzzyCompare(oldPadding.left(), getHorizontalPadding()))) { + emit q->leftPaddingChanged(); + emit q->availableWidthChanged(); + q->paddingChange(getPadding(), oldPadding); + } +} + +void QQuickControlPrivate::setRightPadding(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldPadding = getPadding(); + extra.value().rightPadding = value; + extra.value().hasRightPadding = !reset; + if ((!reset && !qFuzzyCompare(oldPadding.right(), value)) || (reset && !qFuzzyCompare(oldPadding.right(), getHorizontalPadding()))) { + emit q->rightPaddingChanged(); + emit q->availableWidthChanged(); + q->paddingChange(getPadding(), oldPadding); + } +} + +void QQuickControlPrivate::setBottomPadding(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldPadding = getPadding(); + extra.value().bottomPadding = value; + extra.value().hasBottomPadding = !reset; + if ((!reset && !qFuzzyCompare(oldPadding.bottom(), value)) || (reset && !qFuzzyCompare(oldPadding.bottom(), getVerticalPadding()))) { + emit q->bottomPaddingChanged(); + emit q->availableHeightChanged(); + q->paddingChange(getPadding(), oldPadding); + } +} + +void QQuickControlPrivate::setHorizontalPadding(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldPadding = getPadding(); + const qreal oldHorizontalPadding = getHorizontalPadding(); + horizontalPadding = value; + hasHorizontalPadding = !reset; + if ((!reset && !qFuzzyCompare(oldHorizontalPadding, value)) || (reset && !qFuzzyCompare(oldHorizontalPadding, padding))) { + const QMarginsF newPadding = getPadding(); + if (!qFuzzyCompare(newPadding.left(), oldPadding.left())) + emit q->leftPaddingChanged(); + if (!qFuzzyCompare(newPadding.right(), oldPadding.right())) + emit q->rightPaddingChanged(); + emit q->horizontalPaddingChanged(); + emit q->availableWidthChanged(); + q->paddingChange(newPadding, oldPadding); + } +} + +void QQuickControlPrivate::setVerticalPadding(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldPadding = getPadding(); + const qreal oldVerticalPadding = getVerticalPadding(); + verticalPadding = value; + hasVerticalPadding = !reset; + if ((!reset && !qFuzzyCompare(oldVerticalPadding, value)) || (reset && !qFuzzyCompare(oldVerticalPadding, padding))) { + const QMarginsF newPadding = getPadding(); + if (!qFuzzyCompare(newPadding.top(), oldPadding.top())) + emit q->topPaddingChanged(); + if (!qFuzzyCompare(newPadding.bottom(), oldPadding.bottom())) + emit q->bottomPaddingChanged(); + emit q->verticalPaddingChanged(); + emit q->availableHeightChanged(); + q->paddingChange(newPadding, oldPadding); + } +} + +void QQuickControlPrivate::setTopInset(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldInset = getInset(); + extra.value().topInset = value; + extra.value().hasTopInset = !reset; + if (!qFuzzyCompare(oldInset.top(), value)) { + emit q->topInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickControlPrivate::setLeftInset(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldInset = getInset(); + extra.value().leftInset = value; + extra.value().hasLeftInset = !reset; + if (!qFuzzyCompare(oldInset.left(), value)) { + emit q->leftInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickControlPrivate::setRightInset(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldInset = getInset(); + extra.value().rightInset = value; + extra.value().hasRightInset = !reset; + if (!qFuzzyCompare(oldInset.right(), value)) { + emit q->rightInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickControlPrivate::setBottomInset(qreal value, bool reset) +{ + Q_Q(QQuickControl); + const QMarginsF oldInset = getInset(); + extra.value().bottomInset = value; + extra.value().hasBottomInset = !reset; + if (!qFuzzyCompare(oldInset.bottom(), value)) { + emit q->bottomInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickControlPrivate::resizeBackground() +{ + if (!background) + return; + + resizingBackground = true; + + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + bool changeWidth = false; + bool changeHeight = false; + if (((!p->widthValid() || !extra.isAllocated() || !extra->hasBackgroundWidth) && qFuzzyIsNull(background->x())) + || (extra.isAllocated() && (extra->hasLeftInset || extra->hasRightInset))) { + background->setX(getLeftInset()); + changeWidth = !p->width.hasBinding(); + } + if (((!p->heightValid() || !extra.isAllocated() || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y())) + || (extra.isAllocated() && (extra->hasTopInset || extra->hasBottomInset))) { + background->setY(getTopInset()); + changeHeight = !p->height.hasBinding(); + } + if (changeHeight || changeWidth) { + auto newWidth = changeWidth ? + width.valueBypassingBindings() - getLeftInset() - getRightInset() : + p->width.valueBypassingBindings(); + auto newHeight = changeHeight ? + height.valueBypassingBindings() - getTopInset() - getBottomInset() : + p->height.valueBypassingBindings(); + background->setSize({newWidth, newHeight}); + } + + resizingBackground = false; +} + +void QQuickControlPrivate::resizeContent() +{ + Q_Q(QQuickControl); + if (contentItem) { + contentItem->setPosition(QPointF(q->leftPadding(), q->topPadding())); + contentItem->setSize(QSizeF(q->availableWidth(), q->availableHeight())); + } +} + +QQuickItem *QQuickControlPrivate::getContentItem() +{ + if (!contentItem) + executeContentItem(); + return contentItem; +} + +void QQuickControlPrivate::setContentItem_helper(QQuickItem *item, bool notify) +{ + Q_Q(QQuickControl); + if (contentItem == item) + return; + + if (!contentItem.isExecuting()) + cancelContentItem(); + + QQuickItem *oldContentItem = contentItem; + if (oldContentItem) { + disconnect(oldContentItem, &QQuickItem::baselineOffsetChanged, this, &QQuickControlPrivate::updateBaselineOffset); + if (oldContentItem) + QQuickItemPrivate::get(oldContentItem)->removeItemChangeListener(this, QQuickControlPrivate::Focus); + removeImplicitSizeListener(oldContentItem); + } + + contentItem = item; + q->contentItemChange(item, oldContentItem); + QQuickControlPrivate::hideOldItem(oldContentItem); + + if (item) { + connect(contentItem.data(), &QQuickItem::baselineOffsetChanged, this, &QQuickControlPrivate::updateBaselineOffset); + // We need to update the control's focusReason when the contentItem receives or loses focus. Since focusPolicy + // (or other properties impacting focus handling, like QQuickItem::activeFocusOnTab) might change later, and + // since the content item might also change focus programmatically, we always have to listen for those events. + QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickControlPrivate::Focus); + if (!item->parentItem()) + item->setParentItem(q); + if (componentComplete) + resizeContent(); + addImplicitSizeListener(contentItem); + } + + updateImplicitContentSize(); + updateBaselineOffset(); + + if (notify && !contentItem.isExecuting()) + emit q->contentItemChanged(); +} + +qreal QQuickControlPrivate::getContentWidth() const +{ + return contentItem ? contentItem->implicitWidth() : 0; +} + +qreal QQuickControlPrivate::getContentHeight() const +{ + return contentItem ? contentItem->implicitHeight() : 0; +} + +void QQuickControlPrivate::updateImplicitContentWidth() +{ + Q_Q(QQuickControl); + const qreal oldWidth = implicitContentWidth; + implicitContentWidth = getContentWidth(); + if (!qFuzzyCompare(implicitContentWidth, oldWidth)) + emit q->implicitContentWidthChanged(); +} + +void QQuickControlPrivate::updateImplicitContentHeight() +{ + Q_Q(QQuickControl); + const qreal oldHeight = implicitContentHeight; + implicitContentHeight = getContentHeight(); + if (!qFuzzyCompare(implicitContentHeight, oldHeight)) + emit q->implicitContentHeightChanged(); +} + +void QQuickControlPrivate::updateImplicitContentSize() +{ + Q_Q(QQuickControl); + const qreal oldWidth = implicitContentWidth; + const qreal oldHeight = implicitContentHeight; + implicitContentWidth = getContentWidth(); + implicitContentHeight = getContentHeight(); + if (!qFuzzyCompare(implicitContentWidth, oldWidth)) + emit q->implicitContentWidthChanged(); + if (!qFuzzyCompare(implicitContentHeight, oldHeight)) + emit q->implicitContentHeightChanged(); +} + +QPalette QQuickControlPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::System); +} + +#if QT_CONFIG(accessibility) +void QQuickControlPrivate::accessibilityActiveChanged(bool active) +{ + Q_Q(QQuickControl); + return q->accessibilityActiveChanged(active); +} + +QAccessible::Role QQuickControlPrivate::accessibleRole() const +{ + Q_Q(const QQuickControl); + return q->accessibleRole(); +} + +QQuickAccessibleAttached *QQuickControlPrivate::accessibleAttached(const QObject *object) +{ + if (!QAccessible::isActive()) + return nullptr; + return QQuickAccessibleAttached::attachedProperties(object); +} +#endif + +/*! + \internal + + Returns the font that the control w inherits from its ancestors and + QGuiApplication::font. +*/ +QFont QQuickControlPrivate::parentFont(const QQuickItem *item) +{ + QQuickItem *p = item->parentItem(); + while (p) { + if (QQuickControl *control = qobject_cast<QQuickControl *>(p)) + return control->font(); + else if (QQuickLabel *label = qobject_cast<QQuickLabel *>(p)) + return label->font(); + else if (QQuickTextField *textField = qobject_cast<QQuickTextField *>(p)) + return textField->font(); + else if (QQuickTextArea *textArea = qobject_cast<QQuickTextArea *>(p)) + return textArea->font(); + + p = p->parentItem(); + } + + if (QQuickApplicationWindow *window = qobject_cast<QQuickApplicationWindow *>(item->window())) + return window->font(); + + return QQuickTheme::font(QQuickTheme::System); +} + +/*! + \internal + + Determine which font is implicitly imposed on this control by its ancestors + and QGuiApplication::font, resolve this against its own font (attributes from + the implicit font are copied over). Then propagate this font to this + control's children. +*/ +void QQuickControlPrivate::resolveFont() +{ + Q_Q(QQuickControl); + inheritFont(parentFont(q)); +} + +void QQuickControlPrivate::inheritFont(const QFont &font) +{ + Q_Q(QQuickControl); + QFont parentFont = extra.isAllocated() ? extra->requestedFont.resolve(font) : font; + parentFont.setResolveMask(extra.isAllocated() ? extra->requestedFont.resolveMask() | font.resolveMask() : font.resolveMask()); + + const QFont defaultFont = q->defaultFont(); + QFont resolvedFont = parentFont.resolve(defaultFont); + + setFont_helper(resolvedFont); +} + +/*! + \internal + + Assign \a font to this control, and propagate it to all children. +*/ +void QQuickControlPrivate::updateFont(const QFont &font) +{ + Q_Q(QQuickControl); + QFont oldFont = resolvedFont; + resolvedFont = font; + + if (oldFont != font) + q->fontChange(font, oldFont); + + QQuickControlPrivate::updateFontRecur(q, font); + + if (oldFont != font) + emit q->fontChanged(); +} + +void QQuickControlPrivate::updateFontRecur(QQuickItem *item, const QFont &font) +{ + const auto childItems = item->childItems(); + for (QQuickItem *child : childItems) { + if (QQuickControl *control = qobject_cast<QQuickControl *>(child)) + QQuickControlPrivate::get(control)->inheritFont(font); + else if (QQuickLabel *label = qobject_cast<QQuickLabel *>(child)) + QQuickLabelPrivate::get(label)->inheritFont(font); + else if (QQuickTextArea *textArea = qobject_cast<QQuickTextArea *>(child)) + QQuickTextAreaPrivate::get(textArea)->inheritFont(font); + else if (QQuickTextField *textField = qobject_cast<QQuickTextField *>(child)) + QQuickTextFieldPrivate::get(textField)->inheritFont(font); + else + QQuickControlPrivate::updateFontRecur(child, font); + } +} + +QLocale QQuickControlPrivate::calcLocale(const QQuickItem *item) +{ + const QQuickItem *p = item; + while (p) { + if (const QQuickControl *control = qobject_cast<const QQuickControl *>(p)) + return control->locale(); + + QVariant v = p->property("locale"); + if (v.isValid() && v.userType() == QMetaType::QLocale) + return v.toLocale(); + + p = p->parentItem(); + } + + if (item) { + if (QQuickApplicationWindow *window = qobject_cast<QQuickApplicationWindow *>(item->window())) + return window->locale(); + } + + return QLocale(); +} + +void QQuickControlPrivate::updateLocale(const QLocale &l, bool e) +{ + Q_Q(QQuickControl); + if (!e && hasLocale) + return; + + QLocale old = q->locale(); + hasLocale = e; + if (old != l) { + locale = l; + q->localeChange(l, old); + QQuickControlPrivate::updateLocaleRecur(q, l); + emit q->localeChanged(); + } +} + +void QQuickControlPrivate::updateLocaleRecur(QQuickItem *item, const QLocale &l) +{ + const auto childItems = item->childItems(); + for (QQuickItem *child : childItems) { + if (QQuickControl *control = qobject_cast<QQuickControl *>(child)) + QQuickControlPrivate::get(control)->updateLocale(l, false); + else + updateLocaleRecur(child, l); + } +} + +#if QT_CONFIG(quicktemplates2_hover) +void QQuickControlPrivate::updateHoverEnabled(bool enabled, bool xplicit) +{ + Q_Q(QQuickControl); + if (!xplicit && explicitHoverEnabled) + return; + + bool wasEnabled = q->isHoverEnabled(); + explicitHoverEnabled = xplicit; + if (wasEnabled != enabled) { + q->setAcceptHoverEvents(enabled); + QQuickControlPrivate::updateHoverEnabledRecur(q, enabled); + emit q->hoverEnabledChanged(); + } +} + +void QQuickControlPrivate::updateHoverEnabledRecur(QQuickItem *item, bool enabled) +{ + const auto childItems = item->childItems(); + for (QQuickItem *child : childItems) { + if (QQuickControl *control = qobject_cast<QQuickControl *>(child)) + QQuickControlPrivate::get(control)->updateHoverEnabled(enabled, false); + else + updateHoverEnabledRecur(child, enabled); + } +} + +bool QQuickControlPrivate::calcHoverEnabled(const QQuickItem *item) +{ + const QQuickItem *p = item; + while (p) { + // QQuickPopupItem accepts hover events to avoid leaking them through. + // Don't inherit that to the children of the popup, but fallback to the + // environment variable or style hint. + if (qobject_cast<const QQuickPopupItem *>(p)) + break; + + if (const QQuickControl *control = qobject_cast<const QQuickControl *>(p)) + return control->isHoverEnabled(); + + QVariant v = p->property("hoverEnabled"); + if (v.isValid() && v.userType() == QMetaType::Bool) + return v.toBool(); + + p = p->parentItem(); + } + + bool ok = false; + int env = qEnvironmentVariableIntValue("QT_QUICK_CONTROLS_HOVER_ENABLED", &ok); + if (ok) + return env != 0; + + // TODO: QQuickApplicationWindow::isHoverEnabled() + + return QGuiApplication::styleHints()->useHoverEffects(); +} +#endif + +static inline QString contentItemName() { return QStringLiteral("contentItem"); } + +void QQuickControlPrivate::cancelContentItem() +{ + Q_Q(QQuickControl); + quickCancelDeferred(q, contentItemName()); +} + +void QQuickControlPrivate::executeContentItem(bool complete) +{ + Q_Q(QQuickControl); + if (contentItem.wasExecuted()) + return; + + if (!contentItem || complete) + quickBeginDeferred(q, contentItemName(), contentItem); + if (complete) + quickCompleteDeferred(q, contentItemName(), contentItem); +} + +static inline QString backgroundName() { return QStringLiteral("background"); } + +void QQuickControlPrivate::cancelBackground() +{ + Q_Q(QQuickControl); + quickCancelDeferred(q, backgroundName()); +} + +void QQuickControlPrivate::executeBackground(bool complete) +{ + Q_Q(QQuickControl); + if (background.wasExecuted()) + return; + + if (!background || complete) + quickBeginDeferred(q, backgroundName(), background); + if (complete) + quickCompleteDeferred(q, backgroundName(), background); +} + +/* + \internal + + Hides an item that was replaced by a newer one, rather than + deleting it, as the item is typically created in QML and hence + we don't own it. +*/ +void QQuickControlPrivate::hideOldItem(QQuickItem *item) +{ + if (!item) + return; + + qCDebug(lcItemManagement) << "hiding old item" << item; + + item->setVisible(false); + item->setParentItem(nullptr); + +#if QT_CONFIG(accessibility) + // Remove the item from the accessibility tree. + QQuickAccessibleAttached *accessible = accessibleAttached(item); + if (accessible) + accessible->setIgnored(true); +#endif +} + +/* + \internal + + Named "unhide" because it's used for cases where an item + that was previously hidden by \l hideOldItem() wants to be + shown by a control again, such as a ScrollBar in ScrollView. +*/ +void QQuickControlPrivate::unhideOldItem(QQuickControl *control, QQuickItem *item) +{ + Q_ASSERT(item); + qCDebug(lcItemManagement) << "unhiding old item" << item; + + item->setVisible(true); + item->setParentItem(control); + +#if QT_CONFIG(accessibility) + // Add the item back in to the accessibility tree. + QQuickAccessibleAttached *accessible = accessibleAttached(item); + if (accessible) + accessible->setIgnored(false); +#endif +} + +void QQuickControlPrivate::updateBaselineOffset() +{ + Q_Q(QQuickControl); + if (extra.isAllocated() && extra.value().hasBaselineOffset) + return; + + if (!contentItem) + q->QQuickItem::setBaselineOffset(0); + else + q->QQuickItem::setBaselineOffset(getTopPadding() + contentItem->baselineOffset()); +} + +void QQuickControlPrivate::addImplicitSizeListener(QQuickItem *item, ChangeTypes changes) +{ + addImplicitSizeListener(item, this, changes); +} + +void QQuickControlPrivate::removeImplicitSizeListener(QQuickItem *item, ChangeTypes changes) +{ + removeImplicitSizeListener(item, this, changes); +} + +void QQuickControlPrivate::addImplicitSizeListener(QQuickItem *item, QQuickItemChangeListener *listener, ChangeTypes changes) +{ + if (!item || !listener) + return; + QQuickItemPrivate::get(item)->addItemChangeListener(listener, changes); +} + +void QQuickControlPrivate::removeImplicitSizeListener(QQuickItem *item, QQuickItemChangeListener *listener, ChangeTypes changes) +{ + if (!item || !listener) + return; + QQuickItemPrivate::get(item)->removeItemChangeListener(listener, changes); +} + +void QQuickControlPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickControl); + if (item == background) + emit q->implicitBackgroundWidthChanged(); + else if (item == contentItem) + updateImplicitContentWidth(); +} + +void QQuickControlPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickControl); + if (item == background) + emit q->implicitBackgroundHeightChanged(); + else if (item == contentItem) + updateImplicitContentHeight(); +} + +void QQuickControlPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + Q_UNUSED(diff); + if (resizingBackground || item != background || !change.sizeChange()) + return; + + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + // Only set hasBackgroundWidth/Height if it was a width/height change, + // otherwise we're prevented from setting a width/height in the future. + if (change.widthChange()) + extra.value().hasBackgroundWidth = p->widthValid(); + if (change.heightChange()) + extra.value().hasBackgroundHeight = p->heightValid(); + resizeBackground(); +} + +void QQuickControlPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickControl); + if (item == background) { + background = nullptr; + emit q->implicitBackgroundWidthChanged(); + emit q->implicitBackgroundHeightChanged(); + } else if (item == contentItem) { + contentItem = nullptr; + updateImplicitContentSize(); + } +} + +void QQuickControlPrivate::itemFocusChanged(QQuickItem *item, Qt::FocusReason reason) +{ + Q_Q(QQuickControl); + if (item == contentItem || item == q) + q->setFocusReason(reason); +} + +QQuickControl::QQuickControl(QQuickItem *parent) + : QQuickItem(*(new QQuickControlPrivate), parent) +{ + Q_D(QQuickControl); + d->init(); +} + +QQuickControl::QQuickControl(QQuickControlPrivate &dd, QQuickItem *parent) + : QQuickItem(dd, parent) +{ + Q_D(QQuickControl); + d->init(); +} + +QQuickControl::~QQuickControl() +{ + Q_D(QQuickControl); + d->removeImplicitSizeListener(d->background, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + d->removeImplicitSizeListener(d->contentItem); + if (d->contentItem) + QQuickItemPrivate::get(d->contentItem)->removeItemChangeListener(d, QQuickItemPrivate::Focus); +#if QT_CONFIG(accessibility) + QAccessible::removeActivationObserver(d); +#endif +} + +void QQuickControl::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +{ + Q_D(QQuickControl); + QQuickItem::itemChange(change, value); + switch (change) { + case ItemEnabledHasChanged: + enabledChange(); + break; + case ItemVisibleHasChanged: +#if QT_CONFIG(quicktemplates2_hover) + if (!value.boolValue) + setHovered(false); +#endif + break; + case ItemSceneChange: + case ItemParentHasChanged: + if ((change == ItemParentHasChanged && value.item) || (change == ItemSceneChange && value.window)) { + d->resolveFont(); + if (!d->hasLocale) + d->updateLocale(QQuickControlPrivate::calcLocale(d->parentItem), false); // explicit=false +#if QT_CONFIG(quicktemplates2_hover) + if (!d->explicitHoverEnabled) + d->updateHoverEnabled(QQuickControlPrivate::calcHoverEnabled(d->parentItem), false); // explicit=false +#endif + } + break; + case ItemActiveFocusHasChanged: + if (isKeyFocusReason(d->focusReason)) + emit visualFocusChanged(); + break; + default: + break; + } +} + +/*! + \qmlproperty font QtQuick.Controls::Control::font + + This property holds the font currently set for the control. + + This property describes the control's requested font. The font is used by the control's + style when rendering standard components, and is available as a means to ensure that custom + controls can maintain consistency with the native platform's native look and feel. It's common + that different platforms, or different styles, define different fonts for an application. + + The default font depends on the system environment. ApplicationWindow maintains a system/theme + font which serves as a default for all controls. There may also be special font defaults for + certain types of controls. You can also set the default font for controls by either: + + \list + \li passing a custom font to QGuiApplication::setFont(), before loading the QML; or + \li specifying the fonts in the \l {Qt Quick Controls 2 Configuration File}{qtquickcontrols2.conf file}. + \endlist + + Finally, the font is matched against Qt's font database to find the best match. + + Control propagates explicit font properties from parent to children. If you change a specific + property on a control's font, that property propagates to all of the control's children, + overriding any system defaults for that property. + + \code + Page { + font.family: "Courier" + + Column { + Label { + text: qsTr("This will use Courier...") + } + + Switch { + text: qsTr("... and so will this") + } + } + } + \endcode + + For the full list of available font properties, see the + \l [QtQuick]{font}{font QML Basic Type} documentation. +*/ +QFont QQuickControl::font() const +{ + Q_D(const QQuickControl); + return d->resolvedFont; +} + +void QQuickControl::setFont(const QFont &font) +{ + Q_D(QQuickControl); + if (d->extra.value().requestedFont.resolveMask() == font.resolveMask() && d->extra.value().requestedFont == font) + return; + + d->extra.value().requestedFont = font; + d->resolveFont(); +} + +void QQuickControl::resetFont() +{ + setFont(QFont()); +} + +/*! + \qmlproperty real QtQuick.Controls::Control::availableWidth + \readonly + + This property holds the width available to the \l contentItem after + deducting horizontal padding from the \l {Item::}{width} of the control. + + \sa {Control Layout}, padding, leftPadding, rightPadding +*/ +qreal QQuickControl::availableWidth() const +{ + return qMax<qreal>(0.0, width() - leftPadding() - rightPadding()); +} + +/*! + \qmlproperty real QtQuick.Controls::Control::availableHeight + \readonly + + This property holds the height available to the \l contentItem after + deducting vertical padding from the \l {Item::}{height} of the control. + + \sa {Control Layout}, padding, topPadding, bottomPadding +*/ +qreal QQuickControl::availableHeight() const +{ + return qMax<qreal>(0.0, height() - topPadding() - bottomPadding()); +} + +/*! + \qmlproperty real QtQuick.Controls::Control::padding + + This property holds the default padding. + + Padding adds a space between each edge of the content item and the + background item, effectively controlling the size of the content item. To + specify a padding value for a specific edge of the control, set its + relevant property: + + \list + \li \l {Control::}{leftPadding} + \li \l {Control::}{rightPadding} + \li \l {Control::}{topPadding} + \li \l {Control::}{bottomPadding} + \endlist + + \note Different styles may specify the default padding for certain controls + in different ways, and these ways may change over time as the design + guidelines that the style is based on evolve. To ensure that these changes + don't affect the padding values you have specified, it is best to use the + most specific properties available. For example, rather than setting + the \l padding property: + + \code + padding: 0 + \endcode + + set each specific property instead: + + \code + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + bottomPadding: 0 + \endcode + + \sa {Control Layout}, availableWidth, availableHeight, topPadding, leftPadding, rightPadding, bottomPadding +*/ +qreal QQuickControl::padding() const +{ + Q_D(const QQuickControl); + return d->padding; +} + +void QQuickControl::setPadding(qreal padding) +{ + Q_D(QQuickControl); + if (qFuzzyCompare(d->padding, padding)) + return; + + const QMarginsF oldPadding = d->getPadding(); + const qreal oldVerticalPadding = d->getVerticalPadding(); + const qreal oldHorizontalPadding = d->getHorizontalPadding(); + + d->padding = padding; + emit paddingChanged(); + + const QMarginsF newPadding = d->getPadding(); + const qreal newVerticalPadding = d->getVerticalPadding(); + const qreal newHorizontalPadding = d->getHorizontalPadding(); + + if (!qFuzzyCompare(newPadding.top(), oldPadding.top())) + emit topPaddingChanged(); + if (!qFuzzyCompare(newPadding.left(), oldPadding.left())) + emit leftPaddingChanged(); + if (!qFuzzyCompare(newPadding.right(), oldPadding.right())) + emit rightPaddingChanged(); + if (!qFuzzyCompare(newPadding.bottom(), oldPadding.bottom())) + emit bottomPaddingChanged(); + if (!qFuzzyCompare(newVerticalPadding, oldVerticalPadding)) + emit verticalPaddingChanged(); + if (!qFuzzyCompare(newHorizontalPadding, oldHorizontalPadding)) + emit horizontalPaddingChanged(); + if (!qFuzzyCompare(newPadding.top(), oldPadding.top()) || !qFuzzyCompare(newPadding.bottom(), oldPadding.bottom())) + emit availableHeightChanged(); + if (!qFuzzyCompare(newPadding.left(), oldPadding.left()) || !qFuzzyCompare(newPadding.right(), oldPadding.right())) + emit availableWidthChanged(); + + paddingChange(newPadding, oldPadding); +} + +void QQuickControl::resetPadding() +{ + setPadding(0); +} + +/*! + \qmlproperty real QtQuick.Controls::Control::topPadding + + This property holds the top padding. Unless explicitly set, the value + is equal to \c verticalPadding. + + \sa {Control Layout}, padding, bottomPadding, verticalPadding, availableHeight +*/ +qreal QQuickControl::topPadding() const +{ + Q_D(const QQuickControl); + return d->getTopPadding(); +} + +void QQuickControl::setTopPadding(qreal padding) +{ + Q_D(QQuickControl); + d->setTopPadding(padding); +} + +void QQuickControl::resetTopPadding() +{ + Q_D(QQuickControl); + d->setTopPadding(0, true); +} + +/*! + \qmlproperty real QtQuick.Controls::Control::leftPadding + + This property holds the left padding. Unless explicitly set, the value + is equal to \c horizontalPadding. + + \sa {Control Layout}, padding, rightPadding, horizontalPadding, availableWidth +*/ +qreal QQuickControl::leftPadding() const +{ + Q_D(const QQuickControl); + return d->getLeftPadding(); +} + +void QQuickControl::setLeftPadding(qreal padding) +{ + Q_D(QQuickControl); + d->setLeftPadding(padding); +} + +void QQuickControl::resetLeftPadding() +{ + Q_D(QQuickControl); + d->setLeftPadding(0, true); +} + +/*! + \qmlproperty real QtQuick.Controls::Control::rightPadding + + This property holds the right padding. Unless explicitly set, the value + is equal to \c horizontalPadding. + + \sa {Control Layout}, padding, leftPadding, horizontalPadding, availableWidth +*/ +qreal QQuickControl::rightPadding() const +{ + Q_D(const QQuickControl); + return d->getRightPadding(); +} + +void QQuickControl::setRightPadding(qreal padding) +{ + Q_D(QQuickControl); + d->setRightPadding(padding); +} + +void QQuickControl::resetRightPadding() +{ + Q_D(QQuickControl); + d->setRightPadding(0, true); +} + +/*! + \qmlproperty real QtQuick.Controls::Control::bottomPadding + + This property holds the bottom padding. Unless explicitly set, the value + is equal to \c verticalPadding. + + \sa {Control Layout}, padding, topPadding, verticalPadding, availableHeight +*/ +qreal QQuickControl::bottomPadding() const +{ + Q_D(const QQuickControl); + return d->getBottomPadding(); +} + +void QQuickControl::setBottomPadding(qreal padding) +{ + Q_D(QQuickControl); + d->setBottomPadding(padding); +} + +void QQuickControl::resetBottomPadding() +{ + Q_D(QQuickControl); + d->setBottomPadding(0, true); +} + +/*! + \qmlproperty real QtQuick.Controls::Control::spacing + + This property holds the spacing. + + Spacing is useful for controls that have multiple or repetitive building + blocks. For example, some styles use spacing to determine the distance + between the text and indicator of \l CheckBox. Spacing is not enforced by + Control, so each style may interpret it differently, and some may ignore it + altogether. +*/ +qreal QQuickControl::spacing() const +{ + Q_D(const QQuickControl); + return d->spacing; +} + +void QQuickControl::setSpacing(qreal spacing) +{ + Q_D(QQuickControl); + if (qFuzzyCompare(d->spacing, spacing)) + return; + + qreal oldSpacing = d->spacing; + d->spacing = spacing; + emit spacingChanged(); + spacingChange(spacing, oldSpacing); +} + +void QQuickControl::resetSpacing() +{ + setSpacing(0); +} + +/*! + \qmlproperty Locale QtQuick.Controls::Control::locale + + This property holds the locale of the control. + + It contains locale specific properties for formatting data and numbers. + Unless a special locale has been set, this is either the parent's locale + or the default locale. + + Control propagates the locale from parent to children. If you change the + control's locale, that locale propagates to all of the control's children, + overriding the system default locale. + + \sa mirrored +*/ +QLocale QQuickControl::locale() const +{ + Q_D(const QQuickControl); + return d->locale; +} + +void QQuickControl::setLocale(const QLocale &locale) +{ + Q_D(QQuickControl); + if (d->hasLocale && d->locale == locale) + return; + + d->updateLocale(locale, true); // explicit=true +} + +void QQuickControl::resetLocale() +{ + Q_D(QQuickControl); + if (!d->hasLocale) + return; + + d->hasLocale = false; + d->updateLocale(QQuickControlPrivate::calcLocale(d->parentItem), false); // explicit=false +} + +/*! + \qmlproperty bool QtQuick.Controls::Control::mirrored + \readonly + + This property holds whether the control is mirrored. + + This property is provided for convenience. A control is considered mirrored + when its visual layout direction is right-to-left; that is, when + \l {LayoutMirroring::enabled}{LayoutMirroring.enabled} is \c true. + + As of Qt 6.2, the \l locale property no longer affects this property. + + \sa {LayoutMirroring}{LayoutMirroring}, {Right-to-left User Interfaces} +*/ +bool QQuickControl::isMirrored() const +{ + Q_D(const QQuickControl); + return d->isMirrored(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Control::focusPolicy + + This property determines the way the control accepts focus. + + \value Qt.TabFocus The control accepts focus by tabbing. + \value Qt.ClickFocus The control accepts focus by clicking. + \value Qt.StrongFocus The control accepts focus by both tabbing and clicking. + \value Qt.WheelFocus The control accepts focus by tabbing, clicking, and using the mouse wheel. + \value Qt.NoFocus The control does not accept focus. +*/ +Qt::FocusPolicy QQuickControl::focusPolicy() const +{ + Q_D(const QQuickControl); + uint policy = d->focusPolicy; + if (activeFocusOnTab()) + policy |= Qt::TabFocus; + return static_cast<Qt::FocusPolicy>(policy); +} + +void QQuickControl::setFocusPolicy(Qt::FocusPolicy policy) +{ + Q_D(QQuickControl); + if (d->focusPolicy == policy) + return; + + d->focusPolicy = policy; + setActiveFocusOnTab(policy & Qt::TabFocus); + emit focusPolicyChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Control::focusReason + \readonly + + \include qquickcontrol-focusreason.qdocinc + + \sa visualFocus +*/ +Qt::FocusReason QQuickControl::focusReason() const +{ + Q_D(const QQuickControl); + return d->focusReason; +} + +void QQuickControl::setFocusReason(Qt::FocusReason reason) +{ + Q_D(QQuickControl); + if (d->focusReason == reason) + return; + + Qt::FocusReason oldReason = d->focusReason; + d->focusReason = reason; + emit focusReasonChanged(); + if (isKeyFocusReason(oldReason) != isKeyFocusReason(reason)) + emit visualFocusChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Control::visualFocus + \readonly + + This property holds whether the control has visual focus. This property + is \c true when the control has active focus and the focus reason is either + \c Qt.TabFocusReason, \c Qt.BacktabFocusReason, or \c Qt.ShortcutFocusReason. + + In general, for visualizing key focus, this property is preferred over + \l Item::activeFocus. This ensures that key focus is only visualized when + interacting with keys - not when interacting via touch or mouse. + + \sa focusReason, Item::activeFocus +*/ +bool QQuickControl::hasVisualFocus() const +{ + Q_D(const QQuickControl); + return d->activeFocus && isKeyFocusReason(d->focusReason); +} + +/*! + \qmlproperty bool QtQuick.Controls::Control::hovered + \readonly + + This property holds whether the control is hovered. + + \sa hoverEnabled +*/ +bool QQuickControl::isHovered() const +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(const QQuickControl); + return d->hovered; +#else + return false; +#endif +} + +void QQuickControl::setHovered(bool hovered) +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickControl); + if (hovered == d->hovered) + return; + + d->hovered = hovered; + emit hoveredChanged(); + hoverChange(); +#else + Q_UNUSED(hovered); +#endif +} + +/*! + \qmlproperty bool QtQuick.Controls::Control::hoverEnabled + + This property determines whether the control accepts hover events. The default value + is \c Qt.styleHints.useHoverEffects. + + Setting this property propagates the value to all child controls that do not have + \c hoverEnabled explicitly set. + + You can also enable or disable hover effects for all Qt Quick Controls applications + by setting the \c QT_QUICK_CONTROLS_HOVER_ENABLED \l {Supported Environment Variables + in Qt Quick Controls}{environment variable}. + + \sa hovered +*/ +bool QQuickControl::isHoverEnabled() const +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(const QQuickControl); + return d->hoverEnabled; +#else + return false; +#endif +} + +void QQuickControl::setHoverEnabled(bool enabled) +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickControl); + if (d->explicitHoverEnabled && enabled == d->hoverEnabled) + return; + + d->updateHoverEnabled(enabled, true); // explicit=true +#else + Q_UNUSED(enabled); +#endif +} + +void QQuickControl::resetHoverEnabled() +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickControl); + if (!d->explicitHoverEnabled) + return; + + d->explicitHoverEnabled = false; + d->updateHoverEnabled(QQuickControlPrivate::calcHoverEnabled(d->parentItem), false); // explicit=false +#endif +} + +/*! + \qmlproperty bool QtQuick.Controls::Control::wheelEnabled + + This property determines whether the control handles wheel events. The default value is \c false. + + \note Care must be taken when enabling wheel events for controls within scrollable items such + as \l Flickable, as the control will consume the events and hence interrupt scrolling of the + Flickable. +*/ +bool QQuickControl::isWheelEnabled() const +{ + Q_D(const QQuickControl); + return d->wheelEnabled; +} + +void QQuickControl::setWheelEnabled(bool enabled) +{ + Q_D(QQuickControl); + if (d->wheelEnabled == enabled) + return; + + d->wheelEnabled = enabled; + emit wheelEnabledChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::Control::background + + This property holds the background item. + + \code + Button { + id: control + text: qsTr("Button") + background: Rectangle { + implicitWidth: 100 + implicitHeight: 40 + opacity: enabled ? 1 : 0.3 + color: control.down ? "#d0d0d0" : "#e0e0e0" + } + } + \endcode + + \input qquickcontrol-background.qdocinc notes + + \sa {Control Layout} +*/ +QQuickItem *QQuickControl::background() const +{ + QQuickControlPrivate *d = const_cast<QQuickControlPrivate *>(d_func()); + if (!d->background) + d->executeBackground(); + return d->background; +} + +void QQuickControl::setBackground(QQuickItem *background) +{ + Q_D(QQuickControl); + if (d->background == background) + return; + + if (!d->background.isExecuting()) + d->cancelBackground(); + + const qreal oldImplicitBackgroundWidth = implicitBackgroundWidth(); + const qreal oldImplicitBackgroundHeight = implicitBackgroundHeight(); + + if (d->extra.isAllocated()) { + d->extra.value().hasBackgroundWidth = false; + d->extra.value().hasBackgroundHeight = false; + } + + d->removeImplicitSizeListener(d->background, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + QQuickControlPrivate::hideOldItem(d->background); + d->background = background; + + if (background) { + background->setParentItem(this); + if (qFuzzyIsNull(background->z())) + background->setZ(-1); + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + if (p->widthValid() || p->heightValid()) { + d->extra.value().hasBackgroundWidth = p->widthValid(); + d->extra.value().hasBackgroundHeight = p->heightValid(); + } + if (isComponentComplete()) + d->resizeBackground(); + d->addImplicitSizeListener(background, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + } + + if (!qFuzzyCompare(oldImplicitBackgroundWidth, implicitBackgroundWidth())) + emit implicitBackgroundWidthChanged(); + if (!qFuzzyCompare(oldImplicitBackgroundHeight, implicitBackgroundHeight())) + emit implicitBackgroundHeightChanged(); + if (!d->background.isExecuting()) + emit backgroundChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::Control::contentItem + + This property holds the visual content item. + + \code + Button { + id: control + text: qsTr("Button") + contentItem: Label { + text: control.text + font: control.font + verticalAlignment: Text.AlignVCenter + } + } + \endcode + + \note The content item is automatically positioned and resized to fit + within the \l padding of the control. Bindings to the + \l[QtQuick]{Item::}{x}, \l[QtQuick]{Item::}{y}, + \l[QtQuick]{Item::}{width}, and \l[QtQuick]{Item::}{height} + properties of the contentItem are not respected. + + \note Most controls use the implicit size of the content item to calculate + the implicit size of the control itself. If you replace the content item + with a custom one, you should also consider providing a sensible implicit + size for it (unless it is an item like \l Text which has its own implicit + size). + + \sa {Control Layout}, padding +*/ +QQuickItem *QQuickControl::contentItem() const +{ + QQuickControlPrivate *d = const_cast<QQuickControlPrivate *>(d_func()); + if (!d->contentItem) + d->setContentItem_helper(d->getContentItem(), false); + return d->contentItem; +} + +void QQuickControl::setContentItem(QQuickItem *item) +{ + Q_D(QQuickControl); + d->setContentItem_helper(item, true); +} + +qreal QQuickControl::baselineOffset() const +{ + Q_D(const QQuickControl); + return d->baselineOffset; +} + +void QQuickControl::setBaselineOffset(qreal offset) +{ + Q_D(QQuickControl); + d->extra.value().hasBaselineOffset = true; + QQuickItem::setBaselineOffset(offset); +} + +void QQuickControl::resetBaselineOffset() +{ + Q_D(QQuickControl); + if (!d->extra.isAllocated() || !d->extra.value().hasBaselineOffset) + return; + + if (d->extra.isAllocated()) + d->extra.value().hasBaselineOffset = false; + d->updateBaselineOffset(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::horizontalPadding + + This property holds the horizontal padding. Unless explicitly set, the value + is equal to \c padding. + + \sa {Control Layout}, padding, leftPadding, rightPadding, verticalPadding +*/ +qreal QQuickControl::horizontalPadding() const +{ + Q_D(const QQuickControl); + return d->getHorizontalPadding(); +} + +void QQuickControl::setHorizontalPadding(qreal padding) +{ + Q_D(QQuickControl); + d->setHorizontalPadding(padding); +} + +void QQuickControl::resetHorizontalPadding() +{ + Q_D(QQuickControl); + d->setHorizontalPadding(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::verticalPadding + + This property holds the vertical padding. Unless explicitly set, the value + is equal to \c padding. + + \sa {Control Layout}, padding, topPadding, bottomPadding, horizontalPadding +*/ +qreal QQuickControl::verticalPadding() const +{ + Q_D(const QQuickControl); + return d->getVerticalPadding(); +} + +void QQuickControl::setVerticalPadding(qreal padding) +{ + Q_D(QQuickControl); + d->setVerticalPadding(padding); +} + +void QQuickControl::resetVerticalPadding() +{ + Q_D(QQuickControl); + d->setVerticalPadding(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::implicitContentWidth + \readonly + + This property holds the implicit content width. + + For basic controls, the value is equal to \c {contentItem ? contentItem.implicitWidth : 0}. + For types that inherit Container or Pane, the value is calculated based on the content children. + + This is typically used, together with \l implicitBackgroundWidth, to calculate + the \l {Item::}{implicitWidth}: + + \code + Control { + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + } + \endcode + + \sa implicitContentHeight, implicitBackgroundWidth +*/ +qreal QQuickControl::implicitContentWidth() const +{ + Q_D(const QQuickControl); + return d->implicitContentWidth; +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::implicitContentHeight + \readonly + + This property holds the implicit content height. + + For basic controls, the value is equal to \c {contentItem ? contentItem.implicitHeight : 0}. + For types that inherit Container or Pane, the value is calculated based on the content children. + + This is typically used, together with \l implicitBackgroundHeight, to calculate + the \l {Item::}{implicitHeight}: + + \code + Control { + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + } + \endcode + + \sa implicitContentWidth, implicitBackgroundHeight +*/ +qreal QQuickControl::implicitContentHeight() const +{ + Q_D(const QQuickControl); + return d->implicitContentHeight; +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::implicitBackgroundWidth + \readonly + + This property holds the implicit background width. + + The value is equal to \c {background ? background.implicitWidth : 0}. + + This is typically used, together with \l implicitContentWidth, to calculate + the \l {Item::}{implicitWidth}: + + \code + Control { + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + } + \endcode + + \sa implicitBackgroundHeight, implicitContentWidth +*/ +qreal QQuickControl::implicitBackgroundWidth() const +{ + Q_D(const QQuickControl); + if (!d->background) + return 0; + return d->background->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::implicitBackgroundHeight + \readonly + + This property holds the implicit background height. + + The value is equal to \c {background ? background.implicitHeight : 0}. + + This is typically used, together with \l implicitContentHeight, to calculate + the \l {Item::}{implicitHeight}: + + \code + Control { + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + } + \endcode + + \sa implicitBackgroundWidth, implicitContentHeight +*/ +qreal QQuickControl::implicitBackgroundHeight() const +{ + Q_D(const QQuickControl); + if (!d->background) + return 0; + return d->background->implicitHeight(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::topInset + + This property holds the top inset for the background. + + \sa {Control Layout}, bottomInset +*/ +qreal QQuickControl::topInset() const +{ + Q_D(const QQuickControl); + return d->getTopInset(); +} + +void QQuickControl::setTopInset(qreal inset) +{ + Q_D(QQuickControl); + d->setTopInset(inset); +} + +void QQuickControl::resetTopInset() +{ + Q_D(QQuickControl); + d->setTopInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::leftInset + + This property holds the left inset for the background. + + \sa {Control Layout}, rightInset +*/ +qreal QQuickControl::leftInset() const +{ + Q_D(const QQuickControl); + return d->getLeftInset(); +} + +void QQuickControl::setLeftInset(qreal inset) +{ + Q_D(QQuickControl); + d->setLeftInset(inset); +} + +void QQuickControl::resetLeftInset() +{ + Q_D(QQuickControl); + d->setLeftInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::rightInset + + This property holds the right inset for the background. + + \sa {Control Layout}, leftInset +*/ +qreal QQuickControl::rightInset() const +{ + Q_D(const QQuickControl); + return d->getRightInset(); +} + +void QQuickControl::setRightInset(qreal inset) +{ + Q_D(QQuickControl); + d->setRightInset(inset); +} + +void QQuickControl::resetRightInset() +{ + Q_D(QQuickControl); + d->setRightInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Control::bottomInset + + This property holds the bottom inset for the background. + + \sa {Control Layout}, topInset +*/ +qreal QQuickControl::bottomInset() const +{ + Q_D(const QQuickControl); + return d->getBottomInset(); +} + +void QQuickControl::setBottomInset(qreal inset) +{ + Q_D(QQuickControl); + d->setBottomInset(inset); +} + +void QQuickControl::resetBottomInset() +{ + Q_D(QQuickControl); + d->setBottomInset(0, true); +} + +void QQuickControl::classBegin() +{ + Q_D(QQuickControl); + QQuickItem::classBegin(); + d->resolveFont(); +} + +void QQuickControl::componentComplete() +{ + Q_D(QQuickControl); + d->executeBackground(true); + d->executeContentItem(true); + QQuickItem::componentComplete(); + d->resizeBackground(); + d->resizeContent(); + d->updateBaselineOffset(); + if (!d->hasLocale) + d->locale = QQuickControlPrivate::calcLocale(d->parentItem); +#if QT_CONFIG(quicktemplates2_hover) + if (!d->explicitHoverEnabled) + setAcceptHoverEvents(QQuickControlPrivate::calcHoverEnabled(d->parentItem)); +#endif +#if QT_CONFIG(accessibility) + if (QAccessible::isActive()) + accessibilityActiveChanged(true); +#endif +} + +QFont QQuickControl::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::System); +} + +void QQuickControl::focusInEvent(QFocusEvent *event) +{ + QQuickItem::focusInEvent(event); + setFocusReason(event->reason()); +} + +void QQuickControl::focusOutEvent(QFocusEvent *event) +{ + QQuickItem::focusOutEvent(event); + setFocusReason(event->reason()); +} + +#if QT_CONFIG(quicktemplates2_hover) +void QQuickControl::hoverEnterEvent(QHoverEvent *event) +{ + Q_D(QQuickControl); + setHovered(d->hoverEnabled); + event->ignore(); +} + +void QQuickControl::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QQuickControl); + setHovered(d->hoverEnabled && contains(event->position())); + event->ignore(); +} + +void QQuickControl::hoverLeaveEvent(QHoverEvent *event) +{ + setHovered(false); + event->ignore(); +} +#endif + +void QQuickControl::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickControl); + d->handlePress(event->position()); + if (event->source() == Qt::MouseEventSynthesizedByQt) { + d->pressWasTouch = true; + d->previousPressPos = event->position(); + } + event->accept(); +} + +void QQuickControl::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickControl); + d->handleMove(event->position()); + event->accept(); +} + +void QQuickControl::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickControl); + d->handleRelease(event->position()); + event->accept(); +} + +void QQuickControl::mouseUngrabEvent() +{ + Q_D(QQuickControl); + d->handleUngrab(); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickControl::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickControl); + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + for (const QTouchEvent::TouchPoint &point : event->points()) { + if (!d->acceptTouch(point)) + continue; + + switch (point.state()) { + case QEventPoint::Pressed: + d->handlePress(point.position()); + break; + case QEventPoint::Updated: + d->handleMove(point.position()); + break; + case QEventPoint::Released: + d->handleRelease(point.position()); + break; + default: + break; + } + } + break; + + case QEvent::TouchCancel: + d->handleUngrab(); + break; + + default: + QQuickItem::touchEvent(event); + break; + } +} + +void QQuickControl::touchUngrabEvent() +{ + Q_D(QQuickControl); + d->handleUngrab(); +} +#endif + +#if QT_CONFIG(wheelevent) +void QQuickControl::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickControl); + if ((d->focusPolicy & Qt::WheelFocus) == Qt::WheelFocus) + setActiveFocus(this, Qt::MouseFocusReason); + + event->setAccepted(d->wheelEnabled); +} +#endif + +void QQuickControl::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickControl); + QQuickItem::geometryChange(newGeometry, oldGeometry); + d->resizeBackground(); + d->resizeContent(); + if (!qFuzzyCompare(newGeometry.width(), oldGeometry.width())) + emit availableWidthChanged(); + if (!qFuzzyCompare(newGeometry.height(), oldGeometry.height())) + emit availableHeightChanged(); +} + +void QQuickControl::enabledChange() +{ +} + +void QQuickControl::fontChange(const QFont &newFont, const QFont &oldFont) +{ + Q_UNUSED(newFont); + Q_UNUSED(oldFont); +} + +#if QT_CONFIG(quicktemplates2_hover) +void QQuickControl::hoverChange() +{ +} +#endif + +void QQuickControl::mirrorChange() +{ + emit mirroredChanged(); +} + +void QQuickControl::spacingChange(qreal newSpacing, qreal oldSpacing) +{ + Q_UNUSED(newSpacing); + Q_UNUSED(oldSpacing); +} + +void QQuickControl::paddingChange(const QMarginsF &newPadding, const QMarginsF &oldPadding) +{ + Q_D(QQuickControl); + Q_UNUSED(newPadding); + Q_UNUSED(oldPadding); + d->resizeContent(); + d->updateBaselineOffset(); +} + +void QQuickControl::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_UNUSED(newItem); + Q_UNUSED(oldItem); +} + +void QQuickControl::localeChange(const QLocale &newLocale, const QLocale &oldLocale) +{ + Q_UNUSED(newLocale); + Q_UNUSED(oldLocale); +} + +void QQuickControl::insetChange(const QMarginsF &newInset, const QMarginsF &oldInset) +{ + Q_D(QQuickControl); + Q_UNUSED(newInset); + Q_UNUSED(oldInset); + d->resizeBackground(); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickControl::accessibleRole() const +{ + return QAccessible::NoRole; +} + +void QQuickControl::accessibilityActiveChanged(bool active) +{ + if (!active) + return; + + QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(this, true)); + Q_ASSERT(accessibleAttached); + accessibleAttached->setRole(accessibleRole()); +} +#endif + +QString QQuickControl::accessibleName() const +{ +#if QT_CONFIG(accessibility) + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this)) + return accessibleAttached->name(); +#endif + return QString(); +} + +void QQuickControl::maybeSetAccessibleName(const QString &name) +{ +#if QT_CONFIG(accessibility) + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this)) { + if (!accessibleAttached->wasNameExplicitlySet()) + accessibleAttached->setNameImplicitly(name); + } +#else + Q_UNUSED(name); +#endif +} + +QVariant QQuickControl::accessibleProperty(const char *propertyName) +{ +#if QT_CONFIG(accessibility) + if (QAccessible::isActive()) + return QQuickAccessibleAttached::property(this, propertyName); +#endif + Q_UNUSED(propertyName); + return QVariant(); +} + +bool QQuickControl::setAccessibleProperty(const char *propertyName, const QVariant &value) +{ +#if QT_CONFIG(accessibility) + if (QAccessible::isActive()) + return QQuickAccessibleAttached::setProperty(this, propertyName, value); +#endif + Q_UNUSED(propertyName); + Q_UNUSED(value); + return false; +} + +QT_END_NAMESPACE + +#include "moc_qquickcontrol_p.cpp" diff --git a/src/quicktemplates2/qquickcontrol_p.h b/src/quicktemplates2/qquickcontrol_p.h new file mode 100644 index 0000000000..79fb3915b0 --- /dev/null +++ b/src/quicktemplates2/qquickcontrol_p.h @@ -0,0 +1,320 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCONTROL_P_H +#define QQUICKCONTROL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qlocale.h> +#include <QtGui/qpalette.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickpalette_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickControlPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickControl : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QFont font READ font WRITE setFont RESET resetFont NOTIFY fontChanged FINAL) + Q_PROPERTY(qreal availableWidth READ availableWidth NOTIFY availableWidthChanged FINAL) + Q_PROPERTY(qreal availableHeight READ availableHeight NOTIFY availableHeightChanged FINAL) + Q_PROPERTY(qreal padding READ padding WRITE setPadding RESET resetPadding NOTIFY paddingChanged FINAL) + Q_PROPERTY(qreal topPadding READ topPadding WRITE setTopPadding RESET resetTopPadding NOTIFY topPaddingChanged FINAL) + Q_PROPERTY(qreal leftPadding READ leftPadding WRITE setLeftPadding RESET resetLeftPadding NOTIFY leftPaddingChanged FINAL) + Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding RESET resetRightPadding NOTIFY rightPaddingChanged FINAL) + Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding RESET resetBottomPadding NOTIFY bottomPaddingChanged FINAL) + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing RESET resetSpacing NOTIFY spacingChanged FINAL) + Q_PROPERTY(QLocale locale READ locale WRITE setLocale RESET resetLocale NOTIFY localeChanged FINAL) + Q_PROPERTY(bool mirrored READ isMirrored NOTIFY mirroredChanged FINAL) + Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged FINAL) + Q_PROPERTY(Qt::FocusReason focusReason READ focusReason WRITE setFocusReason NOTIFY focusReasonChanged FINAL) + Q_PROPERTY(bool visualFocus READ hasVisualFocus NOTIFY visualFocusChanged FINAL) + Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged FINAL) + Q_PROPERTY(bool hoverEnabled READ isHoverEnabled WRITE setHoverEnabled RESET resetHoverEnabled NOTIFY hoverEnabledChanged FINAL) + Q_PROPERTY(bool wheelEnabled READ isWheelEnabled WRITE setWheelEnabled NOTIFY wheelEnabledChanged FINAL) + Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) + Q_PROPERTY(QQuickItem *contentItem READ contentItem WRITE setContentItem NOTIFY contentItemChanged FINAL) + Q_PROPERTY(qreal baselineOffset READ baselineOffset WRITE setBaselineOffset RESET resetBaselineOffset NOTIFY baselineOffsetChanged FINAL) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal horizontalPadding READ horizontalPadding WRITE setHorizontalPadding RESET resetHorizontalPadding NOTIFY horizontalPaddingChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal verticalPadding READ verticalPadding WRITE setVerticalPadding RESET resetVerticalPadding NOTIFY verticalPaddingChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitContentWidth READ implicitContentWidth NOTIFY implicitContentWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitContentHeight READ implicitContentHeight NOTIFY implicitContentHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundWidth READ implicitBackgroundWidth NOTIFY implicitBackgroundWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundHeight READ implicitBackgroundHeight NOTIFY implicitBackgroundHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal topInset READ topInset WRITE setTopInset RESET resetTopInset NOTIFY topInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal leftInset READ leftInset WRITE setLeftInset RESET resetLeftInset NOTIFY leftInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal rightInset READ rightInset WRITE setRightInset RESET resetRightInset NOTIFY rightInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal bottomInset READ bottomInset WRITE setBottomInset RESET resetBottomInset NOTIFY bottomInsetChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background,contentItem") + QML_NAMED_ELEMENT(Control) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickControl(QQuickItem *parent = nullptr); + ~QQuickControl(); + + QFont font() const; + void setFont(const QFont &font); + void resetFont(); + + qreal availableWidth() const; + qreal availableHeight() const; + + qreal padding() const; + void setPadding(qreal padding); + void resetPadding(); + + qreal topPadding() const; + void setTopPadding(qreal padding); + void resetTopPadding(); + + qreal leftPadding() const; + void setLeftPadding(qreal padding); + void resetLeftPadding(); + + qreal rightPadding() const; + void setRightPadding(qreal padding); + void resetRightPadding(); + + qreal bottomPadding() const; + void setBottomPadding(qreal padding); + void resetBottomPadding(); + + qreal spacing() const; + void setSpacing(qreal spacing); + void resetSpacing(); + + QLocale locale() const; + void setLocale(const QLocale &locale); + void resetLocale(); + + bool isMirrored() const; + + Qt::FocusPolicy focusPolicy() const; + void setFocusPolicy(Qt::FocusPolicy policy); + + Qt::FocusReason focusReason() const; + void setFocusReason(Qt::FocusReason reason); + + bool hasVisualFocus() const; + + bool isHovered() const; + void setHovered(bool hovered); + + bool isHoverEnabled() const; + void setHoverEnabled(bool enabled); + void resetHoverEnabled(); + + bool isWheelEnabled() const; + void setWheelEnabled(bool enabled); + + QQuickItem *background() const; + void setBackground(QQuickItem *background); + + QQuickItem *contentItem() const; + void setContentItem(QQuickItem *item); + + qreal baselineOffset() const; + void setBaselineOffset(qreal offset); + void resetBaselineOffset(); + + // 2.5 (Qt 5.12) + qreal horizontalPadding() const; + void setHorizontalPadding(qreal padding); + void resetHorizontalPadding(); + + qreal verticalPadding() const; + void setVerticalPadding(qreal padding); + void resetVerticalPadding(); + + qreal implicitContentWidth() const; + qreal implicitContentHeight() const; + + qreal implicitBackgroundWidth() const; + qreal implicitBackgroundHeight() const; + + qreal topInset() const; + void setTopInset(qreal inset); + void resetTopInset(); + + qreal leftInset() const; + void setLeftInset(qreal inset); + void resetLeftInset(); + + qreal rightInset() const; + void setRightInset(qreal inset); + void resetRightInset(); + + qreal bottomInset() const; + void setBottomInset(qreal inset); + void resetBottomInset(); + +Q_SIGNALS: + void fontChanged(); + void availableWidthChanged(); + void availableHeightChanged(); + void paddingChanged(); + void topPaddingChanged(); + void leftPaddingChanged(); + void rightPaddingChanged(); + void bottomPaddingChanged(); + void spacingChanged(); + void localeChanged(); + void mirroredChanged(); + void focusPolicyChanged(); + void focusReasonChanged(); + void visualFocusChanged(); + void hoveredChanged(); + void hoverEnabledChanged(); + void wheelEnabledChanged(); + void backgroundChanged(); + void contentItemChanged(); + void baselineOffsetChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void horizontalPaddingChanged(); + Q_REVISION(2, 5) void verticalPaddingChanged(); + Q_REVISION(2, 5) void implicitContentWidthChanged(); + Q_REVISION(2, 5) void implicitContentHeightChanged(); + Q_REVISION(2, 5) void implicitBackgroundWidthChanged(); + Q_REVISION(2, 5) void implicitBackgroundHeightChanged(); + Q_REVISION(2, 5) void topInsetChanged(); + Q_REVISION(2, 5) void leftInsetChanged(); + Q_REVISION(2, 5) void rightInsetChanged(); + Q_REVISION(2, 5) void bottomInsetChanged(); + +protected: + virtual QFont defaultFont() const; + + QQuickControl(QQuickControlPrivate &dd, QQuickItem *parent); + + void classBegin() override; + void componentComplete() override; + + void itemChange(ItemChange change, const ItemChangeData &value) override; + + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; +#if QT_CONFIG(quicktemplates2_hover) + void hoverEnterEvent(QHoverEvent *event) override; + void hoverMoveEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; +#endif + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseUngrabEvent() override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; + void touchUngrabEvent() override; +#endif +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + virtual void fontChange(const QFont &newFont, const QFont &oldFont); +#if QT_CONFIG(quicktemplates2_hover) + virtual void hoverChange(); +#endif + virtual void mirrorChange(); + virtual void spacingChange(qreal newSpacing, qreal oldSpacing); + virtual void paddingChange(const QMarginsF &newPadding, const QMarginsF &oldPadding); + virtual void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem); + virtual void localeChange(const QLocale &newLocale, const QLocale &oldLocale); + virtual void insetChange(const QMarginsF &newInset, const QMarginsF &oldInset); + virtual void enabledChange(); + +#if QT_CONFIG(accessibility) + virtual QAccessible::Role accessibleRole() const; + virtual void accessibilityActiveChanged(bool active); +#endif + + // helper functions which avoid to check QT_CONFIG(accessibility) + QString accessibleName() const; + void maybeSetAccessibleName(const QString &name); + + QVariant accessibleProperty(const char *propertyName); + bool setAccessibleProperty(const char *propertyName, const QVariant &value); + +private: + Q_DISABLE_COPY(QQuickControl) + Q_DECLARE_PRIVATE(QQuickControl) +}; + +struct QQuickItemForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickItem) + QML_ADDED_IN_VERSION(2, 3) +}; + +struct QQuickColorGroupForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickColorGroup) + QML_ADDED_IN_VERSION(6, 0) +}; + +struct QQuickPaletteForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickPalette) + QML_ADDED_IN_VERSION(6, 0) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickControl) + +#endif // QQUICKCONTROL_P_H diff --git a/src/quicktemplates2/qquickcontrol_p_p.h b/src/quicktemplates2/qquickcontrol_p_p.h new file mode 100644 index 0000000000..aa187dbbd6 --- /dev/null +++ b/src/quicktemplates2/qquickcontrol_p_p.h @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCONTROL_P_P_H +#define QQUICKCONTROL_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQuickTemplates2/private/qquickdeferredpointer_p_p.h> +#include <QtQuickTemplates2/private/qquicktheme_p.h> + +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQml/private/qlazilyallocated_p.h> + +#if QT_CONFIG(accessibility) +#include <QtGui/qaccessible.h> +#endif + +#include <QtCore/qloggingcategory.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcItemManagement) + +class QQuickAccessibleAttached; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickControlPrivate : public QQuickItemPrivate, public QQuickItemChangeListener +#if QT_CONFIG(accessibility) + , public QAccessible::ActivationObserver +#endif +{ + Q_DECLARE_PUBLIC(QQuickControl) + +public: + QQuickControlPrivate(); + ~QQuickControlPrivate(); + + static QQuickControlPrivate *get(QQuickControl *control) + { + return control->d_func(); + } + + void init(); + +#if QT_CONFIG(quicktemplates2_multitouch) + virtual bool acceptTouch(const QTouchEvent::TouchPoint &point); +#endif + virtual void handlePress(const QPointF &point); + virtual void handleMove(const QPointF &point); + virtual void handleRelease(const QPointF &point); + virtual void handleUngrab(); + + void mirrorChange() override; + + inline QMarginsF getPadding() const { return QMarginsF(getLeftPadding(), getTopPadding(), getRightPadding(), getBottomPadding()); } + inline qreal getTopPadding() const { return extra.isAllocated() && extra->hasTopPadding ? extra->topPadding : getVerticalPadding(); } + inline qreal getLeftPadding() const { return extra.isAllocated() && extra->hasLeftPadding ? extra->leftPadding : getHorizontalPadding(); } + inline qreal getRightPadding() const { return extra.isAllocated() && extra->hasRightPadding ? extra->rightPadding : getHorizontalPadding(); } + inline qreal getBottomPadding() const { return extra.isAllocated() && extra->hasBottomPadding ? extra->bottomPadding : getVerticalPadding(); } + inline qreal getHorizontalPadding() const { return hasHorizontalPadding ? horizontalPadding : padding; } + inline qreal getVerticalPadding() const { return hasVerticalPadding ? verticalPadding : padding; } + + void setTopPadding(qreal value, bool reset = false); + void setLeftPadding(qreal value, bool reset = false); + void setRightPadding(qreal value, bool reset = false); + void setBottomPadding(qreal value, bool reset = false); + void setHorizontalPadding(qreal value, bool reset = false); + void setVerticalPadding(qreal value, bool reset = false); + + inline QMarginsF getInset() const { return QMarginsF(getLeftInset(), getTopInset(), getRightInset(), getBottomInset()); } + inline qreal getTopInset() const { return extra.isAllocated() ? extra->topInset : 0; } + inline qreal getLeftInset() const { return extra.isAllocated() ? extra->leftInset : 0; } + inline qreal getRightInset() const { return extra.isAllocated() ? extra->rightInset : 0; } + inline qreal getBottomInset() const { return extra.isAllocated() ? extra->bottomInset : 0; } + + void setTopInset(qreal value, bool reset = false); + void setLeftInset(qreal value, bool reset = false); + void setRightInset(qreal value, bool reset = false); + void setBottomInset(qreal value, bool reset = false); + + virtual void resizeBackground(); + virtual void resizeContent(); + + virtual QQuickItem *getContentItem(); + void setContentItem_helper(QQuickItem *item, bool notify = true); + +#if QT_CONFIG(accessibility) + void accessibilityActiveChanged(bool active) override; + QAccessible::Role accessibleRole() const override; + static QQuickAccessibleAttached *accessibleAttached(const QObject *object); +#endif + + virtual void resolveFont(); + void inheritFont(const QFont &font); + void updateFont(const QFont &font); + static void updateFontRecur(QQuickItem *item, const QFont &font); + inline void setFont_helper(const QFont &font) { + if (resolvedFont.resolveMask() == font.resolveMask() && resolvedFont == font) + return; + updateFont(font); + } + static QFont parentFont(const QQuickItem *item); + + void updateLocale(const QLocale &l, bool e); + static void updateLocaleRecur(QQuickItem *item, const QLocale &l); + static QLocale calcLocale(const QQuickItem *item); + +#if QT_CONFIG(quicktemplates2_hover) + void updateHoverEnabled(bool enabled, bool xplicit); + static void updateHoverEnabledRecur(QQuickItem *item, bool enabled); + static bool calcHoverEnabled(const QQuickItem *item); +#endif + + virtual void cancelContentItem(); + virtual void executeContentItem(bool complete = false); + + virtual void cancelBackground(); + virtual void executeBackground(bool complete = false); + + static void hideOldItem(QQuickItem *item); + static void unhideOldItem(QQuickControl *control, QQuickItem *item); + + void updateBaselineOffset(); + + static const ChangeTypes ImplicitSizeChanges; + + void addImplicitSizeListener(QQuickItem *item, ChangeTypes changes = ImplicitSizeChanges); + void removeImplicitSizeListener(QQuickItem *item, ChangeTypes changes = ImplicitSizeChanges); + + static void addImplicitSizeListener(QQuickItem *item, QQuickItemChangeListener *listener, ChangeTypes changes = ImplicitSizeChanges); + static void removeImplicitSizeListener(QQuickItem *item, QQuickItemChangeListener *listener, ChangeTypes changes = ImplicitSizeChanges); + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemDestroyed(QQuickItem *item) override; + void itemFocusChanged(QQuickItem *item, Qt::FocusReason reason) override; + + virtual qreal getContentWidth() const; + virtual qreal getContentHeight() const; + + void updateImplicitContentWidth(); + void updateImplicitContentHeight(); + void updateImplicitContentSize(); + + QPalette defaultPalette() const override; + + struct ExtraData { + bool hasTopPadding = false; + bool hasLeftPadding = false; + bool hasRightPadding = false; + bool hasBottomPadding = false; + bool hasBaselineOffset = false; + bool hasTopInset = false; + bool hasLeftInset = false; + bool hasRightInset = false; + bool hasBottomInset = false; + bool hasBackgroundWidth = false; + bool hasBackgroundHeight = false; + qreal topPadding = 0; + qreal leftPadding = 0; + qreal rightPadding = 0; + qreal bottomPadding = 0; + qreal topInset = 0; + qreal leftInset = 0; + qreal rightInset = 0; + qreal bottomInset = 0; + QFont requestedFont; + }; + QLazilyAllocated<ExtraData> extra; + + bool hasHorizontalPadding = false; + bool hasVerticalPadding = false; + bool hasLocale = false; + bool wheelEnabled = false; +#if QT_CONFIG(quicktemplates2_hover) + bool hovered = false; + bool explicitHoverEnabled = false; +#endif + bool resizingBackground = false; + bool pressWasTouch = false; + int touchId = -1; + QPointF previousPressPos; + qreal padding = 0; + qreal horizontalPadding = 0; + qreal verticalPadding = 0; + qreal implicitContentWidth = 0; + qreal implicitContentHeight = 0; + qreal spacing = 0; + QLocale locale; + QFont resolvedFont; + Qt::FocusPolicy focusPolicy = Qt::NoFocus; + Qt::FocusReason focusReason = Qt::OtherFocusReason; + QQuickDeferredPointer<QQuickItem> background; + QQuickDeferredPointer<QQuickItem> contentItem; +}; + +QT_END_NAMESPACE + +#endif // QQUICKCONTROL_P_P_H diff --git a/src/quicktemplates2/qquickdeferredexecute.cpp b/src/quicktemplates2/qquickdeferredexecute.cpp new file mode 100644 index 0000000000..817415c492 --- /dev/null +++ b/src/quicktemplates2/qquickdeferredexecute.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickdeferredexecute_p_p.h" + +#include <QtCore/qhash.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/private/qqmldata_p.h> +#include <QtQml/private/qqmlcomponent_p.h> +#include <QtQml/private/qqmlobjectcreator_p.h> + +#include <deque> + +QT_BEGIN_NAMESPACE + +namespace QtQuickPrivate { + +typedef QHash<size_t, QQmlComponentPrivate::DeferredState *> DeferredStates; + +static inline size_t qHash(QObject *object, const QString &propertyName) +{ + return ::qHash(object) + ::qHash(propertyName); +} + +Q_GLOBAL_STATIC(DeferredStates, deferredStates) + +static void cancelDeferred(QQmlData *ddata, int propertyIndex) +{ + auto dit = ddata->deferredData.rbegin(); + while (dit != ddata->deferredData.rend()) { + (*dit)->bindings.remove(propertyIndex); + ++dit; + } +} + +static bool beginDeferred(QQmlEnginePrivate *enginePriv, const QQmlProperty &property, QQmlComponentPrivate::DeferredState *deferredState) +{ + QObject *object = property.object(); + QQmlData *ddata = QQmlData::get(object); + Q_ASSERT(!ddata->deferredData.isEmpty()); + + int propertyIndex = property.index(); + int wasInProgress = enginePriv->inProgressCreations; + + for (auto dit = ddata->deferredData.rbegin(); dit != ddata->deferredData.rend(); ++dit) { + QQmlData::DeferredData *deferData = *dit; + + auto bindings = deferData->bindings; + auto range = bindings.equal_range(propertyIndex); + if (range.first == bindings.end()) + continue; + + QQmlComponentPrivate::ConstructionState *state = new QQmlComponentPrivate::ConstructionState; + state->completePending = true; + + QQmlContextData *creationContext = nullptr; + state->creator.reset(new QQmlObjectCreator(deferData->context->parent(), deferData->compilationUnit, creationContext)); + + enginePriv->inProgressCreations++; + + std::deque<const QV4::CompiledData::Binding *> reversedBindings; + std::copy(range.first, range.second, std::front_inserter(reversedBindings)); + state->creator->beginPopulateDeferred(deferData->context); + for (const QV4::CompiledData::Binding *binding : reversedBindings) + state->creator->populateDeferredBinding(property, deferData->deferredIdx, binding); + state->creator->finalizePopulateDeferred(); + state->errors << state->creator->errors; + + deferredState->constructionStates += state; + + // Cleanup any remaining deferred bindings for this property, also in inner contexts, + // to avoid executing them later and overriding the property that was just populated. + cancelDeferred(ddata, propertyIndex); + break; + } + + return enginePriv->inProgressCreations > wasInProgress; +} + +void beginDeferred(QObject *object, const QString &property) +{ + QQmlData *data = QQmlData::get(object); + if (data && !data->deferredData.isEmpty() && !data->wasDeleted(object) && data->context) { + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine()); + + QQmlComponentPrivate::DeferredState *state = new QQmlComponentPrivate::DeferredState; + if (beginDeferred(ep, QQmlProperty(object, property), state)) + deferredStates()->insert(qHash(object, property), state); + else + delete state; + + // Release deferred data for those compilation units that no longer have deferred bindings + data->releaseDeferredData(); + } +} + +void cancelDeferred(QObject *object, const QString &property) +{ + QQmlData *data = QQmlData::get(object); + if (data) + cancelDeferred(data, QQmlProperty(object, property).index()); +} + +void completeDeferred(QObject *object, const QString &property) +{ + QQmlData *data = QQmlData::get(object); + QQmlComponentPrivate::DeferredState *state = deferredStates()->take(qHash(object, property)); + if (data && state && !data->wasDeleted(object)) { + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine()); + QQmlComponentPrivate::completeDeferred(ep, state); + } + delete state; +} + +} // QtQuickPrivate + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickdeferredexecute_p_p.h b/src/quicktemplates2/qquickdeferredexecute_p_p.h new file mode 100644 index 0000000000..55750a6a06 --- /dev/null +++ b/src/quicktemplates2/qquickdeferredexecute_p_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDEFERREDEXECUTE_P_P_H +#define QQUICKDEFERREDEXECUTE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtQuickTemplates2/private/qquickdeferredpointer_p_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +#include <QtQml/private/qqmlvme_p.h> + +QT_BEGIN_NAMESPACE + +class QString; +class QObject; + +namespace QtQuickPrivate { + Q_QUICKTEMPLATES2_PRIVATE_EXPORT void beginDeferred(QObject *object, const QString &property); + Q_QUICKTEMPLATES2_PRIVATE_EXPORT void cancelDeferred(QObject *object, const QString &property); + Q_QUICKTEMPLATES2_PRIVATE_EXPORT void completeDeferred(QObject *object, const QString &property); +} + +template<typename T> +void quickBeginDeferred(QObject *object, const QString &property, QQuickDeferredPointer<T> &delegate) +{ + if (!QQmlVME::componentCompleteEnabled()) + return; + + delegate.setExecuting(true); + QtQuickPrivate::beginDeferred(object, property); + delegate.setExecuting(false); +} + +inline void quickCancelDeferred(QObject *object, const QString &property) +{ + QtQuickPrivate::cancelDeferred(object, property); +} + +template<typename T> +void quickCompleteDeferred(QObject *object, const QString &property, QQuickDeferredPointer<T> &delegate) +{ + Q_ASSERT(!delegate.wasExecuted()); + QtQuickPrivate::completeDeferred(object, property); + delegate.setExecuted(); +} + +QT_END_NAMESPACE + +#endif // QQUICKDEFERREDEXECUTE_P_P_H diff --git a/src/quicktemplates2/qquickdeferredpointer_p_p.h b/src/quicktemplates2/qquickdeferredpointer_p_p.h new file mode 100644 index 0000000000..f70b5dc647 --- /dev/null +++ b/src/quicktemplates2/qquickdeferredpointer_p_p.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDEFERREDPOINTER_P_P_H +#define QQUICKDEFERREDPOINTER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +template<typename T> +class QQuickDeferredPointer +{ +public: + inline QQuickDeferredPointer(); + inline QQuickDeferredPointer(T *); + inline QQuickDeferredPointer(const QQuickDeferredPointer<T> &o); + + inline bool isNull() const; + + inline bool wasExecuted() const; + inline void setExecuted(); + + inline bool isExecuting() const; + inline void setExecuting(bool); + + inline operator T*() const; + inline operator bool() const; + + inline T *data() const; + inline T *operator*() const; + inline T *operator->() const; + + inline QQuickDeferredPointer<T> &operator=(T *); + inline QQuickDeferredPointer<T> &operator=(const QQuickDeferredPointer &o); + +private: + quintptr ptr_value = 0; + + static const quintptr WasExecutedBit = 0x1; + static const quintptr IsExecutingBit = 0x2; + static const quintptr FlagsMask = WasExecutedBit | IsExecutingBit; +}; + +template<typename T> +QQuickDeferredPointer<T>::QQuickDeferredPointer() +{ +} + +template<typename T> +QQuickDeferredPointer<T>::QQuickDeferredPointer(T *v) +: ptr_value(quintptr(v)) +{ + Q_ASSERT((ptr_value & FlagsMask) == 0); +} + +template<typename T> +QQuickDeferredPointer<T>::QQuickDeferredPointer(const QQuickDeferredPointer<T> &o) +: ptr_value(o.ptr_value) +{ +} + +template<typename T> +bool QQuickDeferredPointer<T>::isNull() const +{ + return 0 == (ptr_value & (~FlagsMask)); +} + +template<typename T> +bool QQuickDeferredPointer<T>::wasExecuted() const +{ + return ptr_value & WasExecutedBit; +} + +template<typename T> +void QQuickDeferredPointer<T>::setExecuted() +{ + ptr_value |= WasExecutedBit; +} + +template<typename T> +bool QQuickDeferredPointer<T>::isExecuting() const +{ + return ptr_value & IsExecutingBit; +} + +template<typename T> +void QQuickDeferredPointer<T>::setExecuting(bool b) +{ + if (b) + ptr_value |= IsExecutingBit; + else + ptr_value &= ~IsExecutingBit; +} + +template<typename T> +QQuickDeferredPointer<T>::operator T*() const +{ + return data(); +} + +template<typename T> +QQuickDeferredPointer<T>::operator bool() const +{ + return !isNull(); +} + +template<typename T> +T *QQuickDeferredPointer<T>::data() const +{ + return (T *)(ptr_value & ~FlagsMask); +} + +template<typename T> +T *QQuickDeferredPointer<T>::operator*() const +{ + return (T *)(ptr_value & ~FlagsMask); +} + +template<typename T> +T *QQuickDeferredPointer<T>::operator->() const +{ + return (T *)(ptr_value & ~FlagsMask); +} + +template<typename T> +QQuickDeferredPointer<T> &QQuickDeferredPointer<T>::operator=(T *o) +{ + Q_ASSERT((quintptr(o) & FlagsMask) == 0); + + ptr_value = quintptr(o) | (ptr_value & FlagsMask); + return *this; +} + +template<typename T> +QQuickDeferredPointer<T> &QQuickDeferredPointer<T>::operator=(const QQuickDeferredPointer &o) +{ + ptr_value = o.ptr_value; + return *this; +} + +QT_END_NAMESPACE + +#endif // QQUICKDEFERREDPOINTER_P_P_H diff --git a/src/quicktemplates2/qquickdelaybutton.cpp b/src/quicktemplates2/qquickdelaybutton.cpp new file mode 100644 index 0000000000..25e12263f4 --- /dev/null +++ b/src/quicktemplates2/qquickdelaybutton.cpp @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickdelaybutton_p.h" +#include "qquickabstractbutton_p_p.h" + +#include <QtQuick/private/qquickanimation_p.h> +#include <QtQuick/private/qquicktransition_p.h> +#include <QtQuick/private/qquicktransitionmanager_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype DelayButton + \inherits AbstractButton +//! \instantiates QQuickDelayButton + \inqmlmodule QtQuick.Controls + \since 5.9 + \ingroup qtquickcontrols2-buttons + \brief Check button that triggers when held down long enough. + + \image qtquickcontrols2-delaybutton.gif + + DelayButton is a checkable button that incorporates a delay before the + button becomes \l {AbstractButton::}{checked} and the \l activated() + signal is emitted. This delay prevents accidental presses. + + The current progress is expressed as a decimal value between \c 0.0 + and \c 1.0. The time it takes for \l activated() to be emitted is + measured in milliseconds, and can be set with the \l delay property. + + The progress is indicated by a progress indicator on the button. + + \sa {Customizing DelayButton}, {Button Controls} +*/ + +/*! + \qmlsignal QtQuick.Controls::DelayButton::activated() + + This signal is emitted when \l progress reaches \c 1.0. +*/ + +class QQuickDelayTransitionManager; + +class QQuickDelayButtonPrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickDelayButton) + +public: + void beginTransition(qreal to); + void finishTransition(); + void cancelTransition(); + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::Button); } + + int delay = 300; + qreal progress = 0.0; + QQuickTransition *transition = nullptr; + QScopedPointer<QQuickDelayTransitionManager> transitionManager; +}; + +class QQuickDelayTransitionManager : public QQuickTransitionManager +{ +public: + QQuickDelayTransitionManager(QQuickDelayButton *button) : m_button(button) { } + + void transition(QQuickTransition *transition, qreal progress); + +protected: + void finished() override; + +private: + QQuickDelayButton *m_button = nullptr; +}; + +void QQuickDelayTransitionManager::transition(QQuickTransition *transition, qreal progress) +{ + qmlExecuteDeferred(transition); + + QQmlProperty defaultTarget(m_button, QLatin1String("progress")); + QQmlListProperty<QQuickAbstractAnimation> animations = transition->animations(); + const int count = animations.count(&animations); + for (int i = 0; i < count; ++i) { + QQuickAbstractAnimation *anim = animations.at(&animations, i); + anim->setDefaultTarget(defaultTarget); + } + + QList<QQuickStateAction> actions; + actions << QQuickStateAction(m_button, QLatin1String("progress"), progress); + QQuickTransitionManager::transition(actions, transition, m_button); +} + +void QQuickDelayTransitionManager::finished() +{ + if (qFuzzyCompare(m_button->progress(), qreal(1.0))) + emit m_button->activated(); +} + +void QQuickDelayButtonPrivate::beginTransition(qreal to) +{ + Q_Q(QQuickDelayButton); + if (!transition) { + q->setProgress(to); + finishTransition(); + return; + } + + if (!transitionManager) + transitionManager.reset(new QQuickDelayTransitionManager(q)); + + transitionManager->transition(transition, to); +} + +void QQuickDelayButtonPrivate::finishTransition() +{ + Q_Q(QQuickDelayButton); + if (qFuzzyCompare(progress, qreal(1.0))) + emit q->activated(); +} + +void QQuickDelayButtonPrivate::cancelTransition() +{ + if (transitionManager) + transitionManager->cancel(); +} + +QQuickDelayButton::QQuickDelayButton(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickDelayButtonPrivate), parent) +{ + setCheckable(true); +} + +/*! + \qmlproperty int QtQuick.Controls::DelayButton::delay + + This property holds the time it takes (in milliseconds) for \l progress + to reach \c 1.0 and emit \l activated(). + + The default value is \c 3000 ms. +*/ +int QQuickDelayButton::delay() const +{ + Q_D(const QQuickDelayButton); + return d->delay; +} + +void QQuickDelayButton::setDelay(int delay) +{ + Q_D(QQuickDelayButton); + if (d->delay == delay) + return; + + d->delay = delay; + emit delayChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::DelayButton::progress + \readonly + + This property holds the current progress as displayed by the progress + indicator, in the range \c 0.0 - \c 1.0. +*/ +qreal QQuickDelayButton::progress() const +{ + Q_D(const QQuickDelayButton); + return d->progress; +} + +void QQuickDelayButton::setProgress(qreal progress) +{ + Q_D(QQuickDelayButton); + if (qFuzzyCompare(d->progress, progress)) + return; + + d->progress = progress; + emit progressChanged(); +} + +/*! + \qmlproperty Transition QtQuick.Controls::DelayButton::transition + + This property holds the transition that is applied on the \l progress + property when the button is pressed or released. +*/ +QQuickTransition *QQuickDelayButton::transition() const +{ + Q_D(const QQuickDelayButton); + return d->transition; +} + +void QQuickDelayButton::setTransition(QQuickTransition *transition) +{ + Q_D(QQuickDelayButton); + if (d->transition == transition) + return; + + d->transition = transition; + emit transitionChanged(); +} + +void QQuickDelayButton::buttonChange(ButtonChange change) +{ + Q_D(QQuickDelayButton); + switch (change) { + case ButtonCheckedChange: + d->cancelTransition(); + setProgress(d->checked ? 1.0 : 0.0); + break; + case ButtonPressedChanged: + if (!d->checked) + d->beginTransition(d->pressed ? 1.0 : 0.0); + break; + default: + QQuickAbstractButton::buttonChange(change); + break; + } +} + +void QQuickDelayButton::nextCheckState() +{ + Q_D(QQuickDelayButton); + setChecked(!d->checked && qFuzzyCompare(d->progress, qreal(1.0))); +} + +QFont QQuickDelayButton::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::Button); +} + +QT_END_NAMESPACE + +#include "moc_qquickdelaybutton_p.cpp" diff --git a/src/quicktemplates2/qquickdelaybutton_p.h b/src/quicktemplates2/qquickdelaybutton_p.h new file mode 100644 index 0000000000..a697bca807 --- /dev/null +++ b/src/quicktemplates2/qquickdelaybutton_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDELAYBUTTON_P_H +#define QQUICKDELAYBUTTON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickTransition; +class QQuickDelayButtonPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDelayButton : public QQuickAbstractButton +{ + Q_OBJECT + Q_PROPERTY(int delay READ delay WRITE setDelay NOTIFY delayChanged FINAL) + Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged FINAL) + Q_PROPERTY(QQuickTransition *transition READ transition WRITE setTransition NOTIFY transitionChanged FINAL) + QML_NAMED_ELEMENT(DelayButton) + QML_ADDED_IN_VERSION(2, 2) + +public: + explicit QQuickDelayButton(QQuickItem *parent = nullptr); + + int delay() const; + void setDelay(int delay); + + qreal progress() const; + void setProgress(qreal progress); + + QQuickTransition *transition() const; + void setTransition(QQuickTransition *transition); + +Q_SIGNALS: + void activated(); + void delayChanged(); + void progressChanged(); + void transitionChanged(); + +protected: + void buttonChange(ButtonChange change) override; + void nextCheckState() override; + + QFont defaultFont() const override; + +private: + Q_DISABLE_COPY(QQuickDelayButton) + Q_DECLARE_PRIVATE(QQuickDelayButton) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDelayButton) + +#endif // QQUICKDELAYBUTTON_P_H diff --git a/src/quicktemplates2/qquickdial.cpp b/src/quicktemplates2/qquickdial.cpp new file mode 100644 index 0000000000..2c38ff7558 --- /dev/null +++ b/src/quicktemplates2/qquickdial.cpp @@ -0,0 +1,861 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickdial_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtCore/qmath.h> +#include <QtQuick/private/qquickflickable_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> + +#include <cmath> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Dial + \inherits Control +//! \instantiates QQuickDial + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-input + \brief Circular dial that is rotated to set a value. + + The Dial is similar to a traditional dial knob that is found on devices + such as stereos or industrial equipment. It allows the user to specify a + value within a range. + + \image qtquickcontrols2-dial-no-wrap.gif + + The value of the dial is set with the \l value property. The range is + set with the \l from and \l to properties. To enable or disable wrapping, + use the \l wrap property. + + The dial can be manipulated with a keyboard. It supports the following + actions: + + \table + \header \li \b {Action} \li \b {Key} + \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Left + \row \li Decrease \l value by \l stepSize \li \c Qt.Key_Down + \row \li Set \l value to \l from \li \c Qt.Key_Home + \row \li Increase \l value by \l stepSize \li \c Qt.Key_Right + \row \li Increase \l value by \l stepSize \li \c Qt.Key_Up + \row \li Set \l value to \l to \li \c Qt.Key_End + \endtable + + \include qquickdial.qdocinc inputMode + + \sa {Customizing Dial}, {Input Controls} +*/ + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlsignal QtQuick.Controls::Dial::moved() + + This signal is emitted when the dial has been interactively moved + by the user by either touch, mouse, or keys. +*/ + +static const qreal startAngleRadians = (M_PI * 2.0) * (4.0 / 6.0); +static const qreal startAngle = -140; +static const qreal endAngleRadians = (M_PI * 2.0) * (5.0 / 6.0); +static const qreal endAngle = 140; + +class QQuickDialPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickDial) + +public: + qreal valueAt(qreal position) const; + qreal snapPosition(qreal position) const; + qreal positionAt(const QPointF &point) const; + qreal circularPositionAt(const QPointF &point) const; + qreal linearPositionAt(const QPointF &point) const; + void setPosition(qreal position); + void updatePosition(); + bool isLargeChange(const QPointF &eventPos, qreal proposedPosition) const; + bool isHorizontalOrVertical() const; + + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + void handleUngrab() override; + + void cancelHandle(); + void executeHandle(bool complete = false); + + void updateAllValuesAreInteger(); + + qreal from = 0; + qreal to = 1; + qreal value = 0; + qreal position = 0; + qreal angle = startAngle; + qreal stepSize = 0; + QPointF pressPoint; + qreal positionBeforePress = 0; + QQuickDial::SnapMode snapMode = QQuickDial::NoSnap; + QQuickDial::InputMode inputMode = QQuickDial::Circular; + QQuickDeferredPointer<QQuickItem> handle; + bool wrap = false; + bool live = true; + bool pressed = false; + bool allValuesAreInteger = false; +}; + +qreal QQuickDialPrivate::valueAt(qreal position) const +{ + qreal value = from + (to - from) * position; + + /* play nice with users expecting that integer from, to and stepSize leads to + integer values - given that we are using floating point internally (and in + the API of value), this does not hold, but it is easy enough to handle + */ + if (allValuesAreInteger) + value = qRound(value); + + return value; +} + +qreal QQuickDialPrivate::snapPosition(qreal position) const +{ + const qreal range = to - from; + if (qFuzzyIsNull(range)) + return position; + + const qreal effectiveStep = stepSize / range; + if (qFuzzyIsNull(effectiveStep)) + return position; + + return qRound(position / effectiveStep) * effectiveStep; +} + +qreal QQuickDialPrivate::positionAt(const QPointF &point) const +{ + return inputMode == QQuickDial::Circular ? circularPositionAt(point) : linearPositionAt(point); +} + +qreal QQuickDialPrivate::circularPositionAt(const QPointF &point) const +{ + qreal yy = height / 2.0 - point.y(); + qreal xx = point.x() - width / 2.0; + qreal angle = (xx || yy) ? std::atan2(yy, xx) : 0; + + if (angle < M_PI / -2) + angle = angle + M_PI * 2; + + qreal normalizedAngle = (startAngleRadians - angle) / endAngleRadians; + return normalizedAngle; +} + +qreal QQuickDialPrivate::linearPositionAt(const QPointF &point) const +{ + // This value determines the range (either horizontal or vertical) + // within which the dial can be dragged. + // The larger this value is, the further the drag distance + // must be to go from a position of e.g. 0.0 to 1.0. + qreal dragArea = 0; + + // The linear input mode uses a "relative" input system, + // where the distance from the press point is used to calculate + // the change in position. Moving the mouse above the press + // point increases the position (when inputMode is Vertical), + // and vice versa. This prevents the dial from jumping when clicked. + qreal dragDistance = 0; + + if (inputMode == QQuickDial::Horizontal) { + dragArea = width * 2; + dragDistance = pressPoint.x() - point.x(); + } else { + dragArea = height * 2; + dragDistance = point.y() - pressPoint.y(); + } + const qreal normalisedDifference = dragDistance / dragArea; + return qBound(qreal(0), positionBeforePress - normalisedDifference, qreal(1)); +} + +void QQuickDialPrivate::setPosition(qreal pos) +{ + Q_Q(QQuickDial); + pos = qBound<qreal>(qreal(0), pos, qreal(1)); + if (qFuzzyCompare(position, pos)) + return; + + position = pos; + + angle = startAngle + position * qAbs(endAngle - startAngle); + + emit q->positionChanged(); + emit q->angleChanged(); +} + +void QQuickDialPrivate::updatePosition() +{ + qreal pos = 0; + if (!qFuzzyCompare(from, to)) + pos = (value - from) / (to - from); + setPosition(pos); +} + +bool QQuickDialPrivate::isLargeChange(const QPointF &eventPos, qreal proposedPosition) const +{ + return qAbs(proposedPosition - position) >= qreal(0.5) && eventPos.y() >= height / 2; +} + +bool QQuickDialPrivate::isHorizontalOrVertical() const +{ + return inputMode == QQuickDial::Horizontal || inputMode == QQuickDial::Vertical; +} + +void QQuickDialPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickDial); + QQuickControlPrivate::handlePress(point); + pressPoint = point; + positionBeforePress = position; + q->setPressed(true); +} + +void QQuickDialPrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickDial); + QQuickControlPrivate::handleMove(point); + const qreal oldPos = position; + qreal pos = positionAt(point); + if (snapMode == QQuickDial::SnapAlways) + pos = snapPosition(pos); + + if (wrap || isHorizontalOrVertical() || !isLargeChange(point, pos)) { + if (live) + q->setValue(valueAt(pos)); + else + setPosition(pos); + if (!qFuzzyCompare(pos, oldPos)) + emit q->moved(); + } +} + +void QQuickDialPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickDial); + QQuickControlPrivate::handleRelease(point); + if (q->keepMouseGrab() || q->keepTouchGrab()) { + const qreal oldPos = position; + qreal pos = positionAt(point); + if (snapMode != QQuickDial::NoSnap) + pos = snapPosition(pos); + + if (wrap || isHorizontalOrVertical() || !isLargeChange(point, pos)) + q->setValue(valueAt(pos)); + if (!qFuzzyCompare(pos, oldPos)) + emit q->moved(); + + q->setKeepMouseGrab(false); + q->setKeepTouchGrab(false); + } + + q->setPressed(false); + pressPoint = QPointF(); + positionBeforePress = 0; +} + +void QQuickDialPrivate::handleUngrab() +{ + Q_Q(QQuickDial); + QQuickControlPrivate::handleUngrab(); + pressPoint = QPointF(); + positionBeforePress = 0; + q->setPressed(false); +} + +static inline QString handleName() { return QStringLiteral("handle"); } + +void QQuickDialPrivate::cancelHandle() +{ + Q_Q(QQuickDial); + quickCancelDeferred(q, handleName()); +} + +void QQuickDialPrivate::executeHandle(bool complete) +{ + Q_Q(QQuickDial); + if (handle.wasExecuted()) + return; + + if (!handle || complete) + quickBeginDeferred(q, handleName(), handle); + if (complete) + quickCompleteDeferred(q, handleName(), handle); +} + +template<typename ...Real> +static bool areRepresentableAsInteger(Real... numbers) { + auto check = [](qreal number) -> bool { return std::nearbyint(number) == number; }; + return (... && check(numbers)); +} + +void QQuickDialPrivate::updateAllValuesAreInteger() +{ + allValuesAreInteger = areRepresentableAsInteger(to, from, stepSize) && stepSize != 0.0; +} + +QQuickDial::QQuickDial(QQuickItem *parent) + : QQuickControl(*(new QQuickDialPrivate), parent) +{ + setActiveFocusOnTab(true); + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif +} + +/*! + \qmlproperty real QtQuick.Controls::Dial::from + + This property holds the starting value for the range. The default value is \c 0.0. + + \sa to, value +*/ +qreal QQuickDial::from() const +{ + Q_D(const QQuickDial); + return d->from; +} + +void QQuickDial::setFrom(qreal from) +{ + Q_D(QQuickDial); + if (qFuzzyCompare(d->from, from)) + return; + + d->from = from; + emit fromChanged(); + d->updateAllValuesAreInteger(); + if (isComponentComplete()) { + setValue(d->value); + d->updatePosition(); + } +} + +/*! + \qmlproperty real QtQuick.Controls::Dial::to + + This property holds the end value for the range. The default value is + \c 1.0. + + \sa from, value +*/ +qreal QQuickDial::to() const +{ + Q_D(const QQuickDial); + return d->to; +} + +void QQuickDial::setTo(qreal to) +{ + Q_D(QQuickDial); + if (qFuzzyCompare(d->to, to)) + return; + + d->to = to; + d->updateAllValuesAreInteger(); + emit toChanged(); + if (isComponentComplete()) { + setValue(d->value); + d->updatePosition(); + } +} + +/*! + \qmlproperty real QtQuick.Controls::Dial::value + + This property holds the value in the range \c from - \c to. The default + value is \c 0.0. + + \sa position, live +*/ +qreal QQuickDial::value() const +{ + Q_D(const QQuickDial); + return d->value; +} + +void QQuickDial::setValue(qreal value) +{ + Q_D(QQuickDial); + if (isComponentComplete()) + value = d->from > d->to ? qBound(d->to, value, d->from) : qBound(d->from, value, d->to); + + if (qFuzzyCompare(d->value, value)) + return; + + d->value = value; + d->updatePosition(); + emit valueChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::Dial::position + \readonly + + This property holds the logical position of the handle. + + The position is expressed as a fraction of the control's angle range (the + range within which the handle can be moved) in the range \c {0.0 - 1.0}. + + \sa value, angle +*/ +qreal QQuickDial::position() const +{ + Q_D(const QQuickDial); + return d->position; +} + +/*! + \qmlproperty real QtQuick.Controls::Dial::angle + \readonly + + This property holds the angle of the handle. + + The range is from \c -140 degrees to \c 140 degrees. + + \sa position +*/ +qreal QQuickDial::angle() const +{ + Q_D(const QQuickDial); + return d->angle; +} + +/*! + \qmlproperty real QtQuick.Controls::Dial::stepSize + + This property holds the step size. + + The step size determines the amount by which the dial's value + is increased and decreased when interacted with via the keyboard. + For example, a step size of \c 0.2, will result in the dial's + value increasing and decreasing in increments of \c 0.2. + + The step size is only respected for touch and mouse interaction + when \l snapMode is set to a value other than \c Dial.NoSnap. + + The default value is \c 0.0, which results in an effective step + size of \c 0.1 for keyboard interaction. + + \sa snapMode, increase(), decrease() +*/ +qreal QQuickDial::stepSize() const +{ + Q_D(const QQuickDial); + return d->stepSize; +} + +void QQuickDial::setStepSize(qreal step) +{ + Q_D(QQuickDial); + if (qFuzzyCompare(d->stepSize, step)) + return; + + d->stepSize = step; + d->updateAllValuesAreInteger(); + emit stepSizeChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Dial::snapMode + + This property holds the snap mode. + + The snap mode works with the \l stepSize to allow the handle to snap to + certain points along the dial. + + Possible values: + \value Dial.NoSnap The dial does not snap (default). + \value Dial.SnapAlways The dial snaps while the handle is dragged. + \value Dial.SnapOnRelease The dial does not snap while being dragged, but only after the handle is released. + + \sa stepSize +*/ +QQuickDial::SnapMode QQuickDial::snapMode() const +{ + Q_D(const QQuickDial); + return d->snapMode; +} + +void QQuickDial::setSnapMode(SnapMode mode) +{ + Q_D(QQuickDial); + if (d->snapMode == mode) + return; + + d->snapMode = mode; + emit snapModeChanged(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty enumeration QtQuick.Controls::Dial::inputMode + + This property holds the input mode. + + \include qquickdial.qdocinc inputMode + + The default value is \c Dial.Circular. +*/ +QQuickDial::InputMode QQuickDial::inputMode() const +{ + Q_D(const QQuickDial); + return d->inputMode; +} + +void QQuickDial::setInputMode(QQuickDial::InputMode mode) +{ + Q_D(QQuickDial); + if (d->inputMode == mode) + return; + + d->inputMode = mode; + emit inputModeChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Dial::wrap + + This property holds whether the dial wraps when dragged. + + For example, when this property is set to \c true, dragging the dial past + the \l to position will result in the handle being positioned at the + \l from position, and vice versa: + + \image qtquickcontrols2-dial-wrap.gif + + When this property is \c false, it's not possible to drag the dial across + the from and to values. + + \image qtquickcontrols2-dial-no-wrap.gif + + The default value is \c false. +*/ +bool QQuickDial::wrap() const +{ + Q_D(const QQuickDial); + return d->wrap; +} + +void QQuickDial::setWrap(bool wrap) +{ + Q_D(QQuickDial); + if (d->wrap == wrap) + return; + + d->wrap = wrap; + emit wrapChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Dial::pressed + + This property holds whether the dial is pressed. + + The dial will be pressed when either the mouse is pressed over it, or a key + such as \c Qt.Key_Left is held down. If you'd prefer not to have the dial + be pressed upon key presses (due to styling reasons, for example), you can + use the \l {Keys}{Keys attached property}: + + \code + Dial { + Keys.onLeftPressed: {} + } + \endcode + + This will result in pressed only being \c true upon mouse presses. +*/ +bool QQuickDial::isPressed() const +{ + Q_D(const QQuickDial); + return d->pressed; +} + +void QQuickDial::setPressed(bool pressed) +{ + Q_D(QQuickDial); + if (d->pressed == pressed) + return; + + d->pressed = pressed; + setAccessibleProperty("pressed", pressed); + emit pressedChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::Dial::handle + + This property holds the handle of the dial. + + The handle acts as a visual indicator of the position of the dial. + + \sa {Customizing Dial} +*/ +QQuickItem *QQuickDial::handle() const +{ + QQuickDialPrivate *d = const_cast<QQuickDialPrivate *>(d_func()); + if (!d->handle) + d->executeHandle(); + return d->handle; +} + +void QQuickDial::setHandle(QQuickItem *handle) +{ + Q_D(QQuickDial); + if (handle == d->handle) + return; + + if (!d->handle.isExecuting()) + d->cancelHandle(); + + QQuickControlPrivate::hideOldItem(d->handle); + d->handle = handle; + if (d->handle && !d->handle->parentItem()) + d->handle->setParentItem(this); + if (!d->handle.isExecuting()) + emit handleChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::Dial::live + + This property holds whether the dial provides live updates for the \l value + property while the handle is dragged. + + The default value is \c true. + + \sa value +*/ +bool QQuickDial::live() const +{ + Q_D(const QQuickDial); + return d->live; +} + +void QQuickDial::setLive(bool live) +{ + Q_D(QQuickDial); + if (d->live == live) + return; + + d->live = live; + emit liveChanged(); +} + +/*! + \qmlmethod void QtQuick.Controls::Dial::increase() + + Increases the value by \l stepSize, or \c 0.1 if stepSize is not defined. + + \sa stepSize +*/ +void QQuickDial::increase() +{ + Q_D(QQuickDial); + qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + setValue(d->value + step); +} + +/*! + \qmlmethod void QtQuick.Controls::Dial::decrease() + + Decreases the value by \l stepSize, or \c 0.1 if stepSize is not defined. + + \sa stepSize +*/ +void QQuickDial::decrease() +{ + Q_D(QQuickDial); + qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + setValue(d->value - step); +} + +void QQuickDial::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickDial); + const qreal oldValue = d->value; + switch (event->key()) { + case Qt::Key_Left: + case Qt::Key_Down: + setPressed(true); + if (isMirrored()) + increase(); + else + decrease(); + break; + + case Qt::Key_Right: + case Qt::Key_Up: + setPressed(true); + if (isMirrored()) + decrease(); + else + increase(); + break; + + case Qt::Key_Home: + setPressed(true); + setValue(isMirrored() ? d->to : d->from); + break; + + case Qt::Key_End: + setPressed(true); + setValue(isMirrored() ? d->from : d->to); + break; + + default: + event->ignore(); + QQuickControl::keyPressEvent(event); + break; + } + if (!qFuzzyCompare(d->value, oldValue)) + emit moved(); +} + +void QQuickDial::keyReleaseEvent(QKeyEvent *event) +{ + QQuickControl::keyReleaseEvent(event); + setPressed(false); +} + +void QQuickDial::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickDial); + QQuickControl::mousePressEvent(event); + d->handleMove(event->position()); + setKeepMouseGrab(true); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickDial::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickDial); + switch (event->type()) { + case QEvent::TouchUpdate: + for (const QTouchEvent::TouchPoint &point : event->points()) { + if (!d->acceptTouch(point)) + continue; + + switch (point.state()) { + case QEventPoint::Updated: + if (!keepTouchGrab()) { + bool overXDragThreshold = QQuickWindowPrivate::dragOverThreshold(point.position().x() - d->pressPoint.x(), Qt::XAxis, &point); + setKeepTouchGrab(overXDragThreshold); + + if (!overXDragThreshold) { + bool overYDragThreshold = QQuickWindowPrivate::dragOverThreshold(point.position().y() - d->pressPoint.y(), Qt::YAxis, &point); + setKeepTouchGrab(overYDragThreshold); + } + } + if (keepTouchGrab()) + d->handleMove(point.position()); + break; + + default: + QQuickControl::touchEvent(event); + break; + } + } + break; + + default: + QQuickControl::touchEvent(event); + break; + } +} +#endif + +#if QT_CONFIG(wheelevent) +void QQuickDial::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickDial); + QQuickControl::wheelEvent(event); + if (d->wheelEnabled) { + const qreal oldValue = d->value; + const QPointF angle = event->angleDelta(); + const qreal delta = (qFuzzyIsNull(angle.y()) ? angle.x() : (event->inverted() ? -angle.y() : angle.y())) / int(QWheelEvent::DefaultDeltasPerStep); + const qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + setValue(oldValue + step * delta); + event->setAccepted(!qFuzzyCompare(d->value, oldValue)); + } +} +#endif + +void QQuickDial::mirrorChange() +{ + QQuickControl::mirrorChange(); + emit angleChanged(); +} + +void QQuickDial::componentComplete() +{ + Q_D(QQuickDial); + d->executeHandle(true); + QQuickControl::componentComplete(); + setValue(d->value); + d->updatePosition(); +} + +#if QT_CONFIG(accessibility) +void QQuickDial::accessibilityActiveChanged(bool active) +{ + QQuickControl::accessibilityActiveChanged(active); + + Q_D(QQuickDial); + if (active) + setAccessibleProperty("pressed", d->pressed); +} + +QAccessible::Role QQuickDial::accessibleRole() const +{ + return QAccessible::Dial; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickdial_p.cpp" diff --git a/src/quicktemplates2/qquickdial_p.h b/src/quicktemplates2/qquickdial_p.h new file mode 100644 index 0000000000..67bbc18390 --- /dev/null +++ b/src/quicktemplates2/qquickdial_p.h @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDIAL_P_H +#define QQUICKDIAL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qvariant.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickDialAttached; +class QQuickDialPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDial : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) + Q_PROPERTY(qreal to READ to WRITE setTo NOTIFY toChanged FINAL) + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY(qreal position READ position NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal angle READ angle NOTIFY angleChanged FINAL) + Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL) + Q_PROPERTY(bool wrap READ wrap WRITE setWrap NOTIFY wrapChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) + Q_PROPERTY(QQuickItem *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) + // 2.2 (Qt 5.9) + Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged FINAL REVISION(2, 2)) + // 2.5 (Qt 5.12) + Q_PROPERTY(InputMode inputMode READ inputMode WRITE setInputMode NOTIFY inputModeChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background,handle") + QML_NAMED_ELEMENT(Dial) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickDial(QQuickItem *parent = nullptr); + + qreal from() const; + void setFrom(qreal from); + + qreal to() const; + void setTo(qreal to); + + qreal value() const; + void setValue(qreal value); + + qreal position() const; + + qreal angle() const; + + qreal stepSize() const; + void setStepSize(qreal step); + + enum SnapMode { + NoSnap, + SnapAlways, + SnapOnRelease + }; + Q_ENUM(SnapMode) + + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + enum InputMode { + Circular, + Horizontal, + Vertical, + }; + Q_ENUM(InputMode) + + bool wrap() const; + void setWrap(bool wrap); + + bool isPressed() const; + void setPressed(bool pressed); + + QQuickItem *handle() const; + void setHandle(QQuickItem *handle); + + // 2.2 (Qt 5.9) + bool live() const; + void setLive(bool live); + + // 2.5 (Qt 5.12) + InputMode inputMode() const; + void setInputMode(InputMode mode); + +public Q_SLOTS: + void increase(); + void decrease(); + +Q_SIGNALS: + void fromChanged(); + void toChanged(); + void valueChanged(); + void positionChanged(); + void angleChanged(); + void stepSizeChanged(); + void snapModeChanged(); + void wrapChanged(); + void pressedChanged(); + void handleChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void moved(); + Q_REVISION(2, 2) void liveChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void inputModeChanged(); + +protected: + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + + void mirrorChange() override; + void componentComplete() override; + +#if QT_CONFIG(accessibility) + void accessibilityActiveChanged(bool active) override; + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickDial) + Q_DECLARE_PRIVATE(QQuickDial) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDial) + +#endif // QQUICKDIAL_P_H diff --git a/src/quicktemplates2/qquickdialog.cpp b/src/quicktemplates2/qquickdialog.cpp new file mode 100644 index 0000000000..812d1c1ac2 --- /dev/null +++ b/src/quicktemplates2/qquickdialog.cpp @@ -0,0 +1,578 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickdialog_p.h" +#include "qquickdialog_p_p.h" +#include "qquickdialogbuttonbox_p.h" +#include "qquickabstractbutton_p.h" +#include "qquickpopupitem_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Dialog + \inherits Popup +//! \instantiates QQuickDialog + \inqmlmodule QtQuick.Controls + \ingroup qtquickcontrols2-dialogs + \ingroup qtquickcontrols2-popups + \brief Popup dialog with standard buttons and a title, used for short-term interaction with the user. + \since 5.8 + + A dialog is a popup mostly used for short-term tasks and brief communications + with the user. Similarly to \l ApplicationWindow and \l Page, Dialog is organized + into three sections: \l header, \l {Popup::}{contentItem}, and \l footer. + + \image qtquickcontrols2-page-wireframe.png + + \section1 Dialog Title and Buttons + + Dialog's \l title is displayed by a style-specific title bar that is assigned + as a dialog \l header by default. + + Dialog's standard buttons are managed by a \l DialogButtonBox that is assigned + as a dialog \l footer by default. The dialog's \l standardButtons property is + forwarded to the respective property of the button box. Furthermore, the + \l {DialogButtonBox::}{accepted()} and \l {DialogButtonBox::}{rejected()} + signals of the button box are connected to the respective signals in Dialog. + + \snippet qtquickcontrols2-dialog.qml 1 + + \section1 Modal Dialogs + + A \l {Popup::}{modal} dialog blocks input to other content beneath + the dialog. When a modal dialog is opened, the user must finish + interacting with the dialog and close it before they can access any + other content in the same window. + + \snippet qtquickcontrols2-dialog-modal.qml 1 + + \section1 Modeless Dialogs + + A modeless dialog is a dialog that operates independently of other + content around the dialog. When a modeless dialog is opened, the user + is allowed to interact with both the dialog and the other content in + the same window. + + \snippet qtquickcontrols2-dialog-modeless.qml 1 + + \sa DialogButtonBox, {Popup Controls} +*/ + +/*! + \qmlsignal QtQuick.Controls::Dialog::accepted() + + This signal is emitted when the dialog has been accepted either + interactively or by calling \l accept(). + + \note This signal is \e not emitted when closing the dialog with + \l {Popup::}{close()} or setting \l {Popup::}{visible} to \c false. + + \sa rejected() +*/ + +/*! + \qmlsignal QtQuick.Controls::Dialog::rejected() + + This signal is emitted when the dialog has been rejected either + interactively or by calling \l reject(). + + \note This signal is \e not emitted when closing the dialog with + \l {Popup::}{close()} or setting \l {Popup::}{visible} to \c false. + + \sa accepted() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlsignal QtQuick.Controls::Dialog::applied() + + This signal is emitted when the \c Dialog.Apply standard button is clicked. + + \sa discarded(), reset() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlsignal QtQuick.Controls::Dialog::reset() + + This signal is emitted when the \c Dialog.Reset standard button is clicked. + + \sa discarded(), applied() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlsignal QtQuick.Controls::Dialog::discarded() + + This signal is emitted when the \c Dialog.Discard standard button is clicked. + + \sa reset(), applied() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlsignal QtQuick.Controls::Dialog::helpRequested() + + This signal is emitted when the \c Dialog.Help standard button is clicked. + + \sa accepted(), rejected() +*/ + +QPlatformDialogHelper::ButtonRole QQuickDialogPrivate::buttonRole(QQuickAbstractButton *button) +{ + const QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(button, false)); + return attached ? attached->buttonRole() : QPlatformDialogHelper::InvalidRole; +} + +void QQuickDialogPrivate::handleAccept() +{ + Q_Q(QQuickDialog); + q->accept(); +} + +void QQuickDialogPrivate::handleReject() +{ + Q_Q(QQuickDialog); + q->reject(); +} + +void QQuickDialogPrivate::handleClick(QQuickAbstractButton *button) +{ + Q_Q(QQuickDialog); + switch (buttonRole(button)) { + case QPlatformDialogHelper::ApplyRole: + emit q->applied(); + break; + case QPlatformDialogHelper::ResetRole: + emit q->reset(); + break; + case QPlatformDialogHelper::DestructiveRole: + emit q->discarded(); + break; + case QPlatformDialogHelper::HelpRole: + emit q->helpRequested(); + break; + default: + break; + } +} + +QQuickDialog::QQuickDialog(QObject *parent) + : QQuickDialog(*(new QQuickDialogPrivate), parent) +{ +} + +QQuickDialog::QQuickDialog(QQuickDialogPrivate &dd, QObject *parent) + : QQuickPopup(dd, parent) +{ + Q_D(QQuickDialog); + QObject::connect(d->popupItem, &QQuickPopupItem::titleChanged, this, &QQuickDialog::titleChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::headerChanged, this, &QQuickDialog::headerChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::footerChanged, this, &QQuickDialog::footerChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::implicitHeaderWidthChanged, this, &QQuickDialog::implicitHeaderWidthChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::implicitHeaderHeightChanged, this, &QQuickDialog::implicitHeaderHeightChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::implicitFooterWidthChanged, this, &QQuickDialog::implicitFooterWidthChanged); + QObject::connect(d->popupItem, &QQuickPopupItem::implicitFooterHeightChanged, this, &QQuickDialog::implicitFooterHeightChanged); +} + +QQuickDialog::~QQuickDialog() +{ + Q_D(QQuickDialog); + QObject::disconnect(d->popupItem, &QQuickPopupItem::titleChanged, this, &QQuickDialog::titleChanged); + QObject::disconnect(d->popupItem, &QQuickPopupItem::headerChanged, this, &QQuickDialog::headerChanged); + QObject::disconnect(d->popupItem, &QQuickPopupItem::footerChanged, this, &QQuickDialog::footerChanged); + QObject::disconnect(d->popupItem, &QQuickPopupItem::implicitHeaderWidthChanged, this, &QQuickDialog::implicitHeaderWidthChanged); + QObject::disconnect(d->popupItem, &QQuickPopupItem::implicitHeaderHeightChanged, this, &QQuickDialog::implicitHeaderHeightChanged); + QObject::disconnect(d->popupItem, &QQuickPopupItem::implicitFooterWidthChanged, this, &QQuickDialog::implicitFooterWidthChanged); + QObject::disconnect(d->popupItem, &QQuickPopupItem::implicitFooterHeightChanged, this, &QQuickDialog::implicitFooterHeightChanged); +} + +/*! + \qmlproperty string QtQuick.Controls::Dialog::title + + This property holds the dialog title. + + The title is displayed in the dialog header. + + \code + Dialog { + title: qsTr("About") + + Label { + text: "Lorem ipsum..." + } + } + \endcode +*/ +QString QQuickDialog::title() const +{ + Q_D(const QQuickDialog); + return d->popupItem->title(); +} + +void QQuickDialog::setTitle(const QString &title) +{ + Q_D(QQuickDialog); + d->popupItem->setTitle(title); +} + +/*! + \qmlproperty Item QtQuick.Controls::Dialog::header + + This property holds the dialog header item. The header item is positioned to + the top, and resized to the width of the dialog. The default value is \c null. + + \note Assigning a \l DialogButtonBox as a dialog header automatically connects + its \l {DialogButtonBox::}{accepted()} and \l {DialogButtonBox::}{rejected()} + signals to the respective signals in Dialog. + + \note Assigning a \l DialogButtonBox, \l ToolBar, or \l TabBar as a dialog + header automatically sets the respective \l DialogButtonBox::position, + \l ToolBar::position, or \l TabBar::position property to \c Header. + + \sa footer +*/ +QQuickItem *QQuickDialog::header() const +{ + Q_D(const QQuickDialog); + return d->popupItem->header(); +} + +void QQuickDialog::setHeader(QQuickItem *header) +{ + Q_D(QQuickDialog); + QQuickItem *oldHeader = d->popupItem->header(); + if (oldHeader == header) + return; + + if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(oldHeader)) { + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::accepted, d, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::rejected, d, &QQuickDialogPrivate::handleReject); + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::clicked, d, &QQuickDialogPrivate::handleClick); + if (d->buttonBox == buttonBox) + d->buttonBox = nullptr; + } + + if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(header)) { + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::accepted, d, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::rejected, d, &QQuickDialogPrivate::handleReject); + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::clicked, d, &QQuickDialogPrivate::handleClick); + d->buttonBox = buttonBox; + buttonBox->setStandardButtons(d->standardButtons); + } + + d->popupItem->setHeader(header); +} + +/*! + \qmlproperty Item QtQuick.Controls::Dialog::footer + + This property holds the dialog footer item. The footer item is positioned to + the bottom, and resized to the width of the dialog. The default value is \c null. + + \note Assigning a \l DialogButtonBox as a dialog footer automatically connects + its \l {DialogButtonBox::}{accepted()} and \l {DialogButtonBox::}{rejected()} + signals to the respective signals in Dialog. + + \note Assigning a \l DialogButtonBox, \l ToolBar, or \l TabBar as a dialog + footer automatically sets the respective \l DialogButtonBox::position, + \l ToolBar::position, or \l TabBar::position property to \c Footer. + + \sa header +*/ +QQuickItem *QQuickDialog::footer() const +{ + Q_D(const QQuickDialog); + return d->popupItem->footer(); +} + +void QQuickDialog::setFooter(QQuickItem *footer) +{ + Q_D(QQuickDialog); + QQuickItem *oldFooter = d->popupItem->footer(); + if (oldFooter == footer) + return; + + if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(oldFooter)) { + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::accepted, d, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::rejected, d, &QQuickDialogPrivate::handleReject); + QObjectPrivate::disconnect(buttonBox, &QQuickDialogButtonBox::clicked, d, &QQuickDialogPrivate::handleClick); + if (d->buttonBox == buttonBox) + d->buttonBox = nullptr; + } + if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(footer)) { + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::accepted, d, &QQuickDialogPrivate::handleAccept); + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::rejected, d, &QQuickDialogPrivate::handleReject); + QObjectPrivate::connect(buttonBox, &QQuickDialogButtonBox::clicked, d, &QQuickDialogPrivate::handleClick); + d->buttonBox = buttonBox; + buttonBox->setStandardButtons(d->standardButtons); + } + + d->popupItem->setFooter(footer); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Dialog::standardButtons + + This property holds a combination of standard buttons that are used by the dialog. + + \snippet qtquickcontrols2-dialog.qml 1 + + The buttons will be positioned in the appropriate order for the user's platform. + + Possible flags: + \value Dialog.Ok An "OK" button defined with the \c AcceptRole. + \value Dialog.Open An "Open" button defined with the \c AcceptRole. + \value Dialog.Save A "Save" button defined with the \c AcceptRole. + \value Dialog.Cancel A "Cancel" button defined with the \c RejectRole. + \value Dialog.Close A "Close" button defined with the \c RejectRole. + \value Dialog.Discard A "Discard" or "Don't Save" button, depending on the platform, defined with the \c DestructiveRole. + \value Dialog.Apply An "Apply" button defined with the \c ApplyRole. + \value Dialog.Reset A "Reset" button defined with the \c ResetRole. + \value Dialog.RestoreDefaults A "Restore Defaults" button defined with the \c ResetRole. + \value Dialog.Help A "Help" button defined with the \c HelpRole. + \value Dialog.SaveAll A "Save All" button defined with the \c AcceptRole. + \value Dialog.Yes A "Yes" button defined with the \c YesRole. + \value Dialog.YesToAll A "Yes to All" button defined with the \c YesRole. + \value Dialog.No A "No" button defined with the \c NoRole. + \value Dialog.NoToAll A "No to All" button defined with the \c NoRole. + \value Dialog.Abort An "Abort" button defined with the \c RejectRole. + \value Dialog.Retry A "Retry" button defined with the \c AcceptRole. + \value Dialog.Ignore An "Ignore" button defined with the \c AcceptRole. + \value Dialog.NoButton An invalid button. + + \sa DialogButtonBox +*/ +QPlatformDialogHelper::StandardButtons QQuickDialog::standardButtons() const +{ + Q_D(const QQuickDialog); + return d->standardButtons; +} + +void QQuickDialog::setStandardButtons(QPlatformDialogHelper::StandardButtons buttons) +{ + Q_D(QQuickDialog); + if (d->standardButtons == buttons) + return; + + d->standardButtons = buttons; + if (d->buttonBox) + d->buttonBox->setStandardButtons(buttons); + emit standardButtonsChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod AbstractButton QtQuick.Controls::Dialog::standardButton(StandardButton button) + + Returns the specified standard \a button, or \c null if it does not exist. + + \sa standardButtons +*/ +QQuickAbstractButton *QQuickDialog::standardButton(QPlatformDialogHelper::StandardButton button) const +{ + Q_D(const QQuickDialog); + if (!d->buttonBox) + return nullptr; + return d->buttonBox->standardButton(button); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty int QtQuick.Controls::Dialog::result + + This property holds the result code. + + Standard result codes: + \value Dialog.Accepted The dialog was accepted. + \value Dialog.Rejected The dialog was rejected. + + \sa accept(), reject(), done() +*/ +int QQuickDialog::result() const +{ + Q_D(const QQuickDialog); + return d->result; +} + +void QQuickDialog::setResult(int result) +{ + Q_D(QQuickDialog); + if (d->result == result) + return; + + d->result = result; + emit resultChanged(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Dialog::implicitHeaderWidth + \readonly + + This property holds the implicit header width. + + The value is equal to \c {header && header.visible ? header.implicitWidth : 0}. + + \sa implicitHeaderHeight, implicitFooterWidth +*/ +qreal QQuickDialog::implicitHeaderWidth() const +{ + Q_D(const QQuickDialog); + return d->popupItem->implicitHeaderWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Dialog::implicitHeaderHeight + \readonly + + This property holds the implicit header height. + + The value is equal to \c {header && header.visible ? header.implicitHeight : 0}. + + \sa implicitHeaderWidth, implicitFooterHeight +*/ +qreal QQuickDialog::implicitHeaderHeight() const +{ + Q_D(const QQuickDialog); + return d->popupItem->implicitHeaderHeight(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Dialog::implicitFooterWidth + \readonly + + This property holds the implicit footer width. + + The value is equal to \c {footer && footer.visible ? footer.implicitWidth : 0}. + + \sa implicitFooterHeight, implicitHeaderWidth +*/ +qreal QQuickDialog::implicitFooterWidth() const +{ + Q_D(const QQuickDialog); + return d->popupItem->implicitFooterWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Dialog::implicitFooterHeight + \readonly + + This property holds the implicit footer height. + + The value is equal to \c {footer && footer.visible ? footer.implicitHeight : 0}. + + \sa implicitFooterWidth, implicitHeaderHeight +*/ +qreal QQuickDialog::implicitFooterHeight() const +{ + Q_D(const QQuickDialog); + return d->popupItem->implicitFooterHeight(); +} + +/*! + \qmlmethod void QtQuick.Controls::Dialog::accept() + + Emits the \l accepted() signal and closes the dialog. + + \sa reject(), done() +*/ +void QQuickDialog::accept() +{ + done(Accepted); +} + +/*! + \qmlmethod void QtQuick.Controls::Dialog::reject() + + Emits the \l rejected() signal and closes the dialog. + + \sa accept(), done() +*/ +void QQuickDialog::reject() +{ + done(Rejected); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Dialog::done(int result) + + \list 1 + \li Sets the \a result. + \li Emits \l accepted() or \l rejected() depending on + whether the result is \c Dialog.Accepted or \c Dialog.Rejected, + respectively. + \li Emits \l{Popup::}{closed()}. + \endlist + + \sa accept(), reject(), result +*/ +void QQuickDialog::done(int result) +{ + setResult(result); + + if (result == Accepted) + emit accepted(); + else if (result == Rejected) + emit rejected(); + + close(); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickDialog::accessibleRole() const +{ + return QAccessible::Dialog; +} + +void QQuickDialog::accessibilityActiveChanged(bool active) +{ + Q_D(QQuickDialog); + QQuickPopup::accessibilityActiveChanged(active); + + if (active) + maybeSetAccessibleName(d->popupItem->title()); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickdialog_p.cpp" diff --git a/src/quicktemplates2/qquickdialog_p.h b/src/quicktemplates2/qquickdialog_p.h new file mode 100644 index 0000000000..9b42fa1519 --- /dev/null +++ b/src/quicktemplates2/qquickdialog_p.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDIALOG_P_H +#define QQUICKDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +#include <QtQuickTemplates2/private/qquickpopup_p.h> +#include <QtGui/qpa/qplatformdialoghelper.h> + +QT_BEGIN_NAMESPACE + +class QQuickDialogPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialog : public QQuickPopup +{ + Q_OBJECT + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) + Q_PROPERTY(QQuickItem *header READ header WRITE setHeader NOTIFY headerChanged FINAL) + Q_PROPERTY(QQuickItem *footer READ footer WRITE setFooter NOTIFY footerChanged FINAL) + Q_PROPERTY(QPlatformDialogHelper::StandardButtons standardButtons READ standardButtons WRITE setStandardButtons NOTIFY standardButtonsChanged FINAL) + // 2.3 (Qt 5.10) + Q_PROPERTY(int result READ result WRITE setResult NOTIFY resultChanged FINAL REVISION(2, 3)) + QML_EXTENDED_NAMESPACE(QPlatformDialogHelper) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal implicitHeaderWidth READ implicitHeaderWidth NOTIFY implicitHeaderWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitHeaderHeight READ implicitHeaderHeight NOTIFY implicitHeaderHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitFooterWidth READ implicitFooterWidth NOTIFY implicitFooterWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitFooterHeight READ implicitFooterHeight NOTIFY implicitFooterHeightChanged FINAL REVISION(2, 5)) + QML_NAMED_ELEMENT(Dialog) + QML_ADDED_IN_VERSION(2, 1) + +public: + explicit QQuickDialog(QObject *parent = nullptr); + ~QQuickDialog(); + + QString title() const; + void setTitle(const QString &title); + + QQuickItem *header() const; + void setHeader(QQuickItem *header); + + QQuickItem *footer() const; + void setFooter(QQuickItem *footer); + + QPlatformDialogHelper::StandardButtons standardButtons() const; + void setStandardButtons(QPlatformDialogHelper::StandardButtons buttons); + Q_REVISION(2, 3) Q_INVOKABLE QQuickAbstractButton *standardButton(QPlatformDialogHelper::StandardButton button) const; + + // 2.3 (Qt 5.10) + enum StandardCode { Rejected, Accepted }; + Q_ENUM(StandardCode) + + int result() const; + void setResult(int result); + + // 2.5 (Qt 5.12) + qreal implicitHeaderWidth() const; + qreal implicitHeaderHeight() const; + + qreal implicitFooterWidth() const; + qreal implicitFooterHeight() const; + +public Q_SLOTS: + virtual void accept(); + virtual void reject(); + virtual void done(int result); + +Q_SIGNALS: + void accepted(); + void rejected(); + void titleChanged(); + void headerChanged(); + void footerChanged(); + void standardButtonsChanged(); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void applied(); + Q_REVISION(2, 3) void reset(); + Q_REVISION(2, 3) void discarded(); + Q_REVISION(2, 3) void helpRequested(); + Q_REVISION(2, 3) void resultChanged(); + // 2.5 (Qt 5.12) + void implicitHeaderWidthChanged(); + void implicitHeaderHeightChanged(); + void implicitFooterWidthChanged(); + void implicitFooterHeightChanged(); + +protected: + QQuickDialog(QQuickDialogPrivate &dd, QObject *parent); + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; + void accessibilityActiveChanged(bool active) override; +#endif + +private: + Q_DISABLE_COPY(QQuickDialog) + Q_DECLARE_PRIVATE(QQuickDialog) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDialog) + +#endif // QQUICKDIALOG_P_H diff --git a/src/quicktemplates2/qquickdialog_p_p.h b/src/quicktemplates2/qquickdialog_p_p.h new file mode 100644 index 0000000000..eb40c918a4 --- /dev/null +++ b/src/quicktemplates2/qquickdialog_p_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDIALOG_P_P_H +#define QQUICKDIALOG_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickdialog_p.h> +#include <QtQuickTemplates2/private/qquickpopup_p_p.h> +#include <QtGui/qpa/qplatformdialoghelper.h> + +QT_BEGIN_NAMESPACE + +class QQuickAbstractButton; +class QQuickDialogButtonBox; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogPrivate : public QQuickPopupPrivate +{ + Q_DECLARE_PUBLIC(QQuickDialog) + +public: + static QQuickDialogPrivate *get(QQuickDialog *dialog) + { + return dialog->d_func(); + } + + static QPlatformDialogHelper::ButtonRole buttonRole(QQuickAbstractButton *button); + + virtual void handleAccept(); + virtual void handleReject(); + virtual void handleClick(QQuickAbstractButton *button); + + int result = 0; + QString title; + QQuickDialogButtonBox *buttonBox = nullptr; + QPlatformDialogHelper::StandardButtons standardButtons = QPlatformDialogHelper::NoButton; +}; + +QT_END_NAMESPACE + +#endif // QQUICKDIALOG_P_P_H diff --git a/src/quicktemplates2/qquickdialogbuttonbox.cpp b/src/quicktemplates2/qquickdialogbuttonbox.cpp new file mode 100644 index 0000000000..8c62e4ed68 --- /dev/null +++ b/src/quicktemplates2/qquickdialogbuttonbox.cpp @@ -0,0 +1,868 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickdialogbuttonbox_p.h" +#include "qquickdialogbuttonbox_p_p.h" +#include "qquickabstractbutton_p.h" +#include "qquickbutton_p.h" +#include "qquickdialog_p_p.h" + +#include <QtCore/qpointer.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlcomponent.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype DialogButtonBox + \inherits Container +//! \instantiates QQuickDialogButtonBox + \inqmlmodule QtQuick.Controls + \ingroup qtquickcontrols2-dialogs + \brief A button box used in dialogs. + \since 5.8 + + Dialogs and message boxes typically present buttons in an order that + conforms to the interface guidelines for that platform. Invariably, + different platforms have their dialog buttons in different orders. + DialogButtonBox allows a developer to add buttons to it and will + automatically use the appropriate order for the user's platform. + + Most buttons for a dialog follow certain roles. Such roles include: + + \list + \li Accepting or rejecting the dialog. + \li Asking for help. + \li Performing actions on the dialog itself (such as resetting fields or + applying changes). + \endlist + + There can also be alternate ways of dismissing the dialog which may cause + destructive results. + + Most dialogs have buttons that can almost be considered standard (e.g. + \uicontrol OK and \uicontrol Cancel buttons). It is sometimes convenient + to create these buttons in a standard way. + + There are a couple ways of using DialogButtonBox. One way is to specify + the standard buttons (e.g. \uicontrol OK, \uicontrol Cancel, \uicontrol Save) + and let the button box setup the buttons. + + \image qtquickcontrols2-dialogbuttonbox.png + + \snippet qtquickcontrols2-dialogbuttonbox.qml 1 + + Alternatively, buttons and their roles can be specified by hand: + + \snippet qtquickcontrols2-dialogbuttonbox-attached.qml 1 + + You can also mix and match normal buttons and standard buttons. + + When a button is clicked in the button box, the \l clicked() signal is + emitted for the actual button that is pressed. In addition, the + following signals are automatically emitted when a button with the + respective role(s) is pressed: + + \table + \header + \li Role + \li Signal + \row + \li \c AcceptRole, \c YesRole + \li \l accepted() + \row + \li \c ApplyRole + \li \l applied() + \row + \li \c DiscardRole + \li \l discarded() + \row + \li \c HelpRole + \li \l helpRequested() + \row + \li \c RejectRole, \c NoRole + \li \l rejected() + \row + \li \c ResetRole + \li \l reset() + \endtable + + \sa Dialog +*/ + +/*! + \qmlsignal QtQuick.Controls::DialogButtonBox::accepted() + + This signal is emitted when a button defined with the \c AcceptRole or + \c YesRole is clicked. + + \sa rejected(), clicked(), helpRequested() +*/ + +/*! + \qmlsignal QtQuick.Controls::DialogButtonBox::rejected() + + This signal is emitted when a button defined with the \c RejectRole or + \c NoRole is clicked. + + \sa accepted(), helpRequested(), clicked() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlsignal QtQuick.Controls::DialogButtonBox::applied() + + This signal is emitted when a button defined with the \c ApplyRole is + clicked. + + \sa discarded(), reset() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlsignal QtQuick.Controls::DialogButtonBox::reset() + + This signal is emitted when a button defined with the \c ResetRole is + clicked. + + \sa discarded(), applied() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlsignal QtQuick.Controls::DialogButtonBox::discarded() + + This signal is emitted when a button defined with the \c DiscardRole is + clicked. + + \sa reset(), applied() +*/ + +/*! + \qmlsignal QtQuick.Controls::DialogButtonBox::helpRequested() + + This signal is emitted when a button defined with the \c HelpRole is clicked. + + \sa accepted(), rejected(), clicked() +*/ + +/*! + \qmlsignal QtQuick.Controls::DialogButtonBox::clicked(AbstractButton button) + + This signal is emitted when a \a button inside the button box is clicked. + + \sa accepted(), rejected(), helpRequested() +*/ + +static QPlatformDialogHelper::ButtonLayout platformButtonLayout() +{ + return QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::DialogButtonBoxLayout).value<QPlatformDialogHelper::ButtonLayout>(); +} + +void QQuickDialogButtonBoxPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + QQuickContainerPrivate::itemImplicitWidthChanged(item); + if (item == contentItem) + resizeContent(); + else + updateImplicitContentWidth(); +} + +void QQuickDialogButtonBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + QQuickContainerPrivate::itemImplicitHeightChanged(item); + if (item == contentItem) + resizeContent(); + else + updateImplicitContentHeight(); +} + +// adapted from QStyle::alignedRect() +static QRectF alignedRect(Qt::LayoutDirection direction, Qt::Alignment alignment, const QSizeF &size, const QRectF &rectangle) +{ + alignment = QGuiApplicationPrivate::visualAlignment(direction, alignment); + qreal x = rectangle.x(); + qreal y = rectangle.y(); + qreal w = size.width(); + qreal h = size.height(); + if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter || (alignment & Qt::AlignVertical_Mask) == 0) + y += (rectangle.size().height() - h) / 2; + else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom) + y += rectangle.size().height() - h; + if ((alignment & Qt::AlignRight) == Qt::AlignRight) + x += rectangle.size().width() - w; + else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter) + x += (rectangle.size().width() - w) / 2; + return QRectF(x, y, w, h); +} + +void QQuickDialogButtonBoxPrivate::resizeContent() +{ + Q_Q(QQuickDialogButtonBox); + if (!contentItem || !contentModel) + return; + + QRectF geometry = q->boundingRect().adjusted(q->leftPadding(), q->topPadding(), -q->rightPadding(), -q->bottomPadding()); + if (alignment != 0) + geometry = alignedRect(q->isMirrored() ? Qt::RightToLeft : Qt::LeftToRight, alignment, QSizeF(contentWidth, contentHeight), geometry); + + contentItem->setPosition(geometry.topLeft()); + contentItem->setSize(geometry.size()); +} + +void QQuickDialogButtonBoxPrivate::updateLayout() +{ + Q_Q(QQuickDialogButtonBox); + const int count = contentModel->count(); + if (count <= 0) + return; + + const int halign = alignment & Qt::AlignHorizontal_Mask; + const int valign = alignment & Qt::AlignVertical_Mask; + + QList<QQuickAbstractButton *> buttons; + const qreal cw = (alignment & Qt::AlignHorizontal_Mask) == 0 ? q->availableWidth() : contentWidth; + const qreal itemWidth = (cw - qMax(0, count - 1) * spacing) / count; + + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) { + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!p->widthValid()) { + if (!halign) + item->setWidth(itemWidth); + else + item->resetWidth(); + if (!valign) + item->setHeight(contentHeight); + else + item->resetHeight(); + p->widthValidFlag = false; + } + } + buttons += static_cast<QQuickAbstractButton *>(item); + } + + struct ButtonLayout { + ButtonLayout(QPlatformDialogHelper::ButtonLayout layout) + : m_layout(QPlatformDialogHelper::buttonLayout(Qt::Horizontal, layout)) + { + } + + bool operator()(QQuickAbstractButton *first, QQuickAbstractButton *second) + { + const QPlatformDialogHelper::ButtonRole firstRole = QQuickDialogPrivate::buttonRole(first); + const QPlatformDialogHelper::ButtonRole secondRole = QQuickDialogPrivate::buttonRole(second); + + if (firstRole != secondRole && firstRole != QPlatformDialogHelper::InvalidRole && secondRole != QPlatformDialogHelper::InvalidRole) { + const int *l = m_layout; + while (*l != QPlatformDialogHelper::EOL) { + // Unset the Reverse flag. + const int role = (*l & ~QPlatformDialogHelper::Reverse); + if (role == firstRole) + return true; + if (role == secondRole) + return false; + ++l; + } + } + + if (firstRole == secondRole) + return false; + + return firstRole != QPlatformDialogHelper::InvalidRole; + } + const int *m_layout; + }; + + std::stable_sort(buttons.begin(), buttons.end(), ButtonLayout(static_cast<QPlatformDialogHelper::ButtonLayout>(buttonLayout))); + + for (int i = 0; i < buttons.count() - 1; ++i) + q->insertItem(i, buttons.at(i)); +} + +qreal QQuickDialogButtonBoxPrivate::getContentWidth() const +{ + Q_Q(const QQuickDialogButtonBox); + if (!contentModel) + return 0; + + const int count = contentModel->count(); + const qreal totalSpacing = qMax(0, count - 1) * spacing; + qreal totalWidth = totalSpacing; + qreal maxWidth = 0; + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) { + totalWidth += item->implicitWidth(); + maxWidth = qMax(maxWidth, item->implicitWidth()); + } + } + if ((alignment & Qt::AlignHorizontal_Mask) == 0) + totalWidth = qMax(totalWidth, count * maxWidth + totalSpacing); + return totalWidth; +} + +qreal QQuickDialogButtonBoxPrivate::getContentHeight() const +{ + Q_Q(const QQuickDialogButtonBox); + if (!contentModel) + return 0; + + const int count = contentModel->count(); + qreal maxHeight = 0; + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) + maxHeight = qMax(maxHeight, item->implicitHeight()); + } + return maxHeight; +} + +void QQuickDialogButtonBoxPrivate::handleClick() +{ + Q_Q(QQuickDialogButtonBox); + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender()); + if (!button) + return; + + // Can't fetch this *after* emitting clicked, as clicked may destroy the button + // or change its role. Now changing the role is not possible yet, but arguably + // both clicked and accepted/rejected/etc. should be emitted "atomically" + // depending on whatever role the button had at the time of the click. + const QPlatformDialogHelper::ButtonRole role = QQuickDialogPrivate::buttonRole(button); + QPointer<QQuickDialogButtonBox> guard(q); + + emit q->clicked(button); + + if (!guard) + return; + + switch (role) { + case QPlatformDialogHelper::AcceptRole: + case QPlatformDialogHelper::YesRole: + emit q->accepted(); + break; + case QPlatformDialogHelper::RejectRole: + case QPlatformDialogHelper::NoRole: + emit q->rejected(); + break; + case QPlatformDialogHelper::ApplyRole: + emit q->applied(); + break; + case QPlatformDialogHelper::ResetRole: + emit q->reset(); + break; + case QPlatformDialogHelper::DestructiveRole: + emit q->discarded(); + break; + case QPlatformDialogHelper::HelpRole: + emit q->helpRequested(); + break; + default: + break; + } +} + +QString QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::StandardButton standardButton) +{ + return QPlatformTheme::removeMnemonics(QGuiApplicationPrivate::platformTheme()->standardButtonText(standardButton)); +} + +QQuickAbstractButton *QQuickDialogButtonBoxPrivate::createStandardButton(QPlatformDialogHelper::StandardButton standardButton) +{ + Q_Q(QQuickDialogButtonBox); + if (!delegate) + return nullptr; + + QQmlContext *creationContext = delegate->creationContext(); + if (!creationContext) + creationContext = qmlContext(q); + QQmlContext *context = new QQmlContext(creationContext, q); + context->setContextObject(q); + + QObject *object = delegate->beginCreate(context); + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(object); + if (button) { + QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(button, true)); + QQuickDialogButtonBoxAttachedPrivate::get(attached)->standardButton = standardButton; + attached->setButtonRole(QPlatformDialogHelper::buttonRole(standardButton)); + button->setText(buttonText(standardButton)); + delegate->completeCreate(); + button->setParent(q); + return button; + } + + delete object; + return nullptr; +} + +void QQuickDialogButtonBoxPrivate::removeStandardButtons() +{ + Q_Q(QQuickDialogButtonBox); + int i = q->count() - 1; + while (i >= 0) { + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->itemAt(i)); + if (button) { + QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>( + qmlAttachedPropertiesObject<QQuickDialogButtonBox>(button, false)); + if (attached) { + QQuickDialogButtonBoxAttachedPrivate *p = QQuickDialogButtonBoxAttachedPrivate::get(attached); + if (p->standardButton != QPlatformDialogHelper::NoButton) { + q->removeItem(button); + button->deleteLater(); + } + } + } + --i; + } +} + +void QQuickDialogButtonBoxPrivate::updateLanguage() +{ + Q_Q(QQuickDialogButtonBox); + int i = q->count() - 1; + while (i >= 0) { + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(itemAt(i)); + if (button) { + QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>( + qmlAttachedPropertiesObject<QQuickDialogButtonBox>(button, true)); + const auto boxAttachedPrivate = QQuickDialogButtonBoxAttachedPrivate::get(attached); + const QPlatformDialogHelper::StandardButton standardButton = boxAttachedPrivate->standardButton; + // The button might be a custom one with explicitly specified text, so we shouldn't change it in that case. + if (standardButton != QPlatformDialogHelper::NoButton) { + button->setText(buttonText(standardButton)); + } + } + --i; + } +} + +QQuickDialogButtonBox::QQuickDialogButtonBox(QQuickItem *parent) + : QQuickContainer(*(new QQuickDialogButtonBoxPrivate), parent) +{ + Q_D(QQuickDialogButtonBox); + d->changeTypes |= QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; + d->buttonLayout = platformButtonLayout(); +} + +QQuickDialogButtonBox::~QQuickDialogButtonBox() +{ + Q_D(QQuickDialogButtonBox); + // QQuickContainerPrivate does call this, but as our type information has already been + // destroyed by that point (since this destructor has already run), it won't call our + // implementation. So, we need to make sure our implementation is called. If we don't do this, + // the listener we installed on the contentItem won't get removed, possibly resulting in + // heap-use-after-frees. + contentItemChange(nullptr, d->contentItem); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::DialogButtonBox::position + + This property holds the position of the button box. + + \note If the button box is assigned as a header or footer of ApplicationWindow + or Page, the appropriate position is set automatically. + + Possible values: + \value DialogButtonBox.Header The button box is at the top, as a window or page header. + \value DialogButtonBox.Footer The button box is at the bottom, as a window or page header. + + The default value is \c Footer. + + \sa Dialog::header, Dialog::footer +*/ +QQuickDialogButtonBox::Position QQuickDialogButtonBox::position() const +{ + Q_D(const QQuickDialogButtonBox); + return d->position; +} + +void QQuickDialogButtonBox::setPosition(Position position) +{ + Q_D(QQuickDialogButtonBox); + if (d->position == position) + return; + + d->position = position; + emit positionChanged(); +} + +/*! + \qmlproperty flags QtQuick.Controls::DialogButtonBox::alignment + + This property holds the alignment of the buttons. + + Possible values: + \value undefined The buttons are resized to fill the available space. + \value Qt.AlignLeft The buttons are aligned to the left. + \value Qt.AlignHCenter The buttons are horizontally centered. + \value Qt.AlignRight The buttons are aligned to the right. + \value Qt.AlignTop The buttons are aligned to the top. + \value Qt.AlignVCenter The buttons are vertically centered. + \value Qt.AlignBottom The buttons are aligned to the bottom. +*/ +Qt::Alignment QQuickDialogButtonBox::alignment() const +{ + Q_D(const QQuickDialogButtonBox); + return d->alignment; +} + +void QQuickDialogButtonBox::setAlignment(Qt::Alignment alignment) +{ + Q_D(QQuickDialogButtonBox); + if (d->alignment == alignment) + return; + + d->alignment = alignment; + if (isComponentComplete()) { + d->resizeContent(); + polish(); + } + emit alignmentChanged(); +} + +void QQuickDialogButtonBox::resetAlignment() +{ + setAlignment({}); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::DialogButtonBox::standardButtons + + This property holds a combination of standard buttons that are used by the button box. + + \snippet qtquickcontrols2-dialogbuttonbox.qml 1 + + The buttons will be positioned in the appropriate order for the user's platform. + + Possible flags: + \value DialogButtonBox.Ok An "OK" button defined with the \c AcceptRole. + \value DialogButtonBox.Open An "Open" button defined with the \c AcceptRole. + \value DialogButtonBox.Save A "Save" button defined with the \c AcceptRole. + \value DialogButtonBox.Cancel A "Cancel" button defined with the \c RejectRole. + \value DialogButtonBox.Close A "Close" button defined with the \c RejectRole. + \value DialogButtonBox.Discard A "Discard" or "Don't Save" button, depending on the platform, defined with the \c DestructiveRole. + \value DialogButtonBox.Apply An "Apply" button defined with the \c ApplyRole. + \value DialogButtonBox.Reset A "Reset" button defined with the \c ResetRole. + \value DialogButtonBox.RestoreDefaults A "Restore Defaults" button defined with the \c ResetRole. + \value DialogButtonBox.Help A "Help" button defined with the \c HelpRole. + \value DialogButtonBox.SaveAll A "Save All" button defined with the \c AcceptRole. + \value DialogButtonBox.Yes A "Yes" button defined with the \c YesRole. + \value DialogButtonBox.YesToAll A "Yes to All" button defined with the \c YesRole. + \value DialogButtonBox.No A "No" button defined with the \c NoRole. + \value DialogButtonBox.NoToAll A "No to All" button defined with the \c NoRole. + \value DialogButtonBox.Abort An "Abort" button defined with the \c RejectRole. + \value DialogButtonBox.Retry A "Retry" button defined with the \c AcceptRole. + \value DialogButtonBox.Ignore An "Ignore" button defined with the \c AcceptRole. + \value DialogButtonBox.NoButton An invalid button. + + \sa standardButton() +*/ +QPlatformDialogHelper::StandardButtons QQuickDialogButtonBox::standardButtons() const +{ + Q_D(const QQuickDialogButtonBox); + return d->standardButtons; +} + +void QQuickDialogButtonBox::setStandardButtons(QPlatformDialogHelper::StandardButtons buttons) +{ + Q_D(QQuickDialogButtonBox); + if (d->standardButtons == buttons) + return; + + d->removeStandardButtons(); + + for (int i = QPlatformDialogHelper::FirstButton; i <= QPlatformDialogHelper::LastButton; i<<=1) { + QPlatformDialogHelper::StandardButton standardButton = static_cast<QPlatformDialogHelper::StandardButton>(i); + if (standardButton & buttons) { + QQuickAbstractButton *button = d->createStandardButton(standardButton); + if (button) + addItem(button); + } + } + + if (isComponentComplete()) + polish(); + + d->standardButtons = buttons; + emit standardButtonsChanged(); +} + +/*! + \qmlmethod AbstractButton QtQuick.Controls::DialogButtonBox::standardButton(StandardButton button) + + Returns the specified standard \a button, or \c null if it does not exist. + + \sa standardButtons +*/ +QQuickAbstractButton *QQuickDialogButtonBox::standardButton(QPlatformDialogHelper::StandardButton button) const +{ + Q_D(const QQuickDialogButtonBox); + if (Q_UNLIKELY(!(d->standardButtons & button))) + return nullptr; + for (int i = 0, n = count(); i < n; ++i) { + QQuickAbstractButton *btn = qobject_cast<QQuickAbstractButton *>(d->itemAt(i)); + if (Q_LIKELY(btn)) { + QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(btn, false)); + if (attached && QQuickDialogButtonBoxAttachedPrivate::get(attached)->standardButton == button) + return btn; + } + } + return nullptr; +} + +/*! + \qmlproperty Component QtQuick.Controls::DialogButtonBox::delegate + + This property holds a delegate for creating standard buttons. + + \sa standardButtons +*/ +QQmlComponent *QQuickDialogButtonBox::delegate() const +{ + Q_D(const QQuickDialogButtonBox); + return d->delegate; +} + +void QQuickDialogButtonBox::setDelegate(QQmlComponent* delegate) +{ + Q_D(QQuickDialogButtonBox); + if (d->delegate == delegate) + return; + + delete d->delegate; + d->delegate = delegate; + emit delegateChanged(); +} + +QQuickDialogButtonBoxAttached *QQuickDialogButtonBox::qmlAttachedProperties(QObject *object) +{ + return new QQuickDialogButtonBoxAttached(object); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty enumeration QtQuick.Controls::DialogButtonBox::buttonLayout + + This property holds the button layout policy to be used when arranging the buttons contained in the button box. + The default value is platform-specific. + + Available values: + \value DialogButtonBox.WinLayout Use a policy appropriate for applications on Windows. + \value DialogButtonBox.MacLayout Use a policy appropriate for applications on macOS. + \value DialogButtonBox.KdeLayout Use a policy appropriate for applications on KDE. + \value DialogButtonBox.GnomeLayout Use a policy appropriate for applications on GNOME. + \value DialogButtonBox.AndroidLayout Use a policy appropriate for applications on Android. +*/ +QPlatformDialogHelper::ButtonLayout QQuickDialogButtonBox::buttonLayout() const +{ + Q_D(const QQuickDialogButtonBox); + return d->buttonLayout; +} + +void QQuickDialogButtonBox::setButtonLayout(QPlatformDialogHelper::ButtonLayout layout) +{ + Q_D(QQuickDialogButtonBox); + if (d->buttonLayout == layout) + return; + + d->buttonLayout = layout; + if (isComponentComplete()) + d->updateLayout(); + emit buttonLayoutChanged(); +} + +void QQuickDialogButtonBox::resetButtonLayout() +{ + setButtonLayout(platformButtonLayout()); +} + +void QQuickDialogButtonBox::updatePolish() +{ + Q_D(QQuickDialogButtonBox); + QQuickContainer::updatePolish(); + d->updateLayout(); +} + +bool QQuickDialogButtonBox::event(QEvent *e) +{ + Q_D(QQuickDialogButtonBox); + if (e->type() == QEvent::LanguageChange) + d->updateLanguage(); + return QQuickContainer::event(e); +} + +void QQuickDialogButtonBox::componentComplete() +{ + Q_D(QQuickDialogButtonBox); + QQuickContainer::componentComplete(); + d->updateLayout(); +} + +void QQuickDialogButtonBox::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickDialogButtonBox); + QQuickContainer::geometryChange(newGeometry, oldGeometry); + d->updateLayout(); +} + +void QQuickDialogButtonBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickDialogButtonBox); + QQuickContainer::contentItemChange(newItem, oldItem); + if (oldItem) + QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight); + if (newItem) + QQuickItemPrivate::get(newItem)->addItemChangeListener(d, QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight); +} + +bool QQuickDialogButtonBox::isContent(QQuickItem *item) const +{ + return qobject_cast<QQuickAbstractButton *>(item); +} + +void QQuickDialogButtonBox::itemAdded(int index, QQuickItem *item) +{ + Q_D(QQuickDialogButtonBox); + Q_UNUSED(index); + if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(item)) + QObjectPrivate::connect(button, &QQuickAbstractButton::clicked, d, &QQuickDialogButtonBoxPrivate::handleClick); + if (QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(item, false))) + QQuickDialogButtonBoxAttachedPrivate::get(attached)->setButtonBox(this); + d->updateImplicitContentSize(); + if (isComponentComplete()) + polish(); +} + +void QQuickDialogButtonBox::itemRemoved(int index, QQuickItem *item) +{ + Q_D(QQuickDialogButtonBox); + Q_UNUSED(index); + if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(item)) + QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, d, &QQuickDialogButtonBoxPrivate::handleClick); + if (QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(item, false))) + QQuickDialogButtonBoxAttachedPrivate::get(attached)->setButtonBox(nullptr); + d->updateImplicitContentSize(); + if (isComponentComplete()) + polish(); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickDialogButtonBox::accessibleRole() const +{ + return QAccessible::PageTabList; +} +#endif + +void QQuickDialogButtonBoxAttachedPrivate::setButtonBox(QQuickDialogButtonBox *box) +{ + Q_Q(QQuickDialogButtonBoxAttached); + if (buttonBox == box) + return; + + buttonBox = box; + emit q->buttonBoxChanged(); +} + +QQuickDialogButtonBoxAttached::QQuickDialogButtonBoxAttached(QObject *parent) + : QObject(*(new QQuickDialogButtonBoxAttachedPrivate), parent) +{ + Q_D(QQuickDialogButtonBoxAttached); + QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent); + while (parentItem && !d->buttonBox) { + d->buttonBox = qobject_cast<QQuickDialogButtonBox *>(parentItem); + parentItem = parentItem->parentItem(); + } +} + +/*! + \qmlattachedproperty DialogButtonBox QtQuick.Controls::DialogButtonBox::buttonBox + \readonly + + This attached property holds the button box that manages this button, or + \c null if the button is not in a button box. +*/ +QQuickDialogButtonBox *QQuickDialogButtonBoxAttached::buttonBox() const +{ + Q_D(const QQuickDialogButtonBoxAttached); + return d->buttonBox; +} + +/*! + \qmlattachedproperty enumeration QtQuick.Controls::DialogButtonBox::buttonRole + + This attached property holds the role of each button in a button box. + + \snippet qtquickcontrols2-dialogbuttonbox-attached.qml 1 + + Available values: + \value DialogButtonBox.InvalidRole The button is invalid. + \value DialogButtonBox.AcceptRole Clicking the button causes the dialog to be accepted (e.g. \uicontrol OK). + \value DialogButtonBox.RejectRole Clicking the button causes the dialog to be rejected (e.g. \uicontrol Cancel). + \value DialogButtonBox.DestructiveRole Clicking the button causes a destructive change (e.g. for discarding changes) and closes the dialog. + \value DialogButtonBox.ActionRole Clicking the button causes changes to the elements within the dialog. + \value DialogButtonBox.HelpRole The button can be clicked to request help. + \value DialogButtonBox.YesRole The button is a "Yes"-like button. + \value DialogButtonBox.NoRole The button is a "No"-like button. + \value DialogButtonBox.ResetRole The button resets the dialog's fields to default values. + \value DialogButtonBox.ApplyRole The button applies current changes. +*/ +QPlatformDialogHelper::ButtonRole QQuickDialogButtonBoxAttached::buttonRole() const +{ + Q_D(const QQuickDialogButtonBoxAttached); + return d->buttonRole; +} + +void QQuickDialogButtonBoxAttached::setButtonRole(QPlatformDialogHelper::ButtonRole role) +{ + Q_D(QQuickDialogButtonBoxAttached); + if (d->buttonRole == role) + return; + + d->buttonRole = role; + emit buttonRoleChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquickdialogbuttonbox_p.cpp" diff --git a/src/quicktemplates2/qquickdialogbuttonbox_p.h b/src/quicktemplates2/qquickdialogbuttonbox_p.h new file mode 100644 index 0000000000..48e721e5cd --- /dev/null +++ b/src/quicktemplates2/qquickdialogbuttonbox_p.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDIALOGBUTTONBOX_P_H +#define QQUICKDIALOGBUTTONBOX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +#include <QtQuickTemplates2/private/qquickcontainer_p.h> +#include <QtGui/qpa/qplatformdialoghelper.h> + +QT_BEGIN_NAMESPACE + +class QQmlComponent; +class QQuickDialogButtonBoxPrivate; +class QQuickDialogButtonBoxAttached; +class QQuickDialogButtonBoxAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBox : public QQuickContainer +{ + Q_OBJECT + Q_PROPERTY(Position position READ position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment RESET resetAlignment NOTIFY alignmentChanged FINAL) + Q_PROPERTY(QPlatformDialogHelper::StandardButtons standardButtons READ standardButtons WRITE setStandardButtons NOTIFY standardButtonsChanged FINAL) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) + // 2.5 (Qt 5.12) + Q_PROPERTY(QPlatformDialogHelper::ButtonLayout buttonLayout READ buttonLayout WRITE setButtonLayout RESET resetButtonLayout NOTIFY buttonLayoutChanged FINAL REVISION(2, 5)) + Q_FLAGS(QPlatformDialogHelper::StandardButtons) + QML_NAMED_ELEMENT(DialogButtonBox) + QML_ATTACHED(QQuickDialogButtonBoxAttached) + QML_ADDED_IN_VERSION(2, 1) + +public: + explicit QQuickDialogButtonBox(QQuickItem *parent = nullptr); + ~QQuickDialogButtonBox(); + + enum Position { + Header, + Footer + }; + Q_ENUM(Position) + + Position position() const; + void setPosition(Position position); + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment alignment); + void resetAlignment(); + + QPlatformDialogHelper::StandardButtons standardButtons() const; + void setStandardButtons(QPlatformDialogHelper::StandardButtons buttons); + Q_INVOKABLE QQuickAbstractButton *standardButton(QPlatformDialogHelper::StandardButton button) const; + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + static QQuickDialogButtonBoxAttached *qmlAttachedProperties(QObject *object); + + // 2.5 (Qt 5.12) + Q_ENUMS(QPlatformDialogHelper::ButtonLayout) + + QPlatformDialogHelper::ButtonLayout buttonLayout() const; + void setButtonLayout(QPlatformDialogHelper::ButtonLayout layout); + void resetButtonLayout(); + +Q_SIGNALS: + void accepted(); + void rejected(); + void helpRequested(); + void clicked(QQuickAbstractButton *button); + void positionChanged(); + void alignmentChanged(); + void standardButtonsChanged(); + void delegateChanged(); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void applied(); + Q_REVISION(2, 3) void reset(); + Q_REVISION(2, 3) void discarded(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void buttonLayoutChanged(); + +protected: + void updatePolish() override; + void componentComplete() override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + bool isContent(QQuickItem *item) const override; + void itemAdded(int index, QQuickItem *item) override; + void itemRemoved(int index, QQuickItem *item) override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + bool event(QEvent *e) override; + +private: + Q_DISABLE_COPY(QQuickDialogButtonBox) + Q_DECLARE_PRIVATE(QQuickDialogButtonBox) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBoxAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickDialogButtonBox *buttonBox READ buttonBox NOTIFY buttonBoxChanged FINAL) + Q_PROPERTY(QPlatformDialogHelper::ButtonRole buttonRole READ buttonRole WRITE setButtonRole NOTIFY buttonRoleChanged FINAL) + Q_ENUMS(QPlatformDialogHelper::ButtonRole) + +public: + explicit QQuickDialogButtonBoxAttached(QObject *parent = nullptr); + + QQuickDialogButtonBox *buttonBox() const; + + QPlatformDialogHelper::ButtonRole buttonRole() const; + void setButtonRole(QPlatformDialogHelper::ButtonRole role); + +Q_SIGNALS: + void buttonBoxChanged(); + void buttonRoleChanged(); + +private: + Q_DISABLE_COPY(QQuickDialogButtonBoxAttached) + Q_DECLARE_PRIVATE(QQuickDialogButtonBoxAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDialogButtonBox) +QML_DECLARE_TYPEINFO(QQuickDialogButtonBox, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKDIALOGBUTTONBOX_P_H diff --git a/src/quicktemplates2/qquickdialogbuttonbox_p_p.h b/src/quicktemplates2/qquickdialogbuttonbox_p_p.h new file mode 100644 index 0000000000..5e08b2adf1 --- /dev/null +++ b/src/quicktemplates2/qquickdialogbuttonbox_p_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDIALOGBUTTONBOX_P_P_H +#define QQUICKDIALOGBUTTONBOX_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p_p.h> +#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBoxPrivate : public QQuickContainerPrivate +{ + Q_DECLARE_PUBLIC(QQuickDialogButtonBox) + +public: + static QQuickDialogButtonBoxPrivate *get(QQuickDialogButtonBox *box) + { + return box->d_func(); + } + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + void resizeContent() override; + + void updateLayout(); + + qreal getContentWidth() const override; + qreal getContentHeight() const override; + + void handleClick(); + + static QString buttonText(QPlatformDialogHelper::StandardButton standardButton); + + QQuickAbstractButton *createStandardButton(QPlatformDialogHelper::StandardButton button); + void removeStandardButtons(); + + void updateLanguage(); + + Qt::Alignment alignment; + QQuickDialogButtonBox::Position position = QQuickDialogButtonBox::Footer; + QPlatformDialogHelper::StandardButtons standardButtons = QPlatformDialogHelper::NoButton; + QPlatformDialogHelper::ButtonLayout buttonLayout = QPlatformDialogHelper::UnknownLayout; + QQmlComponent *delegate = nullptr; +}; + +class QQuickDialogButtonBoxAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickDialogButtonBoxAttached) + +public: + static QQuickDialogButtonBoxAttachedPrivate *get(QQuickDialogButtonBoxAttached *q) + { + return q->d_func(); + } + + void setButtonBox(QQuickDialogButtonBox *box); + + QQuickDialogButtonBox *buttonBox = nullptr; + QPlatformDialogHelper::ButtonRole buttonRole = QPlatformDialogHelper::InvalidRole; + QPlatformDialogHelper::StandardButton standardButton = QPlatformDialogHelper::NoButton; +}; + +QT_END_NAMESPACE + +#endif // QQUICKDIALOGBUTTONBOX_P_P_H diff --git a/src/quicktemplates2/qquickdrawer.cpp b/src/quicktemplates2/qquickdrawer.cpp new file mode 100644 index 0000000000..1d89948b4d --- /dev/null +++ b/src/quicktemplates2/qquickdrawer.cpp @@ -0,0 +1,822 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickdrawer_p.h" +#include "qquickdrawer_p_p.h" +#include "qquickpopupitem_p_p.h" +#include "qquickpopuppositioner_p_p.h" + +#include <QtGui/qstylehints.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickwindow_p.h> +#include <QtQuick/private/qquickanimation_p.h> +#include <QtQuick/private/qquicktransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Drawer + \inherits Popup +//! \instantiates QQuickDrawer + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-navigation + \ingroup qtquickcontrols2-popups + \brief Side panel that can be opened and closed using a swipe gesture. + + Drawer provides a swipe-based side panel, similar to those often used in + touch interfaces to provide a central location for navigation. + + \image qtquickcontrols2-drawer.gif + + Drawer can be positioned at any of the four edges of the content item. + The drawer above is positioned against the left edge of the window. The + drawer is then opened by \e "dragging" it out from the left edge of the + window. + + \code + import QtQuick + import QtQuick.Controls + + ApplicationWindow { + id: window + visible: true + + Drawer { + id: drawer + width: 0.66 * window.width + height: window.height + + Label { + text: "Content goes here!" + anchors.centerIn: parent + } + } + } + \endcode + + Drawer is a special type of popup that resides at one of the window \l {edge}{edges}. + By default, Drawer re-parents itself to the window \c overlay, and therefore operates + on window coordinates. It is also possible to manually set the \l{Popup::}{parent} to + something else to make the drawer operate in a specific coordinate space. + + Drawer can be configured to cover only part of its window edge. The following example + illustrates how Drawer can be positioned to appear below a window header: + + \code + import QtQuick + import QtQuick.Controls + + ApplicationWindow { + id: window + visible: true + + header: ToolBar { } + + Drawer { + y: header.height + width: window.width * 0.6 + height: window.height - header.height + } + } + \endcode + + The \l position property determines how much of the drawer is visible, as + a value between \c 0.0 and \c 1.0. It is not possible to set the x-coordinate + (or horizontal margins) of a drawer at the left or right window edge, or the + y-coordinate (or vertical margins) of a drawer at the top or bottom window edge. + + In the image above, the application's contents are \e "pushed" across the + screen. This is achieved by applying a translation to the contents: + + \code + import QtQuick + import QtQuick.Controls + + ApplicationWindow { + id: window + width: 200 + height: 228 + visible: true + + Drawer { + id: drawer + width: 0.66 * window.width + height: window.height + } + + Label { + id: content + + text: "Aa" + font.pixelSize: 96 + anchors.fill: parent + verticalAlignment: Label.AlignVCenter + horizontalAlignment: Label.AlignHCenter + + transform: Translate { + x: drawer.position * content.width * 0.33 + } + } + } + \endcode + + If you would like the application's contents to stay where they are when + the drawer is opened, don't apply a translation. + + Drawer can be configured as a non-closable persistent side panel by + making the Drawer \l {Popup::modal}{non-modal} and \l {interactive} + {non-interactive}. See the \l {Qt Quick Controls 2 - Side Panel}{Side Panel} + example for more details. + + \note On some platforms, certain edges may be reserved for system + gestures and therefore cannot be used with Drawer. For example, the + top and bottom edges may be reserved for system notifications and + control centers on Android and iOS. + + \sa SwipeView, {Customizing Drawer}, {Navigation Controls}, {Popup Controls} +*/ + +class QQuickDrawerPositioner : public QQuickPopupPositioner +{ +public: + QQuickDrawerPositioner(QQuickDrawer *drawer) : QQuickPopupPositioner(drawer) { } + + void reposition() override; +}; + +qreal QQuickDrawerPrivate::offsetAt(const QPointF &point) const +{ + qreal offset = positionAt(point) - position; + + // don't jump when dragged open + if (offset > 0 && position > 0 && !contains(point)) + offset = 0; + + return offset; +} + +qreal QQuickDrawerPrivate::positionAt(const QPointF &point) const +{ + Q_Q(const QQuickDrawer); + QQuickWindow *window = q->window(); + if (!window) + return 0; + + switch (edge) { + case Qt::TopEdge: + return point.y() / q->height(); + case Qt::LeftEdge: + return point.x() / q->width(); + case Qt::RightEdge: + return (window->width() - point.x()) / q->width(); + case Qt::BottomEdge: + return (window->height() - point.y()) / q->height(); + default: + return 0; + } +} + +QQuickPopupPositioner *QQuickDrawerPrivate::getPositioner() +{ + Q_Q(QQuickDrawer); + if (!positioner) + positioner = new QQuickDrawerPositioner(q); + return positioner; +} + +void QQuickDrawerPositioner::reposition() +{ + if (m_positioning) + return; + + QQuickDrawer *drawer = static_cast<QQuickDrawer*>(popup()); + QQuickWindow *window = drawer->window(); + if (!window) + return; + + const qreal position = drawer->position(); + QQuickItem *popupItem = drawer->popupItem(); + switch (drawer->edge()) { + case Qt::LeftEdge: + popupItem->setX((position - 1.0) * popupItem->width()); + break; + case Qt::RightEdge: + popupItem->setX(window->width() - position * popupItem->width()); + break; + case Qt::TopEdge: + popupItem->setY((position - 1.0) * popupItem->height()); + break; + case Qt::BottomEdge: + popupItem->setY(window->height() - position * popupItem->height()); + break; + } + + QQuickPopupPositioner::reposition(); +} + +void QQuickDrawerPrivate::showOverlay() +{ + // managed in setPosition() +} + +void QQuickDrawerPrivate::hideOverlay() +{ + // managed in setPosition() +} + +void QQuickDrawerPrivate::resizeOverlay() +{ + if (!dimmer || !window) + return; + + QRectF geometry(0, 0, window->width(), window->height()); + + if (edge == Qt::LeftEdge || edge == Qt::RightEdge) { + geometry.setY(popupItem->y()); + geometry.setHeight(popupItem->height()); + } else { + geometry.setX(popupItem->x()); + geometry.setWidth(popupItem->width()); + } + + dimmer->setPosition(geometry.topLeft()); + dimmer->setSize(geometry.size()); +} + +static bool isWithinDragMargin(const QQuickDrawer *drawer, const QPointF &pos) +{ + switch (drawer->edge()) { + case Qt::LeftEdge: + return pos.x() <= drawer->dragMargin(); + case Qt::RightEdge: + return pos.x() >= drawer->window()->width() - drawer->dragMargin(); + case Qt::TopEdge: + return pos.y() <= drawer->dragMargin(); + case Qt::BottomEdge: + return pos.y() >= drawer->window()->height() - drawer->dragMargin(); + default: + Q_UNREACHABLE(); + break; + } + return false; +} + +bool QQuickDrawerPrivate::startDrag(QEvent *event) +{ + Q_Q(QQuickDrawer); + if (!window || !interactive || dragMargin < 0.0 || qFuzzyIsNull(dragMargin)) + return false; + + switch (event->type()) { + case QEvent::MouseButtonPress: + if (isWithinDragMargin(q, static_cast<QMouseEvent *>(event)->scenePosition())) { + prepareEnterTransition(); + reposition(); + return handleMouseEvent(window->contentItem(), static_cast<QMouseEvent *>(event)); + } + break; + +#if QT_CONFIG(quicktemplates2_multitouch) + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + for (const QTouchEvent::TouchPoint &point : static_cast<QTouchEvent *>(event)->points()) { + if (point.state() == QEventPoint::Pressed && isWithinDragMargin(q, point.scenePosition())) { + prepareEnterTransition(); + reposition(); + return handleTouchEvent(window->contentItem(), static_cast<QTouchEvent *>(event)); + } + } + break; +#endif + + default: + break; + } + + return false; +} + +static inline bool keepGrab(QQuickItem *item) +{ + return item->keepMouseGrab() || item->keepTouchGrab(); +} + +bool QQuickDrawerPrivate::grabMouse(QQuickItem *item, QMouseEvent *event) +{ + Q_Q(QQuickDrawer); + handleMouseEvent(item, event); + + if (!window || !interactive || keepGrab(popupItem) || keepGrab(item)) + return false; + + const QPointF movePoint = event->scenePosition(); + + // Flickable uses a hard-coded threshold of 15 for flicking, and + // QStyleHints::startDragDistance for dragging. Drawer uses a bit + // larger threshold to avoid being too eager to steal touch (QTBUG-50045) + const int threshold = qMax(20, QGuiApplication::styleHints()->startDragDistance() + 5); + bool overThreshold = false; + if (position > 0 || dragMargin > 0) { + const bool xOverThreshold = QQuickWindowPrivate::dragOverThreshold(movePoint.x() - pressPoint.x(), Qt::XAxis, event, threshold); + const bool yOverThreshold = QQuickWindowPrivate::dragOverThreshold(movePoint.y() - pressPoint.y(), Qt::YAxis, event, threshold); + if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + overThreshold = xOverThreshold && !yOverThreshold; + else + overThreshold = yOverThreshold && !xOverThreshold; + } + + // Don't be too eager to steal presses outside the drawer (QTBUG-53929) + if (overThreshold && qFuzzyCompare(position, qreal(1.0)) && !contains(movePoint)) { + if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + overThreshold = qAbs(movePoint.x() - q->width()) < dragMargin; + else + overThreshold = qAbs(movePoint.y() - q->height()) < dragMargin; + } + + if (overThreshold) { + popupItem->grabMouse(); + popupItem->setKeepMouseGrab(true); + offset = offsetAt(movePoint); + } + + return overThreshold; +} + +#if QT_CONFIG(quicktemplates2_multitouch) +bool QQuickDrawerPrivate::grabTouch(QQuickItem *item, QTouchEvent *event) +{ + Q_Q(QQuickDrawer); + bool handled = handleTouchEvent(item, event); + + if (!window || !interactive || keepGrab(popupItem) || keepGrab(item) || !event->touchPointStates().testFlag(QEventPoint::Updated)) + return handled; + + bool overThreshold = false; + for (const QTouchEvent::TouchPoint &point : event->points()) { + if (!acceptTouch(point) || point.state() != QEventPoint::Updated) + continue; + + const QPointF movePoint = point.scenePosition(); + + // Flickable uses a hard-coded threshold of 15 for flicking, and + // QStyleHints::startDragDistance for dragging. Drawer uses a bit + // larger threshold to avoid being too eager to steal touch (QTBUG-50045) + const int threshold = qMax(20, QGuiApplication::styleHints()->startDragDistance() + 5); + if (position > 0 || dragMargin > 0) { + const bool xOverThreshold = QQuickWindowPrivate::dragOverThreshold(movePoint.x() - pressPoint.x(), Qt::XAxis, &point, threshold); + const bool yOverThreshold = QQuickWindowPrivate::dragOverThreshold(movePoint.y() - pressPoint.y(), Qt::YAxis, &point, threshold); + if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + overThreshold = xOverThreshold && !yOverThreshold; + else + overThreshold = yOverThreshold && !xOverThreshold; + } + + // Don't be too eager to steal presses outside the drawer (QTBUG-53929) + if (overThreshold && qFuzzyCompare(position, qreal(1.0)) && !contains(movePoint)) { + if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + overThreshold = qAbs(movePoint.x() - q->width()) < dragMargin; + else + overThreshold = qAbs(movePoint.y() - q->height()) < dragMargin; + } + + if (overThreshold) { + popupItem->grabTouchPoints(QList<int>() << touchId); + popupItem->setKeepTouchGrab(true); + offset = offsetAt(movePoint); + } + } + + return overThreshold; +} +#endif + +static const qreal openCloseVelocityThreshold = 300; + +// Overrides QQuickPopupPrivate::blockInput, which is called by +// QQuickPopupPrivate::handlePress/Move/Release, which we call in our own +// handlePress/Move/Release overrides. +// This implementation conflates two things: should the event going to the item get +// modally blocked by us? Or should we accept the event and become the grabber? +// Those are two fundamentally different questions for the drawer as a (usually) +// interactive control. +bool QQuickDrawerPrivate::blockInput(QQuickItem *item, const QPointF &point) const +{ + Q_Q(const QQuickDrawer); + + // We want all events, if mouse/touch is already grabbed. + if (popupItem->keepMouseGrab() || popupItem->keepTouchGrab()) + return true; + + // Don't block input to drawer's children/content. + if (popupItem->isAncestorOf(item)) + return false; + + // Don't block outside a drawer's background dimming + if (dimmer && !dimmer->contains(dimmer->mapFromScene(point))) + return false; + + // Accept all events within drag area. + if (isWithinDragMargin(q, point)) + return true; + + // Accept all other events if drawer is modal. + return modal; +} + +bool QQuickDrawerPrivate::handlePress(QQuickItem *item, const QPointF &point, ulong timestamp) +{ + offset = 0; + velocityCalculator.startMeasuring(point, timestamp); + + if (!QQuickPopupPrivate::handlePress(item, point, timestamp)) + return interactive && popupItem == item; + + return true; +} + +bool QQuickDrawerPrivate::handleMove(QQuickItem *item, const QPointF &point, ulong timestamp) +{ + Q_Q(QQuickDrawer); + if (!QQuickPopupPrivate::handleMove(item, point, timestamp)) + return false; + + // limit/reset the offset to the edge of the drawer when pushed from the outside + if (qFuzzyCompare(position, qreal(1.0)) && !contains(point)) + offset = 0; + + bool isGrabbed = popupItem->keepMouseGrab() || popupItem->keepTouchGrab(); + if (isGrabbed) + q->setPosition(positionAt(point) - offset); + + return isGrabbed; +} + +bool QQuickDrawerPrivate::handleRelease(QQuickItem *item, const QPointF &point, ulong timestamp) +{ + auto cleanup = qScopeGuard([this] { + popupItem->setKeepMouseGrab(false); + popupItem->setKeepTouchGrab(false); + pressPoint = QPointF(); + touchId = -1; + }); + if (pressPoint.isNull()) + return false; + if (!popupItem->keepMouseGrab() && !popupItem->keepTouchGrab()) { + velocityCalculator.reset(); + return QQuickPopupPrivate::handleRelease(item, point, timestamp); + } + + velocityCalculator.stopMeasuring(point, timestamp); + + qreal velocity = 0; + if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + velocity = velocityCalculator.velocity().x(); + else + velocity = velocityCalculator.velocity().y(); + + // the velocity is calculated so that swipes from left to right + // and top to bottom have positive velocity, and swipes from right + // to left and bottom to top have negative velocity. + // + // - top/left edge: positive velocity opens, negative velocity closes + // - bottom/right edge: negative velocity opens, positive velocity closes + // + // => invert the velocity for bottom and right edges, for the threshold comparison below + if (edge == Qt::RightEdge || edge == Qt::BottomEdge) + velocity = -velocity; + + if (position > 0.7 || velocity > openCloseVelocityThreshold) { + transitionManager.transitionEnter(); + } else if (position < 0.3 || velocity < -openCloseVelocityThreshold) { + transitionManager.transitionExit(); + } else { + switch (edge) { + case Qt::LeftEdge: + if (point.x() - pressPoint.x() > 0) + transitionManager.transitionEnter(); + else + transitionManager.transitionExit(); + break; + case Qt::RightEdge: + if (point.x() - pressPoint.x() < 0) + transitionManager.transitionEnter(); + else + transitionManager.transitionExit(); + break; + case Qt::TopEdge: + if (point.y() - pressPoint.y() > 0) + transitionManager.transitionEnter(); + else + transitionManager.transitionExit(); + break; + case Qt::BottomEdge: + if (point.y() - pressPoint.y() < 0) + transitionManager.transitionEnter(); + else + transitionManager.transitionExit(); + break; + } + } + + // the cleanup() lambda will run before return + return popupItem->keepMouseGrab() || popupItem->keepTouchGrab(); +} + +void QQuickDrawerPrivate::handleUngrab() +{ + QQuickPopupPrivate::handleUngrab(); + + velocityCalculator.reset(); +} + +static QList<QQuickStateAction> prepareTransition(QQuickDrawer *drawer, QQuickTransition *transition, qreal to) +{ + QList<QQuickStateAction> actions; + if (!transition || !QQuickPopupPrivate::get(drawer)->window || !transition->enabled()) + return actions; + + qmlExecuteDeferred(transition); + + QQmlProperty defaultTarget(drawer, QLatin1String("position")); + QQmlListProperty<QQuickAbstractAnimation> animations = transition->animations(); + int count = animations.count(&animations); + for (int i = 0; i < count; ++i) { + QQuickAbstractAnimation *anim = animations.at(&animations, i); + anim->setDefaultTarget(defaultTarget); + } + + actions << QQuickStateAction(drawer, QLatin1String("position"), to); + return actions; +} + +bool QQuickDrawerPrivate::prepareEnterTransition() +{ + Q_Q(QQuickDrawer); + enterActions = prepareTransition(q, enter, 1.0); + return QQuickPopupPrivate::prepareEnterTransition(); +} + +bool QQuickDrawerPrivate::prepareExitTransition() +{ + Q_Q(QQuickDrawer); + exitActions = prepareTransition(q, exit, 0.0); + return QQuickPopupPrivate::prepareExitTransition(); +} + +bool QQuickDrawerPrivate::setEdge(Qt::Edge e) +{ + Q_Q(QQuickDrawer); + switch (e) { + case Qt::LeftEdge: + case Qt::RightEdge: + allowVerticalMove = true; + allowVerticalResize = true; + allowHorizontalMove = false; + allowHorizontalResize = false; + break; + case Qt::TopEdge: + case Qt::BottomEdge: + allowVerticalMove = false; + allowVerticalResize = false; + allowHorizontalMove = true; + allowHorizontalResize = true; + break; + default: + qmlWarning(q) << "invalid edge value - valid values are: " + << "Qt.TopEdge, Qt.LeftEdge, Qt.RightEdge, Qt.BottomEdge"; + return false; + } + + edge = e; + return true; +} + +QQuickDrawer::QQuickDrawer(QObject *parent) + : QQuickPopup(*(new QQuickDrawerPrivate), parent) +{ + Q_D(QQuickDrawer); + d->dragMargin = QGuiApplication::styleHints()->startDragDistance(); + d->setEdge(Qt::LeftEdge); + + setFocus(true); + setModal(true); + setFiltersChildMouseEvents(true); + setClosePolicy(CloseOnEscape | CloseOnReleaseOutside); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Drawer::edge + + This property holds the edge of the window at which the drawer will + open from. The acceptable values are: + + \value Qt.TopEdge The top edge of the window. + \value Qt.LeftEdge The left edge of the window (default). + \value Qt.RightEdge The right edge of the window. + \value Qt.BottomEdge The bottom edge of the window. +*/ +Qt::Edge QQuickDrawer::edge() const +{ + Q_D(const QQuickDrawer); + return d->edge; +} + +void QQuickDrawer::setEdge(Qt::Edge edge) +{ + Q_D(QQuickDrawer); + if (d->edge == edge) + return; + + if (!d->setEdge(edge)) + return; + + if (isComponentComplete()) + d->reposition(); + emit edgeChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::Drawer::position + + This property holds the position of the drawer relative to its final + destination. That is, the position will be \c 0.0 when the drawer + is fully closed, and \c 1.0 when fully open. +*/ +qreal QQuickDrawer::position() const +{ + Q_D(const QQuickDrawer); + return d->position; +} + +void QQuickDrawer::setPosition(qreal position) +{ + Q_D(QQuickDrawer); + position = qBound<qreal>(0.0, position, 1.0); + if (qFuzzyCompare(d->position, position)) + return; + + d->position = position; + if (isComponentComplete()) + d->reposition(); + if (d->dimmer) + d->dimmer->setOpacity(position); + emit positionChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::Drawer::dragMargin + + This property holds the distance from the screen edge within which + drag actions will open the drawer. Setting the value to \c 0 or less + prevents opening the drawer by dragging. + + The default value is \c Qt.styleHints.startDragDistance. + + \sa interactive +*/ +qreal QQuickDrawer::dragMargin() const +{ + Q_D(const QQuickDrawer); + return d->dragMargin; +} + +void QQuickDrawer::setDragMargin(qreal margin) +{ + Q_D(QQuickDrawer); + if (qFuzzyCompare(d->dragMargin, margin)) + return; + + d->dragMargin = margin; + emit dragMarginChanged(); +} + +void QQuickDrawer::resetDragMargin() +{ + setDragMargin(QGuiApplication::styleHints()->startDragDistance()); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::Drawer::interactive + + This property holds whether the drawer is interactive. A non-interactive + drawer does not react to swipes. + + The default value is \c true. + + \sa dragMargin +*/ +bool QQuickDrawer::isInteractive() const +{ + Q_D(const QQuickDrawer); + return d->interactive; +} + +void QQuickDrawer::setInteractive(bool interactive) +{ + Q_D(QQuickDrawer); + if (d->interactive == interactive) + return; + + setFiltersChildMouseEvents(interactive); + d->interactive = interactive; + emit interactiveChanged(); +} + +bool QQuickDrawer::childMouseEventFilter(QQuickItem *child, QEvent *event) +{ + Q_D(QQuickDrawer); + switch (event->type()) { +#if QT_CONFIG(quicktemplates2_multitouch) + case QEvent::TouchUpdate: + return d->grabTouch(child, static_cast<QTouchEvent *>(event)); + case QEvent::TouchBegin: + case QEvent::TouchEnd: + return d->handleTouchEvent(child, static_cast<QTouchEvent *>(event)); +#endif + case QEvent::MouseMove: + return d->grabMouse(child, static_cast<QMouseEvent *>(event)); + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + return d->handleMouseEvent(child, static_cast<QMouseEvent *>(event)); + default: + break; + } + return false; +} + +void QQuickDrawer::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickDrawer); + d->grabMouse(d->popupItem, event); +} + +bool QQuickDrawer::overlayEvent(QQuickItem *item, QEvent *event) +{ + Q_D(QQuickDrawer); + switch (event->type()) { +#if QT_CONFIG(quicktemplates2_multitouch) + case QEvent::TouchUpdate: + return d->grabTouch(item, static_cast<QTouchEvent *>(event)); +#endif + case QEvent::MouseMove: + return d->grabMouse(item, static_cast<QMouseEvent *>(event)); + default: + break; + } + return QQuickPopup::overlayEvent(item, event); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickDrawer::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickDrawer); + d->grabTouch(d->popupItem, event); +} +#endif + +void QQuickDrawer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickDrawer); + QQuickPopup::geometryChange(newGeometry, oldGeometry); + d->resizeOverlay(); +} + +QT_END_NAMESPACE + +#include "moc_qquickdrawer_p.cpp" diff --git a/src/quicktemplates2/qquickdrawer_p.h b/src/quicktemplates2/qquickdrawer_p.h new file mode 100644 index 0000000000..f16ae771bb --- /dev/null +++ b/src/quicktemplates2/qquickdrawer_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDRAWER_P_H +#define QQUICKDRAWER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpopup_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickDrawerPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDrawer : public QQuickPopup +{ + Q_OBJECT + Q_PROPERTY(Qt::Edge edge READ edge WRITE setEdge NOTIFY edgeChanged FINAL) + Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal dragMargin READ dragMargin WRITE setDragMargin RESET resetDragMargin NOTIFY dragMarginChanged FINAL) + // 2.2 (Qt 5.9) + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive NOTIFY interactiveChanged FINAL REVISION(2, 2)) + QML_NAMED_ELEMENT(Drawer) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickDrawer(QObject *parent = nullptr); + + Qt::Edge edge() const; + void setEdge(Qt::Edge edge); + + qreal position() const; + void setPosition(qreal position); + + qreal dragMargin() const; + void setDragMargin(qreal margin); + void resetDragMargin(); + + // 2.2 (Qt 5.9) + bool isInteractive() const; + void setInteractive(bool interactive); + +Q_SIGNALS: + void edgeChanged(); + void positionChanged(); + void dragMarginChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void interactiveChanged(); + +protected: + bool childMouseEventFilter(QQuickItem *child, QEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + bool overlayEvent(QQuickItem *item, QEvent *event) override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif + + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + +private: + Q_DISABLE_COPY(QQuickDrawer) + Q_DECLARE_PRIVATE(QQuickDrawer) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDrawer) + +#endif // QQUICKDRAWER_P_H diff --git a/src/quicktemplates2/qquickdrawer_p_p.h b/src/quicktemplates2/qquickdrawer_p_p.h new file mode 100644 index 0000000000..5f1086de97 --- /dev/null +++ b/src/quicktemplates2/qquickdrawer_p_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDRAWER_P_P_H +#define QQUICKDRAWER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickdrawer_p.h> +#include <QtQuickTemplates2/private/qquickpopup_p_p.h> +#include <QtQuickTemplates2/private/qquickvelocitycalculator_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickDrawerPrivate : public QQuickPopupPrivate +{ + Q_DECLARE_PUBLIC(QQuickDrawer) + +public: + static QQuickDrawerPrivate *get(QQuickDrawer *drawer) + { + return drawer->d_func(); + } + + qreal offsetAt(const QPointF &point) const; + qreal positionAt(const QPointF &point) const; + + QQuickPopupPositioner *getPositioner() override; + void showOverlay() override; + void hideOverlay() override; + void resizeOverlay() override; + + bool startDrag(QEvent *event); + bool grabMouse(QQuickItem *item, QMouseEvent *event); +#if QT_CONFIG(quicktemplates2_multitouch) + bool grabTouch(QQuickItem *item, QTouchEvent *event); +#endif + bool blockInput(QQuickItem *item, const QPointF &point) const override; + + bool handlePress(QQuickItem* item, const QPointF &point, ulong timestamp) override; + bool handleMove(QQuickItem* item, const QPointF &point, ulong timestamp) override; + bool handleRelease(QQuickItem* item, const QPointF &point, ulong timestamp) override; + void handleUngrab() override; + + bool prepareEnterTransition() override; + bool prepareExitTransition() override; + + bool setEdge(Qt::Edge edge); + + Qt::Edge edge = Qt::LeftEdge; + qreal offset = 0; + qreal position = 0; + qreal dragMargin = 0; + QQuickVelocityCalculator velocityCalculator; +}; + +QT_END_NAMESPACE + +#endif // QQUICKDRAWER_P_P_H diff --git a/src/quicktemplates2/qquickframe.cpp b/src/quicktemplates2/qquickframe.cpp new file mode 100644 index 0000000000..8110e048e4 --- /dev/null +++ b/src/quicktemplates2/qquickframe.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickframe_p.h" +#include "qquickframe_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Frame + \inherits Pane +//! \instantiates QQuickFrame + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-containers + \brief Visual frame for a logical group of controls. + + Frame is used to layout a logical group of controls together within a + visual frame. Frame does not provide a layout of its own, but requires + you to position its contents, for instance by creating a \l RowLayout + or a \l ColumnLayout. + + Items declared as children of a Frame are automatically parented to the + Frame's \l {Control::}{contentItem}. Items created dynamically need to be + explicitly parented to the contentItem. + + If only a single item is used within a Frame, it will resize to fit the + implicit size of its contained item. This makes it particularly suitable + for use together with layouts. + + \image qtquickcontrols2-frame.png + + \snippet qtquickcontrols2-frame.qml 1 + + \sa {Customizing Frame}, {Container Controls} +*/ + +QQuickFrame::QQuickFrame(QQuickItem *parent) + : QQuickPane(*(new QQuickFramePrivate), parent) +{ +} + +QQuickFrame::QQuickFrame(QQuickFramePrivate &dd, QQuickItem *parent) + : QQuickPane(dd, parent) +{ +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickFrame::accessibleRole() const +{ + return QAccessible::Border; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickframe_p.cpp" diff --git a/src/quicktemplates2/qquickframe_p.h b/src/quicktemplates2/qquickframe_p.h new file mode 100644 index 0000000000..f65e281406 --- /dev/null +++ b/src/quicktemplates2/qquickframe_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKFRAME_P_H +#define QQUICKFRAME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpane_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickFramePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickFrame : public QQuickPane +{ + Q_OBJECT + QML_NAMED_ELEMENT(Frame) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickFrame(QQuickItem *parent = nullptr); + +protected: + QQuickFrame(QQuickFramePrivate &dd, QQuickItem *parent); + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickFrame) + Q_DECLARE_PRIVATE(QQuickFrame) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickFrame) + +#endif // QQUICKFRAME_P_H diff --git a/src/quicktemplates2/qquickframe_p_p.h b/src/quicktemplates2/qquickframe_p_p.h new file mode 100644 index 0000000000..ad392dc3a7 --- /dev/null +++ b/src/quicktemplates2/qquickframe_p_p.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKFRAME_P_P_H +#define QQUICKFRAME_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpane_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickFrame; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickFramePrivate : public QQuickPanePrivate +{ +}; + +QT_END_NAMESPACE + +#endif // QQUICKFRAME_P_P_H diff --git a/src/quicktemplates2/qquickgroupbox.cpp b/src/quicktemplates2/qquickgroupbox.cpp new file mode 100644 index 0000000000..3962e2e700 --- /dev/null +++ b/src/quicktemplates2/qquickgroupbox.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickgroupbox_p.h" +#include "qquickframe_p_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype GroupBox + \inherits Frame +//! \instantiates QQuickGroupBox + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-containers + \brief Visual frame and title for a logical group of controls. + + GroupBox is used to layout a logical group of controls together, within + a \l {title}{titled} visual frame. GroupBox does not provide a layout of its own, but + requires you to position its contents, for instance by creating a \l RowLayout + or a \l ColumnLayout. + + Items declared as children of a GroupBox are automatically parented to the + GroupBox's \l {Control::}{contentItem}. Items created dynamically need to be + explicitly parented to the contentItem. + + If only a single item is used within a GroupBox, it will resize to fit the + implicit size of its contained item. This makes it particularly suitable + for use together with layouts. + + \image qtquickcontrols2-groupbox.png + + \snippet qtquickcontrols2-groupbox.qml 1 + + \section2 Checkable GroupBox + + Even though GroupBox has no built-in check box, it is straightforward + to create a checkable GroupBox by pairing it with a CheckBox. + + \image qtquickcontrols2-groupbox-checkable.png + + It is a common pattern to enable or disable the groupbox's children when + its checkbox is toggled on or off, but it is up to the application to decide + on the behavior of the checkbox. + + \snippet qtquickcontrols2-groupbox-checkable.qml 1 + + \sa CheckBox, {Customizing GroupBox}, {Container Controls} +*/ + +class QQuickGroupBoxPrivate : public QQuickFramePrivate +{ + Q_DECLARE_PUBLIC(QQuickGroupBox) + +public: + void cancelLabel(); + void executeLabel(bool complete = false); + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::GroupBox); } + + QString title; + QQuickDeferredPointer<QQuickItem> label; +}; + +static inline QString labelName() { return QStringLiteral("label"); } + +void QQuickGroupBoxPrivate::cancelLabel() +{ + Q_Q(QQuickGroupBox); + quickCancelDeferred(q, labelName()); +} + +void QQuickGroupBoxPrivate::executeLabel(bool complete) +{ + Q_Q(QQuickGroupBox); + if (label.wasExecuted()) + return; + + if (!label || complete) + quickBeginDeferred(q, labelName(), label); + if (complete) + quickCompleteDeferred(q, labelName(), label); +} + +void QQuickGroupBoxPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickGroupBox); + QQuickFramePrivate::itemImplicitWidthChanged(item); + if (item == label) + emit q->implicitLabelWidthChanged(); +} + +void QQuickGroupBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickGroupBox); + QQuickFramePrivate::itemImplicitHeightChanged(item); + if (item == label) + emit q->implicitLabelHeightChanged(); +} + +QQuickGroupBox::QQuickGroupBox(QQuickItem *parent) + : QQuickFrame(*(new QQuickGroupBoxPrivate), parent) +{ +} + +QQuickGroupBox::~QQuickGroupBox() +{ + Q_D(QQuickGroupBox); + d->removeImplicitSizeListener(d->label); +} + +/*! + \qmlproperty string QtQuick.Controls::GroupBox::title + + This property holds the title. + + The title is typically displayed above the groupbox to + summarize its contents. +*/ +QString QQuickGroupBox::title() const +{ + Q_D(const QQuickGroupBox); + return d->title; +} + +void QQuickGroupBox::setTitle(const QString &title) +{ + Q_D(QQuickGroupBox); + if (d->title == title) + return; + + d->title = title; + maybeSetAccessibleName(title); + emit titleChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::GroupBox::label + + This property holds the label item that visualizes \l title. + + \sa {Customizing GroupBox} +*/ +QQuickItem *QQuickGroupBox::label() const +{ + QQuickGroupBoxPrivate *d = const_cast<QQuickGroupBoxPrivate *>(d_func()); + if (!d->label) + d->executeLabel(); + return d->label; +} + +void QQuickGroupBox::setLabel(QQuickItem *label) +{ + Q_D(QQuickGroupBox); + if (d->label == label) + return; + + if (!d->label.isExecuting()) + d->cancelLabel(); + + const qreal oldImplicitLabelWidth = implicitLabelWidth(); + const qreal oldImplicitLabelHeight = implicitLabelHeight(); + + d->removeImplicitSizeListener(d->label); + QQuickControlPrivate::hideOldItem(d->label); + d->label = label; + + if (label) { + if (!label->parentItem()) + label->setParentItem(this); + d->addImplicitSizeListener(label); + } + + if (!qFuzzyCompare(oldImplicitLabelWidth, implicitLabelWidth())) + emit implicitLabelWidthChanged(); + if (!qFuzzyCompare(oldImplicitLabelHeight, implicitLabelHeight())) + emit implicitLabelHeightChanged(); + if (!d->label.isExecuting()) + emit labelChanged(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::GroupBox::implicitLabelWidth + \readonly + + This property holds the implicit label width. + + The value is equal to \c {label ? label.implicitWidth : 0}. + + \sa implicitLabelHeight +*/ +qreal QQuickGroupBox::implicitLabelWidth() const +{ + Q_D(const QQuickGroupBox); + if (!d->label) + return 0; + return d->label->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::GroupBox::implicitLabelHeight + \readonly + + This property holds the implicit label height. + + The value is equal to \c {label ? label.implicitHeight : 0}. + + \sa implicitLabelWidth +*/ +qreal QQuickGroupBox::implicitLabelHeight() const +{ + Q_D(const QQuickGroupBox); + if (!d->label) + return 0; + return d->label->implicitHeight(); +} + +void QQuickGroupBox::componentComplete() +{ + Q_D(QQuickGroupBox); + d->executeLabel(true); + QQuickFrame::componentComplete(); +} + +QFont QQuickGroupBox::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::GroupBox); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickGroupBox::accessibleRole() const +{ + return QAccessible::Grouping; +} + +void QQuickGroupBox::accessibilityActiveChanged(bool active) +{ + Q_D(QQuickGroupBox); + QQuickFrame::accessibilityActiveChanged(active); + + if (active) + maybeSetAccessibleName(d->title); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickgroupbox_p.cpp" diff --git a/src/quicktemplates2/qquickgroupbox_p.h b/src/quicktemplates2/qquickgroupbox_p.h new file mode 100644 index 0000000000..39e20494e9 --- /dev/null +++ b/src/quicktemplates2/qquickgroupbox_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKGROUPBOX_P_H +#define QQUICKGROUPBOX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickframe_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickGroupBoxPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickGroupBox : public QQuickFrame +{ + Q_OBJECT + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) + Q_PROPERTY(QQuickItem *label READ label WRITE setLabel NOTIFY labelChanged FINAL) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal implicitLabelWidth READ implicitLabelWidth NOTIFY implicitLabelWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitLabelHeight READ implicitLabelHeight NOTIFY implicitLabelHeightChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background,contentItem,label") + QML_NAMED_ELEMENT(GroupBox) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickGroupBox(QQuickItem *parent = nullptr); + ~QQuickGroupBox(); + + QString title() const; + void setTitle(const QString &title); + + QQuickItem *label() const; + void setLabel(QQuickItem *label); + + // 2.5 (Qt 5.12) + qreal implicitLabelWidth() const; + qreal implicitLabelHeight() const; + +Q_SIGNALS: + void titleChanged(); + void labelChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void implicitLabelWidthChanged(); + Q_REVISION(2, 5) void implicitLabelHeightChanged(); + +protected: + void componentComplete() override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; + void accessibilityActiveChanged(bool active) override; +#endif + +private: + Q_DISABLE_COPY(QQuickGroupBox) + Q_DECLARE_PRIVATE(QQuickGroupBox) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickGroupBox) + +#endif // QQUICKGROUPBOX_P_H diff --git a/src/quicktemplates2/qquickheaderview.cpp b/src/quicktemplates2/qquickheaderview.cpp new file mode 100644 index 0000000000..94d5635ad1 --- /dev/null +++ b/src/quicktemplates2/qquickheaderview.cpp @@ -0,0 +1,524 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQuickTemplates2/private/qquickheaderview_p_p.h> +#include <algorithm> + +/*! + \qmltype HorizontalHeaderView + \inqmlmodule QtQuick.Controls + \ingroup qtquickcontrols2-containers + \inherits TableView + \brief Provides a horizontal header view to accompany a \l TableView. + + A HorizontalHeaderView provides labeling of the columns of a \l TableView. + To add a horizontal header to a TableView, bind the + \l {HorizontalHeaderView::syncView} {syncView} property to the TableView: + + \snippet qtquickcontrols2-headerview-simple.qml horizontal + + The header displays data from the {syncView}'s model by default, but can + also have its own model. If the model is a QAbstractTableModel, then + the header will display the model's horizontal headerData(); otherwise, + the model's data(). +*/ + +/*! + \qmltype VerticalHeaderView + \inqmlmodule QtQuick.Controls + \ingroup qtquickcontrols2-containers + \inherits TableView + \brief Provides a vertical header view to accompany a \l TableView. + + A VerticalHeaderView provides labeling of the rows of a \l TableView. + To add a vertical header to a TableView, bind the + \l {VerticalHeaderView::syncView} {syncView} property to the TableView: + + \snippet qtquickcontrols2-headerview-simple.qml vertical + + The header displays data from the {syncView}'s model by default, but can + also have its own model. If the model is a QAbstractTableModel, then + the header will display the model's vertical headerData(); otherwise, + the model's data(). +*/ + +/*! + \qmlproperty TableView QtQuick::HorizontalHeaderView::syncView + + This property holds the TableView to synchronize with. + + Once this property is bound to another TableView, both header and table + will synchronize with regard to column widths, column spacing, and flicking + horizontally. + + If the \l model is not explicitly set, then the header will use the syncView's + model to label the columns. + + \sa model TableView +*/ + +/*! + \qmlproperty TableView QtQuick::VerticalHeaderView::syncView + + This property holds the TableView to synchronize with. + + Once this property is bound to another TableView, both header and table + will synchronize with regard to row heights, row spacing, and flicking + vertically. + + If the \l model is not explicitly set, then the header will use the syncView's + model to label the rows. + + \sa model TableView +*/ + +/*! + \qmlproperty QVariant QtQuick::HorizontalHeaderView::model + + This property holds the model providing data for the horizontal header view. + + When model is not explicitly set, the header will use the syncView's + model once syncView is set. + + If model is a QAbstractTableModel, its horizontal headerData() will + be accessed. + + If model is a QAbstractItemModel other than QAbstractTableModel, model's data() + will be accessed. + + Otherwise, the behavior is same as setting TableView::model. + + \sa TableView {TableView::model} {model} QAbstractTableModel +*/ + +/*! + \qmlproperty QVariant QtQuick::VerticalHeaderView::model + + This property holds the model providing data for the vertical header view. + + When model is not explicitly set, it will be synchronized with syncView's model + once syncView is set. + + If model is a QAbstractTableModel, its vertical headerData() will + be accessed. + + If model is a QAbstractItemModel other than QAbstractTableModel, model's data() + will be accessed. + + Otherwise, the behavior is same as setting TableView::model. + + \sa TableView {TableView::model} {model} QAbstractTableModel +*/ + +/*! + \qmlproperty QString QtQuick::HorizontalHeaderView::textRole + + This property holds the model role used to display text in each header cell. + + When the model has multiple roles, textRole can be set to determine which + role should be displayed. + + If model is a QAbstractItemModel then it will default to "display"; otherwise + it is empty. + + \sa QAbstractItemModel::roleNames() +*/ + +/*! + \qmlproperty QString QtQuick::VerticalHeaderView::textRole + + This property holds the model role used to display text in each header cell. + + When the model has multiple roles, textRole can be set to determine which + role should be displayed. + + If model is a QAbstractItemModel then it will default to "display"; otherwise + it is empty. + + \sa QAbstractItemModel::roleNames() +*/ + +QT_BEGIN_NAMESPACE + +QQuickHeaderViewBasePrivate::QQuickHeaderViewBasePrivate() + : QQuickTableViewPrivate() +{ +} + +QQuickHeaderViewBasePrivate::~QQuickHeaderViewBasePrivate() +{ +} + +const QPointer<QQuickItem> QQuickHeaderViewBasePrivate::delegateItemAt(int row, int col) const +{ + return loadedTableItem(QPoint(col, row))->item; +} + +QVariant QQuickHeaderViewBasePrivate::modelImpl() const +{ + if (auto model = m_headerDataProxyModel.sourceModel()) + return QVariant::fromValue(model.data()); + if (auto model = m_transposeProxyModel.sourceModel()) + return QVariant::fromValue(model); + return QQuickTableViewPrivate::modelImpl(); +} + +template <typename P, typename M> +inline bool proxyModelSetter(QQuickHeaderViewBase *const q, P &proxyModel, M *model) +{ + if (model) { + if (model == proxyModel.sourceModel()) + return true; + proxyModel.setSourceModel(model); + const auto &modelVariant = QVariant::fromValue(std::addressof(proxyModel)); + bool isProxyModelChanged = (modelVariant != QQuickTableViewPrivate::get(q)->QQuickTableViewPrivate::modelImpl()); + QQuickTableViewPrivate::get(q)->QQuickTableViewPrivate::setModelImpl(modelVariant); + //Necessary, since TableView's assigned model not changed, but proxy's source changed + if (!isProxyModelChanged) + emit q->modelChanged(); + return true; + } + proxyModel.setSourceModel(nullptr); + return false; +} + +void QQuickHeaderViewBasePrivate::setModelImpl(const QVariant &newModel) +{ + Q_Q(QQuickHeaderViewBase); + m_modelExplicitlySetByUser = true; + // Case 1: newModel is QAbstractTableModel + if (proxyModelSetter(q, m_headerDataProxyModel, newModel.value<QAbstractTableModel *>())) + return; + // Case 2: newModel is QAbstractItemModel but not QAbstractTableModel + if (orientation() == Qt::Horizontal + && proxyModelSetter(q, m_transposeProxyModel, newModel.value<QAbstractItemModel *>())) + return; + + QQuickTableViewPrivate::setModelImpl(newModel); +} + +void QQuickHeaderViewBasePrivate::syncModel() +{ + Q_Q(QQuickHeaderViewBase); + + if (assignedSyncView && !m_modelExplicitlySetByUser) { + auto newModel = assignedSyncView->model(); + if (auto m = newModel.value<QAbstractItemModel *>()) + proxyModelSetter(q, m_headerDataProxyModel, m); + } + + QQuickTableViewPrivate::syncModel(); + + isTransposed = false; + const auto aim = model->abstractItemModel(); + if (orientation() == Qt::Horizontal) { + // For models that are just a list or a number, and especially not a + // table, we transpose the view when the orientation is horizontal. + // The model (list) will then be laid out horizontally rather than + // vertically, which is the otherwise the default. + isTransposed = !aim || aim->columnCount() == 1; + } + if (m_textRole.isEmpty() && aim) + m_textRole = QLatin1String("display"); +} + +void QQuickHeaderViewBasePrivate::syncSyncView() +{ + Q_Q(QQuickHeaderViewBase); + if (assignedSyncDirection != orientation()) { + qmlWarning(q_func()) << "Setting syncDirection other than Qt::" + << QVariant::fromValue(orientation()).toString() + << " is invalid."; + assignedSyncDirection = orientation(); + } + if (assignedSyncView) { + QBoolBlocker fixupGuard(inUpdateContentSize, true); + if (orientation() == Qt::Horizontal) { + q->setLeftMargin(assignedSyncView->leftMargin()); + q->setRightMargin(assignedSyncView->rightMargin()); + } else { + q->setTopMargin(assignedSyncView->topMargin()); + q->setBottomMargin(assignedSyncView->bottomMargin()); + } + } + QQuickTableViewPrivate::syncSyncView(); +} + +QQuickHeaderViewBase::QQuickHeaderViewBase(Qt::Orientation orient, QQuickItem *parent) + : QQuickTableView(*(new QQuickHeaderViewBasePrivate), parent) +{ + d_func()->setOrientation(orient); + setSyncDirection(orient); +} + +QQuickHeaderViewBase::QQuickHeaderViewBase(QQuickHeaderViewBasePrivate &dd, QQuickItem *parent) + : QQuickTableView(dd, parent) +{ +} + +QQuickHeaderViewBase::~QQuickHeaderViewBase() +{ +} + +QString QQuickHeaderViewBase::textRole() const +{ + Q_D(const QQuickHeaderViewBase); + return d->m_textRole; +} + +void QQuickHeaderViewBase::setTextRole(const QString &role) +{ + Q_D(QQuickHeaderViewBase); + if (d->m_textRole == role) + return; + + d->m_textRole = role; + emit textRoleChanged(); +} + +Qt::Orientation QQuickHeaderViewBasePrivate::orientation() const +{ + return m_headerDataProxyModel.orientation(); +} + +void QQuickHeaderViewBasePrivate::setOrientation(Qt::Orientation orientation) +{ + if (QQuickHeaderViewBasePrivate::orientation() == orientation) + return; + m_headerDataProxyModel.setOrientation(orientation); +} + +QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickVerticalHeaderViewPrivate &dd, QQuickItem *parent) + : QQuickHeaderViewBase(dd, parent) +{ +} + +/*! \internal + \class QHeaderDataProxyModel + \brief + QHeaderDataProxyModel is a proxy AbstractItemModel type that maps + source model's headerData() to correspondent data() + */ +QHeaderDataProxyModel::QHeaderDataProxyModel(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +QHeaderDataProxyModel::~QHeaderDataProxyModel() = default; + +void QHeaderDataProxyModel::setSourceModel(QAbstractItemModel *newSourceModel) +{ + if (m_model == newSourceModel) + return; + beginResetModel(); + disconnectFromModel(); + m_model = newSourceModel; + connectToModel(); + endResetModel(); +} + +QModelIndex QHeaderDataProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + return hasIndex(row, column, parent) ? createIndex(row, column) : QModelIndex(); +} + +QModelIndex QHeaderDataProxyModel::parent(const QModelIndex &child) const +{ + Q_UNUSED(child); + return QModelIndex(); +} + +QModelIndex QHeaderDataProxyModel::sibling(int row, int column, const QModelIndex &) const +{ + return index(row, column); +} + +int QHeaderDataProxyModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return m_model.isNull() ? -1 : (m_orientation == Qt::Horizontal ? 1 : m_model->rowCount(parent)); +} + +int QHeaderDataProxyModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return m_model.isNull() ? -1 : (m_orientation == Qt::Vertical ? 1 : m_model->columnCount(parent)); +} + +QVariant QHeaderDataProxyModel::data(const QModelIndex &index, int role) const +{ + if (m_model.isNull()) + return QVariant(); + if (!hasIndex(index.row(), index.column())) + return QModelIndex(); + auto section = m_orientation == Qt::Vertical ? index.row() : index.column(); + return m_model->headerData(section, m_orientation, role); +} + +bool QHeaderDataProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!hasIndex(index.row(), index.column())) + return false; + auto section = m_orientation == Qt::Vertical ? index.row() : index.column(); + auto ret = m_model->setHeaderData(section, m_orientation, value, role); + emit dataChanged(index, index, { role }); + return ret; +} + +bool QHeaderDataProxyModel::hasChildren(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return rowCount(parent) > 0 && columnCount(parent) > 0; + return false; +} + +QVariant QHeaderDataProxyModel::variantValue() const +{ + return QVariant::fromValue(static_cast<QObject *>(const_cast<QHeaderDataProxyModel *>(this))); +} + +void QHeaderDataProxyModel::setOrientation(Qt::Orientation o) +{ + if (o == m_orientation) + return; + beginResetModel(); + m_orientation = o; + endResetModel(); +} + +Qt::Orientation QHeaderDataProxyModel::orientation() const +{ + return m_orientation; +} + +QPointer<QAbstractItemModel> QHeaderDataProxyModel::sourceModel() const +{ + return m_model; +} + +void QHeaderDataProxyModel::connectToModel() +{ + if (m_model.isNull()) + return; + connect(m_model, &QAbstractItemModel::headerDataChanged, + this, [this](Qt::Orientation orient, int first, int last) { + if (orient != orientation()) + return; + if (orient == Qt::Horizontal) { + emit dataChanged(createIndex(0, first), createIndex(0, last)); + } else { + emit dataChanged(createIndex(first, 0), createIndex(last, 0)); + } + }); + connect(m_model, &QAbstractItemModel::modelAboutToBeReset, + this, &QHeaderDataProxyModel::modelAboutToBeReset, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::modelReset, + this, &QHeaderDataProxyModel::modelReset, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsAboutToBeMoved, + this, &QHeaderDataProxyModel::rowsAboutToBeMoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsMoved, + this, &QHeaderDataProxyModel::rowsMoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, + this, &QHeaderDataProxyModel::rowsAboutToBeInserted, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsInserted, + this, &QHeaderDataProxyModel::rowsInserted, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &QHeaderDataProxyModel::rowsAboutToBeRemoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsRemoved, + this, &QHeaderDataProxyModel::rowsRemoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsAboutToBeMoved, + this, &QHeaderDataProxyModel::columnsAboutToBeMoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsMoved, + this, &QHeaderDataProxyModel::columnsMoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsAboutToBeInserted, + this, &QHeaderDataProxyModel::columnsAboutToBeInserted, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsInserted, + this, &QHeaderDataProxyModel::columnsInserted, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsAboutToBeRemoved, + this, &QHeaderDataProxyModel::columnsAboutToBeRemoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsRemoved, + this, &QHeaderDataProxyModel::columnsRemoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::layoutAboutToBeChanged, + this, &QHeaderDataProxyModel::layoutAboutToBeChanged, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::layoutChanged, + this, &QHeaderDataProxyModel::layoutChanged, Qt::UniqueConnection); +} + +void QHeaderDataProxyModel::disconnectFromModel() +{ + if (m_model.isNull()) + return; + m_model->disconnect(this); +} + +QQuickHorizontalHeaderView::QQuickHorizontalHeaderView(QQuickItem *parent) + : QQuickHeaderViewBase(Qt::Horizontal, parent) +{ + setFlickableDirection(FlickableDirection::HorizontalFlick); +} + +QQuickHorizontalHeaderView::~QQuickHorizontalHeaderView() +{ +} + +QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickItem *parent) + : QQuickHeaderViewBase(Qt::Vertical, parent) +{ + setFlickableDirection(FlickableDirection::VerticalFlick); +} + +QQuickVerticalHeaderView::~QQuickVerticalHeaderView() +{ +} + +QQuickHorizontalHeaderViewPrivate::QQuickHorizontalHeaderViewPrivate() = default; + +QQuickHorizontalHeaderViewPrivate::~QQuickHorizontalHeaderViewPrivate() = default; + +QQuickVerticalHeaderViewPrivate::QQuickVerticalHeaderViewPrivate() = default; + +QQuickVerticalHeaderViewPrivate::~QQuickVerticalHeaderViewPrivate() = default; + +QT_END_NAMESPACE + +#include "moc_qquickheaderview_p_p.cpp" + +#include "moc_qquickheaderview_p.cpp" diff --git a/src/quicktemplates2/qquickheaderview_p.h b/src/quicktemplates2/qquickheaderview_p.h new file mode 100644 index 0000000000..b8e8b77aa7 --- /dev/null +++ b/src/quicktemplates2/qquickheaderview_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKHEADERVIEW_P_H +#define QQUICKHEADERVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qquicktableview_p.h> +#include <private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickHeaderViewBase; +class QQuickHeaderViewBasePrivate; +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickHeaderViewBase : public QQuickTableView +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickHeaderViewBase) + Q_PROPERTY(QString textRole READ textRole WRITE setTextRole NOTIFY textRoleChanged FINAL) + +public: + explicit QQuickHeaderViewBase(Qt::Orientation orient, QQuickItem *parent = nullptr); + ~QQuickHeaderViewBase(); + + QString textRole() const; + void setTextRole(const QString &role); + +protected: + QQuickHeaderViewBase(QQuickHeaderViewBasePrivate &dd, QQuickItem *parent); + +Q_SIGNALS: + void textRoleChanged(); + +private: + Q_DISABLE_COPY(QQuickHeaderViewBase) + friend class QQuickHorizontalHeaderView; + friend class QQuickVerticalHeaderView; +}; + +class QQuickHorizontalHeaderViewPrivate; +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickHorizontalHeaderView : public QQuickHeaderViewBase +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickHorizontalHeaderView) + QML_NAMED_ELEMENT(HorizontalHeaderView) + QML_ADDED_IN_VERSION(2, 15) + +public: + QQuickHorizontalHeaderView(QQuickItem *parent = nullptr); + ~QQuickHorizontalHeaderView() override; + +protected: + QQuickHorizontalHeaderView(QQuickHorizontalHeaderViewPrivate &dd, QQuickItem *parent); + +private: + Q_DISABLE_COPY(QQuickHorizontalHeaderView) +}; + +class QQuickVerticalHeaderViewPrivate; +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickVerticalHeaderView : public QQuickHeaderViewBase +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickVerticalHeaderView) + QML_NAMED_ELEMENT(VerticalHeaderView) + QML_ADDED_IN_VERSION(2, 15) + +public: + QQuickVerticalHeaderView(QQuickItem *parent = nullptr); + ~QQuickVerticalHeaderView() override; + +protected: + QQuickVerticalHeaderView(QQuickVerticalHeaderViewPrivate &dd, QQuickItem *parent); + +private: + Q_DISABLE_COPY(QQuickVerticalHeaderView) +}; + +struct QQuickTableViewForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickTableView) + QML_ADDED_IN_VERSION(2, 14) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickHorizontalHeaderView) +QML_DECLARE_TYPE(QQuickVerticalHeaderView) + +#endif // QQUICKHEADERVIEW_P_H diff --git a/src/quicktemplates2/qquickheaderview_p_p.h b/src/quicktemplates2/qquickheaderview_p_p.h new file mode 100644 index 0000000000..655c2a58e6 --- /dev/null +++ b/src/quicktemplates2/qquickheaderview_p_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKHEADERVIEW_P_P_H +#define QQUICKHEADERVIEW_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QAbstractItemModel> +#include <QtCore/QPointer> +#include <QtCore/QTransposeProxyModel> +#include <QtQuick/private/qquicktableview_p_p.h> +#include <private/qquickheaderview_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QHeaderDataProxyModel : public QAbstractItemModel +{ + Q_OBJECT + Q_DISABLE_COPY(QHeaderDataProxyModel) + Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel) +public: + explicit QHeaderDataProxyModel(QObject *parent = nullptr); + ~QHeaderDataProxyModel(); + + void setSourceModel(QAbstractItemModel *newSourceModel); + QPointer<QAbstractItemModel> sourceModel() const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + QModelIndex sibling(int row, int column, const QModelIndex &idx) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + + inline QVariant variantValue() const; + inline Qt::Orientation orientation() const; + inline void setOrientation(Qt::Orientation o); + +private: + inline void connectToModel(); + inline void disconnectFromModel(); + QPointer<QAbstractItemModel> m_model = nullptr; + Qt::Orientation m_orientation = Qt::Horizontal; +}; + +class QQuickHeaderViewBasePrivate : public QQuickTableViewPrivate +{ + Q_DECLARE_PUBLIC(QQuickHeaderViewBase) +public: + QQuickHeaderViewBasePrivate(); + ~QQuickHeaderViewBasePrivate(); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + const QPointer<QQuickItem> delegateItemAt(int row, int col) const; + QVariant modelImpl() const override; + void setModelImpl(const QVariant &newModel) override; + void syncModel() override; + void syncSyncView() override; + +protected: + QHeaderDataProxyModel m_headerDataProxyModel; + QTransposeProxyModel m_transposeProxyModel; + struct SectionSize + { + int section; + qreal previousSize; + }; + QStack<SectionSize> m_hiddenSectionSizes; + bool m_modelExplicitlySetByUser = false; + QString m_textRole; +}; + +class QQuickHorizontalHeaderViewPrivate : public QQuickHeaderViewBasePrivate +{ + Q_DECLARE_PUBLIC(QQuickHorizontalHeaderView) +public: + QQuickHorizontalHeaderViewPrivate(); + ~QQuickHorizontalHeaderViewPrivate(); +}; + +class QQuickVerticalHeaderViewPrivate : public QQuickHeaderViewBasePrivate +{ + Q_DECLARE_PUBLIC(QQuickVerticalHeaderView) +public: + QQuickVerticalHeaderViewPrivate(); + ~QQuickVerticalHeaderViewPrivate(); +}; + +QT_END_NAMESPACE + +#endif // QQUICKHEADERVIEW_P_P_H diff --git a/src/quicktemplates2/qquickicon.cpp b/src/quicktemplates2/qquickicon.cpp new file mode 100644 index 0000000000..88d957f40d --- /dev/null +++ b/src/quicktemplates2/qquickicon.cpp @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickicon_p.h" +#include "qtaggedpointer.h" + +#include <private/qqmlcontextdata_p.h> +#include <private/qqmldata_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickIconPrivate : public QSharedData +{ +public: + // This is based on QFont's resolve_mask. + enum ResolveProperties { + NameResolved = 0x0001, + SourceResolved = 0x0002, + WidthResolved = 0x0004, + HeightResolved = 0x0008, + ColorResolved = 0x0010, + CacheResolved = 0x0020, + AllPropertiesResolved = 0x1ffff + }; + int resolveMask = 0; + + QString name; + QUrl source; + QUrl resolvedSource; + int width = 0; + int height = 0; + QColor color = Qt::transparent; + + // we want DoCache as the default, and thus as the zero value + // so that the tagged pointer can be zero initialized + enum CacheStatus : bool { DoCache, SkipCaching }; + static_assert (DoCache == 0); + /* We use a QTaggedPointer here to save space: + - Without it, we would need an additional boolean, which due to + alignment would increase the class size by sizeof(void *) + - The pointer part stores the "owner" of the QQuickIcon, i.e. + an object which has an icon property. We need the owner to + access its context to resolve relative url's in the way users + expect. + - The tag bits are used to track whether caching is enabled. + */ + QTaggedPointer<QObject, CacheStatus> ownerAndCache = nullptr; + +}; + +QQuickIcon::QQuickIcon() + : d(new QQuickIconPrivate) +{ +} + +QQuickIcon::QQuickIcon(const QQuickIcon &other) + : d(other.d) +{ +} + +QQuickIcon::~QQuickIcon() +{ +} + +QQuickIcon &QQuickIcon::operator=(const QQuickIcon &other) +{ + d = other.d; + return *this; +} + +bool QQuickIcon::operator==(const QQuickIcon &other) const +{ + return d == other.d || (d->name == other.d->name + && d->source == other.d->source + && d->resolvedSource == other.d->resolvedSource + && d->width == other.d->width + && d->height == other.d->height + && d->color == other.d->color + && d->ownerAndCache == other.d->ownerAndCache); +} + +bool QQuickIcon::operator!=(const QQuickIcon &other) const +{ + return !(*this == other); +} + +bool QQuickIcon::isEmpty() const +{ + return d->name.isEmpty() && d->source.isEmpty(); +} + +QString QQuickIcon::name() const +{ + return d->name; +} + +void QQuickIcon::setName(const QString &name) +{ + if ((d->resolveMask & QQuickIconPrivate::NameResolved) && d->name == name) + return; + + d.detach(); + d->name = name; + d->resolveMask |= QQuickIconPrivate::NameResolved; +} + +void QQuickIcon::resetName() +{ + d.detach(); + d->name = QString(); + d->resolveMask &= ~QQuickIconPrivate::NameResolved; +} + +QUrl QQuickIcon::source() const +{ + return d->source; +} + +void QQuickIcon::setSource(const QUrl &source) +{ + if ((d->resolveMask & QQuickIconPrivate::SourceResolved) && d->source == source) + return; + + d.detach(); + d->source = source; + d->resolvedSource.clear(); + d->resolveMask |= QQuickIconPrivate::SourceResolved; +} + +void QQuickIcon::resetSource() +{ + d.detach(); + d->source = QString(); + d->resolvedSource.clear(); + d->resolveMask &= ~QQuickIconPrivate::SourceResolved; +} + +QUrl QQuickIcon::resolvedSource() const +{ + return d->resolvedSource.isEmpty() ? d->source : d->resolvedSource; +} + +// must be called by the property owner (e.g. Button) prior to emitting changed signal. +void QQuickIcon::ensureRelativeSourceResolved(const QObject *owner) +{ + if (d->source.isEmpty()) + return; + if (!d->resolvedSource.isEmpty()) + return; // already resolved relative to (possibly) different owner + const QQmlData *data = QQmlData::get(owner); + if (!data || !data->outerContext) + return; + d.detach(); + d->resolvedSource = data->outerContext->resolvedUrl(d->source); +} + +int QQuickIcon::width() const +{ + return d->width; +} + +void QQuickIcon::setWidth(int width) +{ + if ((d->resolveMask & QQuickIconPrivate::WidthResolved) && d->width == width) + return; + + d.detach(); + d->width = width; + d->resolveMask |= QQuickIconPrivate::WidthResolved; +} + +void QQuickIcon::resetWidth() +{ + d.detach(); + d->width = 0; + d->resolveMask &= ~QQuickIconPrivate::WidthResolved; +} + +int QQuickIcon::height() const +{ + return d->height; +} + +void QQuickIcon::setHeight(int height) +{ + if ((d->resolveMask & QQuickIconPrivate::HeightResolved) && d->height == height) + return; + + d.detach(); + d->height = height; + d->resolveMask |= QQuickIconPrivate::HeightResolved; +} + +void QQuickIcon::resetHeight() +{ + d.detach(); + d->height = 0; + d->resolveMask &= ~QQuickIconPrivate::HeightResolved; +} + +QColor QQuickIcon::color() const +{ + return d->color; +} + +void QQuickIcon::setColor(const QColor &color) +{ + if ((d->resolveMask & QQuickIconPrivate::ColorResolved) && d->color == color) + return; + + d.detach(); + d->color = color; + d->resolveMask |= QQuickIconPrivate::ColorResolved; +} + +void QQuickIcon::resetColor() +{ + d.detach(); + d->color = Qt::transparent; + d->resolveMask &= ~QQuickIconPrivate::ColorResolved; +} + +bool QQuickIcon::cache() const +{ + return d->ownerAndCache.tag() == QQuickIconPrivate::DoCache; +} + +void QQuickIcon::setCache(bool cache) +{ + const auto cacheState = cache ? QQuickIconPrivate::DoCache : QQuickIconPrivate::SkipCaching; + if ((d->resolveMask & QQuickIconPrivate::CacheResolved) && d->ownerAndCache.tag() == cacheState) + return; + + d.detach(); + d->ownerAndCache.setTag(cacheState); + d->resolveMask |= QQuickIconPrivate::CacheResolved; +} + +void QQuickIcon::resetCache() +{ + d.detach(); + d->ownerAndCache.setTag(QQuickIconPrivate::DoCache); + d->resolveMask &= ~QQuickIconPrivate::CacheResolved; +} + +QQuickIcon QQuickIcon::resolve(const QQuickIcon &other) const +{ + QQuickIcon resolved = *this; + resolved.d.detach(); + + if (!(d->resolveMask & QQuickIconPrivate::NameResolved)) + resolved.d->name = other.d->name; + + if (!(d->resolveMask & QQuickIconPrivate::SourceResolved)) { + resolved.d->source = other.d->source; + resolved.d->resolvedSource = other.d->resolvedSource; + } + + if (!(d->resolveMask & QQuickIconPrivate::WidthResolved)) + resolved.d->width = other.d->width; + + if (!(d->resolveMask & QQuickIconPrivate::HeightResolved)) + resolved.d->height = other.d->height; + + if (!(d->resolveMask & QQuickIconPrivate::ColorResolved)) + resolved.d->color = other.d->color; + + if (!(d->resolveMask & QQuickIconPrivate::CacheResolved)) + resolved.d->ownerAndCache.setTag(other.d->ownerAndCache.tag()); + + // owner does not change when resolving an icon + + return resolved; +} + +QT_END_NAMESPACE + +#include "moc_qquickicon_p.cpp" diff --git a/src/quicktemplates2/qquickicon_p.h b/src/quicktemplates2/qquickicon_p.h new file mode 100644 index 0000000000..0312812429 --- /dev/null +++ b/src/quicktemplates2/qquickicon_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKICON_P_H +#define QQUICKICON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qurl.h> +#include <QtCore/qstring.h> +#include <QtCore/qobjectdefs.h> +#include <QtCore/qshareddata.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtGui/qcolor.h> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickIconPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickIcon +{ + Q_GADGET + Q_PROPERTY(QString name READ name WRITE setName RESET resetName FINAL) + Q_PROPERTY(QUrl source READ source WRITE setSource RESET resetSource FINAL) + Q_PROPERTY(int width READ width WRITE setWidth RESET resetWidth FINAL) + Q_PROPERTY(int height READ height WRITE setHeight RESET resetHeight FINAL) + Q_PROPERTY(QColor color READ color WRITE setColor RESET resetColor FINAL) + Q_PROPERTY(bool cache READ cache WRITE setCache RESET resetCache FINAL) + QML_ANONYMOUS + QML_ADDED_IN_VERSION(2, 3) + +public: + QQuickIcon(); + QQuickIcon(const QQuickIcon &other); + ~QQuickIcon(); + + QQuickIcon& operator=(const QQuickIcon &other); + bool operator==(const QQuickIcon &other) const; + bool operator!=(const QQuickIcon &other) const; + + bool isEmpty() const; + + QString name() const; + void setName(const QString &name); + void resetName(); + + QUrl source() const; + void setSource(const QUrl &source); + void resetSource(); + QUrl resolvedSource() const; + void ensureRelativeSourceResolved(const QObject *owner); + + int width() const; + void setWidth(int width); + void resetWidth(); + + int height() const; + void setHeight(int height); + void resetHeight(); + + QColor color() const; + void setColor(const QColor &color); + void resetColor(); + + bool cache() const; + void setCache(bool cache); + void resetCache(); + + QQuickIcon resolve(const QQuickIcon &other) const; + +private: + QExplicitlySharedDataPointer<QQuickIconPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QQUICKICON_P_H diff --git a/src/quicktemplates2/qquickindicatorbutton_p.cpp b/src/quicktemplates2/qquickindicatorbutton_p.cpp new file mode 100644 index 0000000000..016d89a4c6 --- /dev/null +++ b/src/quicktemplates2/qquickindicatorbutton_p.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qquickindicatorbutton_p.h" +#include "qquickdeferredexecute_p_p.h" +#include "qquickcontrol_p_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickIndicatorButton; + +static inline QString indicatorName() { return QStringLiteral("indicator"); } + +void QQuickIndicatorButtonPrivate::cancelIndicator() +{ + Q_Q(QQuickIndicatorButton); + quickCancelDeferred(q, indicatorName()); +} + +void QQuickIndicatorButtonPrivate::executeIndicator(bool complete) +{ + Q_Q(QQuickIndicatorButton); + if (indicator.wasExecuted()) + return; + + if (!indicator || complete) + quickBeginDeferred(q, indicatorName(), indicator); + if (complete) + quickCompleteDeferred(q, indicatorName(), indicator); +} + +QQuickIndicatorButton::QQuickIndicatorButton(QObject *parent) + : QObject(*(new QQuickIndicatorButtonPrivate), parent) +{ +} + +bool QQuickIndicatorButton::isPressed() const +{ + Q_D(const QQuickIndicatorButton); + return d->pressed; +} + +void QQuickIndicatorButton::setPressed(bool pressed) +{ + Q_D(QQuickIndicatorButton); + if (d->pressed == pressed) + return; + + d->pressed = pressed; + emit pressedChanged(); +} + +QQuickItem *QQuickIndicatorButton::indicator() const +{ + QQuickIndicatorButtonPrivate *d = const_cast<QQuickIndicatorButtonPrivate *>(d_func()); + if (!d->indicator) + d->executeIndicator(); + return d->indicator; +} + +void QQuickIndicatorButton::setIndicator(QQuickItem *indicator) +{ + Q_D(QQuickIndicatorButton); + if (d->indicator == indicator) + return; + + if (!d->indicator.isExecuting()) + d->cancelIndicator(); + + const qreal oldImplicitIndicatorWidth = implicitIndicatorWidth(); + const qreal oldImplicitIndicatorHeight = implicitIndicatorHeight(); + + QQuickControl *par = static_cast<QQuickControl *>(parent()); + + QQuickControlPrivate::get(par)->removeImplicitSizeListener(d->indicator); + QQuickControlPrivate::hideOldItem(d->indicator); + d->indicator = indicator; + + if (indicator) { + if (!indicator->parentItem()) + indicator->setParentItem(par); + QQuickControlPrivate::get(par)->addImplicitSizeListener(indicator); + } + + if (!qFuzzyCompare(oldImplicitIndicatorWidth, implicitIndicatorWidth())) + emit implicitIndicatorWidthChanged(); + if (!qFuzzyCompare(oldImplicitIndicatorHeight, implicitIndicatorHeight())) + emit implicitIndicatorHeightChanged(); + if (!d->indicator.isExecuting()) + emit indicatorChanged(); +} + +bool QQuickIndicatorButton::isHovered() const +{ + Q_D(const QQuickIndicatorButton); + return d->hovered; +} + +void QQuickIndicatorButton::setHovered(bool hovered) +{ + Q_D(QQuickIndicatorButton); + if (d->hovered == hovered) + return; + + d->hovered = hovered; + emit hoveredChanged(); +} + +qreal QQuickIndicatorButton::implicitIndicatorWidth() const +{ + Q_D(const QQuickIndicatorButton); + if (!d->indicator) + return 0; + return d->indicator->implicitWidth(); +} + +qreal QQuickIndicatorButton::implicitIndicatorHeight() const +{ + Q_D(const QQuickIndicatorButton); + if (!d->indicator) + return 0; + return d->indicator->implicitHeight(); +} + +QT_END_NAMESPACE + +#include "moc_qquickindicatorbutton_p.cpp" diff --git a/src/quicktemplates2/qquickindicatorbutton_p.h b/src/quicktemplates2/qquickindicatorbutton_p.h new file mode 100644 index 0000000000..44c5b0cfef --- /dev/null +++ b/src/quicktemplates2/qquickindicatorbutton_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QQUICKINDICATORBUTTON_H +#define QQUICKINDICATORBUTTON_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQml/qjsvalue.h> +#include "qquickdeferredpointer_p_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickIndicatorButtonPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickIndicatorButton : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) + Q_PROPERTY(QQuickItem *indicator READ indicator WRITE setIndicator NOTIFY indicatorChanged FINAL) + // 2.1 (Qt 5.8) + Q_PROPERTY(bool hovered READ isHovered WRITE setHovered NOTIFY hoveredChanged FINAL REVISION(2, 1)) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal implicitIndicatorWidth READ implicitIndicatorWidth NOTIFY implicitIndicatorWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitIndicatorHeight READ implicitIndicatorHeight NOTIFY implicitIndicatorHeightChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "indicator") + QML_ANONYMOUS + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickIndicatorButton(QObject *parent); + + bool isPressed() const; + void setPressed(bool pressed); + + QQuickItem *indicator() const; + void setIndicator(QQuickItem *indicator); + + bool isHovered() const; + void setHovered(bool hovered); + + qreal implicitIndicatorWidth() const; + qreal implicitIndicatorHeight() const; + +Q_SIGNALS: + void pressedChanged(); + void indicatorChanged(); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void hoveredChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void implicitIndicatorWidthChanged(); + Q_REVISION(2, 5) void implicitIndicatorHeightChanged(); + +private: + Q_DISABLE_COPY(QQuickIndicatorButton) + Q_DECLARE_PRIVATE(QQuickIndicatorButton) +}; + +class QQuickIndicatorButtonPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickIndicatorButton) + +public: + static QQuickIndicatorButtonPrivate *get(QQuickIndicatorButton *button) + { + return button->d_func(); + } + + void cancelIndicator(); + void executeIndicator(bool complete = false); + + bool pressed = false; + bool hovered = false; + QQuickDeferredPointer<QQuickItem> indicator; +}; + +QT_END_NAMESPACE + +#endif // QQUICKINDICATORBUTTON_H diff --git a/src/quicktemplates2/qquickitemdelegate.cpp b/src/quicktemplates2/qquickitemdelegate.cpp new file mode 100644 index 0000000000..466bd0c25e --- /dev/null +++ b/src/quicktemplates2/qquickitemdelegate.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickitemdelegate_p.h" +#include "qquickitemdelegate_p_p.h" +#include "qquickcontrol_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ItemDelegate + \inherits AbstractButton +//! \instantiates QQuickItemDelegate + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-delegates + \brief Basic item delegate that can be used in various views and controls. + + \image qtquickcontrols2-itemdelegate.gif + + ItemDelegate presents a standard view item. It can be used as a delegate + in various views and controls, such as \l ListView and \l ComboBox. + + ItemDelegate inherits its API from AbstractButton. For instance, you can set + \l {AbstractButton::text}{text}, display an \l {Icons in Qt Quick Controls}{icon}, + and react to \l {AbstractButton::clicked}{clicks} using the AbstractButton API. + + \snippet qtquickcontrols2-itemdelegate.qml 1 + + \sa {Customizing ItemDelegate}, {Delegate Controls} +*/ + +QQuickItemDelegate::QQuickItemDelegate(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickItemDelegatePrivate), parent) +{ + setFocusPolicy(Qt::NoFocus); +} + +QQuickItemDelegate::QQuickItemDelegate(QQuickItemDelegatePrivate &dd, QQuickItem *parent) + : QQuickAbstractButton(dd, parent) +{ + setFocusPolicy(Qt::NoFocus); +} + +/*! + \qmlproperty bool QtQuick.Controls::ItemDelegate::highlighted + + This property holds whether the delegate is highlighted. + + A delegate can be highlighted in order to draw the user's attention towards + it. It has no effect on keyboard interaction. For example, you can + highlight the current item in a ListView using the following code: + + \code + ListView { + id: listView + model: 10 + delegate: ItemDelegate { + text: modelData + highlighted: ListView.isCurrentItem + onClicked: listView.currentIndex = index + } + } + \endcode + + The default value is \c false. +*/ +bool QQuickItemDelegate::isHighlighted() const +{ + Q_D(const QQuickItemDelegate); + return d->highlighted; +} + +void QQuickItemDelegate::setHighlighted(bool highlighted) +{ + Q_D(QQuickItemDelegate); + if (highlighted == d->highlighted) + return; + + d->highlighted = highlighted; + emit highlightedChanged(); +} + +QFont QQuickItemDelegate::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ItemView); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickItemDelegate::accessibleRole() const +{ + return QAccessible::ListItem; +} +#endif + +QPalette QQuickItemDelegatePrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::ItemView); +} + +QT_END_NAMESPACE + +#include "moc_qquickitemdelegate_p.cpp" diff --git a/src/quicktemplates2/qquickitemdelegate_p.h b/src/quicktemplates2/qquickitemdelegate_p.h new file mode 100644 index 0000000000..2568a0bf91 --- /dev/null +++ b/src/quicktemplates2/qquickitemdelegate_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKITEMDELEGATE_P_H +#define QQUICKITEMDELEGATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickItemDelegatePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickItemDelegate : public QQuickAbstractButton +{ + Q_OBJECT + Q_PROPERTY(bool highlighted READ isHighlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL) + QML_NAMED_ELEMENT(ItemDelegate) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickItemDelegate(QQuickItem *parent = nullptr); + + bool isHighlighted() const; + void setHighlighted(bool highlighted); + +Q_SIGNALS: + void highlightedChanged(); + +protected: + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +protected: + QQuickItemDelegate(QQuickItemDelegatePrivate &dd, QQuickItem *parent); + +private: + Q_DISABLE_COPY(QQuickItemDelegate) + Q_DECLARE_PRIVATE(QQuickItemDelegate) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickItemDelegate) + +#endif // QQUICKITEMDELEGATE_P_H diff --git a/src/quicktemplates2/qquickitemdelegate_p_p.h b/src/quicktemplates2/qquickitemdelegate_p_p.h new file mode 100644 index 0000000000..d7604fbe5d --- /dev/null +++ b/src/quicktemplates2/qquickitemdelegate_p_p.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKITEMDELEGATE_P_P_H +#define QQUICKITEMDELEGATE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickItemDelegatePrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickItemDelegate) + +public: + QPalette defaultPalette() const override; + + bool highlighted = false; +}; + +QT_END_NAMESPACE + +#endif // QQUICKITEMDELEGATE_P_P_H diff --git a/src/quicktemplates2/qquicklabel.cpp b/src/quicktemplates2/qquicklabel.cpp new file mode 100644 index 0000000000..9aa501d110 --- /dev/null +++ b/src/quicktemplates2/qquicklabel.cpp @@ -0,0 +1,594 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicklabel_p.h" +#include "qquicklabel_p_p.h" +#include "qquickcontrol_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquicktext_p.h> + +#if QT_CONFIG(accessibility) +#include <QtQuick/private/qquickaccessibleattached_p.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Label + \inherits Text +//! \instantiates QQuickLabel + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup text + \brief Styled text label with inherited font. + + Label extends \l Text with styling and \l {Control::font}{font} + inheritance. The default colors and font are style specific. Label + can also have a visual \l background item. + + \image qtquickcontrols2-label.png + + \snippet qtquickcontrols2-label.qml 1 + + You can use the properties of \l Text to change the appearance of the text as desired: + + \qml + Label { + text: "Hello world" + font.pixelSize: 22 + font.italic: true + } + \endqml + + \sa {Customizing Label} +*/ + +QQuickLabelPrivate::QQuickLabelPrivate() +{ +#if QT_CONFIG(accessibility) + QAccessible::installActivationObserver(this); +#endif +} + +QQuickLabelPrivate::~QQuickLabelPrivate() +{ +#if QT_CONFIG(accessibility) + QAccessible::removeActivationObserver(this); +#endif +} + +void QQuickLabelPrivate::setTopInset(qreal value, bool reset) +{ + Q_Q(QQuickLabel); + const QMarginsF oldInset = getInset(); + extra.value().topInset = value; + extra.value().hasTopInset = !reset; + if (!qFuzzyCompare(oldInset.top(), value)) { + emit q->topInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickLabelPrivate::setLeftInset(qreal value, bool reset) +{ + Q_Q(QQuickLabel); + const QMarginsF oldInset = getInset(); + extra.value().leftInset = value; + extra.value().hasLeftInset = !reset; + if (!qFuzzyCompare(oldInset.left(), value)) { + emit q->leftInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickLabelPrivate::setRightInset(qreal value, bool reset) +{ + Q_Q(QQuickLabel); + const QMarginsF oldInset = getInset(); + extra.value().rightInset = value; + extra.value().hasRightInset = !reset; + if (!qFuzzyCompare(oldInset.right(), value)) { + emit q->rightInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickLabelPrivate::setBottomInset(qreal value, bool reset) +{ + Q_Q(QQuickLabel); + const QMarginsF oldInset = getInset(); + extra.value().bottomInset = value; + extra.value().hasBottomInset = !reset; + if (!qFuzzyCompare(oldInset.bottom(), value)) { + emit q->bottomInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickLabelPrivate::resizeBackground() +{ + if (!background) + return; + + resizingBackground = true; + + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + if (((!p->widthValid() || !extra.isAllocated() || !extra->hasBackgroundWidth) && qFuzzyIsNull(background->x())) + || (extra.isAllocated() && (extra->hasLeftInset || extra->hasRightInset))) { + background->setX(getLeftInset()); + background->setWidth(width - getLeftInset() - getRightInset()); + } + if (((!p->heightValid() || !extra.isAllocated() || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y())) + || (extra.isAllocated() && (extra->hasTopInset || extra->hasBottomInset))) { + background->setY(getTopInset()); + background->setHeight(height - getTopInset() - getBottomInset()); + } + + resizingBackground = false; +} + +/*! + \internal + + Determine which font is implicitly imposed on this control by its ancestors + and QGuiApplication::font, resolve this against its own font (attributes from + the implicit font are copied over). Then propagate this font to this + control's children. +*/ +void QQuickLabelPrivate::resolveFont() +{ + Q_Q(QQuickLabel); + inheritFont(QQuickControlPrivate::parentFont(q)); +} + +void QQuickLabelPrivate::inheritFont(const QFont &font) +{ + QFont parentFont = extra.isAllocated() ? extra->requestedFont.resolve(font) : font; + parentFont.setResolveMask(extra.isAllocated() ? extra->requestedFont.resolveMask() | font.resolveMask() : font.resolveMask()); + + const QFont defaultFont = QQuickTheme::font(QQuickTheme::Label); + QFont resolvedFont = parentFont.resolve(defaultFont); + + setFont_helper(resolvedFont); +} + +/*! + \internal + + Assign \a font to this control, and propagate it to all children. +*/ +void QQuickLabelPrivate::updateFont(const QFont &font) +{ + Q_Q(QQuickLabel); + QFont oldFont = sourceFont; + q->QQuickText::setFont(font); + + QQuickControlPrivate::updateFontRecur(q, font); + + if (oldFont != font) + emit q->fontChanged(); +} + +void QQuickLabelPrivate::textChanged(const QString &text) +{ +#if QT_CONFIG(accessibility) + maybeSetAccessibleName(text); +#else + Q_UNUSED(text); +#endif +} + +#if QT_CONFIG(accessibility) +void QQuickLabelPrivate::accessibilityActiveChanged(bool active) +{ + if (!active) + return; + + Q_Q(QQuickLabel); + QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true)); + Q_ASSERT(accessibleAttached); + accessibleAttached->setRole(accessibleRole()); + maybeSetAccessibleName(text); +} + +QAccessible::Role QQuickLabelPrivate::accessibleRole() const +{ + return QAccessible::StaticText; +} + +void QQuickLabelPrivate::maybeSetAccessibleName(const QString &name) +{ + Q_Q(QQuickLabel); + auto accessibleAttached = qobject_cast<QQuickAccessibleAttached *>( + qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true)); + if (accessibleAttached) { + if (!accessibleAttached->wasNameExplicitlySet()) + accessibleAttached->setNameImplicitly(name); + } +} +#endif + +static inline QString backgroundName() { return QStringLiteral("background"); } + +void QQuickLabelPrivate::cancelBackground() +{ + Q_Q(QQuickLabel); + quickCancelDeferred(q, backgroundName()); +} + +void QQuickLabelPrivate::executeBackground(bool complete) +{ + Q_Q(QQuickLabel); + if (background.wasExecuted()) + return; + + if (!background || complete) + quickBeginDeferred(q, backgroundName(), background); + if (complete) + quickCompleteDeferred(q, backgroundName(), background); +} + +void QQuickLabelPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + Q_UNUSED(diff); + if (resizingBackground || item != background || !change.sizeChange()) + return; + + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + extra.value().hasBackgroundWidth = p->widthValid(); + extra.value().hasBackgroundHeight = p->heightValid(); + resizeBackground(); +} + +void QQuickLabelPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickLabel); + if (item == background) + emit q->implicitBackgroundWidthChanged(); +} + +void QQuickLabelPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickLabel); + if (item == background) + emit q->implicitBackgroundHeightChanged(); +} + +void QQuickLabelPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickLabel); + if (item == background) { + background = nullptr; + emit q->implicitBackgroundWidthChanged(); + emit q->implicitBackgroundHeightChanged(); + } +} + +QPalette QQuickLabelPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::Label); +} + +QQuickLabel::QQuickLabel(QQuickItem *parent) + : QQuickText(*(new QQuickLabelPrivate), parent) +{ + Q_D(QQuickLabel); + QObjectPrivate::connect(this, &QQuickText::textChanged, d, &QQuickLabelPrivate::textChanged); +} + +QQuickLabel::~QQuickLabel() +{ + Q_D(QQuickLabel); + QQuickControlPrivate::removeImplicitSizeListener(d->background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); +} + +QFont QQuickLabel::font() const +{ + return QQuickText::font(); +} + +void QQuickLabel::setFont(const QFont &font) +{ + Q_D(QQuickLabel); + if (d->extra.value().requestedFont.resolveMask() == font.resolveMask() && d->extra.value().requestedFont == font) + return; + + d->extra.value().requestedFont = font; + d->resolveFont(); +} + +/*! + \qmlproperty Item QtQuick.Controls::Label::background + + This property holds the background item. + + \note If the background item has no explicit size specified, it automatically + follows the control's size. In most cases, there is no need to specify + width or height for a background item. + + \sa {Customizing Label} +*/ +QQuickItem *QQuickLabel::background() const +{ + QQuickLabelPrivate *d = const_cast<QQuickLabelPrivate *>(d_func()); + if (!d->background) + d->executeBackground(); + return d->background; +} + +void QQuickLabel::setBackground(QQuickItem *background) +{ + Q_D(QQuickLabel); + if (d->background == background) + return; + + if (!d->background.isExecuting()) + d->cancelBackground(); + + const qreal oldImplicitBackgroundWidth = implicitBackgroundWidth(); + const qreal oldImplicitBackgroundHeight = implicitBackgroundHeight(); + + if (d->extra.isAllocated()) { + d->extra.value().hasBackgroundWidth = false; + d->extra.value().hasBackgroundHeight = false; + } + + QQuickControlPrivate::removeImplicitSizeListener(d->background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + QQuickControlPrivate::hideOldItem(d->background); + d->background = background; + + if (background) { + background->setParentItem(this); + if (qFuzzyIsNull(background->z())) + background->setZ(-1); + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + if (p->widthValid() || p->heightValid()) { + d->extra.value().hasBackgroundWidth = p->widthValid(); + d->extra.value().hasBackgroundHeight = p->heightValid(); + } + if (isComponentComplete()) + d->resizeBackground(); + QQuickControlPrivate::addImplicitSizeListener(background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + } + + if (!qFuzzyCompare(oldImplicitBackgroundWidth, implicitBackgroundWidth())) + emit implicitBackgroundWidthChanged(); + if (!qFuzzyCompare(oldImplicitBackgroundHeight, implicitBackgroundHeight())) + emit implicitBackgroundHeightChanged(); + if (!d->background.isExecuting()) + emit backgroundChanged(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Label::implicitBackgroundWidth + \readonly + + This property holds the implicit background width. + + The value is equal to \c {background ? background.implicitWidth : 0}. + + \sa implicitBackgroundHeight +*/ +qreal QQuickLabel::implicitBackgroundWidth() const +{ + Q_D(const QQuickLabel); + if (!d->background) + return 0; + return d->background->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Label::implicitBackgroundHeight + \readonly + + This property holds the implicit background height. + + The value is equal to \c {background ? background.implicitHeight : 0}. + + \sa implicitBackgroundWidth +*/ +qreal QQuickLabel::implicitBackgroundHeight() const +{ + Q_D(const QQuickLabel); + if (!d->background) + return 0; + return d->background->implicitHeight(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Label::topInset + + This property holds the top inset for the background. + + \sa {Control Layout}, bottomInset +*/ +qreal QQuickLabel::topInset() const +{ + Q_D(const QQuickLabel); + return d->getTopInset(); +} + +void QQuickLabel::setTopInset(qreal inset) +{ + Q_D(QQuickLabel); + d->setTopInset(inset); +} + +void QQuickLabel::resetTopInset() +{ + Q_D(QQuickLabel); + d->setTopInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Label::leftInset + + This property holds the left inset for the background. + + \sa {Control Layout}, rightInset +*/ +qreal QQuickLabel::leftInset() const +{ + Q_D(const QQuickLabel); + return d->getLeftInset(); +} + +void QQuickLabel::setLeftInset(qreal inset) +{ + Q_D(QQuickLabel); + d->setLeftInset(inset); +} + +void QQuickLabel::resetLeftInset() +{ + Q_D(QQuickLabel); + d->setLeftInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Label::rightInset + + This property holds the right inset for the background. + + \sa {Control Layout}, leftInset +*/ +qreal QQuickLabel::rightInset() const +{ + Q_D(const QQuickLabel); + return d->getRightInset(); +} + +void QQuickLabel::setRightInset(qreal inset) +{ + Q_D(QQuickLabel); + d->setRightInset(inset); +} + +void QQuickLabel::resetRightInset() +{ + Q_D(QQuickLabel); + d->setRightInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Label::bottomInset + + This property holds the bottom inset for the background. + + \sa {Control Layout}, topInset +*/ +qreal QQuickLabel::bottomInset() const +{ + Q_D(const QQuickLabel); + return d->getBottomInset(); +} + +void QQuickLabel::setBottomInset(qreal inset) +{ + Q_D(QQuickLabel); + d->setBottomInset(inset); +} + +void QQuickLabel::resetBottomInset() +{ + Q_D(QQuickLabel); + d->setBottomInset(0, true); +} + +void QQuickLabel::classBegin() +{ + Q_D(QQuickLabel); + QQuickText::classBegin(); + d->resolveFont(); +} + +void QQuickLabel::componentComplete() +{ + Q_D(QQuickLabel); + d->executeBackground(true); + QQuickText::componentComplete(); + d->resizeBackground(); +#if QT_CONFIG(accessibility) + if (QAccessible::isActive()) + d->accessibilityActiveChanged(true); +#endif +} + +void QQuickLabel::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +{ + Q_D(QQuickLabel); + QQuickText::itemChange(change, value); + switch (change) { + case ItemEnabledHasChanged: + break; + case ItemSceneChange: + case ItemParentHasChanged: + if ((change == ItemParentHasChanged && value.item) || (change == ItemSceneChange && value.window)) { + d->resolveFont(); + } + break; + default: + break; + } +} + +void QQuickLabel::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickLabel); + QQuickText::geometryChange(newGeometry, oldGeometry); + d->resizeBackground(); +} + +void QQuickLabel::insetChange(const QMarginsF &newInset, const QMarginsF &oldInset) +{ + Q_D(QQuickLabel); + Q_UNUSED(newInset); + Q_UNUSED(oldInset); + d->resizeBackground(); +} + +QT_END_NAMESPACE + +#include "moc_qquicklabel_p.cpp" diff --git a/src/quicktemplates2/qquicklabel_p.h b/src/quicktemplates2/qquicklabel_p.h new file mode 100644 index 0000000000..6ef60be96e --- /dev/null +++ b/src/quicktemplates2/qquicklabel_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKLABEL_P_H +#define QQUICKLABEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qpalette.h> +#include <QtQuick/private/qquicktext_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickLabelPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickLabel : public QQuickText +{ + Q_OBJECT + Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) // override + Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal implicitBackgroundWidth READ implicitBackgroundWidth NOTIFY implicitBackgroundWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundHeight READ implicitBackgroundHeight NOTIFY implicitBackgroundHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal topInset READ topInset WRITE setTopInset RESET resetTopInset NOTIFY topInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal leftInset READ leftInset WRITE setLeftInset RESET resetLeftInset NOTIFY leftInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal rightInset READ rightInset WRITE setRightInset RESET resetRightInset NOTIFY rightInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal bottomInset READ bottomInset WRITE setBottomInset RESET resetBottomInset NOTIFY bottomInsetChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background") + QML_NAMED_ELEMENT(Label) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickLabel(QQuickItem *parent = nullptr); + ~QQuickLabel(); + + QFont font() const; + void setFont(const QFont &font); + + QQuickItem *background() const; + void setBackground(QQuickItem *background); + + // 2.5 (Qt 5.12) + qreal implicitBackgroundWidth() const; + qreal implicitBackgroundHeight() const; + + qreal topInset() const; + void setTopInset(qreal inset); + void resetTopInset(); + + qreal leftInset() const; + void setLeftInset(qreal inset); + void resetLeftInset(); + + qreal rightInset() const; + void setRightInset(qreal inset); + void resetRightInset(); + + qreal bottomInset() const; + void setBottomInset(qreal inset); + void resetBottomInset(); + +Q_SIGNALS: + void fontChanged(); + void backgroundChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void implicitBackgroundWidthChanged(); + Q_REVISION(2, 5) void implicitBackgroundHeightChanged(); + Q_REVISION(2, 5) void topInsetChanged(); + Q_REVISION(2, 5) void leftInsetChanged(); + Q_REVISION(2, 5) void rightInsetChanged(); + Q_REVISION(2, 5) void bottomInsetChanged(); + +protected: + void classBegin() override; + void componentComplete() override; + + void itemChange(ItemChange change, const ItemChangeData &value) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + virtual void insetChange(const QMarginsF &newInset, const QMarginsF &oldInset); + +private: + Q_DISABLE_COPY(QQuickLabel) + Q_DECLARE_PRIVATE(QQuickLabel) +}; + +struct QQuickTemplatesTextForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickText) + QML_ADDED_IN_VERSION(2, 3) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickLabel) + +#endif // QQUICKLABEL_P_H diff --git a/src/quicktemplates2/qquicklabel_p_p.h b/src/quicktemplates2/qquicklabel_p_p.h new file mode 100644 index 0000000000..3171e2f7e4 --- /dev/null +++ b/src/quicktemplates2/qquicklabel_p_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKLABEL_P_P_H +#define QQUICKLABEL_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/private/qlazilyallocated_p.h> +#include <QtQuick/private/qquicktext_p_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuickTemplates2/private/qquickdeferredpointer_p_p.h> +#include <QtQuickTemplates2/private/qquicktheme_p.h> + +#if QT_CONFIG(accessibility) +#include <QtGui/qaccessible.h> +#endif + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QQuickLabelPrivate : public QQuickTextPrivate, public QQuickItemChangeListener +#if QT_CONFIG(accessibility) + , public QAccessible::ActivationObserver +#endif +{ + Q_DECLARE_PUBLIC(QQuickLabel) + +public: + QQuickLabelPrivate(); + ~QQuickLabelPrivate(); + + static QQuickLabelPrivate *get(QQuickLabel *item) + { + return static_cast<QQuickLabelPrivate *>(QObjectPrivate::get(item)); + } + + inline QMarginsF getInset() const { return QMarginsF(getLeftInset(), getTopInset(), getRightInset(), getBottomInset()); } + inline qreal getTopInset() const { return extra.isAllocated() ? extra->topInset : 0; } + inline qreal getLeftInset() const { return extra.isAllocated() ? extra->leftInset : 0; } + inline qreal getRightInset() const { return extra.isAllocated() ? extra->rightInset : 0; } + inline qreal getBottomInset() const { return extra.isAllocated() ? extra->bottomInset : 0; } + + void setTopInset(qreal value, bool reset = false); + void setLeftInset(qreal value, bool reset = false); + void setRightInset(qreal value, bool reset = false); + void setBottomInset(qreal value, bool reset = false); + + void resizeBackground(); + + void resolveFont(); + void inheritFont(const QFont &font); + void updateFont(const QFont &font); + inline void setFont_helper(const QFont &font) { + if (sourceFont.resolveMask() == font.resolveMask() && sourceFont == font) + return; + updateFont(font); + } + + void textChanged(const QString &text); + +#if QT_CONFIG(accessibility) + void accessibilityActiveChanged(bool active) override; + QAccessible::Role accessibleRole() const override; + void maybeSetAccessibleName(const QString &name); +#endif + + void cancelBackground(); + void executeBackground(bool complete = false); + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; + + QPalette defaultPalette() const override; + + struct ExtraData { + bool hasTopInset = false; + bool hasLeftInset = false; + bool hasRightInset = false; + bool hasBottomInset = false; + bool hasBackgroundWidth = false; + bool hasBackgroundHeight = false; + qreal topInset = 0; + qreal leftInset = 0; + qreal rightInset = 0; + qreal bottomInset = 0; + QFont requestedFont; + }; + QLazilyAllocated<ExtraData> extra; + + bool resizingBackground = false; + QPalette resolvedPalette; + QQuickDeferredPointer<QQuickItem> background; +}; + +QT_END_NAMESPACE + +#endif // QQUICKLABEL_P_P_H diff --git a/src/quicktemplates2/qquickmenu.cpp b/src/quicktemplates2/qquickmenu.cpp new file mode 100644 index 0000000000..e045375f89 --- /dev/null +++ b/src/quicktemplates2/qquickmenu.cpp @@ -0,0 +1,1533 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmenu_p.h" +#include "qquickmenu_p_p.h" +#include "qquickmenuitem_p_p.h" +#include "qquickmenubaritem_p.h" +#include "qquickmenubar_p.h" +#include "qquickpopupitem_p_p.h" +#include "qquickpopuppositioner_p_p.h" +#include "qquickaction_p.h" + +#include <QtGui/qevent.h> +#include <QtGui/qcursor.h> +#if QT_CONFIG(shortcut) +#include <QtGui/qkeysequence.h> +#endif +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/private/qqmlengine_p.h> +#include <QtQml/private/qv4scopedvalue_p.h> +#include <QtQml/private/qv4variantobject_p.h> +#include <QtQml/private/qv4qobjectwrapper_p.h> +#include <private/qqmlobjectmodel_p.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuick/private/qquickitemview_p.h> +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquickwindow_p.h> + +QT_BEGIN_NAMESPACE + +// copied from qfusionstyle.cpp +static const int SUBMENU_DELAY = 225; + +/*! + \qmltype Menu + \inherits Popup +//! \instantiates QQuickMenu + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-menus + \ingroup qtquickcontrols2-popups + \brief Menu popup that can be used as a context menu or popup menu. + + \image qtquickcontrols2-menu.png + + Menu has two main use cases: + \list + \li Context menus; for example, a menu that is shown after right clicking + \li Popup menus; for example, a menu that is shown after clicking a button + \endlist + + When used as a context menu, the recommended way of opening the menu is to call + \l popup(). Unless a position is explicitly specified, the menu is positioned at + the mouse cursor on desktop platforms that have a mouse cursor available, and + otherwise centered over its parent item. + + \code + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.RightButton) + contextMenu.popup() + } + onPressAndHold: { + if (mouse.source === Qt.MouseEventNotSynthesized) + contextMenu.popup() + } + + Menu { + id: contextMenu + MenuItem { text: "Cut" } + MenuItem { text: "Copy" } + MenuItem { text: "Paste" } + } + } + \endcode + + When used as a popup menu, it is easiest to specify the position by specifying + the desired \l {Popup::}{x} and \l {Popup::}{y} coordinates using the respective + properties, and call \l {Popup::}{open()} to open the menu. + + \code + Button { + id: fileButton + text: "File" + onClicked: menu.open() + + Menu { + id: menu + y: fileButton.height + + MenuItem { + text: "New..." + } + MenuItem { + text: "Open..." + } + MenuItem { + text: "Save" + } + } + } + \endcode + + Since QtQuick.Controls 2.3 (Qt 5.10), it is also possible to create sub-menus + and declare Action objects inside Menu: + + \code + Menu { + Action { text: "Cut" } + Action { text: "Copy" } + Action { text: "Paste" } + + MenuSeparator { } + + Menu { + title: "Find/Replace" + Action { text: "Find Next" } + Action { text: "Find Previous" } + Action { text: "Replace" } + } + } + \endcode + + Sub-menus are \l {cascade}{cascading} by default on desktop platforms + that have a mouse cursor available. Non-cascading menus are shown one + menu at a time, and centered over the parent menu. + + Typically, menu items are statically declared as children of the menu, but + Menu also provides API to \l {addItem}{add}, \l {insertItem}{insert}, + \l {moveItem}{move} and \l {removeItem}{remove} items dynamically. The + items in a menu can be accessed using \l itemAt() or + \l {Popup::}{contentChildren}. + + Although \l {MenuItem}{MenuItems} are most commonly used with Menu, it can + contain any type of item. + + \section1 Margins + + As it is inherited from Popup, Menu supports \l {Popup::}{margins}. By + default, all of the built-in styles specify \c 0 for Menu's margins to + ensure that the menu is kept within the bounds of the window. To allow a + menu to go outside of the window (to animate it moving into view, for + example), set the margins property to \c -1. + + \sa {Customizing Menu}, MenuItem, {Menu Controls}, {Popup Controls} +*/ + +/*! + \qmlproperty bool QtQuick.Controls::Menu::focus + + This property holds whether the popup wants focus. + + When the popup actually receives focus, \l{Popup::}{activeFocus} + will be \c true. For more information, see + \l {Keyboard Focus in Qt Quick}. + + The default value is \c false. + + \sa {Popup::}{activeFocus} +*/ + +static const QQuickPopup::ClosePolicy cascadingSubMenuClosePolicy = QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent; + +static bool shouldCascade() +{ +#if QT_CONFIG(cursor) + return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows); +#else + return false; +#endif +} + +class QQuickMenuPositioner : public QQuickPopupPositioner +{ +public: + QQuickMenuPositioner(QQuickMenu *menu) : QQuickPopupPositioner(menu) { } + + void reposition() override; +}; + +QQuickMenuPrivate::QQuickMenuPrivate() +{ + cascade = shouldCascade(); +} + +void QQuickMenuPrivate::init() +{ + Q_Q(QQuickMenu); + contentModel = new QQmlObjectModel(q); +} + +QQuickItem *QQuickMenuPrivate::itemAt(int index) const +{ + return qobject_cast<QQuickItem *>(contentModel->get(index)); +} + +void QQuickMenuPrivate::insertItem(int index, QQuickItem *item) +{ + contentData.append(item); + item->setParentItem(contentItem); + if (qobject_cast<QQuickItemView *>(contentItem)) + QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-53262 + if (complete) + resizeItem(item); + QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent); + QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(this, QQuickGeometryChange::Width); + contentModel->insert(index, item); + + QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item); + if (menuItem) { + Q_Q(QQuickMenu); + QQuickMenuItemPrivate::get(menuItem)->setMenu(q); + if (QQuickMenu *subMenu = menuItem->subMenu()) + QQuickMenuPrivate::get(subMenu)->setParentMenu(q); + QObjectPrivate::connect(menuItem, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered); + QObjectPrivate::connect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged); + QObjectPrivate::connect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered); + } +} + +void QQuickMenuPrivate::moveItem(int from, int to) +{ + contentModel->move(from, to); +} + +void QQuickMenuPrivate::removeItem(int index, QQuickItem *item) +{ + contentData.removeOne(item); + + QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent); + QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Geometry); + item->setParentItem(nullptr); + contentModel->remove(index); + + QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item); + if (menuItem) { + QQuickMenuItemPrivate::get(menuItem)->setMenu(nullptr); + if (QQuickMenu *subMenu = menuItem->subMenu()) + QQuickMenuPrivate::get(subMenu)->setParentMenu(nullptr); + QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered); + QObjectPrivate::disconnect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged); + QObjectPrivate::disconnect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered); + } +} + +QQuickItem *QQuickMenuPrivate::beginCreateItem() +{ + Q_Q(QQuickMenu); + if (!delegate) + return nullptr; + + QQmlContext *creationContext = delegate->creationContext(); + if (!creationContext) + creationContext = qmlContext(q); + QQmlContext *context = new QQmlContext(creationContext, q); + context->setContextObject(q); + + QObject *object = delegate->beginCreate(context); + QQuickItem *item = qobject_cast<QQuickItem *>(object); + if (!item) + delete object; + else + QQml_setParent_noEvent(item, q); + + return item; +} + +void QQuickMenuPrivate::completeCreateItem() +{ + if (!delegate) + return; + + delegate->completeCreate(); +} + +QQuickItem *QQuickMenuPrivate::createItem(QQuickMenu *menu) +{ + QQuickItem *item = beginCreateItem(); + if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item)) + QQuickMenuItemPrivate::get(menuItem)->setSubMenu(menu); + completeCreateItem(); + return item; +} + +QQuickItem *QQuickMenuPrivate::createItem(QQuickAction *action) +{ + QQuickItem *item = beginCreateItem(); + if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(item)) + button->setAction(action); + completeCreateItem(); + return item; +} + +void QQuickMenuPrivate::resizeItem(QQuickItem *item) +{ + if (!item || !contentItem) + return; + + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!p->widthValid()) { + item->setWidth(contentItem->width()); + p->widthValidFlag = false; + } +} + +void QQuickMenuPrivate::resizeItems() +{ + if (!contentModel) + return; + + for (int i = 0; i < contentModel->count(); ++i) + resizeItem(itemAt(i)); +} + +void QQuickMenuPrivate::itemChildAdded(QQuickItem *, QQuickItem *child) +{ + // add dynamically reparented items (eg. by a Repeater) + if (!QQuickItemPrivate::get(child)->isTransparentForPositioner() && !contentData.contains(child)) + insertItem(contentModel->count(), child); +} + +void QQuickMenuPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent) +{ + // remove dynamically unparented items (eg. by a Repeater) + if (!parent) + removeItem(contentModel->indexOf(item, nullptr), item); +} + +void QQuickMenuPrivate::itemSiblingOrderChanged(QQuickItem *) +{ + // reorder the restacked items (eg. by a Repeater) + Q_Q(QQuickMenu); + QList<QQuickItem *> siblings = contentItem->childItems(); + + int to = 0; + for (int i = 0; i < siblings.count(); ++i) { + QQuickItem* sibling = siblings.at(i); + if (QQuickItemPrivate::get(sibling)->isTransparentForPositioner()) + continue; + int index = contentModel->indexOf(sibling, nullptr); + q->moveItem(index, to++); + } +} + +void QQuickMenuPrivate::itemDestroyed(QQuickItem *item) +{ + QQuickPopupPrivate::itemDestroyed(item); + int index = contentModel->indexOf(item, nullptr); + if (index != -1) + removeItem(index, item); +} + +void QQuickMenuPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange, const QRectF &) +{ + if (!complete) + return; + + if (item == contentItem) { + // The contentItem's geometry changed, so resize any items + // that don't have explicit widths set so that they fill the width of the menu. + resizeItems(); + } else { + // The geometry of an item in the menu changed. If the item + // doesn't have an explicit width set, make it fill the width of the menu. + resizeItem(item); + } +} + +QQuickPopupPositioner *QQuickMenuPrivate::getPositioner() +{ + Q_Q(QQuickMenu); + if (!positioner) + positioner = new QQuickMenuPositioner(q); + return positioner; +} + +void QQuickMenuPositioner::reposition() +{ + QQuickMenu *menu = static_cast<QQuickMenu *>(popup()); + QQuickMenuPrivate *p = QQuickMenuPrivate::get(menu); + if (p->parentMenu) { + if (p->cascade) { + if (p->popupItem->isMirrored()) + menu->setPosition(QPointF(-menu->width() - p->parentMenu->leftPadding() + menu->overlap(), -menu->topPadding())); + else if (p->parentItem) + menu->setPosition(QPointF(p->parentItem->width() + p->parentMenu->rightPadding() - menu->overlap(), -menu->topPadding())); + } else { + menu->setPosition(QPointF(p->parentMenu->x() + (p->parentMenu->width() - menu->width()) / 2, + p->parentMenu->y() + (p->parentMenu->height() - menu->height()) / 2)); + } + } + QQuickPopupPositioner::reposition(); +} + +bool QQuickMenuPrivate::prepareEnterTransition() +{ + Q_Q(QQuickMenu); + if (parentMenu && !cascade) + parentMenu->close(); + + // If a cascading sub-menu doesn't have enough space to open on + // the right, it flips on the other side of the parent menu. + allowHorizontalFlip = cascade && parentMenu; + + if (!QQuickPopupPrivate::prepareEnterTransition()) + return false; + + if (!hasClosePolicy) { + if (cascade && parentMenu) + closePolicy = cascadingSubMenuClosePolicy; + else + q->resetClosePolicy(); + } + return true; +} + +bool QQuickMenuPrivate::prepareExitTransition() +{ + if (!QQuickPopupPrivate::prepareExitTransition()) + return false; + + stopHoverTimer(); + + QQuickMenu *subMenu = currentSubMenu(); + while (subMenu) { + QPointer<QQuickMenuItem> currentSubMenuItem = QQuickMenuPrivate::get(subMenu)->currentItem; + subMenu->close(); + subMenu = currentSubMenuItem ? currentSubMenuItem->subMenu() : nullptr; + } + return true; +} + +bool QQuickMenuPrivate::blockInput(QQuickItem *item, const QPointF &point) const +{ + // keep the parent menu open when a cascading sub-menu (this menu) is interacted with + return (cascade && parentMenu && contains(point)) || QQuickPopupPrivate::blockInput(item, point); +} + +void QQuickMenuPrivate::onItemHovered() +{ + Q_Q(QQuickMenu); + QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender()); + if (!button || !button->isHovered() || !button->isEnabled() || QQuickAbstractButtonPrivate::get(button)->touchId != -1) + return; + + QQuickMenuItem *oldCurrentItem = currentItem; + + int index = contentModel->indexOf(button, nullptr); + if (index != -1) { + setCurrentIndex(index, Qt::OtherFocusReason); + if (oldCurrentItem != currentItem) { + if (oldCurrentItem) { + QQuickMenu *subMenu = oldCurrentItem->subMenu(); + if (subMenu) + subMenu->close(); + } + if (currentItem) { + QQuickMenu *subMenu = currentItem->menu(); + if (subMenu && subMenu->cascade()) + startHoverTimer(); + } + } + } +} + +void QQuickMenuPrivate::onItemTriggered() +{ + Q_Q(QQuickMenu); + QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(q->sender()); + if (!item) + return; + + if (QQuickMenu *subMenu = item->subMenu()) { + auto subMenuPrivate = QQuickMenuPrivate::get(subMenu); + subMenu->popup(subMenuPrivate->firstEnabledMenuItem()); + } else { + q->dismiss(); + } +} + +void QQuickMenuPrivate::onItemActiveFocusChanged() +{ + Q_Q(QQuickMenu); + QQuickItem *item = qobject_cast<QQuickItem*>(q->sender()); + if (!item->hasActiveFocus()) + return; + + int indexOfItem = contentModel->indexOf(item, nullptr); + QQuickControl *control = qobject_cast<QQuickControl *>(item); + setCurrentIndex(indexOfItem, control ? control->focusReason() : Qt::OtherFocusReason); +} + +QQuickMenu *QQuickMenuPrivate::currentSubMenu() const +{ + if (!currentItem) + return nullptr; + + return currentItem->subMenu(); +} + +void QQuickMenuPrivate::setParentMenu(QQuickMenu *parent) +{ + Q_Q(QQuickMenu); + if (parentMenu == parent) + return; + + if (parentMenu) { + QObject::disconnect(parentMenu.data(), &QQuickMenu::cascadeChanged, q, &QQuickMenu::setCascade); + disconnect(parentMenu.data(), &QQuickMenu::parentChanged, this, &QQuickMenuPrivate::resolveParentItem); + } + if (parent) { + QObject::connect(parent, &QQuickMenu::cascadeChanged, q, &QQuickMenu::setCascade); + connect(parent, &QQuickMenu::parentChanged, this, &QQuickMenuPrivate::resolveParentItem); + } + + parentMenu = parent; + q->resetCascade(); + resolveParentItem(); +} + +static QQuickItem *findParentMenuItem(QQuickMenu *subMenu) +{ + QQuickMenu *menu = QQuickMenuPrivate::get(subMenu)->parentMenu; + for (int i = 0; i < QQuickMenuPrivate::get(menu)->contentModel->count(); ++i) { + QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(menu->itemAt(i)); + if (item && item->subMenu() == subMenu) + return item; + } + return nullptr; +} + +void QQuickMenuPrivate::resolveParentItem() +{ + Q_Q(QQuickMenu); + if (!parentMenu) + q->resetParentItem(); + else if (!cascade) + q->setParentItem(parentMenu->parentItem()); + else + q->setParentItem(findParentMenuItem(q)); +} + +void QQuickMenuPrivate::propagateKeyEvent(QKeyEvent *event) +{ + if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(parentItem)) { + if (QQuickMenu *menu = menuItem->menu()) + QQuickMenuPrivate::get(menu)->propagateKeyEvent(event); + } else if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(parentItem)) { + if (QQuickMenuBar *menuBar = menuBarItem->menuBar()) { + event->accept(); + QCoreApplication::sendEvent(menuBar, event); + } + } +} + +void QQuickMenuPrivate::startHoverTimer() +{ + Q_Q(QQuickMenu); + stopHoverTimer(); + hoverTimer = q->startTimer(SUBMENU_DELAY); +} + +void QQuickMenuPrivate::stopHoverTimer() +{ + Q_Q(QQuickMenu); + if (!hoverTimer) + return; + + q->killTimer(hoverTimer); + hoverTimer = 0; +} + +void QQuickMenuPrivate::setCurrentIndex(int index, Qt::FocusReason reason) +{ + Q_Q(QQuickMenu); + if (currentIndex == index) + return; + + QQuickMenuItem *newCurrentItem = qobject_cast<QQuickMenuItem *>(itemAt(index)); + if (currentItem != newCurrentItem) { + stopHoverTimer(); + if (currentItem) { + currentItem->setHighlighted(false); + if (!newCurrentItem && window) { + QQuickItem *focusItem = QQuickItemPrivate::get(contentItem)->subFocusItem; + if (focusItem) + QQuickWindowPrivate::get(window)->clearFocusInScope(contentItem, focusItem, Qt::OtherFocusReason); + } + } + if (newCurrentItem) { + newCurrentItem->setHighlighted(true); + newCurrentItem->forceActiveFocus(reason); + } + currentItem = newCurrentItem; + } + + currentIndex = index; + emit q->currentIndexChanged(); +} + +bool QQuickMenuPrivate::activateNextItem() +{ + int index = currentIndex; + int count = contentModel->count(); + while (++index < count) { + QQuickItem *item = itemAt(index); + if (!item || !item->activeFocusOnTab() || !item->isEnabled()) + continue; + setCurrentIndex(index, Qt::TabFocusReason); + return true; + } + return false; +} + +bool QQuickMenuPrivate::activatePreviousItem() +{ + int index = currentIndex; + while (--index >= 0) { + QQuickItem *item = itemAt(index); + if (!item || !item->activeFocusOnTab() || !item->isEnabled()) + continue; + setCurrentIndex(index, Qt::BacktabFocusReason); + return true; + } + return false; +} + +QQuickMenuItem *QQuickMenuPrivate::firstEnabledMenuItem() const +{ + for (int i = 0; i < contentModel->count(); ++i) { + QQuickItem *item = itemAt(i); + if (!item || !item->isEnabled()) + continue; + + QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item); + if (!menuItem) + continue; + + return menuItem; + } + return nullptr; +} + +void QQuickMenuPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) +{ + QQuickMenu *q = qobject_cast<QQuickMenu *>(prop->object); + QQuickMenuPrivate *p = QQuickMenuPrivate::get(q); + + QQuickItem *item = qobject_cast<QQuickItem *>(obj); + if (!item) { + if (QQuickAction *action = qobject_cast<QQuickAction *>(obj)) + item = p->createItem(action); + else if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(obj)) + item = p->createItem(menu); + } + + if (item) { + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) { + QQuickItemPrivate::get(item)->addItemChangeListener(p, QQuickItemPrivate::SiblingOrder); + item->setParentItem(p->contentItem); + } else if (p->contentModel->indexOf(item, nullptr) == -1) { + q->addItem(item); + } + } else { + p->contentData.append(obj); + } +} + +qsizetype QQuickMenuPrivate::contentData_count(QQmlListProperty<QObject> *prop) +{ + QQuickMenu *q = static_cast<QQuickMenu *>(prop->object); + return QQuickMenuPrivate::get(q)->contentData.count(); +} + +QObject *QQuickMenuPrivate::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index) +{ + QQuickMenu *q = static_cast<QQuickMenu *>(prop->object); + return QQuickMenuPrivate::get(q)->contentData.value(index); +} + +QPalette QQuickMenuPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::Menu); +} + +void QQuickMenuPrivate::contentData_clear(QQmlListProperty<QObject> *prop) +{ + QQuickMenu *q = static_cast<QQuickMenu *>(prop->object); + QQuickMenuPrivate::get(q)->contentData.clear(); +} + +QQuickMenu::QQuickMenu(QObject *parent) + : QQuickPopup(*(new QQuickMenuPrivate), parent) +{ + Q_D(QQuickMenu); + setFocus(true); + d->init(); + connect(d->contentModel, &QQmlObjectModel::countChanged, this, &QQuickMenu::countChanged); +} + +QQuickMenu::~QQuickMenu() +{ + Q_D(QQuickMenu); + // We have to do this to ensure that the change listeners are removed. + // It's too late to do this in ~QQuickMenuPrivate, as contentModel has already + // been destroyed before that is called. + while (d->contentModel->count() > 0) + d->removeItem(0, d->itemAt(0)); +} + +/*! + \qmlmethod Item QtQuick.Controls::Menu::itemAt(int index) + + Returns the item at \a index, or \c null if it does not exist. +*/ +QQuickItem *QQuickMenu::itemAt(int index) const +{ + Q_D(const QQuickMenu); + return d->itemAt(index); +} + +/*! + \qmlmethod void QtQuick.Controls::Menu::addItem(Item item) + + Adds \a item to the end of the list of items. +*/ +void QQuickMenu::addItem(QQuickItem *item) +{ + Q_D(QQuickMenu); + insertItem(d->contentModel->count(), item); +} + +/*! + \qmlmethod void QtQuick.Controls::Menu::insertItem(int index, Item item) + + Inserts \a item at \a index. +*/ +void QQuickMenu::insertItem(int index, QQuickItem *item) +{ + Q_D(QQuickMenu); + if (!item) + return; + const int count = d->contentModel->count(); + if (index < 0 || index > count) + index = count; + + int oldIndex = d->contentModel->indexOf(item, nullptr); + if (oldIndex != -1) { + if (oldIndex < index) + --index; + if (oldIndex != index) + d->moveItem(oldIndex, index); + } else { + d->insertItem(index, item); + } +} + +/*! + \qmlmethod void QtQuick.Controls::Menu::moveItem(int from, int to) + + Moves an item \a from one index \a to another. +*/ +void QQuickMenu::moveItem(int from, int to) +{ + Q_D(QQuickMenu); + const int count = d->contentModel->count(); + if (from < 0 || from > count - 1) + return; + if (to < 0 || to > count - 1) + to = count - 1; + + if (from != to) + d->moveItem(from, to); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::removeItem(Item item) + + Removes and destroys the specified \a item. +*/ +void QQuickMenu::removeItem(QQuickItem *item) +{ + Q_D(QQuickMenu); + if (!item) + return; + + const int index = d->contentModel->indexOf(item, nullptr); + if (index == -1) + return; + + d->removeItem(index, item); + item->deleteLater(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod MenuItem QtQuick.Controls::Menu::takeItem(int index) + + Removes and returns the item at \a index. + + \note The ownership of the item is transferred to the caller. +*/ +QQuickItem *QQuickMenu::takeItem(int index) +{ + Q_D(QQuickMenu); + const int count = d->contentModel->count(); + if (index < 0 || index >= count) + return nullptr; + + QQuickItem *item = itemAt(index); + if (item) + d->removeItem(index, item); + return item; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod Menu QtQuick.Controls::Menu::menuAt(int index) + + Returns the sub-menu at \a index, or \c null if the index is not valid or + there is no sub-menu at the specified index. +*/ +QQuickMenu *QQuickMenu::menuAt(int index) const +{ + Q_D(const QQuickMenu); + QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index)); + if (!item) + return nullptr; + + return item->subMenu(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::addMenu(Menu menu) + + Adds \a menu as a sub-menu to the end of this menu. +*/ +void QQuickMenu::addMenu(QQuickMenu *menu) +{ + Q_D(QQuickMenu); + insertMenu(d->contentModel->count(), menu); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::insertMenu(int index, Menu menu) + + Inserts \a menu as a sub-menu at \a index. The index is within all items in the menu. +*/ +void QQuickMenu::insertMenu(int index, QQuickMenu *menu) +{ + Q_D(QQuickMenu); + if (!menu) + return; + + insertItem(index, d->createItem(menu)); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::removeMenu(Menu menu) + + Removes and destroys the specified \a menu. +*/ +void QQuickMenu::removeMenu(QQuickMenu *menu) +{ + Q_D(QQuickMenu); + if (!menu) + return; + + const int count = d->contentModel->count(); + for (int i = 0; i < count; ++i) { + QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(i)); + if (!item || item->subMenu() != menu) + continue; + + removeItem(item); + break; + } + + menu->deleteLater(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod Menu QtQuick.Controls::Menu::takeMenu(int index) + + Removes and returns the menu at \a index. The index is within all items in the menu. + + \note The ownership of the menu is transferred to the caller. +*/ +QQuickMenu *QQuickMenu::takeMenu(int index) +{ + Q_D(QQuickMenu); + QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index)); + if (!item) + return nullptr; + + QQuickMenu *subMenu = item->subMenu(); + if (!subMenu) + return nullptr; + + d->removeItem(index, item); + item->deleteLater(); + return subMenu; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod Action QtQuick.Controls::Menu::actionAt(int index) + + Returns the action at \a index, or \c null if the index is not valid or + there is no action at the specified index. +*/ +QQuickAction *QQuickMenu::actionAt(int index) const +{ + Q_D(const QQuickMenu); + QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton *>(d->itemAt(index)); + if (!item) + return nullptr; + + return item->action(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::addAction(Action action) + + Adds \a action to the end of this menu. +*/ +void QQuickMenu::addAction(QQuickAction *action) +{ + Q_D(QQuickMenu); + insertAction(d->contentModel->count(), action); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::insertAction(int index, Action action) + + Inserts \a action at \a index. The index is within all items in the menu. +*/ +void QQuickMenu::insertAction(int index, QQuickAction *action) +{ + Q_D(QQuickMenu); + if (!action) + return; + + insertItem(index, d->createItem(action)); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::removeAction(Action action) + + Removes and destroys the specified \a action. +*/ +void QQuickMenu::removeAction(QQuickAction *action) +{ + Q_D(QQuickMenu); + if (!action) + return; + + const int count = d->contentModel->count(); + for (int i = 0; i < count; ++i) { + QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(i)); + if (!item || item->action() != action) + continue; + + removeItem(item); + break; + } + + action->deleteLater(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod Action QtQuick.Controls::Menu::takeAction(int index) + + Removes and returns the action at \a index. The index is within all items in the menu. + + \note The ownership of the action is transferred to the caller. +*/ +QQuickAction *QQuickMenu::takeAction(int index) +{ + Q_D(QQuickMenu); + QQuickMenuItem *item = qobject_cast<QQuickMenuItem *>(d->itemAt(index)); + if (!item) + return nullptr; + + QQuickAction *action = item->action(); + if (!action) + return nullptr; + + d->removeItem(index, item); + item->deleteLater(); + return action; +} + +/*! + \qmlproperty model QtQuick.Controls::Menu::contentModel + \readonly + + This property holds the model used to display menu items. + + The content model is provided for visualization purposes. It can be assigned + as a model to a content item that presents the contents of the menu. + + \code + Menu { + id: menu + contentItem: ListView { + model: menu.contentModel + } + } + \endcode + + The model allows menu items to be statically declared as children of the + menu. +*/ +QVariant QQuickMenu::contentModel() const +{ + Q_D(const QQuickMenu); + return QVariant::fromValue(d->contentModel); +} + +/*! + \qmlproperty list<Object> QtQuick.Controls::Menu::contentData + \qmldefault + + This property holds the list of content data. + + The list contains all objects that have been declared in QML as children + of the menu, and also items that have been dynamically added or + inserted using the \l addItem() and \l insertItem() methods, respectively. + + \note Unlike \c contentChildren, \c contentData does include non-visual QML + objects. It is not re-ordered when items are inserted or moved. + + \sa Item::data, {Popup::}{contentChildren} +*/ +QQmlListProperty<QObject> QQuickMenu::contentData() +{ + Q_D(QQuickMenu); + if (!d->contentItem) + QQuickControlPrivate::get(d->popupItem)->executeContentItem(); + return QQmlListProperty<QObject>(this, nullptr, + QQuickMenuPrivate::contentData_append, + QQuickMenuPrivate::contentData_count, + QQuickMenuPrivate::contentData_at, + QQuickMenuPrivate::contentData_clear); +} + +/*! + \qmlproperty string QtQuick.Controls::Menu::title + + This property holds the title for the menu. + + The title of a menu is often displayed in the text of a menu item when the + menu is a submenu, and in the text of a tool button when it is in a + menubar. +*/ +QString QQuickMenu::title() const +{ + Q_D(const QQuickMenu); + return d->title; +} + +void QQuickMenu::setTitle(QString &title) +{ + Q_D(QQuickMenu); + if (title == d->title) + return; + d->title = title; + emit titleChanged(title); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::Menu::cascade + + This property holds whether the menu cascades its sub-menus. + + The default value is platform-specific. Menus are cascading by default on + desktop platforms that have a mouse cursor available. Non-cascading menus + are shown one menu at a time, and centered over the parent menu. + + \note Changing the value of the property has no effect while the menu is open. + + \sa overlap +*/ +bool QQuickMenu::cascade() const +{ + Q_D(const QQuickMenu); + return d->cascade; +} + +void QQuickMenu::setCascade(bool cascade) +{ + Q_D(QQuickMenu); + if (d->cascade == cascade) + return; + d->cascade = cascade; + if (d->parentMenu) + d->resolveParentItem(); + emit cascadeChanged(cascade); +} + +void QQuickMenu::resetCascade() +{ + Q_D(QQuickMenu); + if (d->parentMenu) + setCascade(d->parentMenu->cascade()); + else + setCascade(shouldCascade()); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty real QtQuick.Controls::Menu::overlap + + This property holds the amount of pixels by which the menu horizontally overlaps its parent menu. + + The property only has effect when the menu is used as a cascading sub-menu. + + The default value is style-specific. + + \note Changing the value of the property has no effect while the menu is open. + + \sa cascade +*/ +qreal QQuickMenu::overlap() const +{ + Q_D(const QQuickMenu); + return d->overlap; +} + +void QQuickMenu::setOverlap(qreal overlap) +{ + Q_D(QQuickMenu); + if (d->overlap == overlap) + return; + d->overlap = overlap; + emit overlapChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty Component QtQuick.Controls::Menu::delegate + + This property holds the component that is used to create items + to present actions. + + \code + Menu { + Action { text: "Cut" } + Action { text: "Copy" } + Action { text: "Paste" } + } + \endcode + + \sa Action +*/ +QQmlComponent *QQuickMenu::delegate() const +{ + Q_D(const QQuickMenu); + return d->delegate; +} + +void QQuickMenu::setDelegate(QQmlComponent *delegate) +{ + Q_D(QQuickMenu); + if (d->delegate == delegate) + return; + + d->delegate = delegate; + emit delegateChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty int QtQuick.Controls::Menu::currentIndex + + This property holds the index of the currently highlighted item. + + Menu items can be highlighted by mouse hover or keyboard navigation. + + \sa MenuItem::highlighted +*/ +int QQuickMenu::currentIndex() const +{ + Q_D(const QQuickMenu); + return d->currentIndex; +} + +void QQuickMenu::setCurrentIndex(int index) +{ + Q_D(QQuickMenu); + d->setCurrentIndex(index, Qt::OtherFocusReason); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty int QtQuick.Controls::Menu::count + \readonly + + This property holds the number of items. +*/ +int QQuickMenu::count() const +{ + Q_D(const QQuickMenu); + return d->contentModel->count(); +} + +void QQuickMenu::popup(QQuickItem *menuItem) +{ + Q_D(QQuickMenu); + // No position has been explicitly specified, so position the menu at the mouse cursor + // on desktop platforms that have a mouse cursor available and support multiple windows. + QQmlNullableValue<QPointF> pos; +#if QT_CONFIG(cursor) + if (d->parentItem && QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows)) + pos = d->parentItem->mapFromGlobal(QCursor::pos()); +#endif + + // As a fallback, center the menu over its parent item. + if (pos.isNull && d->parentItem) + pos = QPointF((d->parentItem->width() - width()) / 2, (d->parentItem->height() - height()) / 2); + + popup(pos.isNull ? QPointF() : pos.value, menuItem); +} + +void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem) +{ + Q_D(QQuickMenu); + qreal offset = 0; +#if QT_CONFIG(cursor) + if (menuItem) + offset = d->popupItem->mapFromItem(menuItem, QPointF(0, 0)).y(); +#endif + setPosition(pos - QPointF(0, offset)); + + if (menuItem) + d->setCurrentIndex(d->contentModel->indexOf(menuItem, nullptr), Qt::PopupFocusReason); + open(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::popup(MenuItem item = null) + \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, MenuItem item = null) + + Opens the menu at the mouse cursor on desktop platforms that have a mouse cursor + available, and otherwise centers the menu over its \a parent item. + + The menu can be optionally aligned to a specific menu \a item. + + \sa Popup::open() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::popup(point pos, MenuItem item = null) + \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, point pos, MenuItem item = null) + + Opens the menu at the specified position \a pos in the popups coordinate system, + that is, a coordinate relative to its \a parent item. + + The menu can be optionally aligned to a specific menu \a item. + + \sa Popup::open() +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::popup(real x, real y, MenuItem item = null) + \qmlmethod void QtQuick.Controls::Menu::popup(Item parent, real x, real y, MenuItem item = null) + + Opens the menu at the specified position \a x, \a y in the popups coordinate system, + that is, a coordinate relative to its \a parent item. + + The menu can be optionally aligned to a specific menu \a item. + + \sa dismiss(), Popup::open() +*/ +void QQuickMenu::popup(QQmlV4Function *args) +{ + Q_D(QQuickMenu); + const int len = args->length(); + if (len > 4) { + args->v4engine()->throwTypeError(); + return; + } + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + QQmlNullableValue<QPointF> pos; + QQuickItem *menuItem = nullptr; + QQuickItem *parentItem = nullptr; + + if (len > 0) { + // Item parent + QV4::ScopedValue firstArg(scope, (*args)[0]); + if (const QV4::QObjectWrapper *obj = firstArg->as<QV4::QObjectWrapper>()) { + QQuickItem *item = qobject_cast<QQuickItem *>(obj->object()); + if (item && !d->popupItem->isAncestorOf(item)) + parentItem = item; + } else if (firstArg->isUndefined()) { + resetParentItem(); + parentItem = d->parentItem; + } + + // MenuItem item + QV4::ScopedValue lastArg(scope, (*args)[len - 1]); + if (const QV4::QObjectWrapper *obj = lastArg->as<QV4::QObjectWrapper>()) { + QQuickItem *item = qobject_cast<QQuickItem *>(obj->object()); + if (item && d->popupItem->isAncestorOf(item)) + menuItem = item; + } + } + + if (len >= 3 || (!parentItem && len >= 2)) { + // real x, real y + QV4::ScopedValue xArg(scope, (*args)[parentItem ? 1 : 0]); + QV4::ScopedValue yArg(scope, (*args)[parentItem ? 2 : 1]); + if (xArg->isNumber() && yArg->isNumber()) + pos = QPointF(xArg->asDouble(), yArg->asDouble()); + } + + if (pos.isNull && (len >= 2 || (!parentItem && len >= 1))) { + // point pos + QV4::ScopedValue posArg(scope, (*args)[parentItem ? 1 : 0]); + const QVariant var = v4->toVariant(posArg, QMetaType {}); + if (var.userType() == QMetaType::QPointF) + pos = var.toPointF(); + } + + if (parentItem) + setParentItem(parentItem); + + if (pos.isNull) + popup(menuItem); + else + popup(pos, menuItem); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlmethod void QtQuick.Controls::Menu::dismiss() + + Closes all menus in the hierarchy that this menu belongs to. + + \note Unlike \l {Popup::}{close()} that only closes a menu and its sub-menus, + \c dismiss() closes the whole hierarchy of menus, including the parent menus. + In practice, \c close() is suitable e.g. for implementing navigation in a + hierarchy of menus, and \c dismiss() is the appropriate method for closing + the whole hierarchy of menus. + + \sa popup(), Popup::close() +*/ +void QQuickMenu::dismiss() +{ + QQuickMenu *menu = this; + while (menu) { + menu->close(); + menu = QQuickMenuPrivate::get(menu)->parentMenu; + } +} + +void QQuickMenu::componentComplete() +{ + Q_D(QQuickMenu); + QQuickPopup::componentComplete(); + d->resizeItems(); +} + +void QQuickMenu::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickMenu); + QQuickPopup::contentItemChange(newItem, oldItem); + + if (oldItem) { + QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Children); + QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Geometry); + } + if (newItem) { + QQuickItemPrivate::get(newItem)->addItemChangeListener(d, QQuickItemPrivate::Children); + QQuickItemPrivate::get(newItem)->updateOrAddGeometryChangeListener(d, QQuickGeometryChange::Width); + } + + d->contentItem = newItem; +} + +void QQuickMenu::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) +{ + Q_D(QQuickMenu); + QQuickPopup::itemChange(change, data); + + if (change == QQuickItem::ItemVisibleHasChanged) { + if (!data.boolValue && d->cascade) { + // Ensure that when the menu isn't visible, there's no current item + // the next time it's opened. + d->setCurrentIndex(-1, Qt::OtherFocusReason); + } + } +} + +void QQuickMenu::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickMenu); + QQuickPopup::keyPressEvent(event); + + // QTBUG-17051 + // Work around the fact that ListView has no way of distinguishing between + // mouse and keyboard interaction, thanks to the "interactive" bool in Flickable. + // What we actually want is to have a way to always allow keyboard interaction but + // only allow flicking with the mouse when there are too many menu items to be + // shown at once. + switch (event->key()) { + case Qt::Key_Up: + if (!d->activatePreviousItem()) + d->propagateKeyEvent(event); + break; + + case Qt::Key_Down: + d->activateNextItem(); + break; + + case Qt::Key_Left: + case Qt::Key_Right: + event->ignore(); + if (d->popupItem->isMirrored() == (event->key() == Qt::Key_Right)) { + if (d->parentMenu && d->currentItem) { + if (!d->cascade) + d->parentMenu->open(); + close(); + event->accept(); + } + } else { + if (QQuickMenu *subMenu = d->currentSubMenu()) { + auto subMenuPrivate = QQuickMenuPrivate::get(subMenu); + subMenu->popup(subMenuPrivate->firstEnabledMenuItem()); + event->accept(); + } + } + if (!event->isAccepted()) + d->propagateKeyEvent(event); + break; + +#if QT_CONFIG(shortcut) + case Qt::Key_Alt: + // If &mnemonic shortcut is enabled, go back to (possibly) the parent + // menu bar so the shortcut key will be processed by the menu bar. + if (!QKeySequence::mnemonic(QStringLiteral("&A")).isEmpty()) + close(); + break; +#endif + + default: + break; + } +} + +void QQuickMenu::timerEvent(QTimerEvent *event) +{ + Q_D(QQuickMenu); + if (event->timerId() == d->hoverTimer) { + if (QQuickMenu *subMenu = d->currentSubMenu()) + subMenu->open(); + d->stopHoverTimer(); + return; + } + QQuickPopup::timerEvent(event); +} + +QFont QQuickMenu::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::Menu); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickMenu::accessibleRole() const +{ + return QAccessible::PopupMenu; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickmenu_p.cpp" diff --git a/src/quicktemplates2/qquickmenu_p.h b/src/quicktemplates2/qquickmenu_p.h new file mode 100644 index 0000000000..4a690f9c8c --- /dev/null +++ b/src/quicktemplates2/qquickmenu_p.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENU_P_H +#define QQUICKMENU_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/qqmllist.h> +#include <QtQml/qqml.h> + +#include "qquickpopup_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickAction; +class QQmlComponent; +class QQuickMenuItem; +class QQuickMenuPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenu : public QQuickPopup +{ + Q_OBJECT + Q_PROPERTY(QVariant contentModel READ contentModel CONSTANT FINAL) + Q_PROPERTY(QQmlListProperty<QObject> contentData READ contentData FINAL) + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) + // 2.3 (Qt 5.10) + Q_PROPERTY(int count READ count NOTIFY countChanged FINAL REVISION(2, 3)) + Q_PROPERTY(bool cascade READ cascade WRITE setCascade RESET resetCascade NOTIFY cascadeChanged FINAL REVISION(2, 3)) + Q_PROPERTY(qreal overlap READ overlap WRITE setOverlap NOTIFY overlapChanged FINAL REVISION(2, 3)) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL REVISION(2, 3)) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL REVISION(2, 3)) + Q_CLASSINFO("DefaultProperty", "contentData") + QML_NAMED_ELEMENT(Menu) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickMenu(QObject *parent = nullptr); + ~QQuickMenu(); + + Q_INVOKABLE QQuickItem *itemAt(int index) const; + Q_INVOKABLE void addItem(QQuickItem *item); + Q_INVOKABLE void insertItem(int index, QQuickItem *item); + Q_INVOKABLE void moveItem(int from, int to); + Q_INVOKABLE void removeItem(QQuickItem *item); + + QVariant contentModel() const; + QQmlListProperty<QObject> contentData(); + + QString title() const; + void setTitle(QString &title); + + bool cascade() const; + void setCascade(bool cascade); + void resetCascade(); + + qreal overlap() const; + void setOverlap(qreal overlap); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + int currentIndex() const; + void setCurrentIndex(int index); + + // 2.3 (Qt 5.10) + int count() const; + Q_REVISION(2, 3) Q_INVOKABLE QQuickItem *takeItem(int index); + + Q_REVISION(2, 3) Q_INVOKABLE QQuickMenu *menuAt(int index) const; + Q_REVISION(2, 3) Q_INVOKABLE void addMenu(QQuickMenu *menu); + Q_REVISION(2, 3) Q_INVOKABLE void insertMenu(int index, QQuickMenu *menu); + Q_REVISION(2, 3) Q_INVOKABLE void removeMenu(QQuickMenu *menu); + Q_REVISION(2, 3) Q_INVOKABLE QQuickMenu *takeMenu(int index); + + Q_REVISION(2, 3) Q_INVOKABLE QQuickAction *actionAt(int index) const; + Q_REVISION(2, 3) Q_INVOKABLE void addAction(QQuickAction *action); + Q_REVISION(2, 3) Q_INVOKABLE void insertAction(int index, QQuickAction *action); + Q_REVISION(2, 3) Q_INVOKABLE void removeAction(QQuickAction *action); + Q_REVISION(2, 3) Q_INVOKABLE QQuickAction *takeAction(int index); + + void popup(QQuickItem *menuItem = nullptr); + void popup(const QPointF &pos, QQuickItem *menuItem = nullptr); + + Q_REVISION(2, 3) Q_INVOKABLE void popup(QQmlV4Function *args); + Q_REVISION(2, 3) Q_INVOKABLE void dismiss(); + +protected: + void componentComplete() override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) override; + void keyPressEvent(QKeyEvent *event) override; + +Q_SIGNALS: + void titleChanged(const QString &title); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void countChanged(); + Q_REVISION(2, 3) void cascadeChanged(bool cascade); + Q_REVISION(2, 3) void overlapChanged(); + Q_REVISION(2, 3) void delegateChanged(); + Q_REVISION(2, 3) void currentIndexChanged(); + +protected: + void timerEvent(QTimerEvent *event) override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickMenu) + Q_DECLARE_PRIVATE(QQuickMenu) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickMenu) + +#endif // QQUICKMENU_P_H diff --git a/src/quicktemplates2/qquickmenu_p_p.h b/src/quicktemplates2/qquickmenu_p_p.h new file mode 100644 index 0000000000..86701d9bef --- /dev/null +++ b/src/quicktemplates2/qquickmenu_p_p.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENU_P_P_H +#define QQUICKMENU_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qlist.h> +#include <QtCore/qpointer.h> + +#include <QtQuickTemplates2/private/qquickmenu_p.h> +#include <QtQuickTemplates2/private/qquickpopup_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickAction; +class QQmlComponent; +class QQmlObjectModel; +class QQuickMenuItem; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuPrivate : public QQuickPopupPrivate +{ + Q_DECLARE_PUBLIC(QQuickMenu) + +public: + QQuickMenuPrivate(); + + static QQuickMenuPrivate *get(QQuickMenu *menu) + { + return menu->d_func(); + } + + void init(); + + QQuickItem *itemAt(int index) const; + void insertItem(int index, QQuickItem *item); + void moveItem(int from, int to); + void removeItem(int index, QQuickItem *item); + + QQuickItem *beginCreateItem(); + void completeCreateItem(); + + QQuickItem *createItem(QQuickMenu *menu); + QQuickItem *createItem(QQuickAction *action); + + void resizeItem(QQuickItem *item); + void resizeItems(); + + void itemChildAdded(QQuickItem *item, QQuickItem *child) override; + void itemSiblingOrderChanged(QQuickItem *item) override; + void itemParentChanged(QQuickItem *item, QQuickItem *parent) override; + void itemDestroyed(QQuickItem *item) override; + void itemGeometryChanged(QQuickItem *, QQuickGeometryChange change, const QRectF &diff) override; + + QQuickPopupPositioner *getPositioner() override; + bool prepareEnterTransition() override; + bool prepareExitTransition() override; + bool blockInput(QQuickItem *item, const QPointF &point) const override; + + void onItemHovered(); + void onItemTriggered(); + void onItemActiveFocusChanged(); + + QQuickMenu *currentSubMenu() const; + void setParentMenu(QQuickMenu *parent); + void resolveParentItem(); + + void propagateKeyEvent(QKeyEvent *event); + + void startHoverTimer(); + void stopHoverTimer(); + + void setCurrentIndex(int index, Qt::FocusReason reason); + bool activateNextItem(); + bool activatePreviousItem(); + + QQuickMenuItem *firstEnabledMenuItem() const; + + static void contentData_append(QQmlListProperty<QObject> *prop, QObject *obj); + static qsizetype contentData_count(QQmlListProperty<QObject> *prop); + static QObject *contentData_at(QQmlListProperty<QObject> *prop, qsizetype index); + static void contentData_clear(QQmlListProperty<QObject> *prop); + + QPalette defaultPalette() const override; + + bool cascade = false; + int hoverTimer = 0; + int currentIndex = -1; + qreal overlap = 0; + QPointer<QQuickMenu> parentMenu; + QPointer<QQuickMenuItem> currentItem; + QQuickItem *contentItem = nullptr; // TODO: cleanup + QList<QObject *> contentData; + QQmlObjectModel *contentModel; + QQmlComponent *delegate = nullptr; + QString title; +}; + +QT_END_NAMESPACE + +#endif // QQUICKMENU_P_P_H diff --git a/src/quicktemplates2/qquickmenubar.cpp b/src/quicktemplates2/qquickmenubar.cpp new file mode 100644 index 0000000000..a732d88931 --- /dev/null +++ b/src/quicktemplates2/qquickmenubar.cpp @@ -0,0 +1,581 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmenubar_p.h" +#include "qquickmenubar_p_p.h" +#include "qquickmenubaritem_p_p.h" +#include "qquickmenu_p.h" +#include "qquickmenu_p_p.h" + +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlengine.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MenuBar + \inherits Container +//! \instantiates QQuickMenuBar + \inqmlmodule QtQuick.Controls + \since 5.10 + \ingroup qtquickcontrols2-menus + \ingroup qtquickcontrols2-focusscopes + \brief Provides a window menu bar. + + \image qtquickcontrols2-menubar.png + + MenuBar consists of drop-down menus, and is normally located at the top + edge of the window. + + \quotefromfile qtquickcontrols2-menubar.qml + \skipuntil begin + \printto skipfrom + \skipuntil skipto + \printto end + + Typically, menus are statically declared as children of the menu bar, but + MenuBar also provides API to \l {addMenu}{add}, \l {insertMenu}{insert}, + \l {removeMenu}{remove}, and \l {takeMenu}{take} menus dynamically. The + menus in a menu bar can be accessed using \l menuAt(). + + \sa {Customizing MenuBar}, Menu, MenuBarItem, {Menu Controls}, + {Focus Management in Qt Quick Controls} +*/ + +QQuickItem *QQuickMenuBarPrivate::beginCreateItem(QQuickMenu *menu) +{ + Q_Q(QQuickMenuBar); + if (!delegate) + return nullptr; + + QQmlContext *creationContext = delegate->creationContext(); + if (!creationContext) + creationContext = qmlContext(q); + QQmlContext *context = new QQmlContext(creationContext, q); + context->setContextObject(q); + + QObject *object = delegate->beginCreate(context); + QQuickItem *item = qobject_cast<QQuickItem *>(object); + if (!item) { + delete object; + delete context; + return nullptr; + } + + if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(item)) + menuBarItem->setMenu(menu); + item->setParentItem(q); + QQml_setParent_noEvent(item, q); + + return item; +} + +void QQuickMenuBarPrivate::completeCreateItem() +{ + if (!delegate) + return; + + delegate->completeCreate(); +} + +QQuickItem *QQuickMenuBarPrivate::createItem(QQuickMenu *menu) +{ + QQuickItem *item = beginCreateItem(menu); + completeCreateItem(); + return item; +} + +void QQuickMenuBarPrivate::toggleCurrentMenu(bool visible, bool activate) +{ + if (!currentItem || visible == popupMode) + return; + + QQuickMenu *menu = currentItem->menu(); + + triggering = true; + popupMode = visible; + if (menu) + menu->setVisible(visible); + if (!visible) + currentItem->forceActiveFocus(); + else if (menu && activate) + menu->setCurrentIndex(0); + triggering = false; +} + +void QQuickMenuBarPrivate::activateItem(QQuickMenuBarItem *item) +{ + if (currentItem == item) + return; + + if (currentItem) { + currentItem->setHighlighted(false); + if (popupMode) { + if (QQuickMenu *menu = currentItem->menu()) + menu->dismiss(); + } + } + + if (item) { + item->setHighlighted(true); + if (popupMode) { + if (QQuickMenu *menu = item->menu()) + menu->open(); + } + } + + currentItem = item; +} + +void QQuickMenuBarPrivate::activateNextItem() +{ + int index = currentItem ? contentModel->indexOf(currentItem, nullptr) : -1; + if (index >= contentModel->count() - 1) + index = -1; + activateItem(qobject_cast<QQuickMenuBarItem *>(itemAt(++index))); +} + +void QQuickMenuBarPrivate::activatePreviousItem() +{ + int index = currentItem ? contentModel->indexOf(currentItem, nullptr) : contentModel->count(); + if (index <= 0) + index = contentModel->count(); + activateItem(qobject_cast<QQuickMenuBarItem *>(itemAt(--index))); +} + +void QQuickMenuBarPrivate::onItemHovered() +{ + Q_Q(QQuickMenuBar); + QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(q->sender()); + if (!item || item == currentItem || !item->isHovered() || !item->isEnabled() || QQuickMenuBarItemPrivate::get(item)->touchId != -1) + return; + + activateItem(item); +} + +void QQuickMenuBarPrivate::onItemTriggered() +{ + Q_Q(QQuickMenuBar); + QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(q->sender()); + if (!item) + return; + + if (item == currentItem) { + toggleCurrentMenu(!popupMode, false); + } else { + popupMode = true; + activateItem(item); + } +} + +void QQuickMenuBarPrivate::onMenuAboutToHide() +{ + if (triggering || !currentItem || (currentItem->isHovered() && currentItem->isEnabled()) || !currentItem->isHighlighted()) + return; + + popupMode = false; + activateItem(nullptr); +} + +qreal QQuickMenuBarPrivate::getContentWidth() const +{ + Q_Q(const QQuickMenuBar); + const int count = contentModel->count(); + qreal totalWidth = qMax(0, count - 1) * spacing; + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) + totalWidth += item->implicitWidth(); + } + return totalWidth; +} + +qreal QQuickMenuBarPrivate::getContentHeight() const +{ + Q_Q(const QQuickMenuBar); + const int count = contentModel->count(); + qreal maxHeight = 0; + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) + maxHeight = qMax(maxHeight, item->implicitHeight()); + } + return maxHeight; +} + +void QQuickMenuBarPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + QQuickContainerPrivate::itemImplicitWidthChanged(item); + if (item != contentItem) + updateImplicitContentWidth(); +} + +void QQuickMenuBarPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + QQuickContainerPrivate::itemImplicitHeightChanged(item); + if (item != contentItem) + updateImplicitContentHeight(); +} + +void QQuickMenuBarPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) +{ + QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object); + if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(obj)) + obj = QQuickMenuBarPrivate::get(menuBar)->createItem(menu); + QQuickContainerPrivate::contentData_append(prop, obj); +} + +void QQuickMenuBarPrivate::menus_append(QQmlListProperty<QQuickMenu> *prop, QQuickMenu *obj) +{ + QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object); + menuBar->addMenu(obj); +} + +qsizetype QQuickMenuBarPrivate::menus_count(QQmlListProperty<QQuickMenu> *prop) +{ + QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object); + return menuBar->count(); +} + +QQuickMenu *QQuickMenuBarPrivate::menus_at(QQmlListProperty<QQuickMenu> *prop, qsizetype index) +{ + QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object); + return menuBar->menuAt(index); +} + +void QQuickMenuBarPrivate::menus_clear(QQmlListProperty<QQuickMenu> *prop) +{ + QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object); + QQuickMenuBarPrivate::get(menuBar)->contentModel->clear(); +} + +QPalette QQuickMenuBarPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::MenuBar); +} + +QQuickMenuBar::QQuickMenuBar(QQuickItem *parent) + : QQuickContainer(*(new QQuickMenuBarPrivate), parent) +{ + Q_D(QQuickMenuBar); + d->changeTypes |= QQuickItemPrivate::Geometry; + setFlag(ItemIsFocusScope); + setFocusPolicy(Qt::ClickFocus); +} + +/*! + \qmlproperty Component QtQuick.Controls::MenuBar::delegate + + This property holds the component that is used to create menu bar + items to present menus in the menu bar. + + \sa MenuBarItem +*/ +QQmlComponent *QQuickMenuBar::delegate() const +{ + Q_D(const QQuickMenuBar); + return d->delegate; +} + +void QQuickMenuBar::setDelegate(QQmlComponent *delegate) +{ + Q_D(QQuickMenuBar); + if (d->delegate == delegate) + return; + + d->delegate = delegate; + emit delegateChanged(); +} + +/*! + \qmlmethod Menu QtQuick.Controls::MenuBar::menuAt(int index) + + Returns the menu at \a index, or \c null if it does not exist. +*/ +QQuickMenu *QQuickMenuBar::menuAt(int index) const +{ + Q_D(const QQuickMenuBar); + QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(d->itemAt(index)); + if (!item) + return nullptr; + return item->menu(); +} + +/*! + \qmlmethod void QtQuick.Controls::MenuBar::addMenu(Menu menu) + + Adds \a menu to the end of the list of menus. +*/ +void QQuickMenuBar::addMenu(QQuickMenu *menu) +{ + Q_D(QQuickMenuBar); + addItem(d->createItem(menu)); +} + +/*! + \qmlmethod void QtQuick.Controls::MenuBar::insertMenu(int index, Menu menu) + + Inserts \a menu at \a index. +*/ +void QQuickMenuBar::insertMenu(int index, QQuickMenu *menu) +{ + Q_D(QQuickMenuBar); + insertItem(index, d->createItem(menu)); +} + +/*! + \qmlmethod void QtQuick.Controls::MenuBar::removeMenu(Menu menu) + + Removes and destroys the specified \a menu. +*/ +void QQuickMenuBar::removeMenu(QQuickMenu *menu) +{ + Q_D(QQuickMenuBar); + if (!menu) + return; + + const int count = d->contentModel->count(); + for (int i = 0; i < count; ++i) { + QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(itemAt(i)); + if (!item || item->menu() != menu) + continue; + + removeItem(item); + break; + } + + menu->deleteLater(); +} + +/*! + \qmlmethod Menu QtQuick.Controls::MenuBar::takeMenu(int index) + + Removes and returns the menu at \a index. + + \note The ownership of the item is transferred to the caller. +*/ +QQuickMenu *QQuickMenuBar::takeMenu(int index) +{ + Q_D(QQuickMenuBar); + QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(itemAt(index)); + if (!item) + return nullptr; + + QQuickMenu *menu = item->menu(); + if (!menu) + return nullptr; + + d->removeItem(index, item); + item->deleteLater(); + return menu; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty real QtQuick.Controls::MenuBar::contentWidth + + This property holds the content width. It is used for calculating the total + implicit width of the menu bar. + + \note This property is available in MenuBar since QtQuick.Controls 2.3 (Qt 5.10), + but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12). + + \sa Container::contentWidth +*/ + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty real QtQuick.Controls::MenuBar::contentHeight + + This property holds the content height. It is used for calculating the total + implicit height of the menu bar. + + \note This property is available in MenuBar since QtQuick.Controls 2.3 (Qt 5.10), + but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12). + + \sa Container::contentHeight +*/ + +/*! + \qmlproperty list<Menu> QtQuick.Controls::MenuBar::menus + + This property holds the list of menus. + + The list contains all menus that have been declared in QML as children + of the menu bar, and also menus that have been dynamically added or + inserted using the \l addMenu() and \l insertMenu() methods, respectively. +*/ +QQmlListProperty<QQuickMenu> QQuickMenuBarPrivate::menus() +{ + Q_Q(QQuickMenuBar); + return QQmlListProperty<QQuickMenu>(q, nullptr, + QQuickMenuBarPrivate::menus_append, + QQuickMenuBarPrivate::menus_count, + QQuickMenuBarPrivate::menus_at, + QQuickMenuBarPrivate::menus_clear); +} + +QQmlListProperty<QObject> QQuickMenuBarPrivate::contentData() +{ + Q_Q(QQuickMenuBar); + return QQmlListProperty<QObject>(q, nullptr, + QQuickMenuBarPrivate::contentData_append, + QQuickContainerPrivate::contentData_count, + QQuickContainerPrivate::contentData_at, + QQuickContainerPrivate::contentData_clear); +} + +bool QQuickMenuBar::eventFilter(QObject *object, QEvent *event) +{ + return QObject::eventFilter(object, event); +} + +void QQuickMenuBar::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickMenuBar); + QQuickContainer::keyReleaseEvent(event); + + switch (event->key()) { + case Qt::Key_Up: + d->toggleCurrentMenu(false, false); + break; + + case Qt::Key_Down: + d->toggleCurrentMenu(true, true); + break; + + case Qt::Key_Left: + case Qt::Key_Right: + if (isMirrored() == (event->key() == Qt::Key_Left)) + d->activateNextItem(); + else + d->activatePreviousItem(); + break; + case Qt::Key_Escape: + if (d->currentItem) { + d->activateItem(nullptr); + setFocus(false); + } + break; + default: + break; + } +} + +void QQuickMenuBar::keyReleaseEvent(QKeyEvent *event) +{ + QQuickContainer::keyReleaseEvent(event); + + switch (event->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Escape: + event->accept(); + break; + + default: + event->ignore(); + break; + } +} + +void QQuickMenuBar::hoverLeaveEvent(QHoverEvent *event) +{ + Q_D(QQuickMenuBar); + QQuickContainer::hoverLeaveEvent(event); + if (!d->popupMode && d->currentItem) + d->activateItem(nullptr); +} + +bool QQuickMenuBar::isContent(QQuickItem *item) const +{ + return qobject_cast<QQuickMenuBarItem *>(item); +} + +void QQuickMenuBar::itemAdded(int index, QQuickItem *item) +{ + Q_D(QQuickMenuBar); + QQuickContainer::itemAdded(index, item); + if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(item)) { + QQuickMenuBarItemPrivate::get(menuBarItem)->setMenuBar(this); + QObjectPrivate::connect(menuBarItem, &QQuickControl::hoveredChanged, d, &QQuickMenuBarPrivate::onItemHovered); + QObjectPrivate::connect(menuBarItem, &QQuickMenuBarItem::triggered, d, &QQuickMenuBarPrivate::onItemTriggered); + if (QQuickMenu *menu = menuBarItem->menu()) + QObjectPrivate::connect(menu, &QQuickPopup::aboutToHide, d, &QQuickMenuBarPrivate::onMenuAboutToHide); + } + d->updateImplicitContentSize(); + emit menusChanged(); +} + +void QQuickMenuBar::itemMoved(int index, QQuickItem *item) +{ + QQuickContainer::itemMoved(index, item); + emit menusChanged(); +} + +void QQuickMenuBar::itemRemoved(int index, QQuickItem *item) +{ + Q_D(QQuickMenuBar); + QQuickContainer::itemRemoved(index, item); + if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(item)) { + QQuickMenuBarItemPrivate::get(menuBarItem)->setMenuBar(nullptr); + QObjectPrivate::disconnect(menuBarItem, &QQuickControl::hoveredChanged, d, &QQuickMenuBarPrivate::onItemHovered); + QObjectPrivate::disconnect(menuBarItem, &QQuickMenuBarItem::triggered, d, &QQuickMenuBarPrivate::onItemTriggered); + if (QQuickMenu *menu = menuBarItem->menu()) + QObjectPrivate::disconnect(menu, &QQuickPopup::aboutToHide, d, &QQuickMenuBarPrivate::onMenuAboutToHide); + } + d->updateImplicitContentSize(); + emit menusChanged(); +} + +QFont QQuickMenuBar::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::MenuBar); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickMenuBar::accessibleRole() const +{ + return QAccessible::MenuBar; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickmenubar_p.cpp" diff --git a/src/quicktemplates2/qquickmenubar_p.h b/src/quicktemplates2/qquickmenubar_p.h new file mode 100644 index 0000000000..5eb5dabfdd --- /dev/null +++ b/src/quicktemplates2/qquickmenubar_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENUBAR_P_H +#define QQUICKMENUBAR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickMenu; +class QQuickMenuBarPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuBar : public QQuickContainer +{ + Q_OBJECT + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) + Q_PRIVATE_PROPERTY(QQuickMenuBar::d_func(), QQmlListProperty<QQuickMenu> menus READ menus NOTIFY menusChanged FINAL) + Q_PRIVATE_PROPERTY(QQuickMenuBar::d_func(), QQmlListProperty<QObject> contentData READ contentData FINAL) + QML_NAMED_ELEMENT(MenuBar) + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickMenuBar(QQuickItem *parent = nullptr); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + Q_INVOKABLE QQuickMenu *menuAt(int index) const; + Q_INVOKABLE void addMenu(QQuickMenu *menu); + Q_INVOKABLE void insertMenu(int index, QQuickMenu *menu); + Q_INVOKABLE void removeMenu(QQuickMenu *menu); + Q_INVOKABLE QQuickMenu *takeMenu(int index); + +Q_SIGNALS: + void delegateChanged(); + void menusChanged(); + +protected: + bool eventFilter(QObject *object, QEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; + + bool isContent(QQuickItem *item) const override; + void itemAdded(int index, QQuickItem *item) override; + void itemMoved(int index, QQuickItem *item) override; + void itemRemoved(int index, QQuickItem *item) override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickMenuBar) + Q_DECLARE_PRIVATE(QQuickMenuBar) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickMenuBar) + +#endif // QQUICKMENUBAR_P_H diff --git a/src/quicktemplates2/qquickmenubar_p_p.h b/src/quicktemplates2/qquickmenubar_p_p.h new file mode 100644 index 0000000000..0562d7e5dc --- /dev/null +++ b/src/quicktemplates2/qquickmenubar_p_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENUBAR_P_P_H +#define QQUICKMENUBAR_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickmenubar_p.h> +#include <QtQuickTemplates2/private/qquickcontainer_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlComponent; +class QQuickMenuBarItem; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuBarPrivate : public QQuickContainerPrivate +{ + Q_DECLARE_PUBLIC(QQuickMenuBar) + +public: + static QQuickMenuBarPrivate *get(QQuickMenuBar *menuBar) + { + return menuBar->d_func(); + } + + QQmlListProperty<QQuickMenu> menus(); + QQmlListProperty<QObject> contentData(); + + QQuickItem *beginCreateItem(QQuickMenu *menu); + void completeCreateItem(); + + QQuickItem *createItem(QQuickMenu *menu); + + void toggleCurrentMenu(bool visible, bool activate); + void activateItem(QQuickMenuBarItem *item); + void activateNextItem(); + void activatePreviousItem(); + + void onItemHovered(); + void onItemTriggered(); + void onMenuAboutToHide(); + + qreal getContentWidth() const override; + qreal getContentHeight() const override; + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + static void contentData_append(QQmlListProperty<QObject> *prop, QObject *obj); + + static void menus_append(QQmlListProperty<QQuickMenu> *prop, QQuickMenu *obj); + static qsizetype menus_count(QQmlListProperty<QQuickMenu> *prop); + static QQuickMenu *menus_at(QQmlListProperty<QQuickMenu> *prop, qsizetype index); + static void menus_clear(QQmlListProperty<QQuickMenu> *prop); + + QPalette defaultPalette() const override; + + bool popupMode = false; + bool triggering = false; + QQmlComponent *delegate = nullptr; + QPointer<QQuickMenuBarItem> currentItem; +}; + +QT_END_NAMESPACE + +#endif // QQUICKMENUBAR_P_P_H diff --git a/src/quicktemplates2/qquickmenubaritem.cpp b/src/quicktemplates2/qquickmenubaritem.cpp new file mode 100644 index 0000000000..de43adb6a3 --- /dev/null +++ b/src/quicktemplates2/qquickmenubaritem.cpp @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmenubaritem_p.h" +#include "qquickmenubaritem_p_p.h" +#include "qquickmenubar_p.h" +#include "qquickmenu_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MenuBarItem + \inherits AbstractButton +//! \instantiates QQuickMenuBarItem + \inqmlmodule QtQuick.Controls + \since 5.10 + \ingroup qtquickcontrols2-menus + \brief Presents a drop-down menu within a MenuBar. + + MenuBarItem presents a Menu within a MenuBar. The respective drop-down menu + is shown when a MenuBarItem is \l triggered via keyboard, mouse, or touch. + + \image qtquickcontrols2-menubar.png + + MenuBarItem is used as a default \l {MenuBar::}{delegate} type for MenuBar. + Notice that it is not necessary to declare MenuBarItem instances by hand when + using MenuBar. It is sufficient to declare Menu instances as children of the + MenuBar and the respective items are created automatically. + + \sa {Customizing MenuBar}, MenuBar, {Menu Controls} +*/ + +/*! + \qmlsignal void QtQuick.Controls::MenuBarItem::triggered() + + This signal is emitted when the menu bar item is triggered by the user. +*/ + +void QQuickMenuBarItemPrivate::setMenuBar(QQuickMenuBar *newMenuBar) +{ + Q_Q(QQuickMenuBarItem); + if (menuBar == newMenuBar) + return; + + menuBar = newMenuBar; + emit q->menuBarChanged(); +} + +QPalette QQuickMenuBarItemPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::MenuBar); +} + +QQuickMenuBarItem::QQuickMenuBarItem(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickMenuBarItemPrivate), parent) +{ + setFocusPolicy(Qt::NoFocus); + connect(this, &QQuickAbstractButton::clicked, this, &QQuickMenuBarItem::triggered); +} + +/*! + \qmlproperty Menu QtQuick.Controls::MenuBarItem::menuBar + \readonly + + This property holds the menu bar that contains this item, + or \c null if the item is not in a menu bar. +*/ +QQuickMenuBar *QQuickMenuBarItem::menuBar() const +{ + Q_D(const QQuickMenuBarItem); + return d->menuBar; +} + +/*! + \qmlproperty Menu QtQuick.Controls::MenuBarItem::menu + + This property holds the menu that this item presents in a + menu bar, or \c null if this item does not have a menu. +*/ +QQuickMenu *QQuickMenuBarItem::menu() const +{ + Q_D(const QQuickMenuBarItem); + return d->menu; +} + +void QQuickMenuBarItem::setMenu(QQuickMenu *menu) +{ + Q_D(QQuickMenuBarItem); + if (d->menu == menu) + return; + + if (d->menu) + disconnect(d->menu, &QQuickMenu::titleChanged, this, &QQuickAbstractButton::setText); + + if (menu) { + setText(menu->title()); + menu->setY(height()); + menu->setParentItem(this); + menu->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent | QQuickPopup::CloseOnReleaseOutsideParent); + connect(menu, &QQuickMenu::titleChanged, this, &QQuickAbstractButton::setText); + } + + d->menu = menu; + emit menuChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::MenuBarItem::highlighted + + This property holds whether the menu bar item is highlighted by the user. + + A menu bar item can be highlighted by mouse hover or keyboard navigation. + + The default value is \c false. +*/ +bool QQuickMenuBarItem::isHighlighted() const +{ + Q_D(const QQuickMenuBarItem); + return d->highlighted; +} + +void QQuickMenuBarItem::setHighlighted(bool highlighted) +{ + Q_D(QQuickMenuBarItem); + if (highlighted == d->highlighted) + return; + + d->highlighted = highlighted; + emit highlightedChanged(); +} + +void QQuickMenuBarItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickMenuBarItem); + QQuickAbstractButton::geometryChange(newGeometry, oldGeometry); + if (d->menu) + d->menu->setY(newGeometry.height()); +} + +QFont QQuickMenuBarItem::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::MenuBar); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickMenuBarItem::accessibleRole() const +{ + return QAccessible::MenuBar; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickmenubaritem_p.cpp" diff --git a/src/quicktemplates2/qquickmenubaritem_p.h b/src/quicktemplates2/qquickmenubaritem_p.h new file mode 100644 index 0000000000..a913f242bb --- /dev/null +++ b/src/quicktemplates2/qquickmenubaritem_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENUBARITEM_P_H +#define QQUICKMENUBARITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickMenu; +class QQuickMenuBar; +class QQuickMenuBarItemPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuBarItem : public QQuickAbstractButton +{ + Q_OBJECT + Q_PROPERTY(QQuickMenuBar *menuBar READ menuBar NOTIFY menuBarChanged FINAL) + Q_PROPERTY(QQuickMenu *menu READ menu WRITE setMenu NOTIFY menuChanged FINAL) + Q_PROPERTY(bool highlighted READ isHighlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL) + QML_NAMED_ELEMENT(MenuBarItem) + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickMenuBarItem(QQuickItem *parent = nullptr); + + QQuickMenuBar *menuBar() const; + + QQuickMenu *menu() const; + void setMenu(QQuickMenu *menu); + + bool isHighlighted() const; + void setHighlighted(bool highlighted); + +Q_SIGNALS: + void triggered(); + void menuBarChanged(); + void menuChanged(); + void highlightedChanged(); + +protected: + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickMenuBarItem) + Q_DECLARE_PRIVATE(QQuickMenuBarItem) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickMenuBarItem) + +#endif // QQUICKMENUBARITEM_P_H diff --git a/src/quicktemplates2/qquickmenubaritem_p_p.h b/src/quicktemplates2/qquickmenubaritem_p_p.h new file mode 100644 index 0000000000..22c509dcfb --- /dev/null +++ b/src/quicktemplates2/qquickmenubaritem_p_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENUBARITEM_P_P_H +#define QQUICKMENUBARITEM_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickmenubaritem_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickMenu; +class QQuickMenuBar; + +class QQuickMenuBarItemPrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickMenuBarItem) + +public: + static QQuickMenuBarItemPrivate *get(QQuickMenuBarItem *item) + { + return item->d_func(); + } + + void setMenuBar(QQuickMenuBar *menuBar); + + QPalette defaultPalette() const override; + + bool highlighted = false; + QQuickMenu *menu = nullptr; + QQuickMenuBar *menuBar = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKMENUBARITEM_P_P_H diff --git a/src/quicktemplates2/qquickmenuitem.cpp b/src/quicktemplates2/qquickmenuitem.cpp new file mode 100644 index 0000000000..559024416b --- /dev/null +++ b/src/quicktemplates2/qquickmenuitem.cpp @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmenuitem_p.h" +#include "qquickmenuitem_p_p.h" +#include "qquickmenu_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQuick/private/qquickevents_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MenuItem + \inherits AbstractButton +//! \instantiates QQuickMenuItem + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-menus + \brief Presents an item within a Menu. + + MenuItem is a convenience type that implements the AbstractButton API, + providing a familiar way to respond to menu items being \l triggered, for + example. + + MenuItem inherits its API from AbstractButton. For instance, you can set + \l {AbstractButton::text}{text} and \l {Icons in Qt Quick Controls}{icon} + using the AbstractButton API. + + \code + Button { + id: fileButton + text: "File" + onClicked: menu.open() + + Menu { + id: menu + + MenuItem { + text: "New..." + onTriggered: document.reset() + } + MenuItem { + text: "Open..." + onTriggered: openDialog.open() + } + MenuItem { + text: "Save" + onTriggered: saveDialog.open() + } + } + } + \endcode + + \sa {Customizing Menu}, Menu, {Menu Controls} +*/ + +void QQuickMenuItemPrivate::setMenu(QQuickMenu *newMenu) +{ + Q_Q(QQuickMenuItem); + if (menu == newMenu) + return; + + menu = newMenu; + emit q->menuChanged(); +} + +void QQuickMenuItemPrivate::setSubMenu(QQuickMenu *newSubMenu) +{ + Q_Q(QQuickMenuItem); + if (subMenu == newSubMenu) + return; + + if (subMenu) { + QObject::disconnect(subMenu, &QQuickMenu::titleChanged, q, &QQuickAbstractButton::setText); + QObjectPrivate::disconnect(subMenu, &QQuickPopup::enabledChanged, this, &QQuickMenuItemPrivate::updateEnabled); + } + + if (newSubMenu) { + QObject::connect(newSubMenu, &QQuickMenu::titleChanged, q, &QQuickAbstractButton::setText); + QObjectPrivate::connect(newSubMenu, &QQuickPopup::enabledChanged, this, &QQuickMenuItemPrivate::updateEnabled); + q->setText(newSubMenu->title()); + } + + subMenu = newSubMenu; + updateEnabled(); + emit q->subMenuChanged(); +} + +void QQuickMenuItemPrivate::updateEnabled() +{ + Q_Q(QQuickMenuItem); + q->setEnabled(subMenu && subMenu->isEnabled()); +} + +static inline QString arrowName() { return QStringLiteral("arrow"); } + +void QQuickMenuItemPrivate::cancelArrow() +{ + Q_Q(QQuickAbstractButton); + quickCancelDeferred(q, arrowName()); +} + +void QQuickMenuItemPrivate::executeArrow(bool complete) +{ + Q_Q(QQuickMenuItem); + if (arrow.wasExecuted()) + return; + + if (!arrow || complete) + quickBeginDeferred(q, arrowName(), arrow); + if (complete) + quickCompleteDeferred(q, arrowName(), arrow); +} + +bool QQuickMenuItemPrivate::acceptKeyClick(Qt::Key key) const +{ + return key == Qt::Key_Space || key == Qt::Key_Return || key == Qt::Key_Enter; +} + +QPalette QQuickMenuItemPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::Menu); +} + +/*! + \qmlsignal void QtQuick.Controls::MenuItem::triggered() + + This signal is emitted when the menu item is triggered by the user. +*/ + +QQuickMenuItem::QQuickMenuItem(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickMenuItemPrivate), parent) +{ + connect(this, &QQuickAbstractButton::clicked, this, &QQuickMenuItem::triggered); +} + +/*! + \qmlproperty bool QtQuick.Controls::MenuItem::highlighted + + This property holds whether the menu item is highlighted by the user. + + A menu item can be highlighted by mouse hover or keyboard navigation. + + The default value is \c false. + + \sa Menu::currentIndex +*/ +bool QQuickMenuItem::isHighlighted() const +{ + Q_D(const QQuickMenuItem); + return d->highlighted; +} + +void QQuickMenuItem::setHighlighted(bool highlighted) +{ + Q_D(QQuickMenuItem); + if (highlighted == d->highlighted) + return; + + d->highlighted = highlighted; + emit highlightedChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty Item QtQuick.Controls::MenuItem::arrow + + This property holds the sub-menu arrow item. + + \sa {Customizing Menu} +*/ +QQuickItem *QQuickMenuItem::arrow() const +{ + QQuickMenuItemPrivate *d = const_cast<QQuickMenuItemPrivate *>(d_func()); + if (!d->arrow) + d->executeArrow(); + return d->arrow; +} + +void QQuickMenuItem::setArrow(QQuickItem *arrow) +{ + Q_D(QQuickMenuItem); + if (d->arrow == arrow) + return; + + if (!d->arrow.isExecuting()) + d->cancelArrow(); + + QQuickControlPrivate::hideOldItem(d->arrow); + d->arrow = arrow; + if (arrow && !arrow->parentItem()) + arrow->setParentItem(this); + if (!d->arrow.isExecuting()) + emit arrowChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty Menu QtQuick.Controls::MenuItem::menu + \readonly + + This property holds the menu that contains this menu item, + or \c null if the item is not in a menu. +*/ +QQuickMenu *QQuickMenuItem::menu() const +{ + Q_D(const QQuickMenuItem); + return d->menu; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty Menu QtQuick.Controls::MenuItem::subMenu + \readonly + + This property holds the sub-menu that this item presents in + the parent menu, or \c null if this item is not a sub-menu item. +*/ +QQuickMenu *QQuickMenuItem::subMenu() const +{ + Q_D(const QQuickMenuItem); + return d->subMenu; +} + +void QQuickMenuItem::componentComplete() +{ + Q_D(QQuickMenuItem); + d->executeArrow(true); + QQuickAbstractButton::componentComplete(); +} + +QFont QQuickMenuItem::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::Menu); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickMenuItem::accessibleRole() const +{ + return QAccessible::MenuItem; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickmenuitem_p.cpp" diff --git a/src/quicktemplates2/qquickmenuitem_p.h b/src/quicktemplates2/qquickmenuitem_p.h new file mode 100644 index 0000000000..817da76bba --- /dev/null +++ b/src/quicktemplates2/qquickmenuitem_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENUITEM_P_H +#define QQUICKMENUITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickMenu; +class QQuickMenuItemPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuItem : public QQuickAbstractButton +{ + Q_OBJECT + Q_PROPERTY(bool highlighted READ isHighlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL) + // 2.3 (Qt 5.10) + Q_PROPERTY(QQuickItem *arrow READ arrow WRITE setArrow NOTIFY arrowChanged FINAL REVISION(2, 3)) + Q_PROPERTY(QQuickMenu *menu READ menu NOTIFY menuChanged FINAL REVISION(2, 3)) + Q_PROPERTY(QQuickMenu *subMenu READ subMenu NOTIFY subMenuChanged FINAL REVISION(2, 3)) + Q_CLASSINFO("DeferredPropertyNames", "arrow,background,contentItem,indicator") + QML_NAMED_ELEMENT(MenuItem) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickMenuItem(QQuickItem *parent = nullptr); + + bool isHighlighted() const; + void setHighlighted(bool highlighted); + + // 2.3 (Qt 5.10) + QQuickItem *arrow() const; + void setArrow(QQuickItem *arrow); + + QQuickMenu *menu() const; + QQuickMenu *subMenu() const; + +Q_SIGNALS: + void triggered(); + void highlightedChanged(); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void arrowChanged(); + Q_REVISION(2, 3) void menuChanged(); + Q_REVISION(2, 3) void subMenuChanged(); + +protected: + void componentComplete() override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickMenuItem) + Q_DECLARE_PRIVATE(QQuickMenuItem) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickMenuItem) + +#endif // QQUICKMENUITEM_P_H diff --git a/src/quicktemplates2/qquickmenuitem_p_p.h b/src/quicktemplates2/qquickmenuitem_p_p.h new file mode 100644 index 0000000000..966e14e7e9 --- /dev/null +++ b/src/quicktemplates2/qquickmenuitem_p_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENUITEM_P_P_H +#define QQUICKMENUITEM_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickmenuitem_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickMenu; + +class QQuickMenuItemPrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickMenuItem) + +public: + static QQuickMenuItemPrivate *get(QQuickMenuItem *item) + { + return item->d_func(); + } + + void setMenu(QQuickMenu *menu); + void setSubMenu(QQuickMenu *subMenu); + + void updateEnabled(); + + void cancelArrow(); + void executeArrow(bool complete = false); + + bool acceptKeyClick(Qt::Key key) const override; + + QPalette defaultPalette() const override; + + bool highlighted = false; + QQuickDeferredPointer<QQuickItem> arrow; + QQuickMenu *menu = nullptr; + QQuickMenu *subMenu = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKMENUITEM_P_P_H diff --git a/src/quicktemplates2/qquickmenuseparator.cpp b/src/quicktemplates2/qquickmenuseparator.cpp new file mode 100644 index 0000000000..d646ed437d --- /dev/null +++ b/src/quicktemplates2/qquickmenuseparator.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmenuseparator_p.h" +#include "qquickcontrol_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MenuSeparator + \inherits Control +//! \instantiates QQuickMenuSeparator + \inqmlmodule QtQuick.Controls + \since 5.8 + \ingroup qtquickcontrols2-separators + \brief Separates a group of items in a menu from adjacent items. + + MenuSeparator is used to visually distinguish between groups of items in a + menu by separating them with a line. + + \image qtquickcontrols2-menuseparator.png + + \quotefromfile qtquickcontrols2-menuseparator-custom.qml + \skipto import QtQuick + \printuntil import QtQuick.Controls + \skipto Menu + \printto contentItem.parent: window + \skipline contentItem.parent: window + \printuntil text: qsTr("Exit") + \printuntil } + \printuntil } + + \sa {Customizing Menu}, Menu, {Separator Controls} +*/ + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuSeparatorPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickMenuSeparator) + +public: + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::Menu); } +}; + +QQuickMenuSeparator::QQuickMenuSeparator(QQuickItem *parent) + : QQuickControl(*(new QQuickMenuSeparatorPrivate), parent) +{ +} + +QFont QQuickMenuSeparator::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::Menu); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickMenuSeparator::accessibleRole() const +{ + return QAccessible::Separator; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickmenuseparator_p.cpp" diff --git a/src/quicktemplates2/qquickmenuseparator_p.h b/src/quicktemplates2/qquickmenuseparator_p.h new file mode 100644 index 0000000000..330aaac40b --- /dev/null +++ b/src/quicktemplates2/qquickmenuseparator_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKMENUSEPARATOR_P_H +#define QQUICKMENUSEPARATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQuickTemplates2/private/qquicktheme_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickMenuSeparator; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuSeparator : public QQuickControl +{ + Q_OBJECT + QML_NAMED_ELEMENT(MenuSeparator) + QML_ADDED_IN_VERSION(2, 1) + +public: + explicit QQuickMenuSeparator(QQuickItem *parent = nullptr); + +protected: + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickMenuSeparator) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickMenuSeparator) + +#endif // QQUICKMENUSEPARATOR_P_H diff --git a/src/quicktemplates2/qquickoverlay.cpp b/src/quicktemplates2/qquickoverlay.cpp new file mode 100644 index 0000000000..1829f9a30e --- /dev/null +++ b/src/quicktemplates2/qquickoverlay.cpp @@ -0,0 +1,763 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcontrol_p_p.h" +#include "qquickoverlay_p.h" +#include "qquickoverlay_p_p.h" +#include "qquickpopupitem_p_p.h" +#include "qquickpopup_p_p.h" +#include "qquickdrawer_p.h" +#include "qquickdrawer_p_p.h" +#include "qquickapplicationwindow_p.h" +#include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlproperty.h> +#include <QtQml/qqmlcomponent.h> +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Overlay + \inherits Item +//! \instantiates QQuickOverlay + \inqmlmodule QtQuick.Controls + \since 5.10 + \brief A window overlay for popups. + + Overlay provides a layer for popups, ensuring that popups are displayed above + other content and that the background is dimmed when a \l {Popup::}{modal} or + \l {Popup::dim}{dimmed} popup is visible. + + The overlay is an ordinary Item that covers the entire window. It can be used + as a visual parent to position a popup in scene coordinates. + + \include qquickoverlay-popup-parent.qdocinc + + \sa ApplicationWindow +*/ + +QList<QQuickPopup *> QQuickOverlayPrivate::stackingOrderPopups() const +{ + const QList<QQuickItem *> children = paintOrderChildItems(); + + QList<QQuickPopup *> popups; + popups.reserve(children.count()); + + for (auto it = children.crbegin(), end = children.crend(); it != end; ++it) { + QQuickPopup *popup = qobject_cast<QQuickPopup *>((*it)->parent()); + if (popup) + popups += popup; + } + + return popups; +} + +QList<QQuickDrawer *> QQuickOverlayPrivate::stackingOrderDrawers() const +{ + QList<QQuickDrawer *> sorted(allDrawers); + std::sort(sorted.begin(), sorted.end(), [](const QQuickDrawer *one, const QQuickDrawer *another) { + return one->z() > another->z(); + }); + return sorted; +} + +void QQuickOverlayPrivate::itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &) +{ + updateGeometry(); +} + +bool QQuickOverlayPrivate::startDrag(QEvent *event, const QPointF &pos) +{ + Q_Q(QQuickOverlay); + if (allDrawers.isEmpty()) + return false; + + // don't start dragging a drawer if a modal popup overlay is blocking (QTBUG-60602) + QQuickItem *item = q->childAt(pos.x(), pos.y()); + if (item) { + const auto popups = stackingOrderPopups(); + for (QQuickPopup *popup : popups) { + QQuickPopupPrivate *p = QQuickPopupPrivate::get(popup); + if (p->dimmer == item && popup->isVisible() && popup->isModal()) + return false; + } + } + + const QList<QQuickDrawer *> drawers = stackingOrderDrawers(); + for (QQuickDrawer *drawer : drawers) { + QQuickDrawerPrivate *p = QQuickDrawerPrivate::get(drawer); + if (p->startDrag(event)) { + setMouseGrabberPopup(drawer); + return true; + } + } + + return false; +} + +bool QQuickOverlayPrivate::handlePress(QQuickItem *source, QEvent *event, QQuickPopup *target) +{ + if (target) { + if (target->overlayEvent(source, event)) { + setMouseGrabberPopup(target); + return true; + } + return false; + } + + switch (event->type()) { + default: { + if (mouseGrabberPopup) + break; +#if QT_CONFIG(quicktemplates2_multitouch) + Q_FALLTHROUGH(); + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: +#endif + // allow non-modal popups to close themselves, + // and non-dimming modal popups to block the event + const auto popups = stackingOrderPopups(); + for (QQuickPopup *popup : popups) { + if (popup->overlayEvent(source, event)) { + setMouseGrabberPopup(popup); + return true; + } + } + break; + } + } + + event->ignore(); + return false; +} + +bool QQuickOverlayPrivate::handleMove(QQuickItem *source, QEvent *event, QQuickPopup *target) +{ + if (target) + return target->overlayEvent(source, event); + return false; +} + +bool QQuickOverlayPrivate::handleRelease(QQuickItem *source, QEvent *event, QQuickPopup *target) +{ + if (target) { + setMouseGrabberPopup(nullptr); + if (target->overlayEvent(source, event)) { + setMouseGrabberPopup(nullptr); + return true; + } + } else { + const auto popups = stackingOrderPopups(); + for (QQuickPopup *popup : popups) { + if (popup->overlayEvent(source, event)) + return true; + } + } + return false; +} + +bool QQuickOverlayPrivate::handleMouseEvent(QQuickItem *source, QMouseEvent *event, QQuickPopup *target) +{ + switch (event->type()) { + case QEvent::MouseButtonPress: + if (!target && startDrag(event, event->scenePosition())) + return true; + return handlePress(source, event, target); + case QEvent::MouseMove: + return handleMove(source, event, target ? target : mouseGrabberPopup.data()); + case QEvent::MouseButtonRelease: + return handleRelease(source, event, target ? target : mouseGrabberPopup.data()); + default: + break; + } + return false; +} + +bool QQuickOverlayPrivate::handleHoverEvent(QQuickItem *source, QHoverEvent *event, QQuickPopup *target) +{ + switch (event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverMove: + case QEvent::HoverLeave: + if (target) + return target->overlayEvent(source, event); + return false; + default: + Q_UNREACHABLE(); // function must only be called on hover events + break; + } + return false; +} + +#if QT_CONFIG(quicktemplates2_multitouch) +bool QQuickOverlayPrivate::handleTouchEvent(QQuickItem *source, QTouchEvent *event, QQuickPopup *target) +{ + bool handled = false; + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + for (const QTouchEvent::TouchPoint &point : event->points()) { + switch (point.state()) { + case QEventPoint::Pressed: + if (!target && startDrag(event, point.scenePosition())) + handled = true; + else + handled |= handlePress(source, event, target); + break; + case QEventPoint::Updated: + handled |= handleMove(source, event, target ? target : mouseGrabberPopup.data()); + break; + case QEventPoint::Released: + handled |= handleRelease(source, event, target ? target : mouseGrabberPopup.data()); + break; + default: + break; + } + } + break; + + default: + break; + } + + return handled; +} +#endif + +void QQuickOverlayPrivate::addPopup(QQuickPopup *popup) +{ + Q_Q(QQuickOverlay); + allPopups += popup; + if (QQuickDrawer *drawer = qobject_cast<QQuickDrawer *>(popup)) { + allDrawers += drawer; + q->setVisible(!allDrawers.isEmpty() || !q->childItems().isEmpty()); + } +} + +void QQuickOverlayPrivate::removePopup(QQuickPopup *popup) +{ + Q_Q(QQuickOverlay); + allPopups.removeOne(popup); + if (allDrawers.removeOne(qobject_cast<QQuickDrawer *>(popup))) + q->setVisible(!allDrawers.isEmpty() || !q->childItems().isEmpty()); +} + +void QQuickOverlayPrivate::setMouseGrabberPopup(QQuickPopup *popup) +{ + if (popup && !popup->isVisible()) + popup = nullptr; + mouseGrabberPopup = popup; +} + +void QQuickOverlayPrivate::updateGeometry() +{ + Q_Q(QQuickOverlay); + if (!window) + return; + + QPointF pos; + QSizeF size = window->size(); + qreal rotation = 0; + + switch (window->contentOrientation()) { + case Qt::PrimaryOrientation: + case Qt::PortraitOrientation: + size = window->size(); + break; + case Qt::LandscapeOrientation: + rotation = 90; + pos = QPointF((size.width() - size.height()) / 2, -(size.width() - size.height()) / 2); + size.transpose(); + break; + case Qt::InvertedPortraitOrientation: + rotation = 180; + break; + case Qt::InvertedLandscapeOrientation: + rotation = 270; + pos = QPointF((size.width() - size.height()) / 2, -(size.width() - size.height()) / 2); + size.transpose(); + break; + default: + break; + } + + q->setSize(size); + q->setPosition(pos); + q->setRotation(rotation); +} + +QQuickOverlay::QQuickOverlay(QQuickItem *parent) + : QQuickItem(*(new QQuickOverlayPrivate), parent) +{ + Q_D(QQuickOverlay); + setZ(1000001); // DefaultWindowDecoration+1 + setAcceptedMouseButtons(Qt::AllButtons); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif + setFiltersChildMouseEvents(true); + setVisible(false); + + if (parent) { + d->updateGeometry(); + QQuickItemPrivate::get(parent)->addItemChangeListener(d, QQuickItemPrivate::Geometry); + if (QQuickWindow *window = parent->window()) { + window->installEventFilter(this); + QObjectPrivate::connect(window, &QWindow::contentOrientationChanged, d, &QQuickOverlayPrivate::updateGeometry); + } + } +} + +QQuickOverlay::~QQuickOverlay() +{ + Q_D(QQuickOverlay); + if (QQuickItem *parent = parentItem()) + QQuickItemPrivate::get(parent)->removeItemChangeListener(d, QQuickItemPrivate::Geometry); +} + +QQmlComponent *QQuickOverlay::modal() const +{ + Q_D(const QQuickOverlay); + return d->modal; +} + +void QQuickOverlay::setModal(QQmlComponent *modal) +{ + Q_D(QQuickOverlay); + if (d->modal == modal) + return; + + d->modal = modal; + emit modalChanged(); +} + +QQmlComponent *QQuickOverlay::modeless() const +{ + Q_D(const QQuickOverlay); + return d->modeless; +} + +void QQuickOverlay::setModeless(QQmlComponent *modeless) +{ + Q_D(QQuickOverlay); + if (d->modeless == modeless) + return; + + d->modeless = modeless; + emit modelessChanged(); +} + +QQuickOverlay *QQuickOverlay::overlay(QQuickWindow *window) +{ + if (!window) + return nullptr; + + const char *name = "_q_QQuickOverlay"; + QQuickOverlay *overlay = window->property(name).value<QQuickOverlay *>(); + if (!overlay) { + QQuickItem *content = window->contentItem(); + // Do not re-create the overlay if the window is being destroyed + // and thus, its content item no longer has a window associated. + if (content && content->window()) { + overlay = new QQuickOverlay(window->contentItem()); + window->setProperty(name, QVariant::fromValue(overlay)); + } + } + return overlay; +} + +QQuickOverlayAttached *QQuickOverlay::qmlAttachedProperties(QObject *object) +{ + return new QQuickOverlayAttached(object); +} + +void QQuickOverlay::itemChange(ItemChange change, const ItemChangeData &data) +{ + Q_D(QQuickOverlay); + QQuickItem::itemChange(change, data); + + if (change == ItemChildAddedChange || change == ItemChildRemovedChange) { + setVisible(!d->allDrawers.isEmpty() || !childItems().isEmpty()); + if (data.item->parent() == d->mouseGrabberPopup) + d->setMouseGrabberPopup(nullptr); + } +} + +void QQuickOverlay::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickOverlay); + QQuickItem::geometryChange(newGeometry, oldGeometry); + for (QQuickPopup *popup : qAsConst(d->allPopups)) + QQuickPopupPrivate::get(popup)->resizeOverlay(); +} + +void QQuickOverlay::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickOverlay); + d->handleMouseEvent(this, event); +} + +void QQuickOverlay::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickOverlay); + d->handleMouseEvent(this, event); +} + +void QQuickOverlay::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickOverlay); + d->handleMouseEvent(this, event); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickOverlay::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickOverlay); + d->handleTouchEvent(this, event); +} +#endif + +#if QT_CONFIG(wheelevent) +void QQuickOverlay::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickOverlay); + if (d->mouseGrabberPopup) { + d->mouseGrabberPopup->overlayEvent(this, event); + return; + } else { + const auto popups = d->stackingOrderPopups(); + for (QQuickPopup *popup : popups) { + if (popup->overlayEvent(this, event)) + return; + } + } + event->ignore(); +} +#endif + +bool QQuickOverlay::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + Q_D(QQuickOverlay); + const auto popups = d->stackingOrderPopups(); + for (QQuickPopup *popup : popups) { + QQuickPopupPrivate *p = QQuickPopupPrivate::get(popup); + + // Stop filtering overlay events when reaching a popup item or an item + // that is inside the popup. Let the popup content handle its events. + if (item == p->popupItem || p->popupItem->isAncestorOf(item)) + break; + + // Let the popup try closing itself when pressing or releasing over its + // background dimming OR over another popup underneath, in case the popup + // does not have background dimming. + if (item == p->dimmer || !p->popupItem->isAncestorOf(item)) { + bool handled = false; + switch (event->type()) { +#if QT_CONFIG(quicktemplates2_multitouch) + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + handled = d->handleTouchEvent(item, static_cast<QTouchEvent *>(event), popup); + break; +#endif + case QEvent::HoverEnter: + case QEvent::HoverMove: + case QEvent::HoverLeave: + handled = d->handleHoverEvent(item, static_cast<QHoverEvent *>(event), popup); + break; + + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + handled = d->handleMouseEvent(item, static_cast<QMouseEvent *>(event), popup); + break; + + default: + break; + } + if (handled) + return true; + } + } + return false; +} + +bool QQuickOverlay::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QQuickOverlay); + if (!isVisible() || object != d->window) + return false; + + switch (event->type()) { +#if QT_CONFIG(quicktemplates2_multitouch) + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + if (static_cast<QTouchEvent *>(event)->touchPointStates() & QEventPoint::Pressed) + emit pressed(); + if (static_cast<QTouchEvent *>(event)->touchPointStates() & QEventPoint::Released) + emit released(); + + // allow non-modal popups to close on touch release outside + if (!d->mouseGrabberPopup) { + for (const QTouchEvent::TouchPoint &point : static_cast<QTouchEvent *>(event)->points()) { + if (point.state() == QEventPoint::Released) { + if (d->handleRelease(d->window->contentItem(), event, nullptr)) + break; + } + } + } + + QQuickWindowPrivate::get(d->window)->handleTouchEvent(static_cast<QTouchEvent *>(event)); + + // If a touch event hasn't been accepted after being delivered, there + // were no items interested in touch events at any of the touch points. + // Make sure to accept the touch event in order to receive the consequent + // touch events, to be able to close non-modal popups on release outside. + event->accept(); + return true; +#endif + + case QEvent::MouseButtonPress: +#if QT_CONFIG(quicktemplates2_multitouch) + // do not emit pressed() twice when mouse events have been synthesized from touch events + if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized) +#endif + emit pressed(); + + QQuickWindowPrivate::get(d->window)->handleMouseEvent(static_cast<QMouseEvent *>(event)); + + // If a mouse event hasn't been accepted after being delivered, there + // was no item interested in mouse events at the mouse point. Make sure + // to accept the mouse event in order to receive the consequent mouse + // events, to be able to close non-modal popups on release outside. + event->accept(); + return true; + + case QEvent::MouseButtonRelease: +#if QT_CONFIG(quicktemplates2_multitouch) + // do not emit released() twice when mouse events have been synthesized from touch events + if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized) +#endif + emit released(); + + // allow non-modal popups to close on mouse release outside + if (!d->mouseGrabberPopup) + d->handleRelease(d->window->contentItem(), event, nullptr); + break; + + case QEvent::Wheel: { + // If the top item in the drawing-order is blocked by a modal popup, then + // eat the event. There is no scenario where the top most item is blocked + // by a popup, but an item further down in the drawing order is not. + QWheelEvent *we = static_cast<QWheelEvent *>(event); + const QVector<QQuickItem *> targetItems = d->deliveryAgentPrivate()->pointerTargets( + d->window->contentItem(), we, we->point(0), false, false); + if (targetItems.isEmpty()) + break; + + QQuickItem * const topItem = targetItems.first(); + const auto popups = d->stackingOrderPopups(); + for (const auto &popup : popups) { + if (!popup->overlayEvent(topItem, we)) + continue; + const QQuickItem *popupItem = popup->popupItem(); + if (!popupItem) + continue; + if (!popupItem->isAncestorOf(topItem)) + return true; + } + break; + } + + default: + break; + } + + return false; +} + +class QQuickOverlayAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickOverlayAttached) + +public: + void setWindow(QQuickWindow *newWindow); + + QQuickWindow *window = nullptr; + QQmlComponent *modal = nullptr; + QQmlComponent *modeless = nullptr; +}; + +void QQuickOverlayAttachedPrivate::setWindow(QQuickWindow *newWindow) +{ + Q_Q(QQuickOverlayAttached); + if (window == newWindow) + return; + + if (QQuickOverlay *oldOverlay = QQuickOverlay::overlay(window)) { + QObject::disconnect(oldOverlay, &QQuickOverlay::pressed, q, &QQuickOverlayAttached::pressed); + QObject::disconnect(oldOverlay, &QQuickOverlay::released, q, &QQuickOverlayAttached::released); + } + + if (QQuickOverlay *newOverlay = QQuickOverlay::overlay(newWindow)) { + QObject::connect(newOverlay, &QQuickOverlay::pressed, q, &QQuickOverlayAttached::pressed); + QObject::connect(newOverlay, &QQuickOverlay::released, q, &QQuickOverlayAttached::released); + } + + window = newWindow; + emit q->overlayChanged(); +} + +/*! + \qmlattachedsignal QtQuick.Controls::Overlay::pressed() + + This attached signal is emitted when the overlay is pressed by the user while + a popup is visible. + + The signal can be attached to any item, popup, or window. When attached to an + item or a popup, the signal is only emitted if the item or popup is in a window. +*/ + +/*! + \qmlattachedsignal QtQuick.Controls::Overlay::released() + + This attached signal is emitted when the overlay is released by the user while + a popup is visible. + + The signal can be attached to any item, popup, or window. When attached to an + item or a popup, the signal is only emitted if the item or popup is in a window. +*/ + +QQuickOverlayAttached::QQuickOverlayAttached(QObject *parent) + : QObject(*(new QQuickOverlayAttachedPrivate), parent) +{ + Q_D(QQuickOverlayAttached); + if (QQuickItem *item = qobject_cast<QQuickItem *>(parent)) { + d->setWindow(item->window()); + QObjectPrivate::connect(item, &QQuickItem::windowChanged, d, &QQuickOverlayAttachedPrivate::setWindow); + } else if (QQuickPopup *popup = qobject_cast<QQuickPopup *>(parent)) { + d->setWindow(popup->window()); + QObjectPrivate::connect(popup, &QQuickPopup::windowChanged, d, &QQuickOverlayAttachedPrivate::setWindow); + } else { + d->setWindow(qobject_cast<QQuickWindow *>(parent)); + } +} + +/*! + \qmlattachedproperty Overlay QtQuick.Controls::Overlay::overlay + \readonly + + This attached property holds the window overlay item. + + The property can be attached to any item, popup, or window. When attached to an + item or a popup, the value is \c null if the item or popup is not in a window. +*/ +QQuickOverlay *QQuickOverlayAttached::overlay() const +{ + Q_D(const QQuickOverlayAttached); + return QQuickOverlay::overlay(d->window); +} + +/*! + \qmlattachedproperty Component QtQuick.Controls::Overlay::modal + + This attached property holds a component to use as a visual item that implements + background dimming for modal popups. It is created for and stacked below visible + modal popups. + + The property can be attached to any popup. + + For example, to change the color of the background dimming for a modal + popup, the following code can be used: + + \snippet qtquickcontrols2-overlay-modal.qml 1 + + \sa Popup::modal +*/ +QQmlComponent *QQuickOverlayAttached::modal() const +{ + Q_D(const QQuickOverlayAttached); + return d->modal; +} + +void QQuickOverlayAttached::setModal(QQmlComponent *modal) +{ + Q_D(QQuickOverlayAttached); + if (d->modal == modal) + return; + + d->modal = modal; + emit modalChanged(); +} + +/*! + \qmlattachedproperty Component QtQuick.Controls::Overlay::modeless + + This attached property holds a component to use as a visual item that implements + background dimming for modeless popups. It is created for and stacked below visible + dimming popups. + + The property can be attached to any popup. + + For example, to change the color of the background dimming for a modeless + popup, the following code can be used: + + \snippet qtquickcontrols2-overlay-modeless.qml 1 + + \sa Popup::dim +*/ +QQmlComponent *QQuickOverlayAttached::modeless() const +{ + Q_D(const QQuickOverlayAttached); + return d->modeless; +} + +void QQuickOverlayAttached::setModeless(QQmlComponent *modeless) +{ + Q_D(QQuickOverlayAttached); + if (d->modeless == modeless) + return; + + d->modeless = modeless; + emit modelessChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquickoverlay_p.cpp" diff --git a/src/quicktemplates2/qquickoverlay_p.h b/src/quicktemplates2/qquickoverlay_p.h new file mode 100644 index 0000000000..c6e530e280 --- /dev/null +++ b/src/quicktemplates2/qquickoverlay_p.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKOVERLAY_P_H +#define QQUICKOVERLAY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/qquickitem.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlComponent; +class QQuickOverlayPrivate; +class QQuickOverlayAttached; +class QQuickOverlayAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickOverlay : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QQmlComponent *modal READ modal WRITE setModal NOTIFY modalChanged FINAL) + Q_PROPERTY(QQmlComponent *modeless READ modeless WRITE setModeless NOTIFY modelessChanged FINAL) + QML_NAMED_ELEMENT(Overlay) + QML_ATTACHED(QQuickOverlayAttached) + QML_UNCREATABLE("") + QML_ADDED_IN_VERSION(2, 3) + +public: + explicit QQuickOverlay(QQuickItem *parent = nullptr); + ~QQuickOverlay(); + + QQmlComponent *modal() const; + void setModal(QQmlComponent *modal); + + QQmlComponent *modeless() const; + void setModeless(QQmlComponent *modeless); + + static QQuickOverlay *overlay(QQuickWindow *window); + + static QQuickOverlayAttached *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void modalChanged(); + void modelessChanged(); + void pressed(); + void released(); + +protected: + void itemChange(ItemChange change, const ItemChangeData &data) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; + bool eventFilter(QObject *object, QEvent *event) override; + +private: + Q_DISABLE_COPY(QQuickOverlay) + Q_DECLARE_PRIVATE(QQuickOverlay) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickOverlayAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickOverlay *overlay READ overlay NOTIFY overlayChanged FINAL) + Q_PROPERTY(QQmlComponent *modal READ modal WRITE setModal NOTIFY modalChanged FINAL) + Q_PROPERTY(QQmlComponent *modeless READ modeless WRITE setModeless NOTIFY modelessChanged FINAL) + +public: + explicit QQuickOverlayAttached(QObject *parent = nullptr); + + QQuickOverlay *overlay() const; + + QQmlComponent *modal() const; + void setModal(QQmlComponent *modal); + + QQmlComponent *modeless() const; + void setModeless(QQmlComponent *modeless); + +Q_SIGNALS: + void overlayChanged(); + void modalChanged(); + void modelessChanged(); + void pressed(); + void released(); + +private: + Q_DISABLE_COPY(QQuickOverlayAttached) + Q_DECLARE_PRIVATE(QQuickOverlayAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickOverlay) +QML_DECLARE_TYPEINFO(QQuickOverlay, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKOVERLAY_P_H diff --git a/src/quicktemplates2/qquickoverlay_p_p.h b/src/quicktemplates2/qquickoverlay_p_p.h new file mode 100644 index 0000000000..fbb2f86c73 --- /dev/null +++ b/src/quicktemplates2/qquickoverlay_p_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKOVERLAY_P_P_H +#define QQUICKOVERLAY_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickoverlay_p.h> + +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickPopup; +class QQuickDrawer; + +class QQuickOverlayPrivate : public QQuickItemPrivate, public QQuickItemChangeListener +{ + Q_DECLARE_PUBLIC(QQuickOverlay) + +public: + static QQuickOverlayPrivate *get(QQuickOverlay *overlay) + { + return overlay->d_func(); + } + + bool startDrag(QEvent *event, const QPointF &pos); + bool handlePress(QQuickItem *source, QEvent *event, QQuickPopup *target); + bool handleMove(QQuickItem *source, QEvent *event, QQuickPopup *target); + bool handleRelease(QQuickItem *source, QEvent *event, QQuickPopup *target); + + bool handleMouseEvent(QQuickItem *source, QMouseEvent *event, QQuickPopup *target = nullptr); + bool handleHoverEvent(QQuickItem *source, QHoverEvent *event, QQuickPopup *target = nullptr); +#if QT_CONFIG(quicktemplates2_multitouch) + bool handleTouchEvent(QQuickItem *source, QTouchEvent *event, QQuickPopup *target = nullptr); +#endif + + void addPopup(QQuickPopup *popup); + void removePopup(QQuickPopup *popup); + void setMouseGrabberPopup(QQuickPopup *popup); + + QList<QQuickPopup *> stackingOrderPopups() const; + QList<QQuickDrawer *> stackingOrderDrawers() const; + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + + void updateGeometry(); + + QQmlComponent *modal = nullptr; + QQmlComponent *modeless = nullptr; + QList<QQuickPopup *> allPopups; + QList<QQuickDrawer *> allDrawers; + QPointer<QQuickPopup> mouseGrabberPopup; +}; + +QT_END_NAMESPACE + +#endif // QQUICKOVERLAY_P_P_H diff --git a/src/quicktemplates2/qquickpage.cpp b/src/quicktemplates2/qquickpage.cpp new file mode 100644 index 0000000000..a65e53fe82 --- /dev/null +++ b/src/quicktemplates2/qquickpage.cpp @@ -0,0 +1,498 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpage_p.h" +#include "qquickpage_p_p.h" +#include "qquicktabbar_p.h" +#include "qquicktoolbar_p.h" +#include "qquickdialogbuttonbox_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Page + \inherits Pane +//! \instantiates QQuickPage + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Styled page control with support for a header and footer. + + Page is a container control which makes it convenient to add + a \l header and \l footer item to a page. + + \image qtquickcontrols2-page-wireframe.png + + Items declared as children of a Page are: + \list + \li automatically parented to the Page's contentItem. Items created + dynamically need to be explicitly parented to the contentItem. + \li not automatically positioned or resized. + \endlist + + The following example snippet illustrates how to use a page-specific + toolbar header and an application-wide tabbar footer. + + \qml + import QtQuick.Controls + import QtQuick.Layouts + + ApplicationWindow { + visible: true + + StackView { + anchors.fill: parent + + initialItem: Page { + header: ToolBar { + // ... + } + + ColumnLayout { + anchors.fill: parent + // ... + } + } + } + + footer: TabBar { + // ... + } + } + \endqml + + \sa ApplicationWindow, {Container Controls}, + {Focus Management in Qt Quick Controls} +*/ + +static const QQuickItemPrivate::ChangeTypes LayoutChanges = QQuickItemPrivate::Geometry | QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed + | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; + +namespace { + enum Position { + Header, + Footer + }; + + Q_STATIC_ASSERT(int(Header) == int(QQuickTabBar::Header)); + Q_STATIC_ASSERT(int(Footer) == int(QQuickTabBar::Footer)); + + Q_STATIC_ASSERT(int(Header) == int(QQuickToolBar::Header)); + Q_STATIC_ASSERT(int(Footer) == int(QQuickToolBar::Footer)); + + Q_STATIC_ASSERT(int(Header) == int(QQuickDialogButtonBox::Header)); + Q_STATIC_ASSERT(int(Footer) == int(QQuickDialogButtonBox::Footer)); + + static void setPos(QQuickItem *item, Position position) + { + if (QQuickToolBar *toolBar = qobject_cast<QQuickToolBar *>(item)) + toolBar->setPosition(static_cast<QQuickToolBar::Position>(position)); + else if (QQuickTabBar *tabBar = qobject_cast<QQuickTabBar *>(item)) + tabBar->setPosition(static_cast<QQuickTabBar::Position>(position)); + else if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(item)) + buttonBox->setPosition(static_cast<QQuickDialogButtonBox::Position>(position)); + } +} + +void QQuickPagePrivate::relayout() +{ + Q_Q(QQuickPage); + const qreal hh = header && header->isVisible() ? header->height() : 0; + const qreal fh = footer && footer->isVisible() ? footer->height() : 0; + const qreal hsp = hh > 0 ? spacing : 0; + const qreal fsp = fh > 0 ? spacing : 0; + + if (contentItem) { + contentItem->setY(q->topPadding() + hh + hsp); + contentItem->setX(q->leftPadding()); + contentItem->setWidth(q->availableWidth()); + contentItem->setHeight(q->availableHeight() - hh - fh - hsp - fsp); + } + + if (header) + header->setWidth(q->width()); + + if (footer) { + footer->setY(q->height() - footer->height()); + footer->setWidth(q->width()); + } +} + +void QQuickPagePrivate::resizeContent() +{ + relayout(); +} + +void QQuickPagePrivate::itemVisibilityChanged(QQuickItem *item) +{ + Q_Q(QQuickPage); + QQuickPanePrivate::itemVisibilityChanged(item); + if (item == header) { + QBoolBlocker signalGuard(emittingImplicitSizeChangedSignals); + emit q->implicitHeaderWidthChanged(); + emit q->implicitHeaderHeightChanged(); + relayout(); + } else if (item == footer) { + QBoolBlocker signalGuard(emittingImplicitSizeChangedSignals); + emit q->implicitFooterWidthChanged(); + emit q->implicitFooterHeightChanged(); + relayout(); + } +} + +void QQuickPagePrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickPage); + QQuickPanePrivate::itemImplicitWidthChanged(item); + + // Avoid binding loops by skipping signal emission if we're already doing it. + if (emittingImplicitSizeChangedSignals) + return; + + if (item == header) + emit q->implicitHeaderWidthChanged(); + else if (item == footer) + emit q->implicitFooterWidthChanged(); +} + +void QQuickPagePrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickPage); + QQuickPanePrivate::itemImplicitHeightChanged(item); + + // Avoid binding loops by skipping signal emission if we're already doing it. + if (emittingImplicitSizeChangedSignals) + return; + + if (item == header) + emit q->implicitHeaderHeightChanged(); + else if (item == footer) + emit q->implicitFooterHeightChanged(); +} + +void QQuickPagePrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF & diff) +{ + QQuickPanePrivate::itemGeometryChanged(item, change, diff); + if (item == header || item == footer) + relayout(); +} + +void QQuickPagePrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickPage); + QQuickPanePrivate::itemDestroyed(item); + if (item == header) { + header = nullptr; + relayout(); + emit q->implicitHeaderWidthChanged(); + emit q->implicitHeaderHeightChanged(); + emit q->headerChanged(); + } else if (item == footer) { + footer = nullptr; + relayout(); + emit q->implicitFooterWidthChanged(); + emit q->implicitFooterHeightChanged(); + emit q->footerChanged(); + } +} + +QQuickPage::QQuickPage(QQuickItem *parent) + : QQuickPane(*(new QQuickPagePrivate), parent) +{ +} + +QQuickPage::QQuickPage(QQuickPagePrivate &dd, QQuickItem *parent) + : QQuickPane(dd, parent) +{ +} + +QQuickPage::~QQuickPage() +{ + Q_D(QQuickPage); + if (d->header) + QQuickItemPrivate::get(d->header)->removeItemChangeListener(d, LayoutChanges); + if (d->footer) + QQuickItemPrivate::get(d->footer)->removeItemChangeListener(d, LayoutChanges); +} + +/*! + \qmlproperty string QtQuick.Controls::Page::title + + This property holds the page title. + + The title is often displayed at the top of a page to give + the user context about the page they are viewing. + + Page does not render the title itself, but instead relies + on the application to do so. For example: + + \code + ApplicationWindow { + visible: true + width: 400 + height: 400 + + header: Label { + text: view.currentItem.title + horizontalAlignment: Text.AlignHCenter + } + + SwipeView { + id: view + anchors.fill: parent + + Page { + title: qsTr("Home") + } + Page { + title: qsTr("Discover") + } + Page { + title: qsTr("Activity") + } + } + } + \endcode +*/ + +QString QQuickPage::title() const +{ + return d_func()->title; +} + +void QQuickPage::setTitle(const QString &title) +{ + Q_D(QQuickPage); + if (d->title == title) + return; + + d->title = title; + maybeSetAccessibleName(title); + emit titleChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::Page::header + + This property holds the page header item. The header item is positioned to + the top, and resized to the width of the page. The default value is \c null. + + \note Assigning a ToolBar, TabBar, or DialogButtonBox as a page header + automatically sets the respective \l ToolBar::position, \l TabBar::position, + or \l DialogButtonBox::position property to \c Header. + + \sa footer, ApplicationWindow::header +*/ +QQuickItem *QQuickPage::header() const +{ + Q_D(const QQuickPage); + return d->header; +} + +void QQuickPage::setHeader(QQuickItem *header) +{ + Q_D(QQuickPage); + if (d->header == header) + return; + + if (d->header) { + QQuickItemPrivate::get(d->header)->removeItemChangeListener(d, LayoutChanges); + d->header->setParentItem(nullptr); + } + d->header = header; + if (header) { + header->setParentItem(this); + QQuickItemPrivate::get(header)->addItemChangeListener(d, LayoutChanges); + if (qFuzzyIsNull(header->z())) + header->setZ(1); + setPos(header, Header); + } + if (isComponentComplete()) + d->relayout(); + emit headerChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::Page::footer + + This property holds the page footer item. The footer item is positioned to + the bottom, and resized to the width of the page. The default value is \c null. + + \note Assigning a ToolBar, TabBar, or DialogButtonBox as a page footer + automatically sets the respective \l ToolBar::position, \l TabBar::position, + or \l DialogButtonBox::position property to \c Footer. + + \sa header, ApplicationWindow::footer +*/ +QQuickItem *QQuickPage::footer() const +{ + Q_D(const QQuickPage); + return d->footer; +} + +void QQuickPage::setFooter(QQuickItem *footer) +{ + Q_D(QQuickPage); + if (d->footer == footer) + return; + + if (d->footer) { + QQuickItemPrivate::get(d->footer)->removeItemChangeListener(d, LayoutChanges); + d->footer->setParentItem(nullptr); + } + d->footer = footer; + if (footer) { + footer->setParentItem(this); + QQuickItemPrivate::get(footer)->addItemChangeListener(d, LayoutChanges); + if (qFuzzyIsNull(footer->z())) + footer->setZ(1); + setPos(footer, Footer); + } + if (isComponentComplete()) + d->relayout(); + emit footerChanged(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Page::implicitHeaderWidth + \readonly + + This property holds the implicit header width. + + The value is equal to \c {header && header.visible ? header.implicitWidth : 0}. + + \sa implicitHeaderHeight, implicitFooterWidth +*/ +qreal QQuickPage::implicitHeaderWidth() const +{ + Q_D(const QQuickPage); + if (!d->header || !d->header->isVisible()) + return 0; + return d->header->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Page::implicitHeaderHeight + \readonly + + This property holds the implicit header height. + + The value is equal to \c {header && header.visible ? header.implicitHeight : 0}. + + \sa implicitHeaderWidth, implicitFooterHeight +*/ +qreal QQuickPage::implicitHeaderHeight() const +{ + Q_D(const QQuickPage); + if (!d->header || !d->header->isVisible()) + return 0; + return d->header->implicitHeight(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Page::implicitFooterWidth + \readonly + + This property holds the implicit footer width. + + The value is equal to \c {footer && footer.visible ? footer.implicitWidth : 0}. + + \sa implicitFooterHeight, implicitHeaderWidth +*/ +qreal QQuickPage::implicitFooterWidth() const +{ + Q_D(const QQuickPage); + if (!d->footer || !d->footer->isVisible()) + return 0; + return d->footer->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Page::implicitFooterHeight + \readonly + + This property holds the implicit footer height. + + The value is equal to \c {footer && footer.visible ? footer.implicitHeight : 0}. + + \sa implicitFooterWidth, implicitHeaderHeight +*/ +qreal QQuickPage::implicitFooterHeight() const +{ + Q_D(const QQuickPage); + if (!d->footer || !d->footer->isVisible()) + return 0; + return d->footer->implicitHeight(); +} + +void QQuickPage::componentComplete() +{ + Q_D(QQuickPage); + QQuickPane::componentComplete(); + d->relayout(); +} + +void QQuickPage::spacingChange(qreal newSpacing, qreal oldSpacing) +{ + Q_D(QQuickPage); + QQuickPane::spacingChange(newSpacing, oldSpacing); + d->relayout(); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickPage::accessibleRole() const +{ + return QAccessible::PageTab; +} + +void QQuickPage::accessibilityActiveChanged(bool active) +{ + Q_D(QQuickPage); + QQuickPane::accessibilityActiveChanged(active); + + if (active) + maybeSetAccessibleName(d->title); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickpage_p.cpp" diff --git a/src/quicktemplates2/qquickpage_p.h b/src/quicktemplates2/qquickpage_p.h new file mode 100644 index 0000000000..0a65f67f28 --- /dev/null +++ b/src/quicktemplates2/qquickpage_p.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPAGE_P_H +#define QQUICKPAGE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpane_p.h> +#include <QtQml/qqmllist.h> + +QT_BEGIN_NAMESPACE + +class QQuickPagePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPage : public QQuickPane +{ + Q_OBJECT + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) + Q_PROPERTY(QQuickItem *header READ header WRITE setHeader NOTIFY headerChanged FINAL) + Q_PROPERTY(QQuickItem *footer READ footer WRITE setFooter NOTIFY footerChanged FINAL) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal implicitHeaderWidth READ implicitHeaderWidth NOTIFY implicitHeaderWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitHeaderHeight READ implicitHeaderHeight NOTIFY implicitHeaderHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitFooterWidth READ implicitFooterWidth NOTIFY implicitFooterWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitFooterHeight READ implicitFooterHeight NOTIFY implicitFooterHeightChanged FINAL REVISION(2, 5)) + QML_NAMED_ELEMENT(Page) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickPage(QQuickItem *parent = nullptr); + ~QQuickPage(); + + QString title() const; + void setTitle(const QString &title); + + QQuickItem *header() const; + void setHeader(QQuickItem *header); + + QQuickItem *footer() const; + void setFooter(QQuickItem *footer); + + // 2.5 (Qt 5.12) + qreal implicitHeaderWidth() const; + qreal implicitHeaderHeight() const; + + qreal implicitFooterWidth() const; + qreal implicitFooterHeight() const; + +Q_SIGNALS: + void titleChanged(); + void headerChanged(); + void footerChanged(); + // 2.5 (Qt 5.12) + void implicitHeaderWidthChanged(); + void implicitHeaderHeightChanged(); + void implicitFooterWidthChanged(); + void implicitFooterHeightChanged(); + +protected: + QQuickPage(QQuickPagePrivate &dd, QQuickItem *parent); + + void componentComplete() override; + + void spacingChange(qreal newSpacing, qreal oldSpacing) override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; + void accessibilityActiveChanged(bool active) override; +#endif + +private: + Q_DISABLE_COPY(QQuickPage) + Q_DECLARE_PRIVATE(QQuickPage) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPage) + +#endif // QQUICKPAGE_P_H diff --git a/src/quicktemplates2/qquickpage_p_p.h b/src/quicktemplates2/qquickpage_p_p.h new file mode 100644 index 0000000000..6c8b037102 --- /dev/null +++ b/src/quicktemplates2/qquickpage_p_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPAGE_P_P_H +#define QQUICKPAGE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpane_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickPane; + +class QQuickPagePrivate : public QQuickPanePrivate +{ + Q_DECLARE_PUBLIC(QQuickPage) + +public: + void relayout(); + void resizeContent() override; + + void itemVisibilityChanged(QQuickItem *item) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF & diff) override; + void itemDestroyed(QQuickItem *item) override; + + QString title; + QQuickItem *header = nullptr; + QQuickItem *footer = nullptr; + bool emittingImplicitSizeChangedSignals = false; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPAGE_P_P_H diff --git a/src/quicktemplates2/qquickpageindicator.cpp b/src/quicktemplates2/qquickpageindicator.cpp new file mode 100644 index 0000000000..42834962e1 --- /dev/null +++ b/src/quicktemplates2/qquickpageindicator.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpageindicator_p.h" +#include "qquickcontrol_p_p.h" + +#include <QtCore/qmath.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PageIndicator + \inherits Control +//! \instantiates QQuickPageIndicator + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-indicators + \brief Indicates the currently active page. + + PageIndicator is used to indicate the currently active page + in a container of multiple pages. PageIndicator consists of + delegate items that present pages. + + \image qtquickcontrols2-pageindicator.png + + \code + Column { + StackLayout { + id: stackLayout + + Page { + // ... + } + Page { + // ... + } + Page { + // ... + } + } + + PageIndicator { + currentIndex: stackLayout.currentIndex + count: stackLayout.count + } + } + \endcode + + \sa SwipeView, {Customizing PageIndicator}, {Indicator Controls} +*/ + +class QQuickPageIndicatorPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickPageIndicator) + +public: + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + void handleUngrab() override; + + QQuickItem *itemAt(const QPointF &pos) const; + void updatePressed(bool pressed, const QPointF &pos = QPointF()); + void setContextProperty(QQuickItem *item, const QString &name, const QVariant &value); + + void itemChildAdded(QQuickItem *, QQuickItem *child) override; + + int count = 0; + int currentIndex = 0; + bool interactive = false; + QQmlComponent *delegate = nullptr; + QQuickItem *pressedItem = nullptr; +}; + +void QQuickPageIndicatorPrivate::handlePress(const QPointF &point) +{ + QQuickControlPrivate::handlePress(point); + if (interactive) + updatePressed(true, point); +} + +void QQuickPageIndicatorPrivate::handleMove(const QPointF &point) +{ + QQuickControlPrivate::handleMove(point); + if (interactive) + updatePressed(true, point); +} + +void QQuickPageIndicatorPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickPageIndicator); + QQuickControlPrivate::handleRelease(point); + if (interactive) { + if (pressedItem && contentItem) + q->setCurrentIndex(contentItem->childItems().indexOf(pressedItem)); + updatePressed(false); + } +} + +void QQuickPageIndicatorPrivate::handleUngrab() +{ + QQuickControlPrivate::handleUngrab(); + if (interactive) + updatePressed(false); +} + +QQuickItem *QQuickPageIndicatorPrivate::itemAt(const QPointF &pos) const +{ + Q_Q(const QQuickPageIndicator); + if (!contentItem || !q->contains(pos)) + return nullptr; + + QPointF contentPos = q->mapToItem(contentItem, pos); + QQuickItem *item = contentItem->childAt(contentPos.x(), contentPos.y()); + while (item && item->parentItem() != contentItem) + item = item->parentItem(); + if (item && !QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return item; + + // find the nearest + qreal distance = qInf(); + QQuickItem *nearest = nullptr; + const auto childItems = contentItem->childItems(); + for (QQuickItem *child : childItems) { + if (QQuickItemPrivate::get(child)->isTransparentForPositioner()) + continue; + + QPointF center = child->boundingRect().center(); + QPointF pt = contentItem->mapToItem(child, contentPos); + + qreal len = QLineF(center, pt).length(); + if (len < distance) { + distance = len; + nearest = child; + } + } + return nearest; +} + +void QQuickPageIndicatorPrivate::updatePressed(bool pressed, const QPointF &pos) +{ + QQuickItem *prevItem = pressedItem; + pressedItem = pressed ? itemAt(pos) : nullptr; + if (prevItem != pressedItem) { + setContextProperty(prevItem, QStringLiteral("pressed"), false); + setContextProperty(pressedItem, QStringLiteral("pressed"), pressed); + } +} + +void QQuickPageIndicatorPrivate::setContextProperty(QQuickItem *item, const QString &name, const QVariant &value) +{ + QQmlContext *context = qmlContext(item); + if (context && context->isValid()) { + context = context->parentContext(); + if (context && context->isValid()) + context->setContextProperty(name, value); + } +} + +void QQuickPageIndicatorPrivate::itemChildAdded(QQuickItem *, QQuickItem *child) +{ + if (!QQuickItemPrivate::get(child)->isTransparentForPositioner()) + setContextProperty(child, QStringLiteral("pressed"), false); +} + +QQuickPageIndicator::QQuickPageIndicator(QQuickItem *parent) + : QQuickControl(*(new QQuickPageIndicatorPrivate), parent) +{ +} + +/*! + \qmlproperty int QtQuick.Controls::PageIndicator::count + + This property holds the number of pages. +*/ +int QQuickPageIndicator::count() const +{ + Q_D(const QQuickPageIndicator); + return d->count; +} + +void QQuickPageIndicator::setCount(int count) +{ + Q_D(QQuickPageIndicator); + if (d->count == count) + return; + + d->count = count; + emit countChanged(); +} + +/*! + \qmlproperty int QtQuick.Controls::PageIndicator::currentIndex + + This property holds the index of the current page. +*/ +int QQuickPageIndicator::currentIndex() const +{ + Q_D(const QQuickPageIndicator); + return d->currentIndex; +} + +void QQuickPageIndicator::setCurrentIndex(int index) +{ + Q_D(QQuickPageIndicator); + if (d->currentIndex == index) + return; + + d->currentIndex = index; + emit currentIndexChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::PageIndicator::interactive + + This property holds whether the control is interactive. An interactive page indicator + reacts to presses and automatically changes the \l {currentIndex}{current index} + appropriately. + + \snippet qtquickcontrols2-pageindicator-interactive.qml 1 + + \note Page indicators are typically quite small (in order to avoid + distracting the user from the actual content of the user interface). They + can be hard to click, and might not be easily recognized as interactive by + the user. For these reasons, they are best used to complement primary + methods of navigation (such as \l SwipeView), not replace them. + + The default value is \c false. +*/ +bool QQuickPageIndicator::isInteractive() const +{ + Q_D(const QQuickPageIndicator); + return d->interactive; +} + +void QQuickPageIndicator::setInteractive(bool interactive) +{ + Q_D(QQuickPageIndicator); + if (d->interactive == interactive) + return; + + d->interactive = interactive; + if (interactive) { + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif + } else { + setAcceptedMouseButtons(Qt::NoButton); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + unsetCursor(); +#endif + } + emit interactiveChanged(); +} + +/*! + \qmlproperty Component QtQuick.Controls::PageIndicator::delegate + + This property holds a delegate that presents a page. + + The following properties are available in the context of each delegate: + \table + \row \li \b index : int \li The index of the item + \row \li \b pressed : bool \li Whether the item is pressed + \endtable +*/ +QQmlComponent *QQuickPageIndicator::delegate() const +{ + Q_D(const QQuickPageIndicator); + return d->delegate; +} + +void QQuickPageIndicator::setDelegate(QQmlComponent *delegate) +{ + Q_D(QQuickPageIndicator); + if (d->delegate == delegate) + return; + + d->delegate = delegate; + emit delegateChanged(); +} + +void QQuickPageIndicator::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickPageIndicator); + QQuickControl::contentItemChange(newItem, oldItem); + if (oldItem) + QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Children); + if (newItem) + QQuickItemPrivate::get(newItem)->addItemChangeListener(d, QQuickItemPrivate::Children); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickPageIndicator::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickPageIndicator); + if (d->interactive) + QQuickControl::touchEvent(event); + else + event->ignore(); // QTBUG-61785 +} +#endif + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickPageIndicator::accessibleRole() const +{ + return QAccessible::Indicator; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickpageindicator_p.cpp" diff --git a/src/quicktemplates2/qquickpageindicator_p.h b/src/quicktemplates2/qquickpageindicator_p.h new file mode 100644 index 0000000000..a21e88fe49 --- /dev/null +++ b/src/quicktemplates2/qquickpageindicator_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPAGEINDICATOR_P_H +#define QQUICKPAGEINDICATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlComponent; +class QQuickPageIndicatorPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPageIndicator : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged FINAL) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL) + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive NOTIFY interactiveChanged FINAL) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) + QML_NAMED_ELEMENT(PageIndicator) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickPageIndicator(QQuickItem *parent = nullptr); + + int count() const; + void setCount(int count); + + int currentIndex() const; + void setCurrentIndex(int index); + + bool isInteractive() const; + void setInteractive(bool interactive); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + +Q_SIGNALS: + void countChanged(); + void currentIndexChanged(); + void interactiveChanged(); + void delegateChanged(); + +protected: + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickPageIndicator) + Q_DECLARE_PRIVATE(QQuickPageIndicator) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPageIndicator) + +#endif // QQUICKPAGEINDICATOR_P_H diff --git a/src/quicktemplates2/qquickpane.cpp b/src/quicktemplates2/qquickpane.cpp new file mode 100644 index 0000000000..3cdc450fd1 --- /dev/null +++ b/src/quicktemplates2/qquickpane.cpp @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpane_p.h" +#include "qquickpane_p_p.h" +#include "qquickcontentitem_p.h" + +#include <QtCore/qloggingcategory.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPane, "qt.quick.controls.pane") + +/*! + \qmltype Pane + \inherits Control +//! \instantiates QQuickPane + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Provides a background matching with the application style and theme. + + Pane provides a background color that matches with the application style + and theme. Pane does not provide a layout of its own, but requires you to + position its contents, for instance by creating a \l RowLayout or a + \l ColumnLayout. + + Items declared as children of a Pane are automatically parented to the + Pane's \l[QtQuickControls2]{Control::}{contentItem}. Items created + dynamically need to be explicitly parented to the \c contentItem. + + \section1 Content Sizing + + If only a single item is used within a Pane, it will resize to fit the + implicit size of its contained item. This makes it particularly suitable + for use together with layouts. + + \image qtquickcontrols2-pane.png + + \snippet qtquickcontrols2-pane.qml 1 + + Sometimes there might be two items within the pane: + + \code + Pane { + SwipeView { + // ... + } + PageIndicator { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + } + } + \endcode + + In this case, Pane cannot calculate a sensible implicit size. Since we're + anchoring the \l PageIndicator over the \l SwipeView, we can simply set the + content size to the view's implicit size: + + \code + Pane { + contentWidth: view.implicitWidth + contentHeight: view.implicitHeight + + SwipeView { + id: view + // ... + } + PageIndicator { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + } + } + \endcode + + If the \l[QtQuickControls2]{Control::}{contentItem} has no implicit size + and only one child, Pane will use the implicit size of that child. For + example, in the following code, the Pane assumes the size of the Rectangle: + + \code + Pane { + Item { + Rectangle { + implicitWidth: 200 + implicitHeight: 200 + color: "salmon" + } + } + } + \endcode + + \sa {Customizing Pane}, {Container Controls}, + {Focus Management in Qt Quick Controls}, {Event Handling} +*/ + +void QQuickPanePrivate::init() +{ + Q_Q(QQuickPane); + q->setFlag(QQuickItem::ItemIsFocusScope); + q->setAcceptedMouseButtons(Qt::AllButtons); +#if QT_CONFIG(cursor) + q->setCursor(Qt::ArrowCursor); +#endif + connect(q, &QQuickControl::implicitContentWidthChanged, this, &QQuickPanePrivate::updateContentWidth); + connect(q, &QQuickControl::implicitContentHeightChanged, this, &QQuickPanePrivate::updateContentHeight); +} + +QList<QQuickItem *> QQuickPanePrivate::contentChildItems() const +{ + if (!contentItem) + return QList<QQuickItem *>(); + + return contentItem->childItems(); +} + +QQuickItem *QQuickPanePrivate::getContentItem() +{ + Q_Q(QQuickPane); + if (QQuickItem *item = QQuickControlPrivate::getContentItem()) + return item; + + return new QQuickContentItem(q); +} + +void QQuickPanePrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + QQuickControlPrivate::itemImplicitWidthChanged(item); + + if (item == firstChild) + updateImplicitContentWidth(); +} + +void QQuickPanePrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + QQuickControlPrivate::itemImplicitHeightChanged(item); + + if (item == firstChild) + updateImplicitContentHeight(); +} + +void QQuickPanePrivate::contentChildrenChange() +{ + Q_Q(QQuickPane); + QQuickItem *newFirstChild = contentChildItems().value(0); + if (newFirstChild != firstChild) { + if (firstChild) + removeImplicitSizeListener(firstChild); + if (newFirstChild) + addImplicitSizeListener(newFirstChild); + firstChild = newFirstChild; + } + + updateImplicitContentSize(); + emit q->contentChildrenChanged(); +} + +qreal QQuickPanePrivate::getContentWidth() const +{ + if (!contentItem) + return 0; + + const qreal cw = contentItem->implicitWidth(); + if (!qFuzzyIsNull(cw)) + return cw; + + const auto contentChildren = contentChildItems(); + if (contentChildren.count() == 1) + return contentChildren.first()->implicitWidth(); + + return 0; +} + +qreal QQuickPanePrivate::getContentHeight() const +{ + if (!contentItem) + return 0; + + const qreal ch = contentItem->implicitHeight(); + if (!qFuzzyIsNull(ch)) + return ch; + + const auto contentChildren = contentChildItems(); + if (contentChildren.count() == 1) + return contentChildren.first()->implicitHeight(); + + return 0; +} + +void QQuickPanePrivate::updateContentWidth() +{ + Q_Q(QQuickPane); + if (hasContentWidth || qFuzzyCompare(contentWidth, implicitContentWidth)) + return; + + const qreal oldContentWidth = contentWidth; + contentWidth = implicitContentWidth; + qCDebug(lcPane) << "contentWidth of" << q << "changed to" << contentWidth; + q->contentSizeChange(QSizeF(contentWidth, contentHeight), QSizeF(oldContentWidth, contentHeight)); + emit q->contentWidthChanged(); +} + +void QQuickPanePrivate::updateContentHeight() +{ + Q_Q(QQuickPane); + if (hasContentHeight || qFuzzyCompare(contentHeight, implicitContentHeight)) + return; + + const qreal oldContentHeight = contentHeight; + contentHeight = implicitContentHeight; + qCDebug(lcPane) << "contentHeight of" << q << "changed to" << contentHeight; + q->contentSizeChange(QSizeF(contentWidth, contentHeight), QSizeF(contentWidth, oldContentHeight)); + emit q->contentHeightChanged(); +} + +QQuickPane::QQuickPane(QQuickItem *parent) + : QQuickControl(*(new QQuickPanePrivate), parent) +{ + Q_D(QQuickPane); + d->init(); +} + +QQuickPane::~QQuickPane() +{ + Q_D(QQuickPane); + d->removeImplicitSizeListener(d->contentItem); + d->removeImplicitSizeListener(d->firstChild); +} + +QQuickPane::QQuickPane(QQuickPanePrivate &dd, QQuickItem *parent) + : QQuickControl(dd, parent) +{ + Q_D(QQuickPane); + d->init(); +} + +/*! + \qmlproperty real QtQuick.Controls::Pane::contentWidth + + This property holds the content width. It is used for calculating the total + implicit width of the pane. + + For more information, see \l {Content Sizing}. + + \sa contentHeight +*/ +qreal QQuickPane::contentWidth() const +{ + Q_D(const QQuickPane); + return d->contentWidth; +} + +void QQuickPane::setContentWidth(qreal width) +{ + Q_D(QQuickPane); + d->hasContentWidth = true; + if (qFuzzyCompare(d->contentWidth, width)) + return; + + const qreal oldWidth = d->contentWidth; + d->contentWidth = width; + contentSizeChange(QSizeF(width, d->contentHeight), QSizeF(oldWidth, d->contentHeight)); + emit contentWidthChanged(); +} + +void QQuickPane::resetContentWidth() +{ + Q_D(QQuickPane); + if (!d->hasContentWidth) + return; + + d->hasContentHeight = false; + d->updateContentWidth(); +} + +/*! + \qmlproperty real QtQuick.Controls::Pane::contentHeight + + This property holds the content height. It is used for calculating the total + implicit height of the pane. + + For more information, see \l {Content Sizing}. + + \sa contentWidth +*/ +qreal QQuickPane::contentHeight() const +{ + Q_D(const QQuickPane); + return d->contentHeight; +} + +void QQuickPane::setContentHeight(qreal height) +{ + Q_D(QQuickPane); + d->hasContentHeight = true; + if (qFuzzyCompare(d->contentHeight, height)) + return; + + const qreal oldHeight = d->contentHeight; + d->contentHeight = height; + contentSizeChange(QSizeF(d->contentWidth, height), QSizeF(d->contentWidth, oldHeight)); + emit contentHeightChanged(); +} + +void QQuickPane::resetContentHeight() +{ + Q_D(QQuickPane); + if (!d->hasContentHeight) + return; + + d->hasContentHeight = false; + d->updateContentHeight(); +} + +/*! + \qmlproperty list<Object> QtQuick.Controls::Pane::contentData + \qmldefault + + This property holds the list of content data. + + The list contains all objects that have been declared in QML as children + of the pane. + + \note Unlike \c contentChildren, \c contentData does include non-visual QML + objects. + + \sa Item::data, contentChildren +*/ +QQmlListProperty<QObject> QQuickPanePrivate::contentData() +{ + Q_Q(QQuickPane); + return QQmlListProperty<QObject>(q->contentItem(), nullptr, + QQuickItemPrivate::data_append, + QQuickItemPrivate::data_count, + QQuickItemPrivate::data_at, + QQuickItemPrivate::data_clear); +} + +/*! + \qmlproperty list<Item> QtQuick.Controls::Pane::contentChildren + + This property holds the list of content children. + + The list contains all items that have been declared in QML as children + of the pane. + + \note Unlike \c contentData, \c contentChildren does not include non-visual + QML objects. + + \sa Item::children, contentData +*/ +QQmlListProperty<QQuickItem> QQuickPanePrivate::contentChildren() +{ + Q_Q(QQuickPane); + return QQmlListProperty<QQuickItem>(q->contentItem(), nullptr, + QQuickItemPrivate::children_append, + QQuickItemPrivate::children_count, + QQuickItemPrivate::children_at, + QQuickItemPrivate::children_clear); +} + +void QQuickPane::componentComplete() +{ + Q_D(QQuickPane); + QQuickControl::componentComplete(); + d->updateImplicitContentSize(); +} + +void QQuickPane::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickPane); + QQuickControl::contentItemChange(newItem, oldItem); + if (oldItem) { + d->removeImplicitSizeListener(oldItem); + QObjectPrivate::disconnect(oldItem, &QQuickItem::childrenChanged, d, &QQuickPanePrivate::contentChildrenChange); + } + if (newItem) { + d->addImplicitSizeListener(newItem); + QObjectPrivate::connect(newItem, &QQuickItem::childrenChanged, d, &QQuickPanePrivate::contentChildrenChange); + } + d->contentChildrenChange(); +} + +void QQuickPane::contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize) +{ + Q_UNUSED(newSize); + Q_UNUSED(oldSize); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickPane::accessibleRole() const +{ + return QAccessible::Pane; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickpane_p.cpp" diff --git a/src/quicktemplates2/qquickpane_p.h b/src/quicktemplates2/qquickpane_p.h new file mode 100644 index 0000000000..2aeb94cd7e --- /dev/null +++ b/src/quicktemplates2/qquickpane_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPANE_P_H +#define QQUICKPANE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQml/qqmllist.h> + +QT_BEGIN_NAMESPACE + +class QQuickPanePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPane : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth RESET resetContentWidth NOTIFY contentWidthChanged FINAL) + Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight RESET resetContentHeight NOTIFY contentHeightChanged FINAL) + Q_PRIVATE_PROPERTY(QQuickPane::d_func(), QQmlListProperty<QObject> contentData READ contentData FINAL) + Q_PRIVATE_PROPERTY(QQuickPane::d_func(), QQmlListProperty<QQuickItem> contentChildren READ contentChildren NOTIFY contentChildrenChanged FINAL) + Q_CLASSINFO("DefaultProperty", "contentData") + QML_NAMED_ELEMENT(Pane) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickPane(QQuickItem *parent = nullptr); + ~QQuickPane(); + + qreal contentWidth() const; + void setContentWidth(qreal width); + void resetContentWidth(); + + qreal contentHeight() const; + void setContentHeight(qreal height); + void resetContentHeight(); + +Q_SIGNALS: + void contentWidthChanged(); + void contentHeightChanged(); + void contentChildrenChanged(); + +protected: + QQuickPane(QQuickPanePrivate &dd, QQuickItem *parent); + + void componentComplete() override; + + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + virtual void contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize); + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickPane) + Q_DECLARE_PRIVATE(QQuickPane) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPane) + +#endif // QQUICKPANE_P_H diff --git a/src/quicktemplates2/qquickpane_p_p.h b/src/quicktemplates2/qquickpane_p_p.h new file mode 100644 index 0000000000..bd89aee2f8 --- /dev/null +++ b/src/quicktemplates2/qquickpane_p_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPANE_P_P_H +#define QQUICKPANE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickPane; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPanePrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickPane) + +public: + void init(); + + virtual QQmlListProperty<QObject> contentData(); + virtual QQmlListProperty<QQuickItem> contentChildren(); + virtual QList<QQuickItem *> contentChildItems() const; + + QQuickItem *getContentItem() override; + + qreal getContentWidth() const override; + qreal getContentHeight() const override; + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + void contentChildrenChange(); + + void updateContentWidth(); + void updateContentHeight(); + + bool hasContentWidth = false; + bool hasContentHeight = false; + qreal contentWidth = 0; + qreal contentHeight = 0; + QQuickItem *firstChild = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPANE_P_P_H diff --git a/src/quicktemplates2/qquickpopup.cpp b/src/quicktemplates2/qquickpopup.cpp new file mode 100644 index 0000000000..a3492fcb54 --- /dev/null +++ b/src/quicktemplates2/qquickpopup.cpp @@ -0,0 +1,2799 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpopup_p.h" +#include "qquickpopup_p_p.h" +#include "qquickpopupanchors_p.h" +#include "qquickpopupitem_p_p.h" +#include "qquickpopuppositioner_p_p.h" +#include "qquickapplicationwindow_p.h" +#include "qquickoverlay_p_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickdialog_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquicktransition_p.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcDimmer, "qt.quick.controls.popup.dimmer") +Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") + +/*! + \qmltype Popup + \inherits QtObject +//! \instantiates QQuickPopup + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-popups + \ingroup qtquickcontrols2-focusscopes + \brief Base type of popup-like user interface controls. + + Popup is the base type of popup-like user interface controls. It can be + used with \l Window or \l ApplicationWindow. + + \qml + import QtQuick.Window 2.2 + import QtQuick.Controls 2.12 + + ApplicationWindow { + id: window + width: 400 + height: 400 + visible: true + + Button { + text: "Open" + onClicked: popup.open() + } + + Popup { + id: popup + x: 100 + y: 100 + width: 200 + height: 300 + modal: true + focus: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + } + } + \endqml + + In order to ensure that a popup is displayed above other items in the + scene, it is recommended to use ApplicationWindow. ApplicationWindow also + provides background dimming effects. + + Popup does not provide a layout of its own, but requires you to position + its contents, for instance by creating a \l RowLayout or a \l ColumnLayout. + + Items declared as children of a Popup are automatically parented to the + Popups's \l contentItem. Items created dynamically need to be explicitly + parented to the contentItem. + + \section1 Popup Layout + + The following diagram illustrates the layout of a popup within a window: + + \image qtquickcontrols2-popup.png + + The \l implicitWidth and \l implicitHeight of a popup are typically based + on the implicit sizes of the background and the content item plus any insets + and paddings. These properties determine how large the popup will be when no + explicit \l width or \l height is specified. + + The geometry of the \l contentItem is determined by the padding. The following + example reserves 10px padding between the boundaries of the popup and its content: + + \code + Popup { + padding: 10 + + contentItem: Text { + text: "Content" + } + } + \endcode + + The \l background item fills the entire width and height of the popup, + unless insets or an explicit size have been given for it. + + Negative insets can be used to make the background larger than the popup. + The following example uses negative insets to place a shadow outside the + popup's boundaries: + + \code + Popup { + topInset: -2 + leftInset: -2 + rightInset: -6 + bottomInset: -6 + + background: BorderImage { + source: ":/images/shadowed-background.png" + } + } + \endcode + + \section1 Popup Sizing + + If only a single item is used within a Popup, it will resize to fit the + implicit size of its contained item. This makes it particularly suitable + for use together with layouts. + + \code + Popup { + ColumnLayout { + anchors.fill: parent + CheckBox { text: qsTr("E-mail") } + CheckBox { text: qsTr("Calendar") } + CheckBox { text: qsTr("Contacts") } + } + } + \endcode + + Sometimes there might be two items within the popup: + + \code + Popup { + SwipeView { + // ... + } + PageIndicator { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + } + } + \endcode + + In this case, Popup cannot calculate a sensible implicit size. Since we're + anchoring the \l PageIndicator over the \l SwipeView, we can simply set the + content size to the view's implicit size: + + \code + Popup { + contentWidth: view.implicitWidth + contentHeight: view.implicitHeight + + SwipeView { + id: view + // ... + } + PageIndicator { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + } + } + \endcode + + \section1 Popup Positioning + + Similar to items in Qt Quick, Popup's \l x and \l y coordinates are + relative to its parent. This means that opening a popup that is a + child of a \l Button, for example, will cause the popup to be positioned + relative to the button. + + \include qquickoverlay-popup-parent.qdocinc + + Another way to center a popup in the window regardless of its parent item + is to use \l {anchors.centerIn}: + + \snippet qtquickcontrols2-popup.qml centerIn + + To ensure that the popup is positioned within the bounds of the enclosing + window, the \l margins property can be set to a non-negative value. + + \section1 Popup Transitions + + Since Qt 5.15.3 the following properties are restored to their original values from before + the enter transition after the exit transition is completed. + + \list + \li \l opacity + \li \l scale + \endlist + + This allows the built-in styles to animate on these properties without losing any explicitly + defined value. + + \section1 Back/Escape Event Handling + + By default, a Popup will close if the Escape or Back keys are pressed. This + can be problematic for popups which contain items that want to handle those + events themselves. There are two solutions to this: + + \list + \li Set Popup's \l closePolicy to a value that does not include + \c {Popup.CloseOnEscape}. + \li Handle \l {Keys}' \l {Keys::}{shortcutOverride} signal and accept the + event before Popup can. + \endlist + + \sa {Popup Controls}, {Customizing Popup}, ApplicationWindow +*/ + +/*! + \qmlsignal void QtQuick.Controls::Popup::opened() + + This signal is emitted when the popup is opened. + + \sa aboutToShow() +*/ + +/*! + \qmlsignal void QtQuick.Controls::Popup::closed() + + This signal is emitted when the popup is closed. + + \sa aboutToHide() +*/ + +/*! + \qmlsignal void QtQuick.Controls::Popup::aboutToShow() + + This signal is emitted when the popup is about to show. + + \sa opened() +*/ + +/*! + \qmlsignal void QtQuick.Controls::Popup::aboutToHide() + + This signal is emitted when the popup is about to hide. + + \sa closed() +*/ + +const QQuickPopup::ClosePolicy QQuickPopupPrivate::DefaultClosePolicy = QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutside; + +QQuickPopupPrivate::QQuickPopupPrivate() + : transitionManager(this) +{ +} + +void QQuickPopupPrivate::init() +{ + Q_Q(QQuickPopup); + popupItem = new QQuickPopupItem(q); + popupItem->setVisible(false); + q->setParentItem(qobject_cast<QQuickItem *>(parent)); + QObject::connect(popupItem, &QQuickControl::paddingChanged, q, &QQuickPopup::paddingChanged); + QObject::connect(popupItem, &QQuickControl::backgroundChanged, q, &QQuickPopup::backgroundChanged); + QObject::connect(popupItem, &QQuickControl::contentItemChanged, q, &QQuickPopup::contentItemChanged); + QObject::connect(popupItem, &QQuickControl::implicitContentWidthChanged, q, &QQuickPopup::implicitContentWidthChanged); + QObject::connect(popupItem, &QQuickControl::implicitContentHeightChanged, q, &QQuickPopup::implicitContentHeightChanged); + QObject::connect(popupItem, &QQuickControl::implicitBackgroundWidthChanged, q, &QQuickPopup::implicitBackgroundWidthChanged); + QObject::connect(popupItem, &QQuickControl::implicitBackgroundHeightChanged, q, &QQuickPopup::implicitBackgroundHeightChanged); +} + +void QQuickPopupPrivate::closeOrReject() +{ + Q_Q(QQuickPopup); + if (QQuickDialog *dialog = qobject_cast<QQuickDialog*>(q)) + dialog->reject(); + else + q->close(); +} + +bool QQuickPopupPrivate::tryClose(const QPointF &pos, QQuickPopup::ClosePolicy flags) +{ + if (!interactive) + return false; + + static const QQuickPopup::ClosePolicy outsideFlags = QQuickPopup::CloseOnPressOutside | QQuickPopup::CloseOnReleaseOutside; + static const QQuickPopup::ClosePolicy outsideParentFlags = QQuickPopup::CloseOnPressOutsideParent | QQuickPopup::CloseOnReleaseOutsideParent; + + const bool onOutside = closePolicy & (flags & outsideFlags); + const bool onOutsideParent = closePolicy & (flags & outsideParentFlags); + + if ((onOutside && outsidePressed) || (onOutsideParent && outsideParentPressed)) { + if (!contains(pos) && (!dimmer || dimmer->contains(dimmer->mapFromScene(pos)))) { + if (!onOutsideParent || !parentItem || !parentItem->contains(parentItem->mapFromScene(pos))) { + closeOrReject(); + return true; + } + } + } + return false; +} + +bool QQuickPopupPrivate::contains(const QPointF &scenePos) const +{ + return popupItem->contains(popupItem->mapFromScene(scenePos)); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +bool QQuickPopupPrivate::acceptTouch(const QTouchEvent::TouchPoint &point) +{ + if (point.id() == touchId) + return true; + + if (touchId == -1 && point.state() != QEventPoint::Released) { + touchId = point.id(); + return true; + } + + return false; +} +#endif + +bool QQuickPopupPrivate::blockInput(QQuickItem *item, const QPointF &point) const +{ + // don't propagate events within the popup beyond the overlay + if (popupItem->contains(popupItem->mapFromScene(point)) + && item == QQuickOverlay::overlay(window)) { + return true; + } + + // don't block presses and releases + // a) outside a non-modal popup, + // b) to popup children/content, or + // b) outside a modal popups's background dimming + return modal && !popupItem->isAncestorOf(item) && (!dimmer || dimmer->contains(dimmer->mapFromScene(point))); +} + +bool QQuickPopupPrivate::handlePress(QQuickItem *item, const QPointF &point, ulong timestamp) +{ + Q_UNUSED(timestamp); + pressPoint = point; + outsidePressed = !contains(point); + outsideParentPressed = outsidePressed && parentItem && !parentItem->contains(parentItem->mapFromScene(point)); + tryClose(point, QQuickPopup::CloseOnPressOutside | QQuickPopup::CloseOnPressOutsideParent); + return blockInput(item, point); +} + +bool QQuickPopupPrivate::handleMove(QQuickItem *item, const QPointF &point, ulong timestamp) +{ + Q_UNUSED(timestamp); + return blockInput(item, point); +} + +bool QQuickPopupPrivate::handleRelease(QQuickItem *item, const QPointF &point, ulong timestamp) +{ + Q_UNUSED(timestamp); + if (item != popupItem && !contains(pressPoint)) + tryClose(point, QQuickPopup::CloseOnReleaseOutside | QQuickPopup::CloseOnReleaseOutsideParent); + pressPoint = QPointF(); + outsidePressed = false; + outsideParentPressed = false; + touchId = -1; + return blockInput(item, point); +} + +void QQuickPopupPrivate::handleUngrab() +{ + Q_Q(QQuickPopup); + QQuickOverlay *overlay = QQuickOverlay::overlay(window); + if (overlay) { + QQuickOverlayPrivate *p = QQuickOverlayPrivate::get(overlay); + if (p->mouseGrabberPopup == q) + p->mouseGrabberPopup = nullptr; + } + pressPoint = QPointF(); + touchId = -1; +} + +bool QQuickPopupPrivate::handleMouseEvent(QQuickItem *item, QMouseEvent *event) +{ + switch (event->type()) { + case QEvent::MouseButtonPress: + return handlePress(item, event->scenePosition(), event->timestamp()); + case QEvent::MouseMove: + return handleMove(item, event->scenePosition(), event->timestamp()); + case QEvent::MouseButtonRelease: + return handleRelease(item, event->scenePosition(), event->timestamp()); + default: + Q_UNREACHABLE(); + return false; + } +} + +bool QQuickPopupPrivate::handleHoverEvent(QQuickItem *item, QHoverEvent *event) +{ + switch (event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverMove: + case QEvent::HoverLeave: + return blockInput(item, event->scenePosition()); + default: + Q_UNREACHABLE(); + return false; + } +} + +#if QT_CONFIG(quicktemplates2_multitouch) +bool QQuickPopupPrivate::handleTouchEvent(QQuickItem *item, QTouchEvent *event) +{ + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + for (const QTouchEvent::TouchPoint &point : event->points()) { + if (event->type() != QEvent::TouchEnd && !acceptTouch(point)) + return blockInput(item, point.position()); + + switch (point.state()) { + case QEventPoint::Pressed: + return handlePress(item, item->mapToScene(point.position()), event->timestamp()); + case QEventPoint::Updated: + return handleMove(item, item->mapToScene(point.position()), event->timestamp()); + case QEventPoint::Released: + return handleRelease(item, item->mapToScene(point.position()), event->timestamp()); + default: + break; + } + } + break; + + case QEvent::TouchCancel: + handleUngrab(); + break; + + default: + break; + } + + return false; +} +#endif + +bool QQuickPopupPrivate::prepareEnterTransition() +{ + Q_Q(QQuickPopup); + if (!window) { + qmlWarning(q) << "cannot find any window to open popup in."; + return false; + } + + if (transitionState == EnterTransition && transitionManager.isRunning()) + return false; + + if (transitionState != EnterTransition) { + popupItem->setParentItem(QQuickOverlay::overlay(window)); + if (dim) + createOverlay(); + showOverlay(); + emit q->aboutToShow(); + visible = true; + transitionState = EnterTransition; + popupItem->setVisible(true); + getPositioner()->setParentItem(parentItem); + emit q->visibleChanged(); + + if (focus) + popupItem->setFocus(true, Qt::PopupFocusReason); + } + return true; +} + +bool QQuickPopupPrivate::prepareExitTransition() +{ + Q_Q(QQuickPopup); + if (transitionState == ExitTransition && transitionManager.isRunning()) + return false; + + // We need to cache the original scale and opacity values so we can reset it after + // the exit transition is done so they have the original values again + prevScale = popupItem->scale(); + prevOpacity = popupItem->opacity(); + + if (transitionState != ExitTransition) { + // The setFocus(false) call below removes any active focus before we're + // able to check it in finalizeExitTransition. + if (!hadActiveFocusBeforeExitTransition) + hadActiveFocusBeforeExitTransition = popupItem->hasActiveFocus(); + if (focus) + popupItem->setFocus(false, Qt::PopupFocusReason); + transitionState = ExitTransition; + hideOverlay(); + emit q->aboutToHide(); + emit q->openedChanged(); + } + return true; +} + +void QQuickPopupPrivate::finalizeEnterTransition() +{ + Q_Q(QQuickPopup); + transitionState = NoTransition; + getPositioner()->reposition(); + emit q->openedChanged(); + opened(); +} + +void QQuickPopupPrivate::finalizeExitTransition() +{ + Q_Q(QQuickPopup); + getPositioner()->setParentItem(nullptr); + if (popupItem) { + popupItem->setParentItem(nullptr); + popupItem->setVisible(false); + } + destroyOverlay(); + + if (hadActiveFocusBeforeExitTransition && window) { + // restore focus to the next popup in chain, or to the window content if there are no other popups open + QQuickPopup *nextFocusPopup = nullptr; + if (QQuickOverlay *overlay = QQuickOverlay::overlay(window)) { + const auto stackingOrderPopups = QQuickOverlayPrivate::get(overlay)->stackingOrderPopups(); + for (auto popup : stackingOrderPopups) { + if (QQuickPopupPrivate::get(popup)->transitionState != ExitTransition + && popup->hasFocus()) { + nextFocusPopup = popup; + break; + } + } + } + if (nextFocusPopup) { + nextFocusPopup->forceActiveFocus(Qt::PopupFocusReason); + } else { + QQuickApplicationWindow *applicationWindow = qobject_cast<QQuickApplicationWindow*>(window); + if (applicationWindow) + applicationWindow->contentItem()->setFocus(true, Qt::PopupFocusReason); + else + window->contentItem()->setFocus(true, Qt::PopupFocusReason); + } + } + + visible = false; + transitionState = NoTransition; + hadActiveFocusBeforeExitTransition = false; + emit q->visibleChanged(); + emit q->closed(); + if (popupItem) { + popupItem->setScale(prevScale); + popupItem->setOpacity(prevOpacity); + } +} + +void QQuickPopupPrivate::opened() +{ + Q_Q(QQuickPopup); + emit q->opened(); +} + +QMarginsF QQuickPopupPrivate::getMargins() const +{ + Q_Q(const QQuickPopup); + return QMarginsF(q->leftMargin(), q->topMargin(), q->rightMargin(), q->bottomMargin()); +} + +void QQuickPopupPrivate::setTopMargin(qreal value, bool reset) +{ + Q_Q(QQuickPopup); + qreal oldMargin = q->topMargin(); + topMargin = value; + hasTopMargin = !reset; + if ((!reset && !qFuzzyCompare(oldMargin, value)) || (reset && !qFuzzyCompare(oldMargin, margins))) { + emit q->topMarginChanged(); + q->marginsChange(QMarginsF(leftMargin, topMargin, rightMargin, bottomMargin), + QMarginsF(leftMargin, oldMargin, rightMargin, bottomMargin)); + } +} + +void QQuickPopupPrivate::setLeftMargin(qreal value, bool reset) +{ + Q_Q(QQuickPopup); + qreal oldMargin = q->leftMargin(); + leftMargin = value; + hasLeftMargin = !reset; + if ((!reset && !qFuzzyCompare(oldMargin, value)) || (reset && !qFuzzyCompare(oldMargin, margins))) { + emit q->leftMarginChanged(); + q->marginsChange(QMarginsF(leftMargin, topMargin, rightMargin, bottomMargin), + QMarginsF(oldMargin, topMargin, rightMargin, bottomMargin)); + } +} + +void QQuickPopupPrivate::setRightMargin(qreal value, bool reset) +{ + Q_Q(QQuickPopup); + qreal oldMargin = q->rightMargin(); + rightMargin = value; + hasRightMargin = !reset; + if ((!reset && !qFuzzyCompare(oldMargin, value)) || (reset && !qFuzzyCompare(oldMargin, margins))) { + emit q->rightMarginChanged(); + q->marginsChange(QMarginsF(leftMargin, topMargin, rightMargin, bottomMargin), + QMarginsF(leftMargin, topMargin, oldMargin, bottomMargin)); + } +} + +void QQuickPopupPrivate::setBottomMargin(qreal value, bool reset) +{ + Q_Q(QQuickPopup); + qreal oldMargin = q->bottomMargin(); + bottomMargin = value; + hasBottomMargin = !reset; + if ((!reset && !qFuzzyCompare(oldMargin, value)) || (reset && !qFuzzyCompare(oldMargin, margins))) { + emit q->bottomMarginChanged(); + q->marginsChange(QMarginsF(leftMargin, topMargin, rightMargin, bottomMargin), + QMarginsF(leftMargin, topMargin, rightMargin, oldMargin)); + } +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty Object QtQuick.Controls::Popup::anchors.centerIn + + Anchors provide a way to position an item by specifying its + relationship with other items. + + A common use case is to center a popup within its parent. One way to do + this is with the \l[QtQuick]{Item::}{x} and \l[QtQuick]{Item::}{y} properties. Anchors offer + a more convenient approach: + + \qml + Pane { + // ... + + Popup { + anchors.centerIn: parent + } + } + \endqml + + It is also possible to center the popup in the window by using \l Overlay: + + \snippet qtquickcontrols2-popup.qml centerIn + + This makes it easy to center a popup in the window from any component. + + \note Popups can only be centered within their immediate parent or + the window overlay; trying to center in other items will produce a warning. + + \sa {Popup Positioning}, {Item::}{anchors}, {Using Qt Quick Controls types + in property declarations} +*/ +QQuickPopupAnchors *QQuickPopupPrivate::getAnchors() +{ + Q_Q(QQuickPopup); + if (!anchors) + anchors = new QQuickPopupAnchors(q); + return anchors; +} + +QQuickPopupPositioner *QQuickPopupPrivate::getPositioner() +{ + Q_Q(QQuickPopup); + if (!positioner) + positioner = new QQuickPopupPositioner(q); + return positioner; +} + +void QQuickPopupPrivate::setWindow(QQuickWindow *newWindow) +{ + Q_Q(QQuickPopup); + if (window == newWindow) + return; + + if (window) { + QQuickOverlay *overlay = QQuickOverlay::overlay(window); + if (overlay) + QQuickOverlayPrivate::get(overlay)->removePopup(q); + } + + window = newWindow; + + if (newWindow) { + QQuickOverlay *overlay = QQuickOverlay::overlay(newWindow); + if (overlay) + QQuickOverlayPrivate::get(overlay)->addPopup(q); + + QQuickControlPrivate *p = QQuickControlPrivate::get(popupItem); + p->resolveFont(); + if (QQuickApplicationWindow *appWindow = qobject_cast<QQuickApplicationWindow *>(newWindow)) + p->updateLocale(appWindow->locale(), false); // explicit=false + } + + emit q->windowChanged(newWindow); + + if (complete && visible && window) + transitionManager.transitionEnter(); +} + +void QQuickPopupPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickPopup); + if (item == parentItem) + q->setParentItem(nullptr); +} + +void QQuickPopupPrivate::reposition() +{ + getPositioner()->reposition(); +} + +QPalette QQuickPopupPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::System); +} + +static QQuickItem *createDimmer(QQmlComponent *component, QQuickPopup *popup, QQuickItem *parent) +{ + QQuickItem *item = nullptr; + if (component) { + QQmlContext *creationContext = component->creationContext(); + if (!creationContext) + creationContext = qmlContext(popup); + QQmlContext *context = new QQmlContext(creationContext, popup); + context->setContextObject(popup); + item = qobject_cast<QQuickItem*>(component->beginCreate(context)); + } + + // when there is no overlay component available (with plain QQuickWindow), + // use a plain QQuickItem as a fallback to block hover events + if (!item && popup->isModal()) + item = new QQuickItem; + + if (item) { + item->setOpacity(popup->isVisible() ? 1.0 : 0.0); + item->setParentItem(parent); + item->stackBefore(popup->popupItem()); + item->setZ(popup->z()); + // needed for the virtual keyboard to set a containment mask on the dimmer item + qCDebug(lcDimmer) << "dimmer" << item << "registered with" << parent; + parent->setProperty("_q_dimmerItem", QVariant::fromValue<QQuickItem*>(item)); + if (popup->isModal()) { + item->setAcceptedMouseButtons(Qt::AllButtons); +#if QT_CONFIG(cursor) + item->setCursor(Qt::ArrowCursor); +#endif +#if QT_CONFIG(quicktemplates2_hover) + // TODO: switch to QStyleHints::useHoverEffects in Qt 5.8 + item->setAcceptHoverEvents(true); + // item->setAcceptHoverEvents(QGuiApplication::styleHints()->useHoverEffects()); + // connect(QGuiApplication::styleHints(), &QStyleHints::useHoverEffectsChanged, item, &QQuickItem::setAcceptHoverEvents); +#endif + } + if (component) + component->completeCreate(); + } + qCDebug(lcDimmer) << "finished creating dimmer from component" << component + << "for popup" << popup << "with parent" << parent << "- item is:" << item; + return item; +} + +void QQuickPopupPrivate::createOverlay() +{ + Q_Q(QQuickPopup); + QQuickOverlay *overlay = QQuickOverlay::overlay(window); + if (!overlay) + return; + + QQmlComponent *component = nullptr; + QQuickOverlayAttached *overlayAttached = qobject_cast<QQuickOverlayAttached *>(qmlAttachedPropertiesObject<QQuickOverlay>(q, false)); + if (overlayAttached) + component = modal ? overlayAttached->modal() : overlayAttached->modeless(); + + if (!component) + component = modal ? overlay->modal() : overlay->modeless(); + + if (!dimmer) + dimmer = createDimmer(component, q, overlay); + resizeOverlay(); +} + +void QQuickPopupPrivate::destroyOverlay() +{ + if (dimmer) { + qCDebug(lcDimmer) << "destroying dimmer" << dimmer; + if (QObject *dimmerParentItem = dimmer->parentItem()) { + if (dimmerParentItem->property("_q_dimmerItem").value<QQuickItem*>() == dimmer) + dimmerParentItem->setProperty("_q_dimmerItem", QVariant()); + } + dimmer->setParentItem(nullptr); + dimmer->deleteLater(); + dimmer = nullptr; + } +} + +void QQuickPopupPrivate::toggleOverlay() +{ + destroyOverlay(); + if (dim) + createOverlay(); +} + +void QQuickPopupPrivate::showOverlay() +{ + // use QQmlProperty instead of QQuickItem::setOpacity() to trigger QML Behaviors + if (dim && dimmer) + QQmlProperty::write(dimmer, QStringLiteral("opacity"), 1.0); +} + +void QQuickPopupPrivate::hideOverlay() +{ + // use QQmlProperty instead of QQuickItem::setOpacity() to trigger QML Behaviors + if (dim && dimmer) + QQmlProperty::write(dimmer, QStringLiteral("opacity"), 0.0); +} + +void QQuickPopupPrivate::resizeOverlay() +{ + if (!dimmer) + return; + + qreal w = window ? window->width() : 0; + qreal h = window ? window->height() : 0; + dimmer->setSize(QSizeF(w, h)); +} + +QQuickPopupTransitionManager::QQuickPopupTransitionManager(QQuickPopupPrivate *popup) + : popup(popup) +{ +} + +void QQuickPopupTransitionManager::transitionEnter() +{ + if (popup->transitionState == QQuickPopupPrivate::ExitTransition) + cancel(); + + if (!popup->prepareEnterTransition()) + return; + + if (popup->window) + transition(popup->enterActions, popup->enter, popup->q_func()); + else + finished(); +} + +void QQuickPopupTransitionManager::transitionExit() +{ + if (!popup->prepareExitTransition()) + return; + + if (popup->window) + transition(popup->exitActions, popup->exit, popup->q_func()); + else + finished(); +} + +void QQuickPopupTransitionManager::finished() +{ + if (popup->transitionState == QQuickPopupPrivate::EnterTransition) + popup->finalizeEnterTransition(); + else if (popup->transitionState == QQuickPopupPrivate::ExitTransition) + popup->finalizeExitTransition(); +} + +QQuickPopup::QQuickPopup(QObject *parent) + : QObject(*(new QQuickPopupPrivate), parent) +{ + Q_D(QQuickPopup); + d->init(); +} + +QQuickPopup::QQuickPopup(QQuickPopupPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ + Q_D(QQuickPopup); + d->init(); +} + +QQuickPopup::~QQuickPopup() +{ + Q_D(QQuickPopup); + setParentItem(nullptr); + d->popupItem->ungrabShortcut(); + + // If the popup is destroyed before the exit transition finishes, + // the necessary cleanup (removing modal dimmers that block mouse events, + // emitting closed signal, etc.) won't happen. That's why we do it manually here. + if (d->transitionState == QQuickPopupPrivate::ExitTransition && d->transitionManager.isRunning()) + d->finalizeExitTransition(); + + delete d->popupItem; + d->popupItem = nullptr; + delete d->positioner; + d->positioner = nullptr; +} + +/*! + \qmlmethod void QtQuick.Controls::Popup::open() + + Opens the popup. + + \sa visible +*/ +void QQuickPopup::open() +{ + setVisible(true); +} + +/*! + \qmlmethod void QtQuick.Controls::Popup::close() + + Closes the popup. + + \sa visible +*/ +void QQuickPopup::close() +{ + setVisible(false); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::x + + This property holds the x-coordinate of the popup. + + \sa y, z +*/ +qreal QQuickPopup::x() const +{ + Q_D(const QQuickPopup); + return d->effectiveX; +} + +void QQuickPopup::setX(qreal x) +{ + Q_D(QQuickPopup); + setPosition(QPointF(x, d->y)); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::y + + This property holds the y-coordinate of the popup. + + \sa x, z +*/ +qreal QQuickPopup::y() const +{ + Q_D(const QQuickPopup); + return d->effectiveY; +} + +void QQuickPopup::setY(qreal y) +{ + Q_D(QQuickPopup); + setPosition(QPointF(d->x, y)); +} + +QPointF QQuickPopup::position() const +{ + Q_D(const QQuickPopup); + return QPointF(d->effectiveX, d->effectiveY); +} + +void QQuickPopup::setPosition(const QPointF &pos) +{ + Q_D(QQuickPopup); + const bool xChange = !qFuzzyCompare(d->x, pos.x()); + const bool yChange = !qFuzzyCompare(d->y, pos.y()); + if (!xChange && !yChange) + return; + + d->x = pos.x(); + d->y = pos.y(); + if (d->popupItem->isVisible()) { + d->reposition(); + } else { + if (xChange) + emit xChanged(); + if (yChange) + emit yChanged(); + } +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::z + + This property holds the z-value of the popup. Z-value determines + the stacking order of popups. + + If two visible popups have the same z-value, the last one that + was opened will be on top. + + The default z-value is \c 0. + + \sa x, y +*/ +qreal QQuickPopup::z() const +{ + Q_D(const QQuickPopup); + return d->popupItem->z(); +} + +void QQuickPopup::setZ(qreal z) +{ + Q_D(QQuickPopup); + if (qFuzzyCompare(z, d->popupItem->z())) + return; + d->popupItem->setZ(z); + emit zChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::width + + This property holds the width of the popup. +*/ +qreal QQuickPopup::width() const +{ + Q_D(const QQuickPopup); + return d->popupItem->width(); +} + +void QQuickPopup::setWidth(qreal width) +{ + Q_D(QQuickPopup); + d->hasWidth = true; + d->popupItem->setWidth(width); +} + +void QQuickPopup::resetWidth() +{ + Q_D(QQuickPopup); + if (!d->hasWidth) + return; + + d->hasWidth = false; + d->popupItem->resetWidth(); + if (d->popupItem->isVisible()) + d->reposition(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::height + + This property holds the height of the popup. +*/ +qreal QQuickPopup::height() const +{ + Q_D(const QQuickPopup); + return d->popupItem->height(); +} + +void QQuickPopup::setHeight(qreal height) +{ + Q_D(QQuickPopup); + d->hasHeight = true; + d->popupItem->setHeight(height); +} + +void QQuickPopup::resetHeight() +{ + Q_D(QQuickPopup); + if (!d->hasHeight) + return; + + d->hasHeight = false; + d->popupItem->resetHeight(); + if (d->popupItem->isVisible()) + d->reposition(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::implicitWidth + + This property holds the implicit width of the popup. +*/ +qreal QQuickPopup::implicitWidth() const +{ + Q_D(const QQuickPopup); + return d->popupItem->implicitWidth(); +} + +void QQuickPopup::setImplicitWidth(qreal width) +{ + Q_D(QQuickPopup); + d->popupItem->setImplicitWidth(width); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::implicitHeight + + This property holds the implicit height of the popup. +*/ +qreal QQuickPopup::implicitHeight() const +{ + Q_D(const QQuickPopup); + return d->popupItem->implicitHeight(); +} + +void QQuickPopup::setImplicitHeight(qreal height) +{ + Q_D(QQuickPopup); + d->popupItem->setImplicitHeight(height); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::contentWidth + + This property holds the content width. It is used for calculating the + total implicit width of the Popup. + + For more information, see \l {Popup Sizing}. + + \sa contentHeight +*/ +qreal QQuickPopup::contentWidth() const +{ + Q_D(const QQuickPopup); + return d->popupItem->contentWidth(); +} + +void QQuickPopup::setContentWidth(qreal width) +{ + Q_D(QQuickPopup); + d->popupItem->setContentWidth(width); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::contentHeight + + This property holds the content height. It is used for calculating the + total implicit height of the Popup. + + For more information, see \l {Popup Sizing}. + + \sa contentWidth +*/ +qreal QQuickPopup::contentHeight() const +{ + Q_D(const QQuickPopup); + return d->popupItem->contentHeight(); +} + +void QQuickPopup::setContentHeight(qreal height) +{ + Q_D(QQuickPopup); + d->popupItem->setContentHeight(height); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::availableWidth + \readonly + + This property holds the width available to the \l contentItem after + deducting horizontal padding from the \l {Item::}{width} of the popup. + + \sa padding, leftPadding, rightPadding +*/ +qreal QQuickPopup::availableWidth() const +{ + Q_D(const QQuickPopup); + return d->popupItem->availableWidth(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::availableHeight + \readonly + + This property holds the height available to the \l contentItem after + deducting vertical padding from the \l {Item::}{height} of the popup. + + \sa padding, topPadding, bottomPadding +*/ +qreal QQuickPopup::availableHeight() const +{ + Q_D(const QQuickPopup); + return d->popupItem->availableHeight(); +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlproperty real QtQuick.Controls::Popup::spacing + + This property holds the spacing. + + Spacing is useful for popups that have multiple or repetitive building + blocks. For example, some styles use spacing to determine the distance + between the header, content, and footer of \l Dialog. Spacing is not + enforced by Popup, so each style may interpret it differently, and some + may ignore it altogether. +*/ +qreal QQuickPopup::spacing() const +{ + Q_D(const QQuickPopup); + return d->popupItem->spacing(); +} + +void QQuickPopup::setSpacing(qreal spacing) +{ + Q_D(QQuickPopup); + d->popupItem->setSpacing(spacing); +} + +void QQuickPopup::resetSpacing() +{ + setSpacing(0); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::margins + + This property holds the distance between the edges of the popup and the + edges of its window. + + A popup with negative margins is not pushed within the bounds + of the enclosing window. The default value is \c -1. + + \sa topMargin, leftMargin, rightMargin, bottomMargin, {Popup Layout} +*/ +qreal QQuickPopup::margins() const +{ + Q_D(const QQuickPopup); + return d->margins; +} + +void QQuickPopup::setMargins(qreal margins) +{ + Q_D(QQuickPopup); + if (qFuzzyCompare(d->margins, margins)) + return; + QMarginsF oldMargins(leftMargin(), topMargin(), rightMargin(), bottomMargin()); + d->margins = margins; + emit marginsChanged(); + QMarginsF newMargins(leftMargin(), topMargin(), rightMargin(), bottomMargin()); + if (!qFuzzyCompare(newMargins.top(), oldMargins.top())) + emit topMarginChanged(); + if (!qFuzzyCompare(newMargins.left(), oldMargins.left())) + emit leftMarginChanged(); + if (!qFuzzyCompare(newMargins.right(), oldMargins.right())) + emit rightMarginChanged(); + if (!qFuzzyCompare(newMargins.bottom(), oldMargins.bottom())) + emit bottomMarginChanged(); + marginsChange(newMargins, oldMargins); +} + +void QQuickPopup::resetMargins() +{ + setMargins(-1); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::topMargin + + This property holds the distance between the top edge of the popup and + the top edge of its window. + + A popup with a negative top margin is not pushed within the top edge + of the enclosing window. The default value is \c -1. + + \sa margins, bottomMargin, {Popup Layout} +*/ +qreal QQuickPopup::topMargin() const +{ + Q_D(const QQuickPopup); + if (d->hasTopMargin) + return d->topMargin; + return d->margins; +} + +void QQuickPopup::setTopMargin(qreal margin) +{ + Q_D(QQuickPopup); + d->setTopMargin(margin); +} + +void QQuickPopup::resetTopMargin() +{ + Q_D(QQuickPopup); + d->setTopMargin(-1, true); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::leftMargin + + This property holds the distance between the left edge of the popup and + the left edge of its window. + + A popup with a negative left margin is not pushed within the left edge + of the enclosing window. The default value is \c -1. + + \sa margins, rightMargin, {Popup Layout} +*/ +qreal QQuickPopup::leftMargin() const +{ + Q_D(const QQuickPopup); + if (d->hasLeftMargin) + return d->leftMargin; + return d->margins; +} + +void QQuickPopup::setLeftMargin(qreal margin) +{ + Q_D(QQuickPopup); + d->setLeftMargin(margin); +} + +void QQuickPopup::resetLeftMargin() +{ + Q_D(QQuickPopup); + d->setLeftMargin(-1, true); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::rightMargin + + This property holds the distance between the right edge of the popup and + the right edge of its window. + + A popup with a negative right margin is not pushed within the right edge + of the enclosing window. The default value is \c -1. + + \sa margins, leftMargin, {Popup Layout} +*/ +qreal QQuickPopup::rightMargin() const +{ + Q_D(const QQuickPopup); + if (d->hasRightMargin) + return d->rightMargin; + return d->margins; +} + +void QQuickPopup::setRightMargin(qreal margin) +{ + Q_D(QQuickPopup); + d->setRightMargin(margin); +} + +void QQuickPopup::resetRightMargin() +{ + Q_D(QQuickPopup); + d->setRightMargin(-1, true); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::bottomMargin + + This property holds the distance between the bottom edge of the popup and + the bottom edge of its window. + + A popup with a negative bottom margin is not pushed within the bottom edge + of the enclosing window. The default value is \c -1. + + \sa margins, topMargin, {Popup Layout} +*/ +qreal QQuickPopup::bottomMargin() const +{ + Q_D(const QQuickPopup); + if (d->hasBottomMargin) + return d->bottomMargin; + return d->margins; +} + +void QQuickPopup::setBottomMargin(qreal margin) +{ + Q_D(QQuickPopup); + d->setBottomMargin(margin); +} + +void QQuickPopup::resetBottomMargin() +{ + Q_D(QQuickPopup); + d->setBottomMargin(-1, true); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::padding + + This property holds the default padding. + + \include qquickpopup-padding.qdocinc + + \sa availableWidth, availableHeight, topPadding, leftPadding, rightPadding, bottomPadding +*/ +qreal QQuickPopup::padding() const +{ + Q_D(const QQuickPopup); + return d->popupItem->padding(); +} + +void QQuickPopup::setPadding(qreal padding) +{ + Q_D(QQuickPopup); + d->popupItem->setPadding(padding); +} + +void QQuickPopup::resetPadding() +{ + Q_D(QQuickPopup); + d->popupItem->resetPadding(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::topPadding + + This property holds the top padding. Unless explicitly set, the value + is equal to \c verticalPadding. + + \include qquickpopup-padding.qdocinc + + \sa padding, bottomPadding, verticalPadding, availableHeight +*/ +qreal QQuickPopup::topPadding() const +{ + Q_D(const QQuickPopup); + return d->popupItem->topPadding(); +} + +void QQuickPopup::setTopPadding(qreal padding) +{ + Q_D(QQuickPopup); + d->popupItem->setTopPadding(padding); +} + +void QQuickPopup::resetTopPadding() +{ + Q_D(QQuickPopup); + d->popupItem->resetTopPadding(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::leftPadding + + This property holds the left padding. Unless explicitly set, the value + is equal to \c horizontalPadding. + + \include qquickpopup-padding.qdocinc + + \sa padding, rightPadding, horizontalPadding, availableWidth +*/ +qreal QQuickPopup::leftPadding() const +{ + Q_D(const QQuickPopup); + return d->popupItem->leftPadding(); +} + +void QQuickPopup::setLeftPadding(qreal padding) +{ + Q_D(QQuickPopup); + d->popupItem->setLeftPadding(padding); +} + +void QQuickPopup::resetLeftPadding() +{ + Q_D(QQuickPopup); + d->popupItem->resetLeftPadding(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::rightPadding + + This property holds the right padding. Unless explicitly set, the value + is equal to \c horizontalPadding. + + \include qquickpopup-padding.qdocinc + + \sa padding, leftPadding, horizontalPadding, availableWidth +*/ +qreal QQuickPopup::rightPadding() const +{ + Q_D(const QQuickPopup); + return d->popupItem->rightPadding(); +} + +void QQuickPopup::setRightPadding(qreal padding) +{ + Q_D(QQuickPopup); + d->popupItem->setRightPadding(padding); +} + +void QQuickPopup::resetRightPadding() +{ + Q_D(QQuickPopup); + d->popupItem->resetRightPadding(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::bottomPadding + + This property holds the bottom padding. Unless explicitly set, the value + is equal to \c verticalPadding. + + \include qquickpopup-padding.qdocinc + + \sa padding, topPadding, verticalPadding, availableHeight +*/ +qreal QQuickPopup::bottomPadding() const +{ + Q_D(const QQuickPopup); + return d->popupItem->bottomPadding(); +} + +void QQuickPopup::setBottomPadding(qreal padding) +{ + Q_D(QQuickPopup); + d->popupItem->setBottomPadding(padding); +} + +void QQuickPopup::resetBottomPadding() +{ + Q_D(QQuickPopup); + d->popupItem->resetBottomPadding(); +} + +/*! + \qmlproperty Locale QtQuick.Controls::Popup::locale + + This property holds the locale of the popup. + + \sa mirrored, {LayoutMirroring}{LayoutMirroring} +*/ +QLocale QQuickPopup::locale() const +{ + Q_D(const QQuickPopup); + return d->popupItem->locale(); +} + +void QQuickPopup::setLocale(const QLocale &locale) +{ + Q_D(QQuickPopup); + d->popupItem->setLocale(locale); +} + +void QQuickPopup::resetLocale() +{ + Q_D(QQuickPopup); + d->popupItem->resetLocale(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::Popup::mirrored + \readonly + + This property holds whether the popup is mirrored. + + This property is provided for convenience. A popup is considered mirrored + when its visual layout direction is right-to-left; that is, when using a + right-to-left locale. + + \sa locale, {Right-to-left User Interfaces} +*/ +bool QQuickPopup::isMirrored() const +{ + Q_D(const QQuickPopup); + return d->popupItem->isMirrored(); +} + +/*! + \qmlproperty font QtQuick.Controls::Popup::font + + This property holds the font currently set for the popup. + + Popup propagates explicit font properties to its children. If you change a specific + property on a popup's font, that property propagates to all of the popup's children, + overriding any system defaults for that property. + + \code + Popup { + font.family: "Courier" + + Column { + Label { + text: qsTr("This will use Courier...") + } + + Switch { + text: qsTr("... and so will this") + } + } + } + \endcode + + \sa Control::font, ApplicationWindow::font +*/ +QFont QQuickPopup::font() const +{ + Q_D(const QQuickPopup); + return d->popupItem->font(); +} + +void QQuickPopup::setFont(const QFont &font) +{ + Q_D(QQuickPopup); + d->popupItem->setFont(font); +} + +void QQuickPopup::resetFont() +{ + Q_D(QQuickPopup); + d->popupItem->resetFont(); +} + +QQuickWindow *QQuickPopup::window() const +{ + Q_D(const QQuickPopup); + return d->window; +} + +QQuickItem *QQuickPopup::popupItem() const +{ + Q_D(const QQuickPopup); + return d->popupItem; +} + +/*! + \qmlproperty Item QtQuick.Controls::Popup::parent + + This property holds the parent item. +*/ +QQuickItem *QQuickPopup::parentItem() const +{ + Q_D(const QQuickPopup); + return d->parentItem; +} + +void QQuickPopup::setParentItem(QQuickItem *parent) +{ + Q_D(QQuickPopup); + if (d->parentItem == parent) + return; + + if (d->parentItem) { + QObjectPrivate::disconnect(d->parentItem, &QQuickItem::windowChanged, d, &QQuickPopupPrivate::setWindow); + QQuickItemPrivate::get(d->parentItem)->removeItemChangeListener(d, QQuickItemPrivate::Destroyed); + } + d->parentItem = parent; + QQuickPopupPositioner *positioner = d->getPositioner(); + if (positioner->parentItem()) + positioner->setParentItem(parent); + if (parent) { + QObjectPrivate::connect(parent, &QQuickItem::windowChanged, d, &QQuickPopupPrivate::setWindow); + QQuickItemPrivate::get(d->parentItem)->addItemChangeListener(d, QQuickItemPrivate::Destroyed); + } else { + close(); + } + d->setWindow(parent ? parent->window() : nullptr); + emit parentChanged(); +} + +void QQuickPopup::resetParentItem() +{ + if (QQuickWindow *window = qobject_cast<QQuickWindow *>(parent())) + setParentItem(window->contentItem()); + else + setParentItem(qobject_cast<QQuickItem *>(parent())); +} + +/*! + \qmlproperty Item QtQuick.Controls::Popup::background + + This property holds the background item. + + \note If the background item has no explicit size specified, it automatically + follows the popup's size. In most cases, there is no need to specify + width or height for a background item. + + \note Most popups use the implicit size of the background item to calculate + the implicit size of the popup itself. If you replace the background item + with a custom one, you should also consider providing a sensible implicit + size for it (unless it is an item like \l Image which has its own implicit + size). + + \sa {Customizing Popup} +*/ +QQuickItem *QQuickPopup::background() const +{ + Q_D(const QQuickPopup); + return d->popupItem->background(); +} + +void QQuickPopup::setBackground(QQuickItem *background) +{ + Q_D(QQuickPopup); + d->popupItem->setBackground(background); +} + +/*! + \qmlproperty Item QtQuick.Controls::Popup::contentItem + + This property holds the content item of the popup. + + The content item is the visual implementation of the popup. When the + popup is made visible, the content item is automatically reparented to + the \l {Overlay::overlay}{overlay item}. + + \note The content item is automatically resized to fit within the + \l padding of the popup. + + \note Most popups use the implicit size of the content item to calculate + the implicit size of the popup itself. If you replace the content item + with a custom one, you should also consider providing a sensible implicit + size for it (unless it is an item like \l Text which has its own implicit + size). + + \sa {Customizing Popup} +*/ +QQuickItem *QQuickPopup::contentItem() const +{ + Q_D(const QQuickPopup); + return d->popupItem->contentItem(); +} + +void QQuickPopup::setContentItem(QQuickItem *item) +{ + Q_D(QQuickPopup); + d->popupItem->setContentItem(item); +} + +/*! + \qmlproperty list<Object> QtQuick.Controls::Popup::contentData + \qmldefault + + This property holds the list of content data. + + The list contains all objects that have been declared in QML as children + of the popup. + + \note Unlike \c contentChildren, \c contentData does include non-visual QML + objects. + + \sa Item::data, contentChildren +*/ +QQmlListProperty<QObject> QQuickPopupPrivate::contentData() +{ + QQuickControlPrivate *p = QQuickControlPrivate::get(popupItem); + if (!p->contentItem) + p->executeContentItem(); + return QQmlListProperty<QObject>(popupItem->contentItem(), nullptr, + QQuickItemPrivate::data_append, + QQuickItemPrivate::data_count, + QQuickItemPrivate::data_at, + QQuickItemPrivate::data_clear); +} + +/*! + \qmlproperty list<Item> QtQuick.Controls::Popup::contentChildren + + This property holds the list of content children. + + The list contains all items that have been declared in QML as children + of the popup. + + \note Unlike \c contentData, \c contentChildren does not include non-visual + QML objects. + + \sa Item::children, contentData +*/ +QQmlListProperty<QQuickItem> QQuickPopupPrivate::contentChildren() +{ + return QQmlListProperty<QQuickItem>(popupItem->contentItem(), nullptr, + QQuickItemPrivate::children_append, + QQuickItemPrivate::children_count, + QQuickItemPrivate::children_at, + QQuickItemPrivate::children_clear); +} + +/*! + \qmlproperty bool QtQuick.Controls::Popup::clip + + This property holds whether clipping is enabled. The default value is \c false. +*/ +bool QQuickPopup::clip() const +{ + Q_D(const QQuickPopup); + return d->popupItem->clip(); +} + +void QQuickPopup::setClip(bool clip) +{ + Q_D(QQuickPopup); + if (clip == d->popupItem->clip()) + return; + d->popupItem->setClip(clip); + emit clipChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Popup::focus + + This property holds whether the popup wants focus. + + When the popup actually receives focus, \l activeFocus will be \c true. + For more information, see \l {Keyboard Focus in Qt Quick}. + + The default value is \c false. + + \sa activeFocus +*/ +bool QQuickPopup::hasFocus() const +{ + Q_D(const QQuickPopup); + return d->focus; +} + +void QQuickPopup::setFocus(bool focus) +{ + Q_D(QQuickPopup); + if (d->focus == focus) + return; + d->focus = focus; + emit focusChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Popup::activeFocus + \readonly + + This property holds whether the popup has active focus. + + \sa focus, {Keyboard Focus in Qt Quick} +*/ +bool QQuickPopup::hasActiveFocus() const +{ + Q_D(const QQuickPopup); + return d->popupItem->hasActiveFocus(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Popup::modal + + This property holds whether the popup is modal. + + Modal popups often have a distinctive background dimming effect defined + in \l {Overlay::modal}{Overlay.modal}, and do not allow press + or release events through to items beneath them. For example, if the user + accidentally clicks outside of a popup, any item beneath that popup at + the location of the click will not receive the event. + + On desktop platforms, it is common for modal popups to be closed only when + the escape key is pressed. To achieve this behavior, set + \l closePolicy to \c Popup.CloseOnEscape. By default, \c closePolicy + is set to \c {Popup.CloseOnEscape | Popup.CloseOnPressOutside}, which + means that clicking outside of a modal popup will close it. + + The default value is \c false. + + \sa dim +*/ +bool QQuickPopup::isModal() const +{ + Q_D(const QQuickPopup); + return d->modal; +} + +void QQuickPopup::setModal(bool modal) +{ + Q_D(QQuickPopup); + if (d->modal == modal) + return; + d->modal = modal; + if (d->complete && d->visible) + d->toggleOverlay(); + emit modalChanged(); + + QQuickItemPrivate::get(d->popupItem)->isTabFence = modal; + + if (!d->hasDim) { + setDim(modal); + d->hasDim = false; + } +} + +/*! + \qmlproperty bool QtQuick.Controls::Popup::dim + + This property holds whether the popup dims the background. + + Unless explicitly set, this property follows the value of \l modal. To + return to the default value, set this property to \c undefined. + + \sa modal, {Overlay::modeless}{Overlay.modeless} +*/ +bool QQuickPopup::dim() const +{ + Q_D(const QQuickPopup); + return d->dim; +} + +void QQuickPopup::setDim(bool dim) +{ + Q_D(QQuickPopup); + d->hasDim = true; + + if (d->dim == dim) + return; + + d->dim = dim; + if (d->complete && d->visible) + d->toggleOverlay(); + emit dimChanged(); +} + +void QQuickPopup::resetDim() +{ + Q_D(QQuickPopup); + if (!d->hasDim) + return; + + setDim(d->modal); + d->hasDim = false; +} + +/*! + \qmlproperty bool QtQuick.Controls::Popup::visible + + This property holds whether the popup is visible. The default value is \c false. + + \sa open(), close(), opened +*/ +bool QQuickPopup::isVisible() const +{ + Q_D(const QQuickPopup); + return d->visible && d->popupItem->isVisible(); +} + +void QQuickPopup::setVisible(bool visible) +{ + Q_D(QQuickPopup); + if (d->visible == visible && d->transitionState != QQuickPopupPrivate::ExitTransition) + return; + + if (d->complete) { + if (visible) + d->transitionManager.transitionEnter(); + else + d->transitionManager.transitionExit(); + } else { + d->visible = visible; + } +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::Popup::enabled + + This property holds whether the popup is enabled. The default value is \c true. + + \sa visible, Item::enabled +*/ +bool QQuickPopup::isEnabled() const +{ + Q_D(const QQuickPopup); + return d->popupItem->isEnabled(); +} + +void QQuickPopup::setEnabled(bool enabled) +{ + Q_D(QQuickPopup); + d->popupItem->setEnabled(enabled); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::Popup::opened + + This property holds whether the popup is fully open. The popup is considered opened + when it's visible and neither the \l enter nor \l exit transitions are running. + + \sa open(), close(), visible +*/ +bool QQuickPopup::isOpened() const +{ + Q_D(const QQuickPopup); + return d->transitionState == QQuickPopupPrivate::NoTransition && isVisible(); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::opacity + + This property holds the opacity of the popup. Opacity is specified as a number between + \c 0.0 (fully transparent) and \c 1.0 (fully opaque). The default value is \c 1.0. + + \sa visible +*/ +qreal QQuickPopup::opacity() const +{ + Q_D(const QQuickPopup); + return d->popupItem->opacity(); +} + +void QQuickPopup::setOpacity(qreal opacity) +{ + Q_D(QQuickPopup); + d->popupItem->setOpacity(opacity); +} + +/*! + \qmlproperty real QtQuick.Controls::Popup::scale + + This property holds the scale factor of the popup. The default value is \c 1.0. + + A scale of less than \c 1.0 causes the popup to be rendered at a smaller size, + and a scale greater than \c 1.0 renders the popup at a larger size. Negative + scales are not supported. +*/ +qreal QQuickPopup::scale() const +{ + Q_D(const QQuickPopup); + return d->popupItem->scale(); +} + +void QQuickPopup::setScale(qreal scale) +{ + Q_D(QQuickPopup); + if (qFuzzyCompare(scale, d->popupItem->scale())) + return; + d->popupItem->setScale(scale); + emit scaleChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Popup::closePolicy + + This property determines the circumstances under which the popup closes. + The flags can be combined to allow several ways of closing the popup. + + The available values are: + \value Popup.NoAutoClose The popup will only close when manually instructed to do so. + \value Popup.CloseOnPressOutside The popup will close when the mouse is pressed outside of it. + \value Popup.CloseOnPressOutsideParent The popup will close when the mouse is pressed outside of its parent. + \value Popup.CloseOnReleaseOutside The popup will close when the mouse is released outside of it. + \value Popup.CloseOnReleaseOutsideParent The popup will close when the mouse is released outside of its parent. + \value Popup.CloseOnEscape The popup will close when the escape key is pressed while the popup + has active focus. + + The \c {CloseOnPress*} and \c {CloseOnRelease*} policies only apply for events + outside of popups. That is, if there are two popups open and the first has + \c Popup.CloseOnPressOutside as its policy, clicking on the second popup will + not result in the first closing. + + The default value is \c {Popup.CloseOnEscape | Popup.CloseOnPressOutside}. + This default value may interfere with existing shortcuts in the application + that makes use of the \e Escape key. + + \note There is a known limitation that the \c Popup.CloseOnReleaseOutside + and \c Popup.CloseOnReleaseOutsideParent policies only work with + \l modal popups. +*/ +QQuickPopup::ClosePolicy QQuickPopup::closePolicy() const +{ + Q_D(const QQuickPopup); + return d->closePolicy; +} + +void QQuickPopup::setClosePolicy(ClosePolicy policy) +{ + Q_D(QQuickPopup); + d->hasClosePolicy = true; + if (d->closePolicy == policy) + return; + d->closePolicy = policy; + if (isVisible()) { + if (policy & QQuickPopup::CloseOnEscape) + d->popupItem->grabShortcut(); + else + d->popupItem->ungrabShortcut(); + } + emit closePolicyChanged(); +} + +void QQuickPopup::resetClosePolicy() +{ + Q_D(QQuickPopup); + setClosePolicy(QQuickPopupPrivate::DefaultClosePolicy); + d->hasClosePolicy = false; +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Popup::transformOrigin + + This property holds the origin point for transformations in enter and exit transitions. + + Nine transform origins are available, as shown in the image below. + The default transform origin is \c Popup.Center. + + \image qtquickcontrols2-popup-transformorigin.png + + \sa enter, exit, Item::transformOrigin +*/ +QQuickPopup::TransformOrigin QQuickPopup::transformOrigin() const +{ + Q_D(const QQuickPopup); + return static_cast<TransformOrigin>(d->popupItem->transformOrigin()); +} + +void QQuickPopup::setTransformOrigin(TransformOrigin origin) +{ + Q_D(QQuickPopup); + d->popupItem->setTransformOrigin(static_cast<QQuickItem::TransformOrigin>(origin)); +} + +/*! + \qmlproperty Transition QtQuick.Controls::Popup::enter + + This property holds the transition that is applied to the popup item + when the popup is opened and enters the screen. + + The following example animates the opacity of the popup when it enters + the screen: + \code + Popup { + enter: Transition { + NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 } + } + } + \endcode + + \sa exit +*/ +QQuickTransition *QQuickPopup::enter() const +{ + Q_D(const QQuickPopup); + return d->enter; +} + +void QQuickPopup::setEnter(QQuickTransition *transition) +{ + Q_D(QQuickPopup); + if (d->enter == transition) + return; + d->enter = transition; + emit enterChanged(); +} + +/*! + \qmlproperty Transition QtQuick.Controls::Popup::exit + + This property holds the transition that is applied to the popup item + when the popup is closed and exits the screen. + + The following example animates the opacity of the popup when it exits + the screen: + \code + Popup { + exit: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 } + } + } + \endcode + + \sa enter +*/ +QQuickTransition *QQuickPopup::exit() const +{ + Q_D(const QQuickPopup); + return d->exit; +} + +void QQuickPopup::setExit(QQuickTransition *transition) +{ + Q_D(QQuickPopup); + if (d->exit == transition) + return; + d->exit = transition; + emit exitChanged(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::horizontalPadding + + This property holds the horizontal padding. Unless explicitly set, the value + is equal to \c padding. + + \include qquickpopup-padding.qdocinc + + \sa padding, leftPadding, rightPadding, verticalPadding +*/ +qreal QQuickPopup::horizontalPadding() const +{ + Q_D(const QQuickPopup); + return d->popupItem->horizontalPadding(); +} + +void QQuickPopup::setHorizontalPadding(qreal padding) +{ + Q_D(QQuickPopup); + d->popupItem->setHorizontalPadding(padding); +} + +void QQuickPopup::resetHorizontalPadding() +{ + Q_D(QQuickPopup); + d->popupItem->resetHorizontalPadding(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::verticalPadding + + This property holds the vertical padding. Unless explicitly set, the value + is equal to \c padding. + + \include qquickpopup-padding.qdocinc + + \sa padding, topPadding, bottomPadding, horizontalPadding +*/ +qreal QQuickPopup::verticalPadding() const +{ + Q_D(const QQuickPopup); + return d->popupItem->verticalPadding(); +} + +void QQuickPopup::setVerticalPadding(qreal padding) +{ + Q_D(QQuickPopup); + d->popupItem->setVerticalPadding(padding); +} + +void QQuickPopup::resetVerticalPadding() +{ + Q_D(QQuickPopup); + d->popupItem->resetVerticalPadding(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::implicitContentWidth + \readonly + + This property holds the implicit content width. + + The value is calculated based on the content children. + + \sa implicitContentHeight, implicitBackgroundWidth +*/ +qreal QQuickPopup::implicitContentWidth() const +{ + Q_D(const QQuickPopup); + return d->popupItem->implicitContentWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::implicitContentHeight + \readonly + + This property holds the implicit content height. + + The value is calculated based on the content children. + + \sa implicitContentWidth, implicitBackgroundHeight +*/ +qreal QQuickPopup::implicitContentHeight() const +{ + Q_D(const QQuickPopup); + return d->popupItem->implicitContentHeight(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::implicitBackgroundWidth + \readonly + + This property holds the implicit background width. + + The value is equal to \c {background ? background.implicitWidth : 0}. + + \sa implicitBackgroundHeight, implicitContentWidth +*/ +qreal QQuickPopup::implicitBackgroundWidth() const +{ + Q_D(const QQuickPopup); + return d->popupItem->implicitBackgroundWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::implicitBackgroundHeight + \readonly + + This property holds the implicit background height. + + The value is equal to \c {background ? background.implicitHeight : 0}. + + \sa implicitBackgroundWidth, implicitContentHeight +*/ +qreal QQuickPopup::implicitBackgroundHeight() const +{ + Q_D(const QQuickPopup); + return d->popupItem->implicitBackgroundHeight(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::topInset + + This property holds the top inset for the background. + + \sa {Popup Layout}, bottomInset +*/ +qreal QQuickPopup::topInset() const +{ + Q_D(const QQuickPopup); + return d->popupItem->topInset(); +} + +void QQuickPopup::setTopInset(qreal inset) +{ + Q_D(QQuickPopup); + d->popupItem->setTopInset(inset); +} + +void QQuickPopup::resetTopInset() +{ + Q_D(QQuickPopup); + d->popupItem->resetTopInset(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::leftInset + + This property holds the left inset for the background. + + \sa {Popup Layout}, rightInset +*/ +qreal QQuickPopup::leftInset() const +{ + Q_D(const QQuickPopup); + return d->popupItem->leftInset(); +} + +void QQuickPopup::setLeftInset(qreal inset) +{ + Q_D(QQuickPopup); + d->popupItem->setLeftInset(inset); +} + +void QQuickPopup::resetLeftInset() +{ + Q_D(QQuickPopup); + d->popupItem->resetLeftInset(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::rightInset + + This property holds the right inset for the background. + + \sa {Popup Layout}, leftInset +*/ +qreal QQuickPopup::rightInset() const +{ + Q_D(const QQuickPopup); + return d->popupItem->rightInset(); +} + +void QQuickPopup::setRightInset(qreal inset) +{ + Q_D(QQuickPopup); + d->popupItem->setRightInset(inset); +} + +void QQuickPopup::resetRightInset() +{ + Q_D(QQuickPopup); + d->popupItem->resetRightInset(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Popup::bottomInset + + This property holds the bottom inset for the background. + + \sa {Popup Layout}, topInset +*/ +qreal QQuickPopup::bottomInset() const +{ + Q_D(const QQuickPopup); + return d->popupItem->bottomInset(); +} + +void QQuickPopup::setBottomInset(qreal inset) +{ + Q_D(QQuickPopup); + d->popupItem->setBottomInset(inset); +} + +void QQuickPopup::resetBottomInset() +{ + Q_D(QQuickPopup); + d->popupItem->resetBottomInset(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty palette QtQuick.Controls::Popup::palette + + This property holds the palette currently set for the popup. + + Popup propagates explicit palette properties to its children. If you change a specific + property on a popup's palette, that property propagates to all of the popup's children, + overriding any system defaults for that property. + + \code + Popup { + palette.text: "red" + + Column { + Label { + text: qsTr("This will use red color...") + } + + Switch { + text: qsTr("... and so will this") + } + } + } + \endcode + + \sa Item::palette, Window::palette, ColorGroup, Palette +*/ + +bool QQuickPopup::filtersChildMouseEvents() const +{ + Q_D(const QQuickPopup); + return d->popupItem->filtersChildMouseEvents(); +} + +void QQuickPopup::setFiltersChildMouseEvents(bool filter) +{ + Q_D(QQuickPopup); + d->popupItem->setFiltersChildMouseEvents(filter); +} + +/*! + \qmlmethod QtQuick.Controls::Popup::forceActiveFocus(enumeration reason = Qt.OtherFocusReason) + + Forces active focus on the popup with the given \a reason. + + This method sets focus on the popup and ensures that all ancestor + \l FocusScope objects in the object hierarchy are also given \l focus. + + \sa activeFocus, Qt::FocusReason +*/ +void QQuickPopup::forceActiveFocus(Qt::FocusReason reason) +{ + Q_D(QQuickPopup); + d->popupItem->forceActiveFocus(reason); +} + +void QQuickPopup::classBegin() +{ + Q_D(QQuickPopup); + d->complete = false; + QQmlContext *context = qmlContext(this); + if (context) + QQmlEngine::setContextForObject(d->popupItem, context); + d->popupItem->classBegin(); +} + +void QQuickPopup::componentComplete() +{ + Q_D(QQuickPopup); + qCDebug(lcPopup) << "componentComplete" << this; + if (!parentItem()) + resetParentItem(); + + if (d->visible && d->window) + d->transitionManager.transitionEnter(); + + d->complete = true; + d->popupItem->componentComplete(); + + if (isVisible()) { + if (d->closePolicy & QQuickPopup::CloseOnEscape) + d->popupItem->grabShortcut(); + else + d->popupItem->ungrabShortcut(); + } +} + +bool QQuickPopup::isComponentComplete() const +{ + Q_D(const QQuickPopup); + return d->complete; +} + +bool QQuickPopup::childMouseEventFilter(QQuickItem *child, QEvent *event) +{ + Q_UNUSED(child); + Q_UNUSED(event); + return false; +} + +void QQuickPopup::focusInEvent(QFocusEvent *event) +{ + event->accept(); +} + +void QQuickPopup::focusOutEvent(QFocusEvent *event) +{ + event->accept(); +} + +void QQuickPopup::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickPopup); + event->accept(); + + if (hasActiveFocus() && (event->key() == Qt::Key_Tab || event->key() == Qt::Key_Backtab)) + QQuickItemPrivate::focusNextPrev(d->popupItem, event->key() == Qt::Key_Tab); +} + +void QQuickPopup::keyReleaseEvent(QKeyEvent *event) +{ + event->accept(); +} + +void QQuickPopup::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickPopup); + event->setAccepted(d->handleMouseEvent(d->popupItem, event)); +} + +void QQuickPopup::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickPopup); + event->setAccepted(d->handleMouseEvent(d->popupItem, event)); +} + +void QQuickPopup::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickPopup); + event->setAccepted(d->handleMouseEvent(d->popupItem, event)); +} + +void QQuickPopup::mouseDoubleClickEvent(QMouseEvent *event) +{ + event->accept(); +} + +void QQuickPopup::mouseUngrabEvent() +{ + Q_D(QQuickPopup); + d->handleUngrab(); +} + +bool QQuickPopup::overlayEvent(QQuickItem *item, QEvent *event) +{ + Q_D(QQuickPopup); + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::MouseMove: + case QEvent::Wheel: + if (d->modal) + event->accept(); + return d->modal; + +#if QT_CONFIG(quicktemplates2_multitouch) + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + return d->handleTouchEvent(item, static_cast<QTouchEvent *>(event)); +#endif + case QEvent::HoverEnter: + case QEvent::HoverMove: + case QEvent::HoverLeave: + return d->handleHoverEvent(item, static_cast<QHoverEvent *>(event)); + + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + return d->handleMouseEvent(item, static_cast<QMouseEvent *>(event)); + + default: + return false; + } +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickPopup::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickPopup); + d->handleTouchEvent(d->popupItem, event); +} + +void QQuickPopup::touchUngrabEvent() +{ + Q_D(QQuickPopup); + d->handleUngrab(); +} +#endif + +#if QT_CONFIG(wheelevent) +void QQuickPopup::wheelEvent(QWheelEvent *event) +{ + event->accept(); +} +#endif + +void QQuickPopup::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_UNUSED(newItem); + Q_UNUSED(oldItem); +} + +void QQuickPopup::contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize) +{ + qCDebug(lcPopup) << "contentSizeChange called on" << this << "with newSize" << newSize << "oldSize" << oldSize; + if (!qFuzzyCompare(newSize.width(), oldSize.width())) + emit contentWidthChanged(); + if (!qFuzzyCompare(newSize.height(), oldSize.height())) + emit contentHeightChanged(); +} + +void QQuickPopup::fontChange(const QFont &newFont, const QFont &oldFont) +{ + Q_UNUSED(newFont); + Q_UNUSED(oldFont); + emit fontChanged(); +} + +void QQuickPopup::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickPopup); + qCDebug(lcPopup) << "geometryChange called on" << this << "with newGeometry" << newGeometry << "oldGeometry" << oldGeometry; + d->reposition(); + if (!qFuzzyCompare(newGeometry.width(), oldGeometry.width())) { + emit widthChanged(); + emit availableWidthChanged(); + } + if (!qFuzzyCompare(newGeometry.height(), oldGeometry.height())) { + emit heightChanged(); + emit availableHeightChanged(); + } +} + +void QQuickPopup::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) +{ + Q_D(QQuickPopup); + + switch (change) { + case QQuickItem::ItemActiveFocusHasChanged: + emit activeFocusChanged(); + break; + case QQuickItem::ItemOpacityHasChanged: + emit opacityChanged(); + break; + case QQuickItem::ItemVisibleHasChanged: + if (isComponentComplete() && d->closePolicy & CloseOnEscape) { + if (data.boolValue) + d->popupItem->grabShortcut(); + else + d->popupItem->ungrabShortcut(); + } + break; + default: + break; + } +} + +void QQuickPopup::localeChange(const QLocale &newLocale, const QLocale &oldLocale) +{ + Q_UNUSED(newLocale); + Q_UNUSED(oldLocale); + emit localeChanged(); +} + +void QQuickPopup::marginsChange(const QMarginsF &newMargins, const QMarginsF &oldMargins) +{ + Q_D(QQuickPopup); + Q_UNUSED(newMargins); + Q_UNUSED(oldMargins); + d->reposition(); +} + +void QQuickPopup::paddingChange(const QMarginsF &newPadding, const QMarginsF &oldPadding) +{ + const bool tp = !qFuzzyCompare(newPadding.top(), oldPadding.top()); + const bool lp = !qFuzzyCompare(newPadding.left(), oldPadding.left()); + const bool rp = !qFuzzyCompare(newPadding.right(), oldPadding.right()); + const bool bp = !qFuzzyCompare(newPadding.bottom(), oldPadding.bottom()); + + if (tp) + emit topPaddingChanged(); + if (lp) + emit leftPaddingChanged(); + if (rp) + emit rightPaddingChanged(); + if (bp) + emit bottomPaddingChanged(); + + if (lp || rp) { + emit horizontalPaddingChanged(); + emit availableWidthChanged(); + } + if (tp || bp) { + emit verticalPaddingChanged(); + emit availableHeightChanged(); + } +} + +void QQuickPopup::spacingChange(qreal newSpacing, qreal oldSpacing) +{ + Q_UNUSED(newSpacing); + Q_UNUSED(oldSpacing); + emit spacingChanged(); +} + +void QQuickPopup::insetChange(const QMarginsF &newInset, const QMarginsF &oldInset) +{ + if (!qFuzzyCompare(newInset.top(), oldInset.top())) + emit topInsetChanged(); + if (!qFuzzyCompare(newInset.left(), oldInset.left())) + emit leftInsetChanged(); + if (!qFuzzyCompare(newInset.right(), oldInset.right())) + emit rightInsetChanged(); + if (!qFuzzyCompare(newInset.bottom(), oldInset.bottom())) + emit bottomInsetChanged(); +} + +QFont QQuickPopup::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::System); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickPopup::accessibleRole() const +{ + return QAccessible::Dialog; +} + +void QQuickPopup::accessibilityActiveChanged(bool active) +{ + Q_UNUSED(active); +} +#endif + +QString QQuickPopup::accessibleName() const +{ + Q_D(const QQuickPopup); + return d->popupItem->accessibleName(); +} + +void QQuickPopup::maybeSetAccessibleName(const QString &name) +{ + Q_D(QQuickPopup); + d->popupItem->maybeSetAccessibleName(name); +} + +QVariant QQuickPopup::accessibleProperty(const char *propertyName) +{ + Q_D(const QQuickPopup); + return d->popupItem->accessibleProperty(propertyName); +} + +bool QQuickPopup::setAccessibleProperty(const char *propertyName, const QVariant &value) +{ + Q_D(QQuickPopup); + return d->popupItem->setAccessibleProperty(propertyName, value); +} + +QT_END_NAMESPACE + +#include "moc_qquickpopup_p.cpp" diff --git a/src/quicktemplates2/qquickpopup_p.h b/src/quicktemplates2/qquickpopup_p.h new file mode 100644 index 0000000000..cdbc3cc4be --- /dev/null +++ b/src/quicktemplates2/qquickpopup_p.h @@ -0,0 +1,478 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOPUP_P_H +#define QQUICKPOPUP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtCore/qmargins.h> +#include <QtGui/qevent.h> +#include <QtCore/qlocale.h> +#include <QtGui/qfont.h> +#include <QtGui/qpalette.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtQml/qqml.h> +#include <QtQml/qqmllist.h> +#include <QtQml/qqmlparserstatus.h> +#include <QtQuick/qquickitem.h> + +#if QT_CONFIG(accessibility) +#include <QtGui/qaccessible.h> +#endif + +QT_BEGIN_NAMESPACE + +class QQuickWindow; +class QQuickPopupAnchors; +class QQuickPopupPrivate; +class QQuickTransition; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopup : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged FINAL) + Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged FINAL) + Q_PROPERTY(qreal z READ z WRITE setZ NOTIFY zChanged FINAL) + Q_PROPERTY(qreal width READ width WRITE setWidth RESET resetWidth NOTIFY widthChanged FINAL) + Q_PROPERTY(qreal height READ height WRITE setHeight RESET resetHeight NOTIFY heightChanged FINAL) + Q_PROPERTY(qreal implicitWidth READ implicitWidth WRITE setImplicitWidth NOTIFY implicitWidthChanged FINAL) + Q_PROPERTY(qreal implicitHeight READ implicitHeight WRITE setImplicitHeight NOTIFY implicitHeightChanged FINAL) + Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth NOTIFY contentWidthChanged FINAL) + Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight NOTIFY contentHeightChanged FINAL) + Q_PROPERTY(qreal availableWidth READ availableWidth NOTIFY availableWidthChanged FINAL) + Q_PROPERTY(qreal availableHeight READ availableHeight NOTIFY availableHeightChanged FINAL) + Q_PROPERTY(qreal margins READ margins WRITE setMargins RESET resetMargins NOTIFY marginsChanged FINAL) + Q_PROPERTY(qreal topMargin READ topMargin WRITE setTopMargin RESET resetTopMargin NOTIFY topMarginChanged FINAL) + Q_PROPERTY(qreal leftMargin READ leftMargin WRITE setLeftMargin RESET resetLeftMargin NOTIFY leftMarginChanged FINAL) + Q_PROPERTY(qreal rightMargin READ rightMargin WRITE setRightMargin RESET resetRightMargin NOTIFY rightMarginChanged FINAL) + Q_PROPERTY(qreal bottomMargin READ bottomMargin WRITE setBottomMargin RESET resetBottomMargin NOTIFY bottomMarginChanged FINAL) + Q_PROPERTY(qreal padding READ padding WRITE setPadding RESET resetPadding NOTIFY paddingChanged FINAL) + Q_PROPERTY(qreal topPadding READ topPadding WRITE setTopPadding RESET resetTopPadding NOTIFY topPaddingChanged FINAL) + Q_PROPERTY(qreal leftPadding READ leftPadding WRITE setLeftPadding RESET resetLeftPadding NOTIFY leftPaddingChanged FINAL) + Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding RESET resetRightPadding NOTIFY rightPaddingChanged FINAL) + Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding RESET resetBottomPadding NOTIFY bottomPaddingChanged FINAL) + Q_PROPERTY(QLocale locale READ locale WRITE setLocale RESET resetLocale NOTIFY localeChanged FINAL) + Q_PROPERTY(QFont font READ font WRITE setFont RESET resetFont NOTIFY fontChanged FINAL) + Q_PROPERTY(QQuickItem *parent READ parentItem WRITE setParentItem RESET resetParentItem NOTIFY parentChanged FINAL) + Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) + Q_PROPERTY(QQuickItem *contentItem READ contentItem WRITE setContentItem NOTIFY contentItemChanged FINAL) + Q_PRIVATE_PROPERTY(QQuickPopup::d_func(), QQmlListProperty<QObject> contentData READ contentData) + Q_PRIVATE_PROPERTY(QQuickPopup::d_func(), QQmlListProperty<QQuickItem> contentChildren READ contentChildren NOTIFY contentChildrenChanged FINAL) + Q_PROPERTY(bool clip READ clip WRITE setClip NOTIFY clipChanged FINAL) + Q_PROPERTY(bool focus READ hasFocus WRITE setFocus NOTIFY focusChanged FINAL) + Q_PROPERTY(bool activeFocus READ hasActiveFocus NOTIFY activeFocusChanged FINAL) + Q_PROPERTY(bool modal READ isModal WRITE setModal NOTIFY modalChanged FINAL) + Q_PROPERTY(bool dim READ dim WRITE setDim RESET resetDim NOTIFY dimChanged FINAL) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged FINAL) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged FINAL) + Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged FINAL) + Q_PROPERTY(ClosePolicy closePolicy READ closePolicy WRITE setClosePolicy RESET resetClosePolicy NOTIFY closePolicyChanged FINAL) + Q_PROPERTY(TransformOrigin transformOrigin READ transformOrigin WRITE setTransformOrigin FINAL) + Q_PROPERTY(QQuickTransition *enter READ enter WRITE setEnter NOTIFY enterChanged FINAL) + Q_PROPERTY(QQuickTransition *exit READ exit WRITE setExit NOTIFY exitChanged FINAL) + // 2.1 (Qt 5.8) + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing RESET resetSpacing NOTIFY spacingChanged FINAL REVISION(2, 1)) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool opened READ isOpened NOTIFY openedChanged FINAL REVISION(2, 3)) + Q_PROPERTY(bool mirrored READ isMirrored NOTIFY mirroredChanged FINAL REVISION(2, 3)) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged FINAL REVISION(2, 3)) + Q_PRIVATE_PROPERTY(QQuickPopup::d_func(), QQuickPalette *palette READ palette WRITE setPalette RESET resetPalette NOTIFY paletteChanged REVISION(2, 3)) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal horizontalPadding READ horizontalPadding WRITE setHorizontalPadding RESET resetHorizontalPadding NOTIFY horizontalPaddingChanged FINAL) + Q_PROPERTY(qreal verticalPadding READ verticalPadding WRITE setVerticalPadding RESET resetVerticalPadding NOTIFY verticalPaddingChanged FINAL) + Q_PRIVATE_PROPERTY(QQuickPopup::d_func(), QQuickPopupAnchors *anchors READ getAnchors DESIGNABLE false CONSTANT FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitContentWidth READ implicitContentWidth NOTIFY implicitContentWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitContentHeight READ implicitContentHeight NOTIFY implicitContentHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundWidth READ implicitBackgroundWidth NOTIFY implicitBackgroundWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundHeight READ implicitBackgroundHeight NOTIFY implicitBackgroundHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal topInset READ topInset WRITE setTopInset RESET resetTopInset NOTIFY topInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal leftInset READ leftInset WRITE setLeftInset RESET resetLeftInset NOTIFY leftInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal rightInset READ rightInset WRITE setRightInset RESET resetRightInset NOTIFY rightInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal bottomInset READ bottomInset WRITE setBottomInset RESET resetBottomInset NOTIFY bottomInsetChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background,contentItem") + Q_CLASSINFO("DefaultProperty", "contentData") + QML_NAMED_ELEMENT(Popup) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickPopup(QObject *parent = nullptr); + ~QQuickPopup(); + + qreal x() const; + void setX(qreal x); + + qreal y() const; + void setY(qreal y); + + QPointF position() const; + void setPosition(const QPointF &pos); + + qreal z() const; + void setZ(qreal z); + + qreal width() const; + void setWidth(qreal width); + void resetWidth(); + + qreal height() const; + void setHeight(qreal height); + void resetHeight(); + + qreal implicitWidth() const; + void setImplicitWidth(qreal width); + + qreal implicitHeight() const; + void setImplicitHeight(qreal height); + + qreal contentWidth() const; + void setContentWidth(qreal width); + + qreal contentHeight() const; + void setContentHeight(qreal height); + + qreal availableWidth() const; + qreal availableHeight() const; + + qreal margins() const; + void setMargins(qreal margins); + void resetMargins(); + + qreal topMargin() const; + void setTopMargin(qreal margin); + void resetTopMargin(); + + qreal leftMargin() const; + void setLeftMargin(qreal margin); + void resetLeftMargin(); + + qreal rightMargin() const; + void setRightMargin(qreal margin); + void resetRightMargin(); + + qreal bottomMargin() const; + void setBottomMargin(qreal margin); + void resetBottomMargin(); + + qreal padding() const; + void setPadding(qreal padding); + void resetPadding(); + + qreal topPadding() const; + void setTopPadding(qreal padding); + void resetTopPadding(); + + qreal leftPadding() const; + void setLeftPadding(qreal padding); + void resetLeftPadding(); + + qreal rightPadding() const; + void setRightPadding(qreal padding); + void resetRightPadding(); + + qreal bottomPadding() const; + void setBottomPadding(qreal padding); + void resetBottomPadding(); + + QLocale locale() const; + void setLocale(const QLocale &locale); + void resetLocale(); + + QFont font() const; + void setFont(const QFont &font); + void resetFont(); + + QQuickWindow *window() const; + QQuickItem *popupItem() const; + + QQuickItem *parentItem() const; + void setParentItem(QQuickItem *parent); + void resetParentItem(); + + QQuickItem *background() const; + void setBackground(QQuickItem *background); + + QQuickItem *contentItem() const; + void setContentItem(QQuickItem *item); + + bool clip() const; + void setClip(bool clip); + + bool hasFocus() const; + void setFocus(bool focus); + + bool hasActiveFocus() const; + + bool isModal() const; + void setModal(bool modal); + + bool dim() const; + void setDim(bool dim); + void resetDim(); + + bool isVisible() const; + virtual void setVisible(bool visible); + + qreal opacity() const; + void setOpacity(qreal opacity); + + qreal scale() const; + void setScale(qreal scale); + + enum ClosePolicyFlag { + NoAutoClose = 0x00, + CloseOnPressOutside = 0x01, + CloseOnPressOutsideParent = 0x02, + CloseOnReleaseOutside = 0x04, + CloseOnReleaseOutsideParent = 0x08, + CloseOnEscape = 0x10 + }; + Q_DECLARE_FLAGS(ClosePolicy, ClosePolicyFlag) + Q_FLAG(ClosePolicy) + + ClosePolicy closePolicy() const; + void setClosePolicy(ClosePolicy policy); + void resetClosePolicy(); + + // keep in sync with Item.TransformOrigin + enum TransformOrigin { + TopLeft, Top, TopRight, + Left, Center, Right, + BottomLeft, Bottom, BottomRight + }; + Q_ENUM(TransformOrigin) + + TransformOrigin transformOrigin() const; + void setTransformOrigin(TransformOrigin); + + QQuickTransition *enter() const; + void setEnter(QQuickTransition *transition); + + QQuickTransition *exit() const; + void setExit(QQuickTransition *transition); + + bool filtersChildMouseEvents() const; + void setFiltersChildMouseEvents(bool filter); + + Q_INVOKABLE void forceActiveFocus(Qt::FocusReason reason = Qt::OtherFocusReason); + + // 2.1 (Qt 5.8) + qreal spacing() const; + void setSpacing(qreal spacing); + void resetSpacing(); + + // 2.3 (Qt 5.10) + bool isOpened() const; + bool isMirrored() const; + + bool isEnabled() const; + void setEnabled(bool enabled); + + // 2.5 (Qt 5.12) + qreal horizontalPadding() const; + void setHorizontalPadding(qreal padding); + void resetHorizontalPadding(); + + qreal verticalPadding() const; + void setVerticalPadding(qreal padding); + void resetVerticalPadding(); + + qreal implicitContentWidth() const; + qreal implicitContentHeight() const; + + qreal implicitBackgroundWidth() const; + qreal implicitBackgroundHeight() const; + + qreal topInset() const; + void setTopInset(qreal inset); + void resetTopInset(); + + qreal leftInset() const; + void setLeftInset(qreal inset); + void resetLeftInset(); + + qreal rightInset() const; + void setRightInset(qreal inset); + void resetRightInset(); + + qreal bottomInset() const; + void setBottomInset(qreal inset); + void resetBottomInset(); + +public Q_SLOTS: + void open(); + void close(); + +Q_SIGNALS: + void opened(); + void closed(); + void aboutToShow(); + void aboutToHide(); + void xChanged(); + void yChanged(); + void zChanged(); + void widthChanged(); + void heightChanged(); + void implicitWidthChanged(); + void implicitHeightChanged(); + void contentWidthChanged(); + void contentHeightChanged(); + void availableWidthChanged(); + void availableHeightChanged(); + void marginsChanged(); + void topMarginChanged(); + void leftMarginChanged(); + void rightMarginChanged(); + void bottomMarginChanged(); + void paddingChanged(); + void topPaddingChanged(); + void leftPaddingChanged(); + void rightPaddingChanged(); + void bottomPaddingChanged(); + void fontChanged(); + void localeChanged(); + void parentChanged(); + void backgroundChanged(); + void contentItemChanged(); + void contentChildrenChanged(); + void clipChanged(); + void focusChanged(); + void activeFocusChanged(); + void modalChanged(); + void dimChanged(); + void visibleChanged(); + void opacityChanged(); + void scaleChanged(); + void closePolicyChanged(); + void enterChanged(); + void exitChanged(); + void windowChanged(QQuickWindow *window); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void spacingChanged(); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void openedChanged(); + Q_REVISION(2, 3) void mirroredChanged(); + Q_REVISION(2, 3) void enabledChanged(); + Q_REVISION(2, 3) void paletteChanged(); + Q_REVISION(2, 3) void paletteCreated(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void horizontalPaddingChanged(); + Q_REVISION(2, 5) void verticalPaddingChanged(); + Q_REVISION(2, 5) void implicitContentWidthChanged(); + Q_REVISION(2, 5) void implicitContentHeightChanged(); + Q_REVISION(2, 5) void implicitBackgroundWidthChanged(); + Q_REVISION(2, 5) void implicitBackgroundHeightChanged(); + Q_REVISION(2, 5) void topInsetChanged(); + Q_REVISION(2, 5) void leftInsetChanged(); + Q_REVISION(2, 5) void rightInsetChanged(); + Q_REVISION(2, 5) void bottomInsetChanged(); + +protected: + QQuickPopup(QQuickPopupPrivate &dd, QObject *parent); + + void classBegin() override; + void componentComplete() override; + bool isComponentComplete() const; + + virtual bool childMouseEventFilter(QQuickItem *child, QEvent *event); + virtual void focusInEvent(QFocusEvent *event); + virtual void focusOutEvent(QFocusEvent *event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void keyReleaseEvent(QKeyEvent *event); + virtual void mousePressEvent(QMouseEvent *event); + virtual void mouseMoveEvent(QMouseEvent *event); + virtual void mouseReleaseEvent(QMouseEvent *event); + virtual void mouseDoubleClickEvent(QMouseEvent *event); + virtual void mouseUngrabEvent(); + virtual bool overlayEvent(QQuickItem *item, QEvent *event); +#if QT_CONFIG(quicktemplates2_multitouch) + virtual void touchEvent(QTouchEvent *event); + virtual void touchUngrabEvent(); +#endif +#if QT_CONFIG(wheelevent) + virtual void wheelEvent(QWheelEvent *event); +#endif + + virtual void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem); + virtual void contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize); + virtual void fontChange(const QFont &newFont, const QFont &oldFont); + virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry); + virtual void localeChange(const QLocale &newLocale, const QLocale &oldLocale); + virtual void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data); + virtual void marginsChange(const QMarginsF &newMargins, const QMarginsF &oldMargins); + virtual void paddingChange(const QMarginsF &newPadding, const QMarginsF &oldPadding); + virtual void spacingChange(qreal newSpacing, qreal oldSpacing); + virtual void insetChange(const QMarginsF &newInset, const QMarginsF &oldInset); + + virtual QFont defaultFont() const; + +#if QT_CONFIG(accessibility) + virtual QAccessible::Role accessibleRole() const; + virtual void accessibilityActiveChanged(bool active); +#endif + + QString accessibleName() const; + void maybeSetAccessibleName(const QString &name); + + QVariant accessibleProperty(const char *propertyName); + bool setAccessibleProperty(const char *propertyName, const QVariant &value); + +private: + Q_DISABLE_COPY(QQuickPopup) + Q_DECLARE_PRIVATE(QQuickPopup) + friend class QQuickPopupItem; + friend class QQuickOverlay; + friend class QQuickOverlayPrivate; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPopup::ClosePolicy) + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPopup) + +#endif // QQUICKPOPUP_P_H diff --git a/src/quicktemplates2/qquickpopup_p_p.h b/src/quicktemplates2/qquickpopup_p_p.h new file mode 100644 index 0000000000..82aa6308ed --- /dev/null +++ b/src/quicktemplates2/qquickpopup_p_p.h @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOPUP_P_P_H +#define QQUICKPOPUP_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpopup_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQuickTemplates2/private/qquicktheme_p.h> + +#include <QtCore/private/qobject_p.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuick/private/qquicktransitionmanager_p_p.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickTransition; +class QQuickTransitionManager; +class QQuickPopup; +class QQuickPopupAnchors; +class QQuickPopupItem; +class QQuickPopupPrivate; +class QQuickPopupPositioner; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupTransitionManager : public QQuickTransitionManager +{ +public: + QQuickPopupTransitionManager(QQuickPopupPrivate *popup); + + void transitionEnter(); + void transitionExit(); + +protected: + void finished() override; + +private: + QQuickPopupPrivate *popup = nullptr; +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupPrivate + : public QObjectPrivate + , public QQuickItemChangeListener + , public QQuickPaletteProviderPrivateBase<QQuickPopup, QQuickPopupPrivate> +{ + Q_DECLARE_PUBLIC(QQuickPopup) + +public: + QQuickPopupPrivate(); + + static QQuickPopupPrivate *get(QQuickPopup *popup) + { + return popup->d_func(); + } + + QQmlListProperty<QObject> contentData(); + QQmlListProperty<QQuickItem> contentChildren(); + + void init(); + void closeOrReject(); + bool tryClose(const QPointF &pos, QQuickPopup::ClosePolicy flags); + + bool contains(const QPointF &scenePos) const; + +#if QT_CONFIG(quicktemplates2_multitouch) + virtual bool acceptTouch(const QTouchEvent::TouchPoint &point); +#endif + virtual bool blockInput(QQuickItem *item, const QPointF &point) const; + + virtual bool handlePress(QQuickItem* item, const QPointF &point, ulong timestamp); + virtual bool handleMove(QQuickItem* item, const QPointF &point, ulong timestamp); + virtual bool handleRelease(QQuickItem* item, const QPointF &point, ulong timestamp); + virtual void handleUngrab(); + + bool handleMouseEvent(QQuickItem *item, QMouseEvent *event); + bool handleHoverEvent(QQuickItem *item, QHoverEvent *event); +#if QT_CONFIG(quicktemplates2_multitouch) + bool handleTouchEvent(QQuickItem *item, QTouchEvent *event); +#endif + + void reposition(); + + void createOverlay(); + void destroyOverlay(); + void toggleOverlay(); + virtual void showOverlay(); + virtual void hideOverlay(); + virtual void resizeOverlay(); + + virtual bool prepareEnterTransition(); + virtual bool prepareExitTransition(); + virtual void finalizeEnterTransition(); + virtual void finalizeExitTransition(); + + virtual void opened(); + + QMarginsF getMargins() const; + + void setTopMargin(qreal value, bool reset = false); + void setLeftMargin(qreal value, bool reset = false); + void setRightMargin(qreal value, bool reset = false); + void setBottomMargin(qreal value, bool reset = false); + + QQuickPopupAnchors *getAnchors(); + virtual QQuickPopupPositioner *getPositioner(); + + void setWindow(QQuickWindow *window); + void itemDestroyed(QQuickItem *item) override; + + QPalette defaultPalette() const override; + + enum TransitionState { + NoTransition, EnterTransition, ExitTransition + }; + + static const QQuickPopup::ClosePolicy DefaultClosePolicy; + + bool focus = false; + bool modal = false; + bool dim = false; + bool hasDim = false; + bool visible = false; + bool complete = true; + bool positioning = false; + bool hasWidth = false; + bool hasHeight = false; + bool hasTopMargin = false; + bool hasLeftMargin = false; + bool hasRightMargin = false; + bool hasBottomMargin = false; + bool allowVerticalFlip = false; + bool allowHorizontalFlip = false; + bool allowVerticalMove = true; + bool allowHorizontalMove = true; + bool allowVerticalResize = true; + bool allowHorizontalResize = true; + bool hadActiveFocusBeforeExitTransition = false; + bool interactive = true; + bool hasClosePolicy = false; + bool outsidePressed = false; + bool outsideParentPressed = false; + int touchId = -1; + qreal x = 0; + qreal y = 0; + qreal effectiveX = 0; + qreal effectiveY = 0; + qreal margins = -1; + qreal topMargin = 0; + qreal leftMargin = 0; + qreal rightMargin = 0; + qreal bottomMargin = 0; + QPointF pressPoint; + TransitionState transitionState = NoTransition; + QQuickPopup::ClosePolicy closePolicy = DefaultClosePolicy; + QQuickItem *parentItem = nullptr; + QQuickItem *dimmer = nullptr; + QPointer<QQuickWindow> window; + QQuickTransition *enter = nullptr; + QQuickTransition *exit = nullptr; + QQuickPopupItem *popupItem = nullptr; + QQuickPopupPositioner *positioner = nullptr; + QList<QQuickStateAction> enterActions; + QList<QQuickStateAction> exitActions; + QQuickPopupTransitionManager transitionManager; + QQuickPopupAnchors *anchors = nullptr; + qreal prevOpacity = 0; + qreal prevScale = 0; + + friend class QQuickPopupTransitionManager; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOPUP_P_P_H diff --git a/src/quicktemplates2/qquickpopupanchors.cpp b/src/quicktemplates2/qquickpopupanchors.cpp new file mode 100644 index 0000000000..0d286cc01a --- /dev/null +++ b/src/quicktemplates2/qquickpopupanchors.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpopupanchors_p.h" +#include "qquickpopupanchors_p_p.h" +#include "qquickpopup_p_p.h" + +QT_BEGIN_NAMESPACE + +QQuickPopupAnchors::QQuickPopupAnchors(QQuickPopup *popup) + : QObject(*(new QQuickPopupAnchorsPrivate), popup) +{ + Q_D(QQuickPopupAnchors); + d->popup = popup; +} + +QQuickPopupAnchors::~QQuickPopupAnchors() +{ + Q_D(const QQuickPopupAnchors); + if (d->centerIn) { + auto centerInPrivate = QQuickItemPrivate::get(d->centerIn); + centerInPrivate->removeItemChangeListener(this, QQuickItemPrivate::Destroyed); + } +} + +QQuickItem *QQuickPopupAnchors::centerIn() const +{ + Q_D(const QQuickPopupAnchors); + return d->centerIn; +} + +void QQuickPopupAnchors::setCenterIn(QQuickItem *item) +{ + Q_D(QQuickPopupAnchors); + if (item == d->centerIn) + return; + + if (d->centerIn) { + auto centerInPrivate = QQuickItemPrivate::get(d->centerIn); + centerInPrivate->removeItemChangeListener(this, QQuickItemPrivate::Destroyed); + } + + d->centerIn = item; + + if (d->centerIn) { + auto centerInPrivate = QQuickItemPrivate::get(d->centerIn); + centerInPrivate->addItemChangeListener(this, QQuickItemPrivate::Destroyed); + } + + QQuickPopupPrivate::get(d->popup)->reposition(); + + emit centerInChanged(); +} + +void QQuickPopupAnchors::resetCenterIn() +{ + setCenterIn(nullptr); +} + +void QQuickPopupAnchors::itemDestroyed(QQuickItem *) +{ + resetCenterIn(); +} + +QT_END_NAMESPACE + +#include "moc_qquickpopupanchors_p.cpp" diff --git a/src/quicktemplates2/qquickpopupanchors_p.h b/src/quicktemplates2/qquickpopupanchors_p.h new file mode 100644 index 0000000000..dce1c5b26a --- /dev/null +++ b/src/quicktemplates2/qquickpopupanchors_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOPUPANCHORS_P_H +#define QQUICKPOPUPANCHORS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtQml/qqml.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickItem; +class QQuickPopupAnchorsPrivate; +class QQuickPopup; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupAnchors : public QObject, public QQuickItemChangeListener +{ + Q_OBJECT + Q_PROPERTY(QQuickItem *centerIn READ centerIn WRITE setCenterIn RESET resetCenterIn NOTIFY centerInChanged) + QML_ANONYMOUS + QML_ADDED_IN_VERSION(2, 5) + +public: + explicit QQuickPopupAnchors(QQuickPopup *popup); + ~QQuickPopupAnchors(); + + QQuickItem *centerIn() const; + void setCenterIn(QQuickItem *item); + void resetCenterIn(); + +Q_SIGNALS: + void centerInChanged(); + +private: + void itemDestroyed(QQuickItem *item) override; + + Q_DISABLE_COPY(QQuickPopupAnchors) + Q_DECLARE_PRIVATE(QQuickPopupAnchors) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPopupAnchors) + +#endif // QQUICKPOPUPANCHORS_P_H diff --git a/src/quicktemplates2/qquickpopupanchors_p_p.h b/src/quicktemplates2/qquickpopupanchors_p_p.h new file mode 100644 index 0000000000..989dc6df8f --- /dev/null +++ b/src/quicktemplates2/qquickpopupanchors_p_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOPUPANCHORS_P_P_H +#define QQUICKPOPUPANCHORS_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qobject_p.h> +#include <QtQuickTemplates2/private/qquickpopup_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickItem; +class QQuickPopup; + +class QQuickPopupAnchorsPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickPopupAnchors) + +public: + static QQuickPopupAnchorsPrivate *get(QQuickPopupAnchors *popupAnchors) + { + return popupAnchors->d_func(); + } + + QQuickPopup *popup = nullptr; + QQuickItem *centerIn = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOPUPANCHORS_P_P_H diff --git a/src/quicktemplates2/qquickpopupitem.cpp b/src/quicktemplates2/qquickpopupitem.cpp new file mode 100644 index 0000000000..4111cbef99 --- /dev/null +++ b/src/quicktemplates2/qquickpopupitem.cpp @@ -0,0 +1,433 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpopupitem_p_p.h" +#include "qquickapplicationwindow_p.h" +#include "qquickshortcutcontext_p_p.h" +#include "qquickpage_p_p.h" +#include "qquickcontentitem_p.h" +#include "qquickpopup_p_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtCore/qloggingcategory.h> +#if QT_CONFIG(shortcut) +# include <QtGui/private/qshortcutmap_p.h> +#endif +#include <QtGui/private/qguiapplication_p.h> + +#if QT_CONFIG(accessibility) +#include <QtQuick/private/qquickaccessibleattached_p.h> +#endif + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPopupItem, "qt.quick.controls.popupitem") + +QQuickPopupItemPrivate::QQuickPopupItemPrivate(QQuickPopup *popup) + : popup(popup) +{ + isTabFence = true; +} + +void QQuickPopupItemPrivate::implicitWidthChanged() +{ + qCDebug(lcPopupItem).nospace() << "implicitWidthChanged called on " << q_func() << "; new implicitWidth is " << implicitWidth; + QQuickPagePrivate::implicitWidthChanged(); + emit popup->implicitWidthChanged(); +} + +void QQuickPopupItemPrivate::implicitHeightChanged() +{ + qCDebug(lcPopupItem).nospace() << "implicitHeightChanged called on " << q_func() << "; new implicitHeight is " << implicitHeight; + QQuickPagePrivate::implicitHeightChanged(); + emit popup->implicitHeightChanged(); +} + +void QQuickPopupItemPrivate::resolveFont() +{ + if (QQuickApplicationWindow *window = qobject_cast<QQuickApplicationWindow *>(popup->window())) + inheritFont(window->font()); + else + inheritFont(QQuickTheme::font(QQuickTheme::System)); +} + +QQuickItem *QQuickPopupItemPrivate::getContentItem() +{ + Q_Q(QQuickPopupItem); + if (QQuickItem *item = QQuickPagePrivate::getContentItem()) + return item; + + return new QQuickContentItem(popup, q); +} + +static inline QString contentItemName() { return QStringLiteral("contentItem"); } + +void QQuickPopupItemPrivate::cancelContentItem() +{ + quickCancelDeferred(popup, contentItemName()); +} + +void QQuickPopupItemPrivate::executeContentItem(bool complete) +{ + if (contentItem.wasExecuted()) + return; + + if (!contentItem || complete) + quickBeginDeferred(popup, contentItemName(), contentItem); + if (complete) + quickCompleteDeferred(popup, contentItemName(), contentItem); +} + +static inline QString backgroundName() { return QStringLiteral("background"); } + +void QQuickPopupItemPrivate::cancelBackground() +{ + quickCancelDeferred(popup, backgroundName()); +} + +void QQuickPopupItemPrivate::executeBackground(bool complete) +{ + if (background.wasExecuted()) + return; + + if (!background || complete) + quickBeginDeferred(popup, backgroundName(), background); + if (complete) + quickCompleteDeferred(popup, backgroundName(), background); +} + +QQuickPopupItem::QQuickPopupItem(QQuickPopup *popup) + : QQuickPage(*(new QQuickPopupItemPrivate(popup)), nullptr) +{ + setParent(popup); + setFlag(ItemIsFocusScope); + setAcceptedMouseButtons(Qt::AllButtons); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif + + connect(popup, &QQuickPopup::paletteChanged, this, &QQuickItem::paletteChanged); + connect(popup, &QQuickPopup::paletteCreated, this, &QQuickItem::paletteCreated); + +#if QT_CONFIG(quicktemplates2_hover) + // TODO: switch to QStyleHints::useHoverEffects in Qt 5.8 + setHoverEnabled(true); + // setAcceptHoverEvents(QGuiApplication::styleHints()->useHoverEffects()); + // connect(QGuiApplication::styleHints(), &QStyleHints::useHoverEffectsChanged, this, &QQuickItem::setAcceptHoverEvents); +#endif +} + +void QQuickPopupItem::grabShortcut() +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickPopupItem); + QGuiApplicationPrivate *pApp = QGuiApplicationPrivate::instance(); + if (!d->backId) + d->backId = pApp->shortcutMap.addShortcut(this, Qt::Key_Back, Qt::WindowShortcut, QQuickShortcutContext::matcher); + if (!d->escapeId) + d->escapeId = pApp->shortcutMap.addShortcut(this, Qt::Key_Escape, Qt::WindowShortcut, QQuickShortcutContext::matcher); +#endif +} + +void QQuickPopupItem::ungrabShortcut() +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickPopupItem); + QGuiApplicationPrivate *pApp = QGuiApplicationPrivate::instance(); + if (d->backId) { + pApp->shortcutMap.removeShortcut(d->backId, this); + d->backId = 0; + } + if (d->escapeId) { + pApp->shortcutMap.removeShortcut(d->escapeId, this); + d->escapeId = 0; + } +#endif +} + +QQuickPalette *QQuickPopupItemPrivate::palette() const +{ + return QQuickPopupPrivate::get(popup)->palette(); +} + +void QQuickPopupItemPrivate::setPalette(QQuickPalette *p) +{ + QQuickPopupPrivate::get(popup)->setPalette(p); +} + +void QQuickPopupItemPrivate::resetPalette() +{ + QQuickPopupPrivate::get(popup)->resetPalette(); +} + +QPalette QQuickPopupItemPrivate::defaultPalette() const +{ + return QQuickPopupPrivate::get(popup)->defaultPalette(); +} + +bool QQuickPopupItemPrivate::providesPalette() const +{ + return QQuickPopupPrivate::get(popup)->providesPalette(); +} + +QPalette QQuickPopupItemPrivate::parentPalette() const +{ + return QQuickPopupPrivate::get(popup)->parentPalette(); +} + +void QQuickPopupItem::updatePolish() +{ + Q_D(QQuickPopupItem); + return QQuickPopupPrivate::get(d->popup)->reposition(); +} + +bool QQuickPopupItem::event(QEvent *event) +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickPopupItem); + if (event->type() == QEvent::Shortcut) { + QShortcutEvent *se = static_cast<QShortcutEvent *>(event); + if (se->shortcutId() == d->escapeId || se->shortcutId() == d->backId) { + QQuickPopupPrivate *p = QQuickPopupPrivate::get(d->popup); + if (p->interactive) { + p->closeOrReject(); + return true; + } + } + } +#endif + return QQuickItem::event(event); +} + +bool QQuickPopupItem::childMouseEventFilter(QQuickItem *child, QEvent *event) +{ + Q_D(QQuickPopupItem); + return d->popup->childMouseEventFilter(child, event); +} + +void QQuickPopupItem::focusInEvent(QFocusEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->focusInEvent(event); +} + +void QQuickPopupItem::focusOutEvent(QFocusEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->focusOutEvent(event); +} + +void QQuickPopupItem::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->keyPressEvent(event); +} + +void QQuickPopupItem::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->keyReleaseEvent(event); +} + +void QQuickPopupItem::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->mousePressEvent(event); +} + +void QQuickPopupItem::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->mouseMoveEvent(event); +} + +void QQuickPopupItem::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->mouseReleaseEvent(event); +} + +void QQuickPopupItem::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->mouseDoubleClickEvent(event); +} + +void QQuickPopupItem::mouseUngrabEvent() +{ + Q_D(QQuickPopupItem); + d->popup->mouseUngrabEvent(); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickPopupItem::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->touchEvent(event); +} + +void QQuickPopupItem::touchUngrabEvent() +{ + Q_D(QQuickPopupItem); + d->popup->touchUngrabEvent(); +} +#endif + +#if QT_CONFIG(wheelevent) +void QQuickPopupItem::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->wheelEvent(event); +} +#endif + +void QQuickPopupItem::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickPopupItem); + QQuickPage::contentItemChange(newItem, oldItem); + d->popup->contentItemChange(newItem, oldItem); +} + +void QQuickPopupItem::contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize) +{ + Q_D(QQuickPopupItem); + qCDebug(lcPopupItem) << "contentSizeChange called on" << this << "newSize" << newSize << "oldSize" << oldSize; + QQuickPage::contentSizeChange(newSize, oldSize); + d->popup->contentSizeChange(newSize, oldSize); +} + +void QQuickPopupItem::fontChange(const QFont &newFont, const QFont &oldFont) +{ + Q_D(QQuickPopupItem); + QQuickPage::fontChange(newFont, oldFont); + d->popup->fontChange(newFont, oldFont); +} + +void QQuickPopupItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickPopupItem); + qCDebug(lcPopupItem) << "geometryChange called on" << this << "newGeometry" << newGeometry << "oldGeometry" << oldGeometry; + QQuickPage::geometryChange(newGeometry, oldGeometry); + d->popup->geometryChange(newGeometry, oldGeometry); +} + +void QQuickPopupItem::localeChange(const QLocale &newLocale, const QLocale &oldLocale) +{ + Q_D(QQuickPopupItem); + QQuickPage::localeChange(newLocale, oldLocale); + d->popup->localeChange(newLocale, oldLocale); +} + +void QQuickPopupItem::mirrorChange() +{ + Q_D(QQuickPopupItem); + emit d->popup->mirroredChanged(); +} + +void QQuickPopupItem::itemChange(ItemChange change, const ItemChangeData &data) +{ + Q_D(QQuickPopupItem); + QQuickPage::itemChange(change, data); + d->popup->itemChange(change, data); +} + +void QQuickPopupItem::paddingChange(const QMarginsF &newPadding, const QMarginsF &oldPadding) +{ + Q_D(QQuickPopupItem); + QQuickPage::paddingChange(newPadding, oldPadding); + d->popup->paddingChange(newPadding, oldPadding); +} + +void QQuickPopupItem::enabledChange() +{ + Q_D(QQuickPopupItem); + // Just having QQuickPopup connect our QQuickItem::enabledChanged() signal + // to its enabledChanged() signal is enough for the enabled property to work, + // but we must also ensure that its paletteChanged() signal is emitted + // so that bindings to palette are re-evaluated, because QQuickControl::palette() + // returns a different palette depending on whether or not the control is enabled. + // To save a connection, we also emit enabledChanged here. + emit d->popup->enabledChanged(); +} + +QFont QQuickPopupItem::defaultFont() const +{ + Q_D(const QQuickPopupItem); + return d->popup->defaultFont(); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickPopupItem::accessibleRole() const +{ + Q_D(const QQuickPopupItem); + return d->popup->accessibleRole(); +} + +void QQuickPopupItem::accessibilityActiveChanged(bool active) +{ + Q_D(const QQuickPopupItem); + // Can't just use d->popup->accessibleName() here, because that refers to the accessible + // name of us, the popup item, which is not what we want. + const QQuickAccessibleAttached *popupAccessibleAttached = QQuickControlPrivate::accessibleAttached(d->popup); + const QString oldPopupName = popupAccessibleAttached ? popupAccessibleAttached->name() : QString(); + const bool wasNameExplicitlySetOnPopup = popupAccessibleAttached && popupAccessibleAttached->wasNameExplicitlySet(); + + QQuickPage::accessibilityActiveChanged(active); + + QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this); + const QString ourName = accessibleAttached ? accessibleAttached->name() : QString(); + if (wasNameExplicitlySetOnPopup && accessibleAttached && ourName != oldPopupName) { + // The user set Accessible.name on the Popup. Since the Popup and its popup item + // have different accessible attached properties, the popup item doesn't know that + // a name was set on the Popup by the user, and that it should use that, rather than + // whatever QQuickPage sets. That's why we need to do it here. + // To avoid it being overridden by the call to accessibilityActiveChanged() below, + // we set it explicitly. It's safe to do this as the popup item is an internal implementation detail. + accessibleAttached->setName(oldPopupName); + } + + // This allows the different popup types to set a name on their popup item accordingly. + // For example: Dialog uses its title and ToolTip uses its text. + d->popup->accessibilityActiveChanged(active); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickpopupitem_p_p.cpp" diff --git a/src/quicktemplates2/qquickpopupitem_p_p.h b/src/quicktemplates2/qquickpopupitem_p_p.h new file mode 100644 index 0000000000..04486650a0 --- /dev/null +++ b/src/quicktemplates2/qquickpopupitem_p_p.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOPUPITEM_P_P_H +#define QQUICKPOPUPITEM_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpage_p.h> +#include <QtQuickTemplates2/private/qquickpage_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickPopup; +class QQuickPopupItemPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupItem : public QQuickPage +{ + Q_OBJECT + +public: + explicit QQuickPopupItem(QQuickPopup *popup); + + void grabShortcut(); + void ungrabShortcut(); + +protected: + void updatePolish() override; + + bool event(QEvent *event) override; + bool childMouseEventFilter(QQuickItem *child, QEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void mouseUngrabEvent() override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; + void touchUngrabEvent() override; +#endif +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + void contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize) override; + void fontChange(const QFont &newFont, const QFont &oldFont) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + void localeChange(const QLocale &newLocale, const QLocale &oldLocale) override; + void mirrorChange() override; + void itemChange(ItemChange change, const ItemChangeData &data) override; + void paddingChange(const QMarginsF &newPadding, const QMarginsF &oldPadding) override; + void enabledChange() override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; + void accessibilityActiveChanged(bool active) override; +#endif + +private: + Q_DISABLE_COPY(QQuickPopupItem) + Q_DECLARE_PRIVATE(QQuickPopupItem) + friend class QQuickPopup; +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupItemPrivate : public QQuickPagePrivate +{ + Q_DECLARE_PUBLIC(QQuickPopupItem) + +public: + QQuickPopupItemPrivate(QQuickPopup *popup); + + void implicitWidthChanged() override; + void implicitHeightChanged() override; + + void resolveFont() override; + + QQuickItem *getContentItem() override; + + void cancelContentItem() override; + void executeContentItem(bool complete = false) override; + + void cancelBackground() override; + void executeBackground(bool complete = false) override; + + QQuickPalette *palette() const override; + void setPalette(QQuickPalette* p) override; + void resetPalette() override; + + QPalette defaultPalette() const override; + bool providesPalette() const override; + + QPalette parentPalette() const override; + + int backId = 0; + int escapeId = 0; + QQuickPopup *popup = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOPUPITEM_P_P_H diff --git a/src/quicktemplates2/qquickpopuppositioner.cpp b/src/quicktemplates2/qquickpopuppositioner.cpp new file mode 100644 index 0000000000..bbdc3848cd --- /dev/null +++ b/src/quicktemplates2/qquickpopuppositioner.cpp @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickoverlay_p.h" +#include "qquickpopuppositioner_p_p.h" +#include "qquickpopupanchors_p.h" +#include "qquickpopupitem_p_p.h" +#include "qquickpopup_p_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPopupPositioner, "qt.quick.controls.popuppositioner") + +static const QQuickItemPrivate::ChangeTypes AncestorChangeTypes = QQuickItemPrivate::Geometry + | QQuickItemPrivate::Parent + | QQuickItemPrivate::Children; + +static const QQuickItemPrivate::ChangeTypes ItemChangeTypes = QQuickItemPrivate::Geometry + | QQuickItemPrivate::Parent; + +QQuickPopupPositioner::QQuickPopupPositioner(QQuickPopup *popup) + : m_popup(popup) +{ +} + +QQuickPopupPositioner::~QQuickPopupPositioner() +{ + if (m_parentItem) { + QQuickItemPrivate::get(m_parentItem)->removeItemChangeListener(this, ItemChangeTypes); + removeAncestorListeners(m_parentItem->parentItem()); + } +} + +QQuickPopup *QQuickPopupPositioner::popup() const +{ + return m_popup; +} + +QQuickItem *QQuickPopupPositioner::parentItem() const +{ + return m_parentItem; +} + +void QQuickPopupPositioner::setParentItem(QQuickItem *parent) +{ + if (m_parentItem == parent) + return; + + if (m_parentItem) { + QQuickItemPrivate::get(m_parentItem)->removeItemChangeListener(this, ItemChangeTypes); + removeAncestorListeners(m_parentItem->parentItem()); + } + + m_parentItem = parent; + + if (!parent) + return; + + QQuickItemPrivate::get(parent)->addItemChangeListener(this, ItemChangeTypes); + addAncestorListeners(parent->parentItem()); + // Store the scale property so the end result of any transition that could effect the scale + // does not influence the top left of the final popup, so it doesn't appear to flip from one + // position to another as a result + m_popupScale = m_popup->popupItem()->scale(); + if (m_popup->popupItem()->isVisible()) + QQuickPopupPrivate::get(m_popup)->reposition(); +} + +void QQuickPopupPositioner::reposition() +{ + QQuickItem *popupItem = m_popup->popupItem(); + if (!popupItem->isVisible()) + return; + + if (m_positioning) { + popupItem->polish(); + return; + } + + qCDebug(lcPopupPositioner) << "reposition called for" << m_popup; + + const qreal w = popupItem->width() * m_popupScale; + const qreal h = popupItem->height() * m_popupScale; + const qreal iw = popupItem->implicitWidth() * m_popupScale; + const qreal ih = popupItem->implicitHeight() * m_popupScale; + + bool widthAdjusted = false; + bool heightAdjusted = false; + QQuickPopupPrivate *p = QQuickPopupPrivate::get(m_popup); + + const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr; + const QQuickOverlay *centerInOverlay = qobject_cast<const QQuickOverlay*>(centerInParent); + QRectF rect(!centerInParent ? p->allowHorizontalMove ? p->x : popupItem->x() : 0, + !centerInParent ? p->allowVerticalMove ? p->y : popupItem->y() : 0, + !p->hasWidth && iw > 0 ? iw : w, + !p->hasHeight && ih > 0 ? ih : h); + if (m_parentItem) { + // m_parentItem is the parent that the popup should open in, + // and popupItem()->parentItem() is the overlay, so the mapToItem() calls below + // effectively map the rect to scene coordinates. + if (centerInParent) { + if (centerInParent != parentItem() && !centerInOverlay) { + qmlWarning(m_popup) << "Popup can only be centered within its immediate parent or Overlay.overlay"; + return; + } + + if (centerInOverlay) { + rect.moveCenter(QPointF(qRound(centerInOverlay->width() / 2.0), qRound(centerInOverlay->height() / 2.0))); + } else { + const QPointF parentItemCenter = QPointF(qRound(m_parentItem->width() / 2), qRound(m_parentItem->height() / 2)); + rect.moveCenter(m_parentItem->mapToItem(popupItem->parentItem(), parentItemCenter)); + } + } else { + rect.moveTopLeft(m_parentItem->mapToItem(popupItem->parentItem(), rect.topLeft())); + } + + if (p->window) { + const QMarginsF margins = p->getMargins(); + QRectF bounds(qMax<qreal>(0.0, margins.left()), + qMax<qreal>(0.0, margins.top()), + p->window->width() - qMax<qreal>(0.0, margins.left()) - qMax<qreal>(0.0, margins.right()), + p->window->height() - qMax<qreal>(0.0, margins.top()) - qMax<qreal>(0.0, margins.bottom())); + if (p->window->contentOrientation() == Qt::LandscapeOrientation || p->window->contentOrientation() == Qt::InvertedLandscapeOrientation) + bounds = bounds.transposed(); + + // if the popup doesn't fit horizontally inside the window, try flipping it around (left <-> right) + if (p->allowHorizontalFlip && (rect.left() < bounds.left() || rect.right() > bounds.right())) { + const QPointF newTopLeft(m_parentItem->width() - p->x - rect.width(), p->y); + const QRectF flipped(m_parentItem->mapToItem(popupItem->parentItem(), newTopLeft), + rect.size()); + if (flipped.intersected(bounds).width() > rect.intersected(bounds).width()) + rect.moveLeft(flipped.left()); + } + + // if the popup doesn't fit vertically inside the window, try flipping it around (above <-> below) + if (p->allowVerticalFlip && (rect.top() < bounds.top() || rect.bottom() > bounds.bottom())) { + const QPointF newTopLeft(p->x, m_parentItem->height() - p->y - rect.height()); + const QRectF flipped(m_parentItem->mapToItem(popupItem->parentItem(), newTopLeft), + rect.size()); + if (flipped.intersected(bounds).height() > rect.intersected(bounds).height()) + rect.moveTop(flipped.top()); + } + + // push inside the margins if specified + if (p->allowVerticalMove) { + if (margins.top() >= 0 && rect.top() < bounds.top()) + rect.moveTop(margins.top()); + if (margins.bottom() >= 0 && rect.bottom() > bounds.bottom()) + rect.moveBottom(bounds.bottom()); + } + if (p->allowHorizontalMove) { + if (margins.left() >= 0 && rect.left() < bounds.left()) + rect.moveLeft(margins.left()); + if (margins.right() >= 0 && rect.right() > bounds.right()) + rect.moveRight(bounds.right()); + } + + if (iw > 0 && (rect.left() < bounds.left() || rect.right() > bounds.right())) { + // neither the flipped or pushed geometry fits inside the window, choose + // whichever side (left vs. right) fits larger part of the popup + if (p->allowHorizontalMove && p->allowHorizontalFlip) { + if (rect.left() < bounds.left() && bounds.left() + rect.width() <= bounds.right()) + rect.moveLeft(bounds.left()); + else if (rect.right() > bounds.right() && bounds.right() - rect.width() >= bounds.left()) + rect.moveRight(bounds.right()); + } + + // as a last resort, adjust the width to fit the window + if (p->allowHorizontalResize) { + if (rect.left() < bounds.left()) { + rect.setLeft(bounds.left()); + widthAdjusted = true; + } + if (rect.right() > bounds.right()) { + rect.setRight(bounds.right()); + widthAdjusted = true; + } + } + } else if (iw > 0 && rect.left() >= bounds.left() && rect.right() <= bounds.right() + && iw != w) { + // restore original width + rect.setWidth(iw); + widthAdjusted = true; + } + + if (ih > 0 && (rect.top() < bounds.top() || rect.bottom() > bounds.bottom())) { + // neither the flipped or pushed geometry fits inside the window, choose + // whichever side (above vs. below) fits larger part of the popup + if (p->allowVerticalMove && p->allowVerticalFlip) { + if (rect.top() < bounds.top() && bounds.top() + rect.height() <= bounds.bottom()) + rect.moveTop(bounds.top()); + else if (rect.bottom() > bounds.bottom() && bounds.bottom() - rect.height() >= bounds.top()) + rect.moveBottom(bounds.bottom()); + } + + // as a last resort, adjust the height to fit the window + if (p->allowVerticalResize) { + if (rect.top() < bounds.top()) { + rect.setTop(bounds.top()); + heightAdjusted = true; + } + if (rect.bottom() > bounds.bottom()) { + rect.setBottom(bounds.bottom()); + heightAdjusted = true; + } + } + } else if (ih > 0 && rect.top() >= bounds.top() && rect.bottom() <= bounds.bottom() + && ih != h) { + // restore original height + rect.setHeight(ih); + heightAdjusted = true; + } + } + } + + m_positioning = true; + + popupItem->setPosition(rect.topLeft()); + + // If the popup was assigned a parent, rect will be in scene coordinates, + // so we need to map its top left back to item coordinates. + // However, if centering within the overlay, the coordinates will be relative + // to the window, so we don't need to do anything. + const QPointF effectivePos = m_parentItem && !centerInOverlay ? m_parentItem->mapFromScene(rect.topLeft()) : rect.topLeft(); + if (!qFuzzyCompare(p->effectiveX, effectivePos.x())) { + p->effectiveX = effectivePos.x(); + emit m_popup->xChanged(); + } + if (!qFuzzyCompare(p->effectiveY, effectivePos.y())) { + p->effectiveY = effectivePos.y(); + emit m_popup->yChanged(); + } + + if (!p->hasWidth && widthAdjusted && rect.width() > 0) { + popupItem->setWidth(rect.width() / m_popupScale); + // The popup doesn't have an explicit width, so we should respect that by not + // making our call above an explicit assignment. If we don't, the popup won't + // resize after being repositioned in some cases. + QQuickItemPrivate::get(popupItem)->widthValidFlag = false; + } + if (!p->hasHeight && heightAdjusted && rect.height() > 0) { + popupItem->setHeight(rect.height() / m_popupScale); + QQuickItemPrivate::get(popupItem)->heightValidFlag = false; + } + m_positioning = false; + + qCDebug(lcPopupPositioner) << "- new popupItem geometry:" + << popupItem->x() << popupItem->y() << popupItem->width() << popupItem->height(); +} + +void QQuickPopupPositioner::itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &) +{ + if (m_parentItem && m_popup->popupItem()->isVisible()) + QQuickPopupPrivate::get(m_popup)->reposition(); +} + +void QQuickPopupPositioner::itemParentChanged(QQuickItem *, QQuickItem *parent) +{ + addAncestorListeners(parent); +} + +void QQuickPopupPositioner::itemChildRemoved(QQuickItem *item, QQuickItem *child) +{ + if (child == m_parentItem || child->isAncestorOf(m_parentItem)) + removeAncestorListeners(item); +} + +void QQuickPopupPositioner::removeAncestorListeners(QQuickItem *item) +{ + if (item == m_parentItem) + return; + + QQuickItem *p = item; + while (p) { + QQuickItemPrivate::get(p)->removeItemChangeListener(this, AncestorChangeTypes); + p = p->parentItem(); + } +} + +void QQuickPopupPositioner::addAncestorListeners(QQuickItem *item) +{ + if (item == m_parentItem) + return; + + QQuickItem *p = item; + while (p) { + QQuickItemPrivate::get(p)->updateOrAddItemChangeListener(this, AncestorChangeTypes); + p = p->parentItem(); + } +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickpopuppositioner_p_p.h b/src/quicktemplates2/qquickpopuppositioner_p_p.h new file mode 100644 index 0000000000..74bc467c61 --- /dev/null +++ b/src/quicktemplates2/qquickpopuppositioner_p_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPOPUPPOSITIONER_P_P_H +#define QQUICKPOPUPPOSITIONER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickItem; +class QQuickPopup; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupPositioner : public QQuickItemChangeListener +{ +public: + explicit QQuickPopupPositioner(QQuickPopup *popup); + ~QQuickPopupPositioner(); + + QQuickPopup *popup() const; + + QQuickItem *parentItem() const; + void setParentItem(QQuickItem *parent); + + virtual void reposition(); + +protected: + void itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &) override; + void itemParentChanged(QQuickItem *, QQuickItem *parent) override; + void itemChildRemoved(QQuickItem *, QQuickItem *child) override; + + void removeAncestorListeners(QQuickItem *item); + void addAncestorListeners(QQuickItem *item); + + bool m_positioning = false; + QQuickItem *m_parentItem = nullptr; + QQuickPopup *m_popup = nullptr; + qreal m_popupScale = 1.0; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOPUPPOSITIONER_P_P_H diff --git a/src/quicktemplates2/qquickpresshandler.cpp b/src/quicktemplates2/qquickpresshandler.cpp new file mode 100644 index 0000000000..d9ed484be9 --- /dev/null +++ b/src/quicktemplates2/qquickpresshandler.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpresshandler_p_p.h" + +#include <QtCore/private/qobject_p.h> +#include <QtGui/qguiapplication.h> +#include <QtGui/qstylehints.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuickTemplates2/private/qquicktextarea_p.h> +#include <QtQuickTemplates2/private/qquicktextfield_p.h> + +QT_BEGIN_NAMESPACE + +void QQuickPressHandler::mousePressEvent(QMouseEvent *event) +{ + longPress = false; + pressPos = event->position(); + if (Qt::LeftButton == (event->buttons() & Qt::LeftButton)) { + timer.start(QGuiApplication::styleHints()->mousePressAndHoldInterval(), control); + delayedMousePressEvent = new QMouseEvent(event->type(), event->position().toPoint(), event->button(), event->buttons(), event->modifiers()); + } else { + timer.stop(); + } + + if (isSignalConnected(control, "pressed(QQuickMouseEvent*)", pressedSignalIndex)) { + QQuickMouseEvent mev; + mev.reset(pressPos.x(), pressPos.y(), event->button(), event->buttons(), + event->modifiers(), false/*isClick*/, false/*wasHeld*/); + mev.setAccepted(true); + QQuickMouseEvent *mevPtr = &mev; + void *args[] = { nullptr, &mevPtr }; + QMetaObject::metacall(control, QMetaObject::InvokeMetaMethod, pressedSignalIndex, args); + event->setAccepted(mev.isAccepted()); + } +} + +void QQuickPressHandler::mouseMoveEvent(QMouseEvent *event) +{ + if (qAbs(int(event->position().x() - pressPos.x())) > QGuiApplication::styleHints()->startDragDistance()) + timer.stop(); +} + +void QQuickPressHandler::mouseReleaseEvent(QMouseEvent *event) +{ + if (!longPress) { + timer.stop(); + + if (isSignalConnected(control, "released(QQuickMouseEvent*)", releasedSignalIndex)) { + QQuickMouseEvent mev; + mev.reset(pressPos.x(), pressPos.y(), event->button(), event->buttons(), + event->modifiers(), false/*isClick*/, false/*wasHeld*/); + mev.setAccepted(true); + QQuickMouseEvent *mevPtr = &mev; + void *args[] = { nullptr, &mevPtr }; + QMetaObject::metacall(control, QMetaObject::InvokeMetaMethod, releasedSignalIndex, args); + event->setAccepted(mev.isAccepted()); + } + } +} + +void QQuickPressHandler::timerEvent(QTimerEvent *) +{ + timer.stop(); + clearDelayedMouseEvent(); + + longPress = isSignalConnected(control, "pressAndHold(QQuickMouseEvent*)", pressAndHoldSignalIndex); + if (longPress) { + QQuickMouseEvent mev; +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + mev.reset(pressPos.x(), pressPos.y(), Qt::LeftButton, Qt::LeftButton, + QGuiApplication::keyboardModifiers(), false/*isClick*/, true/*wasHeld*/); +QT_WARNING_POP + mev.setAccepted(true); + // Use fast signal invocation since we already got its index + QQuickMouseEvent *mevPtr = &mev; + void *args[] = { nullptr, &mevPtr }; + QMetaObject::metacall(control, QMetaObject::InvokeMetaMethod, pressAndHoldSignalIndex, args); + if (!mev.isAccepted()) + longPress = false; + } +} + +void QQuickPressHandler::clearDelayedMouseEvent() +{ + if (delayedMousePressEvent) { + delete delayedMousePressEvent; + delayedMousePressEvent = 0; + } +} + +bool QQuickPressHandler::isActive() +{ + return !(timer.isActive() || longPress); +} + +bool QQuickPressHandler::isSignalConnected(QQuickItem *item, const char *signalName, int &signalIndex) +{ + if (signalIndex == -1) + signalIndex = item->metaObject()->indexOfSignal(signalName); + Q_ASSERT(signalIndex != -1); + const auto signalMetaMethod = item->metaObject()->method(signalIndex); + if (QQuickTextArea *textArea = qobject_cast<QQuickTextArea*>(item)) { + return textArea->isSignalConnected(signalMetaMethod); + } else if (QQuickTextField *textField = qobject_cast<QQuickTextField*>(item)) { + return textField->isSignalConnected(signalMetaMethod); + } + qFatal("Unhandled control type for signal name: %s", signalName); + return false; +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickpresshandler_p_p.h b/src/quicktemplates2/qquickpresshandler_p_p.h new file mode 100644 index 0000000000..19312cddf4 --- /dev/null +++ b/src/quicktemplates2/qquickpresshandler_p_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPRESSHANDLER_P_P_H +#define QQUICKPRESSHANDLER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qpoint.h> +#include <QtCore/qbasictimer.h> + +QT_BEGIN_NAMESPACE + +class QQuickItem; +class QMouseEvent; +class QTimerEvent; + +struct QQuickPressHandler +{ + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void timerEvent(QTimerEvent *event); + + void clearDelayedMouseEvent(); + bool isActive(); + + static bool isSignalConnected(QQuickItem *item, const char *signalName, int &signalIndex); + + QQuickItem *control = nullptr; + QBasicTimer timer; + QPointF pressPos; + bool longPress = false; + int pressAndHoldSignalIndex = -1; + int pressedSignalIndex = -1; + int releasedSignalIndex = -1; + QMouseEvent *delayedMousePressEvent = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPRESSHANDLER_P_P_H diff --git a/src/quicktemplates2/qquickprogressbar.cpp b/src/quicktemplates2/qquickprogressbar.cpp new file mode 100644 index 0000000000..f4bc52b9be --- /dev/null +++ b/src/quicktemplates2/qquickprogressbar.cpp @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickprogressbar_p.h" +#include "qquickcontrol_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ProgressBar + \inherits Control +//! \instantiates QQuickProgressBar + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-indicators + \brief Indicates the progress of an operation. + + \image qtquickcontrols2-progressbar.gif + + ProgressBar indicates the progress of an operation. The value should be updated + regularly. The range is defined by \l from and \l to, which both can contain any value. + + \code + ProgressBar { + value: 0.5 + } + \endcode + + ProgressBar also supports a special \l indeterminate mode, which is useful, + for example, when unable to determine the size of the item being downloaded, + or if the download progress gets interrupted due to a network disconnection. + + \image qtquickcontrols2-progressbar-indeterminate.gif + + \code + ProgressBar { + indeterminate: true + } + \endcode + + The indeterminate mode is similar to a \l BusyIndicator. Both can be used + to indicate background activity. The main difference is visual, and that + ProgressBar can also present a concrete amount of progress (when it can be + determined). Due to the visual difference, indeterminate progress bars and + busy indicators fit different places in user interfaces. Typical places for + an indeterminate progress bar: + \list + \li at the bottom of a \l ToolBar + \li inline within the content of a \l Page + \li in an \l ItemDelegate to show the progress of a particular item + \endlist + + \sa {Customizing ProgressBar}, BusyIndicator, {Indicator Controls} +*/ + +class QQuickProgressBarPrivate : public QQuickControlPrivate +{ +public: + qreal from = 0; + qreal to = 1; + qreal value = 0; + bool indeterminate = false; +}; + +QQuickProgressBar::QQuickProgressBar(QQuickItem *parent) + : QQuickControl(*(new QQuickProgressBarPrivate), parent) +{ +} + +/*! + \qmlproperty real QtQuick.Controls::ProgressBar::from + + This property holds the starting value for the progress. The default value is \c 0.0. + + \sa to, value +*/ +qreal QQuickProgressBar::from() const +{ + Q_D(const QQuickProgressBar); + return d->from; +} + +void QQuickProgressBar::setFrom(qreal from) +{ + Q_D(QQuickProgressBar); + if (qFuzzyCompare(d->from, from)) + return; + + d->from = from; + emit fromChanged(); + emit positionChanged(); + emit visualPositionChanged(); + if (isComponentComplete()) + setValue(d->value); +} + +/*! + \qmlproperty real QtQuick.Controls::ProgressBar::to + + This property holds the end value for the progress. The default value is \c 1.0. + + \sa from, value +*/ +qreal QQuickProgressBar::to() const +{ + Q_D(const QQuickProgressBar); + return d->to; +} + +void QQuickProgressBar::setTo(qreal to) +{ + Q_D(QQuickProgressBar); + if (qFuzzyCompare(d->to, to)) + return; + + d->to = to; + emit toChanged(); + emit positionChanged(); + emit visualPositionChanged(); + if (isComponentComplete()) + setValue(d->value); +} + +/*! + \qmlproperty real QtQuick.Controls::ProgressBar::value + + This property holds the progress value. The default value is \c 0.0. + + \sa from, to, position +*/ +qreal QQuickProgressBar::value() const +{ + Q_D(const QQuickProgressBar); + return d->value; +} + +void QQuickProgressBar::setValue(qreal value) +{ + Q_D(QQuickProgressBar); + if (isComponentComplete()) + value = d->from > d->to ? qBound(d->to, value, d->from) : qBound(d->from, value, d->to); + + if (qFuzzyCompare(d->value, value)) + return; + + d->value = value; + emit valueChanged(); + emit positionChanged(); + emit visualPositionChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::ProgressBar::position + \readonly + + This property holds the logical position of the progress. + + The position is expressed as a fraction of the value, in the range + \c {0.0 - 1.0}. For visualizing the progress, the right-to-left + aware \l visualPosition should be used instead. + + \sa value, visualPosition +*/ +qreal QQuickProgressBar::position() const +{ + Q_D(const QQuickProgressBar); + if (qFuzzyCompare(d->from, d->to)) + return 0; + return (d->value - d->from) / (d->to - d->from); +} + +/*! + \qmlproperty real QtQuick.Controls::ProgressBar::visualPosition + \readonly + + This property holds the visual position of the progress. + + The position is expressed as a fraction of the value, in the range \c {0.0 - 1.0}. + When the control is \l {Control::mirrored}{mirrored}, \c visuaPosition is equal + to \c {1.0 - position}. This makes \c visualPosition suitable for visualizing + the progress, taking right-to-left support into account. + + \sa position, value +*/ +qreal QQuickProgressBar::visualPosition() const +{ + if (isMirrored()) + return 1.0 - position(); + return position(); +} + +/*! + \qmlproperty bool QtQuick.Controls::ProgressBar::indeterminate + + This property holds whether the progress bar is in indeterminate mode. + A progress bar in indeterminate mode displays that an operation is in progress, but it + doesn't show how much progress has been made. + + \image qtquickcontrols2-progressbar-indeterminate.gif +*/ +bool QQuickProgressBar::isIndeterminate() const +{ + Q_D(const QQuickProgressBar); + return d->indeterminate; +} + +void QQuickProgressBar::setIndeterminate(bool indeterminate) +{ + Q_D(QQuickProgressBar); + if (d->indeterminate == indeterminate) + return; + + d->indeterminate = indeterminate; + emit indeterminateChanged(); +} + +void QQuickProgressBar::mirrorChange() +{ + QQuickControl::mirrorChange(); + if (!qFuzzyCompare(position(), qreal(0.5))) + emit visualPositionChanged(); +} + +void QQuickProgressBar::componentComplete() +{ + Q_D(QQuickProgressBar); + QQuickControl::componentComplete(); + setValue(d->value); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickProgressBar::accessibleRole() const +{ + return QAccessible::ProgressBar; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickprogressbar_p.cpp" diff --git a/src/quicktemplates2/qquickprogressbar_p.h b/src/quicktemplates2/qquickprogressbar_p.h new file mode 100644 index 0000000000..8322670fcd --- /dev/null +++ b/src/quicktemplates2/qquickprogressbar_p.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPROGRESSBAR_P_H +#define QQUICKPROGRESSBAR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickProgressBarPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickProgressBar : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) + Q_PROPERTY(qreal to READ to WRITE setTo NOTIFY toChanged FINAL) + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY(qreal position READ position NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal visualPosition READ visualPosition NOTIFY visualPositionChanged FINAL) + Q_PROPERTY(bool indeterminate READ isIndeterminate WRITE setIndeterminate NOTIFY indeterminateChanged FINAL) + QML_NAMED_ELEMENT(ProgressBar) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickProgressBar(QQuickItem *parent = nullptr); + + qreal from() const; + void setFrom(qreal from); + + qreal to() const; + void setTo(qreal to); + + qreal value() const; + void setValue(qreal value); + + qreal position() const; + qreal visualPosition() const; + + bool isIndeterminate() const; + void setIndeterminate(bool indeterminate); + +Q_SIGNALS: + void fromChanged(); + void toChanged(); + void valueChanged(); + void positionChanged(); + void visualPositionChanged(); + void indeterminateChanged(); + +protected: + void mirrorChange() override; + void componentComplete() override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickProgressBar) + Q_DECLARE_PRIVATE(QQuickProgressBar) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickProgressBar) + +#endif // QQUICKPROGRESSBAR_P_H diff --git a/src/quicktemplates2/qquickradiobutton.cpp b/src/quicktemplates2/qquickradiobutton.cpp new file mode 100644 index 0000000000..3f1262e03a --- /dev/null +++ b/src/quicktemplates2/qquickradiobutton.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickradiobutton_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickabstractbutton_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RadioButton + \inherits AbstractButton +//! \instantiates QQuickRadioButton + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-buttons + \brief Exclusive radio button that can be toggled on or off. + + \image qtquickcontrols2-radiobutton.gif + + RadioButton presents an option button that can be toggled on (checked) or + off (unchecked). Radio buttons are typically used to select one option + from a set of options. + + RadioButton inherits its API from \l AbstractButton. For instance, + you can set \l {AbstractButton::text}{text} and react to + \l {AbstractButton::clicked}{clicks} using the AbstractButton API. + The state of the radio button can be set with the + \l {AbstractButton::}{checked} property. + + Radio buttons are \l {AbstractButton::autoExclusive}{auto-exclusive} + by default. Only one button can be checked at any time amongst radio + buttons that belong to the same parent item; checking another button + automatically unchecks the previously checked one. For radio buttons + that do not share a common parent, ButtonGroup can be used to manage + exclusivity. + + \l RadioDelegate is similar to RadioButton, except that it is typically + used in views. + + \code + ColumnLayout { + RadioButton { + checked: true + text: qsTr("First") + } + RadioButton { + text: qsTr("Second") + } + RadioButton { + text: qsTr("Third") + } + } + \endcode + + \sa ButtonGroup, {Customizing RadioButton}, {Button Controls}, RadioDelegate +*/ + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRadioButtonPrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickRadioButton) + +public: + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::RadioButton); } +}; + +QQuickRadioButton::QQuickRadioButton(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickRadioButtonPrivate), parent) +{ + setCheckable(true); + setAutoExclusive(true); +} + +QFont QQuickRadioButton::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::RadioButton); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickRadioButton::accessibleRole() const +{ + return QAccessible::RadioButton; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickradiobutton_p.cpp" diff --git a/src/quicktemplates2/qquickradiobutton_p.h b/src/quicktemplates2/qquickradiobutton_p.h new file mode 100644 index 0000000000..29e1892294 --- /dev/null +++ b/src/quicktemplates2/qquickradiobutton_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKRADIOBUTTON_P_H +#define QQUICKRADIOBUTTON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +#include <QtQuickTemplates2/private/qquicktheme_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickRadioButtonPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRadioButton : public QQuickAbstractButton +{ + Q_OBJECT + QML_NAMED_ELEMENT(RadioButton) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickRadioButton(QQuickItem *parent = nullptr); + +protected: + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickRadioButton) + +#endif // QQUICKRADIOBUTTON_P_H diff --git a/src/quicktemplates2/qquickradiodelegate.cpp b/src/quicktemplates2/qquickradiodelegate.cpp new file mode 100644 index 0000000000..836739389d --- /dev/null +++ b/src/quicktemplates2/qquickradiodelegate.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickradiodelegate_p.h" +#include "qquickabstractbutton_p_p.h" +#include "qquickitemdelegate_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RadioDelegate + \inherits ItemDelegate +//! \instantiates QQuickRadioDelegate + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-delegates + \brief Exclusive item delegate with a radio indicator that can be toggled on or off. + + \image qtquickcontrols2-radiodelegate.gif + + RadioDelegate presents an item delegate that can be toggled on (checked) or + off (unchecked). Radio delegates are typically used to select one option + from a set of options. + + RadioDelegate inherits its API from \l ItemDelegate, which is inherited + from AbstractButton. For instance, you can set \l {AbstractButton::text}{text}, + and react to \l {AbstractButton::clicked}{clicks} using the AbstractButton + API. The state of the radio delegate can be set with the + \l {AbstractButton::}{checked} property. + + Radio delegates are \l {AbstractButton::autoExclusive}{auto-exclusive} + by default. Only one delegate can be checked at any time amongst radio + delegates that belong to the same parent item; checking another delegate + automatically unchecks the previously checked one. For radio delegates + that do not share a common parent, ButtonGroup can be used to manage + exclusivity. + + \l RadioButton is similar to RadioDelegate, except that it is typically + not used in views, but rather when there are only a few options, and often + with the requirement that each button is uniquely identifiable. + + \code + ButtonGroup { + id: buttonGroup + } + + ListView { + model: ["Option 1", "Option 2", "Option 3"] + delegate: RadioDelegate { + text: modelData + checked: index == 0 + ButtonGroup.group: buttonGroup + } + } + \endcode + + \sa {Customizing RadioDelegate}, {Delegate Controls}, RadioButton +*/ + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRadioDelegatePrivate : public QQuickItemDelegatePrivate +{ + Q_DECLARE_PUBLIC(QQuickRadioDelegate) + +public: + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ListView); } +}; + +QQuickRadioDelegate::QQuickRadioDelegate(QQuickItem *parent) + : QQuickItemDelegate(*(new QQuickRadioDelegatePrivate), parent) +{ + setCheckable(true); + setAutoExclusive(true); +} + +QFont QQuickRadioDelegate::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ListView); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickRadioDelegate::accessibleRole() const +{ + return QAccessible::RadioButton; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickradiodelegate_p.cpp" diff --git a/src/quicktemplates2/qquickradiodelegate_p.h b/src/quicktemplates2/qquickradiodelegate_p.h new file mode 100644 index 0000000000..0ddff985ce --- /dev/null +++ b/src/quicktemplates2/qquickradiodelegate_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKRADIODELEGATE_P_H +#define QQUICKRADIODELEGATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickitemdelegate_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickRadioDelegatePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRadioDelegate : public QQuickItemDelegate +{ + Q_OBJECT + QML_NAMED_ELEMENT(RadioDelegate) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickRadioDelegate(QQuickItem *parent = nullptr); + +protected: + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DECLARE_PRIVATE(QQuickRadioDelegate) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickRadioDelegate) + +#endif // QQUICKRADIODELEGATE_P_H diff --git a/src/quicktemplates2/qquickrangeslider.cpp b/src/quicktemplates2/qquickrangeslider.cpp new file mode 100644 index 0000000000..4cedde66dc --- /dev/null +++ b/src/quicktemplates2/qquickrangeslider.cpp @@ -0,0 +1,1344 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickrangeslider_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtCore/qscopedpointer.h> +#include <QtQuick/private/qquickwindow_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RangeSlider + \inherits Control +//! \instantiates QQuickRangeSlider + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-input + \ingroup qtquickcontrols2-focusscopes + \brief Used to select a range of values by sliding two handles along a track. + + \image qtquickcontrols2-rangeslider.gif + + RangeSlider is used to select a range specified by two values, by sliding + each handle along a track. + + In the example below, custom \l from and \l to values are set, and the + initial positions of the \l first and \l second handles are set: + + \code + RangeSlider { + from: 1 + to: 100 + first.value: 25 + second.value: 75 + } + \endcode + + In order to perform an action when the value for a particular handle changes, + use the following syntax: + + \code + first.onMoved: console.log("first.value changed to " + first.value) + \endcode + + The \l {first.position} and \l {second.position} properties are expressed as + fractions of the control's size, in the range \c {0.0 - 1.0}. + The \l {first.visualPosition} and \l {second.visualPosition} properties are + the same, except that they are reversed in a + \l {Right-to-left User Interfaces}{right-to-left} application. + The \c visualPosition is useful for positioning the handles when styling + RangeSlider. In the example above, \l {first.visualPosition} will be \c 0.24 + in a left-to-right application, and \c 0.76 in a right-to-left application. + + For a slider that allows the user to select a single value, see \l Slider. + + \sa {Customizing RangeSlider}, {Input Controls}, + {Focus Management in Qt Quick Controls} +*/ + +class QQuickRangeSliderNodePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickRangeSliderNode) +public: + QQuickRangeSliderNodePrivate(qreal value, QQuickRangeSlider *slider) + : value(value), + slider(slider) + { + } + + bool isFirst() const; + + void setPosition(qreal position, bool ignoreOtherPosition = false); + void updatePosition(bool ignoreOtherPosition = false); + + void cancelHandle(); + void executeHandle(bool complete = false); + + static QQuickRangeSliderNodePrivate *get(QQuickRangeSliderNode *node); + + qreal value = 0; + bool isPendingValue = false; + qreal pendingValue = 0; + qreal position = 0; + QQuickDeferredPointer<QQuickItem> handle; + QQuickRangeSlider *slider = nullptr; + bool pressed = false; + bool hovered = false; + int touchId = -1; +}; + +bool QQuickRangeSliderNodePrivate::isFirst() const +{ + return this == get(slider->first()); +} + +void QQuickRangeSliderNodePrivate::setPosition(qreal position, bool ignoreOtherPosition) +{ + Q_Q(QQuickRangeSliderNode); + + const qreal min = isFirst() || ignoreOtherPosition ? 0.0 : qMax<qreal>(0.0, slider->first()->position()); + const qreal max = !isFirst() || ignoreOtherPosition ? 1.0 : qMin<qreal>(1.0, slider->second()->position()); + position = qBound(min, position, max); + if (!qFuzzyCompare(this->position, position)) { + this->position = position; + emit q->positionChanged(); + emit q->visualPositionChanged(); + } +} + +void QQuickRangeSliderNodePrivate::updatePosition(bool ignoreOtherPosition) +{ + qreal pos = 0; + if (!qFuzzyCompare(slider->from(), slider->to())) + pos = (value - slider->from()) / (slider->to() - slider->from()); + setPosition(pos, ignoreOtherPosition); +} + +static inline QString handleName() { return QStringLiteral("handle"); } + +void QQuickRangeSliderNodePrivate::cancelHandle() +{ + Q_Q(QQuickRangeSliderNode); + quickCancelDeferred(q, handleName()); +} + +void QQuickRangeSliderNodePrivate::executeHandle(bool complete) +{ + Q_Q(QQuickRangeSliderNode); + if (handle.wasExecuted()) + return; + + if (!handle || complete) + quickBeginDeferred(q, handleName(), handle); + if (complete) + quickCompleteDeferred(q, handleName(), handle); +} + +QQuickRangeSliderNodePrivate *QQuickRangeSliderNodePrivate::get(QQuickRangeSliderNode *node) +{ + return node->d_func(); +} + +QQuickRangeSliderNode::QQuickRangeSliderNode(qreal value, QQuickRangeSlider *slider) + : QObject(*(new QQuickRangeSliderNodePrivate(value, slider)), slider) +{ +} + +QQuickRangeSliderNode::~QQuickRangeSliderNode() +{ +} + +qreal QQuickRangeSliderNode::value() const +{ + Q_D(const QQuickRangeSliderNode); + return d->value; +} + +void QQuickRangeSliderNode::setValue(qreal value) +{ + Q_D(QQuickRangeSliderNode); + if (!d->slider->isComponentComplete()) { + d->pendingValue = value; + d->isPendingValue = true; + return; + } + + // First, restrict the first value to be within to and from. + const qreal smaller = qMin(d->slider->to(), d->slider->from()); + const qreal larger = qMax(d->slider->to(), d->slider->from()); + value = qBound(smaller, value, larger); + + // Then, ensure that it doesn't go past the other value, + // a check that depends on whether or not the range is inverted. + const bool invertedRange = d->slider->from() > d->slider->to(); + if (d->isFirst()) { + if (invertedRange) { + if (value < d->slider->second()->value()) + value = d->slider->second()->value(); + } else { + if (value > d->slider->second()->value()) + value = d->slider->second()->value(); + } + } else { + if (invertedRange) { + if (value > d->slider->first()->value()) + value = d->slider->first()->value(); + } else { + if (value < d->slider->first()->value()) + value = d->slider->first()->value(); + } + } + + if (!qFuzzyCompare(d->value, value)) { + d->value = value; + d->updatePosition(); + emit valueChanged(); + } +} + +qreal QQuickRangeSliderNode::position() const +{ + Q_D(const QQuickRangeSliderNode); + return d->position; +} + +qreal QQuickRangeSliderNode::visualPosition() const +{ + Q_D(const QQuickRangeSliderNode); + if (d->slider->orientation() == Qt::Vertical || d->slider->isMirrored()) + return 1.0 - d->position; + return d->position; +} + +QQuickItem *QQuickRangeSliderNode::handle() const +{ + QQuickRangeSliderNodePrivate *d = const_cast<QQuickRangeSliderNodePrivate *>(d_func()); + if (!d->handle) + d->executeHandle(); + return d->handle; +} + +void QQuickRangeSliderNode::setHandle(QQuickItem *handle) +{ + Q_D(QQuickRangeSliderNode); + if (d->handle == handle) + return; + + if (!d->handle.isExecuting()) + d->cancelHandle(); + + const qreal oldImplicitHandleWidth = implicitHandleWidth(); + const qreal oldImplicitHandleHeight = implicitHandleHeight(); + + QQuickControlPrivate::get(d->slider)->removeImplicitSizeListener(d->handle); + QQuickControlPrivate::hideOldItem(d->handle); + d->handle = handle; + + if (handle) { + if (!handle->parentItem()) + handle->setParentItem(d->slider); + + QQuickItem *firstHandle = QQuickRangeSliderNodePrivate::get(d->slider->first())->handle; + QQuickItem *secondHandle = QQuickRangeSliderNodePrivate::get(d->slider->second())->handle; + if (firstHandle && secondHandle) { + // The order of property assignments in QML is undefined, + // but we need the first handle to be before the second due + // to focus order constraints, so check for that here. + const QList<QQuickItem *> childItems = d->slider->childItems(); + const int firstIndex = childItems.indexOf(firstHandle); + const int secondIndex = childItems.indexOf(secondHandle); + if (firstIndex != -1 && secondIndex != -1 && firstIndex > secondIndex) { + firstHandle->stackBefore(secondHandle); + // Ensure we have some way of knowing which handle is above + // the other when it comes to mouse presses, and also that + // they are rendered in the correct order. + secondHandle->setZ(secondHandle->z() + 1); + } + } + + handle->setActiveFocusOnTab(true); + QQuickControlPrivate::get(d->slider)->addImplicitSizeListener(handle); + } + + if (!qFuzzyCompare(oldImplicitHandleWidth, implicitHandleWidth())) + emit implicitHandleWidthChanged(); + if (!qFuzzyCompare(oldImplicitHandleHeight, implicitHandleHeight())) + emit implicitHandleHeightChanged(); + if (!d->handle.isExecuting()) + emit handleChanged(); +} + +bool QQuickRangeSliderNode::isPressed() const +{ + Q_D(const QQuickRangeSliderNode); + return d->pressed; +} + +void QQuickRangeSliderNode::setPressed(bool pressed) +{ + Q_D(QQuickRangeSliderNode); + if (d->pressed == pressed) + return; + + d->pressed = pressed; + d->slider->setAccessibleProperty("pressed", pressed || d->slider->second()->isPressed()); + emit pressedChanged(); +} + +bool QQuickRangeSliderNode::isHovered() const +{ + Q_D(const QQuickRangeSliderNode); + return d->hovered; +} + +void QQuickRangeSliderNode::setHovered(bool hovered) +{ + Q_D(QQuickRangeSliderNode); + if (d->hovered == hovered) + return; + + d->hovered = hovered; + emit hoveredChanged(); +} + +qreal QQuickRangeSliderNode::implicitHandleWidth() const +{ + Q_D(const QQuickRangeSliderNode); + if (!d->handle) + return 0; + return d->handle->implicitWidth(); +} + +qreal QQuickRangeSliderNode::implicitHandleHeight() const +{ + Q_D(const QQuickRangeSliderNode); + if (!d->handle) + return 0; + return d->handle->implicitHeight(); +} + +void QQuickRangeSliderNode::increase() +{ + Q_D(QQuickRangeSliderNode); + qreal step = qFuzzyIsNull(d->slider->stepSize()) ? 0.1 : d->slider->stepSize(); + setValue(d->value + step); +} + +void QQuickRangeSliderNode::decrease() +{ + Q_D(QQuickRangeSliderNode); + qreal step = qFuzzyIsNull(d->slider->stepSize()) ? 0.1 : d->slider->stepSize(); + setValue(d->value - step); +} + +static const qreal defaultFrom = 0.0; +static const qreal defaultTo = 1.0; + +class QQuickRangeSliderPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickRangeSlider) + +public: + QQuickRangeSliderNode *pressedNode(int touchId = -1) const; + +#if QT_CONFIG(quicktemplates2_multitouch) + bool acceptTouch(const QTouchEvent::TouchPoint &point) override; +#endif + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + void handleUngrab() override; + + void updateHover(const QPointF &pos); + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + bool live = true; + qreal from = defaultFrom; + qreal to = defaultTo; + qreal stepSize = 0; + qreal touchDragThreshold = -1; + QQuickRangeSliderNode *first = nullptr; + QQuickRangeSliderNode *second = nullptr; + QPointF pressPoint; + Qt::Orientation orientation = Qt::Horizontal; + QQuickRangeSlider::SnapMode snapMode = QQuickRangeSlider::NoSnap; +}; + +static qreal valueAt(const QQuickRangeSlider *slider, qreal position) +{ + return slider->from() + (slider->to() - slider->from()) * position; +} + +static qreal snapPosition(const QQuickRangeSlider *slider, qreal position) +{ + const qreal range = slider->to() - slider->from(); + if (qFuzzyIsNull(range)) + return position; + + const qreal effectiveStep = slider->stepSize() / range; + if (qFuzzyIsNull(effectiveStep)) + return position; + + return qRound(position / effectiveStep) * effectiveStep; +} + +static qreal positionAt(const QQuickRangeSlider *slider, QQuickItem *handle, const QPointF &point) +{ + if (slider->orientation() == Qt::Horizontal) { + const qreal hw = handle ? handle->width() : 0; + const qreal offset = hw / 2; + const qreal extent = slider->availableWidth() - hw; + if (!qFuzzyIsNull(extent)) { + if (slider->isMirrored()) + return (slider->width() - point.x() - slider->rightPadding() - offset) / extent; + return (point.x() - slider->leftPadding() - offset) / extent; + } + } else { + const qreal hh = handle ? handle->height() : 0; + const qreal offset = hh / 2; + const qreal extent = slider->availableHeight() - hh; + if (!qFuzzyIsNull(extent)) + return (slider->height() - point.y() - slider->bottomPadding() - offset) / extent; + } + return 0; +} + +QQuickRangeSliderNode *QQuickRangeSliderPrivate::pressedNode(int touchId) const +{ + if (touchId == -1) + return first->isPressed() ? first : (second->isPressed() ? second : nullptr); + if (QQuickRangeSliderNodePrivate::get(first)->touchId == touchId) + return first; + if (QQuickRangeSliderNodePrivate::get(second)->touchId == touchId) + return second; + return nullptr; +} + +#if QT_CONFIG(quicktemplates2_multitouch) +bool QQuickRangeSliderPrivate::acceptTouch(const QTouchEvent::TouchPoint &point) +{ + int firstId = QQuickRangeSliderNodePrivate::get(first)->touchId; + int secondId = QQuickRangeSliderNodePrivate::get(second)->touchId; + + if (((firstId == -1 || secondId == -1) && point.state() == QEventPoint::Pressed) || point.id() == firstId || point.id() == secondId) { + touchId = point.id(); + return true; + } + + return false; +} +#endif + +void QQuickRangeSliderPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickRangeSlider); + QQuickControlPrivate::handlePress(point); + pressPoint = point; + + QQuickItem *firstHandle = first->handle(); + QQuickItem *secondHandle = second->handle(); + const bool firstHit = firstHandle && !first->isPressed() && firstHandle->contains(q->mapToItem(firstHandle, point)); + const bool secondHit = secondHandle && !second->isPressed() && secondHandle->contains(q->mapToItem(secondHandle, point)); + QQuickRangeSliderNode *hitNode = nullptr; + QQuickRangeSliderNode *otherNode = nullptr; + + if (firstHit && secondHit) { + // choose highest + hitNode = firstHandle->z() > secondHandle->z() ? first : second; + otherNode = firstHandle->z() > secondHandle->z() ? second : first; + } else if (firstHit) { + hitNode = first; + otherNode = second; + } else if (secondHit) { + hitNode = second; + otherNode = first; + } else { + // find the nearest + const qreal firstPos = positionAt(q, firstHandle, point); + const qreal secondPos = positionAt(q, secondHandle, point); + const qreal firstDistance = qAbs(firstPos - first->position()); + const qreal secondDistance = qAbs(secondPos - second->position()); + + if (qFuzzyCompare(firstDistance, secondDistance)) { + // same distance => choose the one that can be moved towards the press position + const bool inverted = from > to; + if ((!inverted && firstPos < first->position()) || (inverted && firstPos > first->position())) { + hitNode = first; + otherNode = second; + } else { + hitNode = second; + otherNode = first; + } + } else if (firstDistance < secondDistance) { + hitNode = first; + otherNode = second; + } else { + hitNode = second; + otherNode = first; + } + } + + if (hitNode) { + hitNode->setPressed(true); + if (QQuickItem *handle = hitNode->handle()) + handle->setZ(1); + QQuickRangeSliderNodePrivate::get(hitNode)->touchId = touchId; + } + if (otherNode) { + if (QQuickItem *handle = otherNode->handle()) + handle->setZ(0); + } +} + +void QQuickRangeSliderPrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickRangeSlider); + QQuickControlPrivate::handleMove(point); + QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId); + if (pressedNode) { + const qreal oldPos = pressedNode->position(); + qreal pos = positionAt(q, pressedNode->handle(), point); + if (snapMode == QQuickRangeSlider::SnapAlways) + pos = snapPosition(q, pos); + if (live) + pressedNode->setValue(valueAt(q, pos)); + else + QQuickRangeSliderNodePrivate::get(pressedNode)->setPosition(pos); + + if (!qFuzzyCompare(pressedNode->position(), oldPos)) + emit pressedNode->moved(); + } +} + +void QQuickRangeSliderPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickRangeSlider); + QQuickControlPrivate::handleRelease(point); + pressPoint = QPointF(); + + QQuickRangeSliderNode *pressedNode = QQuickRangeSliderPrivate::pressedNode(touchId); + if (!pressedNode) + return; + QQuickRangeSliderNodePrivate *pressedNodePrivate = QQuickRangeSliderNodePrivate::get(pressedNode); + + if (q->keepMouseGrab() || q->keepTouchGrab()) { + const qreal oldPos = pressedNode->position(); + qreal pos = positionAt(q, pressedNode->handle(), point); + if (snapMode != QQuickRangeSlider::NoSnap) + pos = snapPosition(q, pos); + qreal val = valueAt(q, pos); + if (!qFuzzyCompare(val, pressedNode->value())) + pressedNode->setValue(val); + else if (snapMode != QQuickRangeSlider::NoSnap) + pressedNodePrivate->setPosition(pos); + q->setKeepMouseGrab(false); + q->setKeepTouchGrab(false); + + if (!qFuzzyCompare(pressedNode->position(), oldPos)) + emit pressedNode->moved(); + } + pressedNode->setPressed(false); + pressedNodePrivate->touchId = -1; +} + +void QQuickRangeSliderPrivate::handleUngrab() +{ + QQuickControlPrivate::handleUngrab(); + pressPoint = QPointF(); + first->setPressed(false); + second->setPressed(false); + QQuickRangeSliderNodePrivate::get(first)->touchId = -1; + QQuickRangeSliderNodePrivate::get(second)->touchId = -1; +} + +void QQuickRangeSliderPrivate::updateHover(const QPointF &pos) +{ + Q_Q(QQuickRangeSlider); + QQuickItem *firstHandle = first->handle(); + QQuickItem *secondHandle = second->handle(); + bool firstHandleHovered = firstHandle && firstHandle->isEnabled() + && firstHandle->contains(q->mapToItem(firstHandle, pos)); + bool secondHandleHovered = secondHandle && secondHandle->isEnabled() + && secondHandle->contains(q->mapToItem(secondHandle, pos)); + + if (firstHandleHovered && secondHandleHovered) { + // Only hover the handle with the higher Z value. + const bool firstHandleHasHigherZ = firstHandle->z() > secondHandle->z(); + firstHandleHovered = firstHandleHasHigherZ; + secondHandleHovered = !firstHandleHasHigherZ; + } + first->setHovered(firstHandleHovered); + second->setHovered(secondHandleHovered); +} + +void QQuickRangeSliderPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + QQuickControlPrivate::itemImplicitWidthChanged(item); + if (item == first->handle()) + emit first->implicitHandleWidthChanged(); + else if (item == second->handle()) + emit second->implicitHandleWidthChanged(); +} + +void QQuickRangeSliderPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + QQuickControlPrivate::itemImplicitHeightChanged(item); + if (item == first->handle()) + emit first->implicitHandleHeightChanged(); + else if (item == second->handle()) + emit second->implicitHandleHeightChanged(); +} + +QQuickRangeSlider::QQuickRangeSlider(QQuickItem *parent) + : QQuickControl(*(new QQuickRangeSliderPrivate), parent) +{ + Q_D(QQuickRangeSlider); + d->first = new QQuickRangeSliderNode(0.0, this); + d->second = new QQuickRangeSliderNode(1.0, this); + + setFlag(QQuickItem::ItemIsFocusScope); + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif +} + +QQuickRangeSlider::~QQuickRangeSlider() +{ + Q_D(QQuickRangeSlider); + d->removeImplicitSizeListener(d->first->handle()); + d->removeImplicitSizeListener(d->second->handle()); +} + +/*! + \qmlproperty real QtQuick.Controls::RangeSlider::from + + This property holds the starting value for the range. The default value is \c 0.0. + + \sa to, first.value, second.value +*/ +qreal QQuickRangeSlider::from() const +{ + Q_D(const QQuickRangeSlider); + return d->from; +} + +void QQuickRangeSlider::setFrom(qreal from) +{ + Q_D(QQuickRangeSlider); + if (qFuzzyCompare(d->from, from)) + return; + + d->from = from; + emit fromChanged(); + + if (isComponentComplete()) { + d->first->setValue(d->first->value()); + d->second->setValue(d->second->value()); + auto *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first); + auto *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second); + firstPrivate->updatePosition(true); + secondPrivate->updatePosition(); + } +} + +/*! + \qmlproperty real QtQuick.Controls::RangeSlider::to + + This property holds the end value for the range. The default value is \c 1.0. + + \sa from, first.value, second.value +*/ +qreal QQuickRangeSlider::to() const +{ + Q_D(const QQuickRangeSlider); + return d->to; +} + +void QQuickRangeSlider::setTo(qreal to) +{ + Q_D(QQuickRangeSlider); + if (qFuzzyCompare(d->to, to)) + return; + + d->to = to; + emit toChanged(); + + if (isComponentComplete()) { + d->first->setValue(d->first->value()); + d->second->setValue(d->second->value()); + auto *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first); + auto *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second); + firstPrivate->updatePosition(true); + secondPrivate->updatePosition(); + } +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty qreal QtQuick.Controls::RangeSlider::touchDragThreshold + + This property holds the threshold (in logical pixels) at which a touch drag event will be initiated. + The mouse drag threshold won't be affected. + The default value is \c Qt.styleHints.startDragDistance. + + \sa QStyleHints + +*/ +qreal QQuickRangeSlider::touchDragThreshold() const +{ + Q_D(const QQuickRangeSlider); + return d->touchDragThreshold; +} + +void QQuickRangeSlider::setTouchDragThreshold(qreal touchDragThreshold) +{ + Q_D(QQuickRangeSlider); + if (d->touchDragThreshold == touchDragThreshold) + return; + + d->touchDragThreshold = touchDragThreshold; + emit touchDragThresholdChanged(); +} + +void QQuickRangeSlider::resetTouchDragThreshold() +{ + setTouchDragThreshold(-1); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlmethod real QtQuick.Controls::RangeSlider::valueAt(real position) + + Returns the value for the given \a position. + + \sa first.value, second.value, first.position, second.position, live +*/ +qreal QQuickRangeSlider::valueAt(qreal position) const +{ + Q_D(const QQuickRangeSlider); + const qreal value = (d->to - d->from) * position; + if (qFuzzyIsNull(d->stepSize)) + return d->from + value; + return d->from + qRound(value / d->stepSize) * d->stepSize; +} + +/*! + \qmlproperty real QtQuick.Controls::RangeSlider::first.value + \qmlproperty real QtQuick.Controls::RangeSlider::first.position + \qmlproperty real QtQuick.Controls::RangeSlider::first.visualPosition + \qmlproperty Item QtQuick.Controls::RangeSlider::first.handle + \qmlproperty bool QtQuick.Controls::RangeSlider::first.pressed + \qmlproperty bool QtQuick.Controls::RangeSlider::first.hovered + \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleWidth + \qmlproperty real QtQuick.Controls::RangeSlider::first.implicitHandleHeight + + \table + \header + \li Property + \li Description + \row + \li value + \li This property holds the value of the first handle in the range + \c from - \c to. + + If \l from is greater than \l to, the value of the first handle + must be greater than the second, and vice versa. + + The default value is \c 0.0. + \row + \li handle + \li This property holds the first handle item. + \row + \li visualPosition + \li This property holds the visual position of the first handle. + + The position is expressed as a fraction of the control's size, in the range + \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the + value is equal to \c {1.0 - position}. This makes the value suitable for + visualizing the slider, taking right-to-left support into account. + \row + \li position + \li This property holds the logical position of the first handle. + + The position is expressed as a fraction of the control's size, in the range + \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware + \l {first.visualPosition}{visualPosition} should be used instead. + \row + \li pressed + \li This property holds whether the first handle is pressed by either touch, + mouse, or keys. + \row + \li hovered + \li This property holds whether the first handle is hovered. + This property was introduced in QtQuick.Controls 2.1. + \row + \li implicitHandleWidth + \li This property holds the implicit width of the first handle. + This property was introduced in QtQuick.Controls 2.5. + \row + \li implicitHandleHeight + \li This property holds the implicit height of the first handle. + This property was introduced in QtQuick.Controls 2.5. + \endtable + + \sa first.moved(), first.increase(), first.decrease() +*/ +QQuickRangeSliderNode *QQuickRangeSlider::first() const +{ + Q_D(const QQuickRangeSlider); + return d->first; +} + +/*! + \qmlsignal void QtQuick.Controls::RangeSlider::first.moved() + \qmlsignal void QtQuick.Controls::RangeSlider::second.moved() + \since QtQuick.Controls 2.5 + + This signal is emitted when either the first or second handle has been + interactively moved by the user by either touch, mouse, or keys. + + \sa first, second +*/ + +/*! + \qmlproperty real QtQuick.Controls::RangeSlider::second.value + \qmlproperty real QtQuick.Controls::RangeSlider::second.position + \qmlproperty real QtQuick.Controls::RangeSlider::second.visualPosition + \qmlproperty Item QtQuick.Controls::RangeSlider::second.handle + \qmlproperty bool QtQuick.Controls::RangeSlider::second.pressed + \qmlproperty bool QtQuick.Controls::RangeSlider::second.hovered + \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleWidth + \qmlproperty real QtQuick.Controls::RangeSlider::second.implicitHandleHeight + + \table + \header + \li Property + \li Description + \row + \li value + \li This property holds the value of the second handle in the range + \c from - \c to. + + If \l from is greater than \l to, the value of the first handle + must be greater than the second, and vice versa. + + The default value is \c 0.0. + \row + \li handle + \li This property holds the second handle item. + \row + \li visualPosition + \li This property holds the visual position of the second handle. + + The position is expressed as a fraction of the control's size, in the range + \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the + value is equal to \c {1.0 - position}. This makes the value suitable for + visualizing the slider, taking right-to-left support into account. + \row + \li position + \li This property holds the logical position of the second handle. + + The position is expressed as a fraction of the control's size, in the range + \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware + \l {second.visualPosition}{visualPosition} should be used instead. + \row + \li pressed + \li This property holds whether the second handle is pressed by either touch, + mouse, or keys. + \row + \li hovered + \li This property holds whether the second handle is hovered. + This property was introduced in QtQuick.Controls 2.1. + \row + \li implicitHandleWidth + \li This property holds the implicit width of the second handle. + This property was introduced in QtQuick.Controls 2.5. + \row + \li implicitHandleHeight + \li This property holds the implicit height of the second handle. + This property was introduced in QtQuick.Controls 2.5. + \endtable + + \sa second.moved(), second.increase(), second.decrease() +*/ +QQuickRangeSliderNode *QQuickRangeSlider::second() const +{ + Q_D(const QQuickRangeSlider); + return d->second; +} + +/*! + \qmlproperty real QtQuick.Controls::RangeSlider::stepSize + + This property holds the step size. The default value is \c 0.0. + + \sa snapMode, first.increase(), first.decrease() +*/ +qreal QQuickRangeSlider::stepSize() const +{ + Q_D(const QQuickRangeSlider); + return d->stepSize; +} + +void QQuickRangeSlider::setStepSize(qreal step) +{ + Q_D(QQuickRangeSlider); + if (qFuzzyCompare(d->stepSize, step)) + return; + + d->stepSize = step; + emit stepSizeChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::RangeSlider::snapMode + + This property holds the snap mode. + + The snap mode determines how the slider handles behave with + regards to the \l stepSize. + + Possible values: + \value RangeSlider.NoSnap The slider does not snap (default). + \value RangeSlider.SnapAlways The slider snaps while the handle is dragged. + \value RangeSlider.SnapOnRelease The slider does not snap while being dragged, but only after the handle is released. + + For visual explanations of the various modes, see the + \l {Slider::}{snapMode} documentation of \l Slider. + + \sa stepSize +*/ +QQuickRangeSlider::SnapMode QQuickRangeSlider::snapMode() const +{ + Q_D(const QQuickRangeSlider); + return d->snapMode; +} + +void QQuickRangeSlider::setSnapMode(SnapMode mode) +{ + Q_D(QQuickRangeSlider); + if (d->snapMode == mode) + return; + + d->snapMode = mode; + emit snapModeChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::RangeSlider::orientation + + This property holds the orientation. + + Possible values: + \value Qt.Horizontal Horizontal (default) + \value Qt.Vertical Vertical + + \sa horizontal, vertical +*/ +Qt::Orientation QQuickRangeSlider::orientation() const +{ + Q_D(const QQuickRangeSlider); + return d->orientation; +} + +void QQuickRangeSlider::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickRangeSlider); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + emit orientationChanged(); +} + +/*! + \qmlmethod void QtQuick.Controls::RangeSlider::setValues(real firstValue, real secondValue) + + Sets \l first.value and \l second.value with the given arguments. + + If \l to is larger than \l from and \a firstValue is larger than + \a secondValue, firstValue will be clamped to secondValue. + + If \l from is larger than \l to and secondValue is larger than + firstValue, secondValue will be clamped to firstValue. + + This function may be necessary to set the first and second values + after the control has been completed, as there is a circular + dependency between firstValue and secondValue which can cause + assigned values to be clamped to each other. + + \sa stepSize +*/ +void QQuickRangeSlider::setValues(qreal firstValue, qreal secondValue) +{ + Q_D(QQuickRangeSlider); + // Restrict the values to be within to and from. + const qreal smaller = qMin(d->to, d->from); + const qreal larger = qMax(d->to, d->from); + firstValue = qBound(smaller, firstValue, larger); + secondValue = qBound(smaller, secondValue, larger); + + if (d->from > d->to) { + // If the from and to values are reversed, the secondValue + // might be less than the first value, which is not allowed. + if (secondValue > firstValue) + secondValue = firstValue; + } else { + // Otherwise, clamp first to second if it's too large. + if (firstValue > secondValue) + firstValue = secondValue; + } + + // Then set both values. If they didn't change, no change signal will be emitted. + QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first); + if (firstValue != firstPrivate->value) { + firstPrivate->value = firstValue; + emit d->first->valueChanged(); + } + + QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second); + if (secondValue != secondPrivate->value) { + secondPrivate->value = secondValue; + emit d->second->valueChanged(); + } + + // After we've set both values, then we can update the positions. + // If we don't do this last, the positions may be incorrect. + firstPrivate->updatePosition(true); + secondPrivate->updatePosition(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::RangeSlider::live + + This property holds whether the slider provides live updates for the \l first.value + and \l second.value properties while the respective handles are dragged. + + The default value is \c true. + + \sa first.value, second.value +*/ +bool QQuickRangeSlider::live() const +{ + Q_D(const QQuickRangeSlider); + return d->live; +} + +void QQuickRangeSlider::setLive(bool live) +{ + Q_D(QQuickRangeSlider); + if (d->live == live) + return; + + d->live = live; + emit liveChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::RangeSlider::horizontal + \readonly + + This property holds whether the slider is horizontal. + + \sa orientation +*/ +bool QQuickRangeSlider::isHorizontal() const +{ + Q_D(const QQuickRangeSlider); + return d->orientation == Qt::Horizontal; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::RangeSlider::vertical + \readonly + + This property holds whether the slider is vertical. + + \sa orientation +*/ +bool QQuickRangeSlider::isVertical() const +{ + Q_D(const QQuickRangeSlider); + return d->orientation == Qt::Vertical; +} + +void QQuickRangeSlider::focusInEvent(QFocusEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::focusInEvent(event); + + // The active focus ends up to RangeSlider when using forceActiveFocus() + // or QML KeyNavigation. We must forward the focus to one of the handles, + // because RangeSlider handles key events for the focused handle. If + // neither handle has active focus, RangeSlider doesn't do anything. + QQuickItem *handle = nextItemInFocusChain(); + // QQuickItem::nextItemInFocusChain() only works as desired with + // Qt::TabFocusAllControls. otherwise pick the first handle + if (!handle || handle == this) + handle = d->first->handle(); + if (handle) + handle->forceActiveFocus(event->reason()); +} + +void QQuickRangeSlider::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::keyPressEvent(event); + + QQuickRangeSliderNode *focusNode = d->first->handle()->hasActiveFocus() + ? d->first : (d->second->handle()->hasActiveFocus() ? d->second : nullptr); + if (!focusNode) + return; + + const qreal oldValue = focusNode->value(); + if (d->orientation == Qt::Horizontal) { + if (event->key() == Qt::Key_Left) { + focusNode->setPressed(true); + if (isMirrored()) + focusNode->increase(); + else + focusNode->decrease(); + event->accept(); + } else if (event->key() == Qt::Key_Right) { + focusNode->setPressed(true); + if (isMirrored()) + focusNode->decrease(); + else + focusNode->increase(); + event->accept(); + } + } else { + if (event->key() == Qt::Key_Up) { + focusNode->setPressed(true); + focusNode->increase(); + event->accept(); + } else if (event->key() == Qt::Key_Down) { + focusNode->setPressed(true); + focusNode->decrease(); + event->accept(); + } + } + if (!qFuzzyCompare(focusNode->value(), oldValue)) + emit focusNode->moved(); +} + +void QQuickRangeSlider::hoverEnterEvent(QHoverEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::hoverEnterEvent(event); + d->updateHover(event->position()); + event->ignore(); +} + +void QQuickRangeSlider::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::hoverMoveEvent(event); + d->updateHover(event->position()); + event->ignore(); +} + +void QQuickRangeSlider::hoverLeaveEvent(QHoverEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::hoverLeaveEvent(event); + d->first->setHovered(false); + d->second->setHovered(false); + event->ignore(); +} + +void QQuickRangeSlider::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::keyReleaseEvent(event); + d->first->setPressed(false); + d->second->setPressed(false); +} + +void QQuickRangeSlider::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickRangeSlider); + QQuickControl::mousePressEvent(event); + d->handleMove(event->position()); + setKeepMouseGrab(true); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickRangeSlider::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickRangeSlider); + switch (event->type()) { + case QEvent::TouchUpdate: + for (const QTouchEvent::TouchPoint &point : event->points()) { + if (!d->acceptTouch(point)) + continue; + + switch (point.state()) { + case QEventPoint::Pressed: + d->handlePress(point.position()); + break; + case QEventPoint::Updated: + if (!keepTouchGrab()) { + if (d->orientation == Qt::Horizontal) + setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.position().x() - point.pressPosition().x(), Qt::XAxis, &point, qRound(d->touchDragThreshold))); + else + setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.position().y() - point.pressPosition().y(), Qt::YAxis, &point, qRound(d->touchDragThreshold))); + } + if (keepTouchGrab()) + d->handleMove(point.position()); + break; + case QEventPoint::Released: + d->handleRelease(point.position()); + break; + default: + break; + } + } + break; + + default: + QQuickControl::touchEvent(event); + break; + } +} +#endif + +void QQuickRangeSlider::mirrorChange() +{ + Q_D(QQuickRangeSlider); + QQuickControl::mirrorChange(); + emit d->first->visualPositionChanged(); + emit d->second->visualPositionChanged(); +} + +void QQuickRangeSlider::classBegin() +{ + Q_D(QQuickRangeSlider); + QQuickControl::classBegin(); + + QQmlContext *context = qmlContext(this); + if (context) { + QQmlEngine::setContextForObject(d->first, context); + QQmlEngine::setContextForObject(d->second, context); + } +} + +void QQuickRangeSlider::componentComplete() +{ + Q_D(QQuickRangeSlider); + QQuickRangeSliderNodePrivate *firstPrivate = QQuickRangeSliderNodePrivate::get(d->first); + QQuickRangeSliderNodePrivate *secondPrivate = QQuickRangeSliderNodePrivate::get(d->second); + firstPrivate->executeHandle(true); + secondPrivate->executeHandle(true); + + QQuickControl::componentComplete(); + + if (firstPrivate->isPendingValue || secondPrivate->isPendingValue + || !qFuzzyCompare(d->from, defaultFrom) || !qFuzzyCompare(d->to, defaultTo)) { + // Properties were set while we were loading. To avoid clamping issues that occur when setting the + // values of first and second overriding values set by the user, set them all at once at the end. + // Another reason that we must set these values here is that the from and to values might have made the old range invalid. + setValues(firstPrivate->isPendingValue ? firstPrivate->pendingValue : firstPrivate->value, + secondPrivate->isPendingValue ? secondPrivate->pendingValue : secondPrivate->value); + + firstPrivate->pendingValue = 0; + firstPrivate->isPendingValue = false; + secondPrivate->pendingValue = 0; + secondPrivate->isPendingValue = false; + } else { + // If there was no pending data, we must still update the positions, + // as first.setValue()/second.setValue() won't be called as part of default construction. + // Don't need to ignore the second position when updating the first position here, + // as our default values are guaranteed to be valid. + firstPrivate->updatePosition(); + secondPrivate->updatePosition(); + } +} + +/*! + \qmlmethod void QtQuick.Controls::RangeSlider::first.increase() + + Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined. + + \sa first +*/ + +/*! + \qmlmethod void QtQuick.Controls::RangeSlider::first.decrease() + + Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined. + + \sa first +*/ + +/*! + \qmlmethod void QtQuick.Controls::RangeSlider::second.increase() + + Increases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined. + + \sa second +*/ + +/*! + \qmlmethod void QtQuick.Controls::RangeSlider::second.decrease() + + Decreases the value of the handle by stepSize, or \c 0.1 if stepSize is not defined. + + \sa second +*/ + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickRangeSlider::accessibleRole() const +{ + return QAccessible::Slider; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickrangeslider_p.cpp" diff --git a/src/quicktemplates2/qquickrangeslider_p.h b/src/quicktemplates2/qquickrangeslider_p.h new file mode 100644 index 0000000000..d767228e10 --- /dev/null +++ b/src/quicktemplates2/qquickrangeslider_p.h @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKRANGESLIDER_P_H +#define QQUICKRANGESLIDER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickRangeSliderPrivate; +class QQuickRangeSliderNode; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRangeSlider : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) + Q_PROPERTY(qreal to READ to WRITE setTo NOTIFY toChanged FINAL) + Q_PROPERTY(QQuickRangeSliderNode *first READ first CONSTANT FINAL) + Q_PROPERTY(QQuickRangeSliderNode *second READ second CONSTANT FINAL) + Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + // 2.2 (Qt 5.9) + Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged FINAL REVISION(2, 2)) + Q_PROPERTY(bool horizontal READ isHorizontal NOTIFY orientationChanged FINAL REVISION(2, 3)) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool vertical READ isVertical NOTIFY orientationChanged FINAL REVISION(2, 3)) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal touchDragThreshold READ touchDragThreshold WRITE setTouchDragThreshold RESET resetTouchDragThreshold NOTIFY touchDragThresholdChanged FINAL REVISION(2, 5)) + QML_NAMED_ELEMENT(RangeSlider) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickRangeSlider(QQuickItem *parent = nullptr); + ~QQuickRangeSlider(); + + qreal from() const; + void setFrom(qreal from); + + qreal to() const; + void setTo(qreal to); + + QQuickRangeSliderNode *first() const; + QQuickRangeSliderNode *second() const; + + qreal stepSize() const; + void setStepSize(qreal step); + + enum SnapMode { + NoSnap, + SnapAlways, + SnapOnRelease + }; + Q_ENUM(SnapMode) + + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + Q_INVOKABLE void setValues(qreal firstValue, qreal secondValue); + + // 2.2 (Qt 5.9) + bool live() const; + void setLive(bool live); + + // 2.3 (Qt 5.10) + bool isHorizontal() const; + bool isVertical() const; + + // 2.5 (Qt 5.12) + qreal touchDragThreshold() const; + void setTouchDragThreshold(qreal touchDragThreshold); + void resetTouchDragThreshold(); + Q_REVISION(2, 5) Q_INVOKABLE qreal valueAt(qreal position) const; + +Q_SIGNALS: + void fromChanged(); + void toChanged(); + void stepSizeChanged(); + void snapModeChanged(); + void orientationChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void liveChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void touchDragThresholdChanged(); + +protected: + void focusInEvent(QFocusEvent *event) override; + void hoverEnterEvent(QHoverEvent *event) override; + void hoverMoveEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif + void mirrorChange() override; + void classBegin() override; + void componentComplete() override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + friend class QQuickRangeSliderNode; + + Q_DISABLE_COPY(QQuickRangeSlider) + Q_DECLARE_PRIVATE(QQuickRangeSlider) +}; + +class QQuickRangeSliderNodePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRangeSliderNode : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY(qreal position READ position NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal visualPosition READ visualPosition NOTIFY visualPositionChanged FINAL) + Q_PROPERTY(QQuickItem *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) + // 2.1 (Qt 5.8) + Q_PROPERTY(bool hovered READ isHovered WRITE setHovered NOTIFY hoveredChanged FINAL REVISION(2, 1)) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal implicitHandleWidth READ implicitHandleWidth NOTIFY implicitHandleWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitHandleHeight READ implicitHandleHeight NOTIFY implicitHandleHeightChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "handle") + QML_ANONYMOUS + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickRangeSliderNode(qreal value, QQuickRangeSlider *slider); + ~QQuickRangeSliderNode(); + + qreal value() const; + void setValue(qreal value); + + qreal position() const; + qreal visualPosition() const; + + QQuickItem *handle() const; + void setHandle(QQuickItem *handle); + + bool isPressed() const; + void setPressed(bool pressed); + + // 2.1 (Qt 5.8) + bool isHovered() const; + void setHovered(bool hovered); + + // 2.5 (Qt 5.12) + qreal implicitHandleWidth() const; + qreal implicitHandleHeight() const; + +public Q_SLOTS: + void increase(); + void decrease(); + +Q_SIGNALS: + void valueChanged(); + void positionChanged(); + void visualPositionChanged(); + void handleChanged(); + void pressedChanged(); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void hoveredChanged(); + // 2.5 (Qt 5.12) + /*Q_REVISION(2, 5)*/ void moved(); + /*Q_REVISION(2, 5)*/ void implicitHandleWidthChanged(); + /*Q_REVISION(2, 5)*/ void implicitHandleHeightChanged(); + +private: + Q_DISABLE_COPY(QQuickRangeSliderNode) + Q_DECLARE_PRIVATE(QQuickRangeSliderNode) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickRangeSlider) + +#endif // QQUICKRANGESLIDER_P_H diff --git a/src/quicktemplates2/qquickroundbutton.cpp b/src/quicktemplates2/qquickroundbutton.cpp new file mode 100644 index 0000000000..0f1b366cd9 --- /dev/null +++ b/src/quicktemplates2/qquickroundbutton.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickroundbutton_p.h" + +#include <QtQuickTemplates2/private/qquickbutton_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RoundButton + \inherits Button +//! \instantiates QQuickRoundButton + \inqmlmodule QtQuick.Controls + \since 5.8 + \ingroup qtquickcontrols2-buttons + \brief A push-button control with rounded corners that can be clicked by the user. + + \image qtquickcontrols2-roundbutton.png + + RoundButton is identical to \l Button, except that it has a \l radius property + which allows the corners to be rounded without having to customize the + \l background. + + \snippet qtquickcontrols2-roundbutton.qml 1 + + \sa {Customizing RoundButton}, {Button Controls} +*/ + +class QQuickRoundButtonPrivate : public QQuickButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickRoundButton) + +public: + void setRadius(qreal newRadius = -1.0); + + qreal radius = 0; + bool explicitRadius = false; +}; + +void QQuickRoundButtonPrivate::setRadius(qreal newRadius) +{ + Q_Q(QQuickRoundButton); + const qreal oldRadius = radius; + if (newRadius < 0) + radius = qMax<qreal>(0, qMin<qreal>(width, height) / 2); + else + radius = newRadius; + + if (!qFuzzyCompare(radius, oldRadius)) + emit q->radiusChanged(); +} + +QQuickRoundButton::QQuickRoundButton(QQuickItem *parent) + : QQuickButton(*(new QQuickRoundButtonPrivate), parent) +{ +} + +/*! + \qmlproperty real QtQuick.Controls::RoundButton::radius + + This property holds the radius of the button. + + To create a relatively square button that has slightly rounded corners, + use a small value, such as \c 3. + + To create a completely circular button (the default), use a value that is + equal to half of the width or height of the button, and make the button's + width and height identical. + + To reset this property back to the default value, set its value to + \c undefined. +*/ +qreal QQuickRoundButton::radius() const +{ + Q_D(const QQuickRoundButton); + return d->radius; +} + +void QQuickRoundButton::setRadius(qreal radius) +{ + Q_D(QQuickRoundButton); + d->explicitRadius = true; + d->setRadius(radius); +} + +void QQuickRoundButton::resetRadius() +{ + Q_D(QQuickRoundButton); + d->explicitRadius = false; + d->setRadius(); +} + +void QQuickRoundButton::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickRoundButton); + QQuickControl::geometryChange(newGeometry, oldGeometry); + if (!d->explicitRadius) + d->setRadius(); +} + +QT_END_NAMESPACE + +#include "moc_qquickroundbutton_p.cpp" diff --git a/src/quicktemplates2/qquickroundbutton_p.h b/src/quicktemplates2/qquickroundbutton_p.h new file mode 100644 index 0000000000..fdd46cf8f6 --- /dev/null +++ b/src/quicktemplates2/qquickroundbutton_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKROUNDBUTTON_P_H +#define QQUICKROUNDBUTTON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickRoundButtonPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRoundButton : public QQuickButton +{ + Q_OBJECT + Q_PROPERTY(qreal radius READ radius WRITE setRadius RESET resetRadius NOTIFY radiusChanged FINAL) + QML_NAMED_ELEMENT(RoundButton) + QML_ADDED_IN_VERSION(2, 1) + +public: + explicit QQuickRoundButton(QQuickItem *parent = nullptr); + + qreal radius() const; + void setRadius(qreal radius); + void resetRadius(); + +Q_SIGNALS: + void radiusChanged(); + +protected: + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + +private: + Q_DISABLE_COPY(QQuickRoundButton) + Q_DECLARE_PRIVATE(QQuickRoundButton) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickRoundButton) + +#endif // QQUICKROUNDBUTTON_P_H diff --git a/src/quicktemplates2/qquickscrollbar.cpp b/src/quicktemplates2/qquickscrollbar.cpp new file mode 100644 index 0000000000..fbcd772dc5 --- /dev/null +++ b/src/quicktemplates2/qquickscrollbar.cpp @@ -0,0 +1,1281 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickscrollbar_p.h" +#include "qquickscrollbar_p_p.h" +#include "qquickscrollview_p.h" + +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickflickable_p.h> +#if QT_CONFIG(accessibility) +#include <QtQuick/private/qquickaccessibleattached_p.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ScrollBar + \inherits Control +//! \instantiates QQuickScrollBar + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-indicators + \brief Vertical or horizontal interactive scroll bar. + + \image qtquickcontrols2-scrollbar.gif + + ScrollBar is an interactive bar that can be used to scroll to a specific + position. A scroll bar can be either \l vertical or \l horizontal, and can + be attached to any \l Flickable, such as \l ListView and \l GridView. + It can also be used with \l ScrollView. + + \code + Flickable { + // ... + ScrollBar.vertical: ScrollBar { } + } + \endcode + + \section1 Attaching ScrollBar to a Flickable + + When ScrollBar is attached \l {ScrollBar::vertical}{vertically} or + \l {ScrollBar::horizontal}{horizontally} to a Flickable, its geometry and + the following properties are automatically set and updated as appropriate: + + \list + \li \l orientation + \li \l position + \li \l size + \li \l active + \endlist + + An attached ScrollBar re-parents itself to the target Flickable. A vertically + attached ScrollBar resizes itself to the height of the Flickable, and positions + itself to either side of it based on the \l {Control::mirrored}{layout direction}. + A horizontally attached ScrollBar resizes itself to the width of the Flickable, + and positions itself to the bottom. The automatic geometry management can be disabled + by specifying another parent for the attached ScrollBar. This can be useful, for + example, if the ScrollBar should be placed outside a clipping Flickable. This is + demonstrated by the following example: + + \code + Flickable { + id: flickable + clip: true + // ... + ScrollBar.vertical: ScrollBar { + parent: flickable.parent + anchors.top: flickable.top + anchors.left: flickable.right + anchors.bottom: flickable.bottom + } + } + \endcode + + Notice that ScrollBar does not filter key events of the Flickable it is + attached to. The following example illustrates how to implement scrolling + with up and down keys: + + \code + Flickable { + focus: true + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() + + ScrollBar.vertical: ScrollBar { id: scrollBar } + } + \endcode + + \section1 Binding the Active State of Horizontal and Vertical Scroll Bars + + Horizontal and vertical scroll bars do not share the \l active state with + each other by default. In order to keep both bars visible whilst scrolling + to either direction, establish a two-way binding between the active states + as presented by the following example: + + \snippet qtquickcontrols2-scrollbar-active.qml 1 + + \section1 Non-attached Scroll Bars + + It is possible to create an instance of ScrollBar without using the + attached property API. This is useful when the behavior of the attached + scroll bar is not sufficient or a \l Flickable is not in use. In the + following example, horizontal and vertical scroll bars are used to + scroll over the text without using \l Flickable: + + \snippet qtquickcontrols2-scrollbar-non-attached.qml 1 + + \image qtquickcontrols2-scrollbar-non-attached.png + + When using a non-attached ScrollBar, the following must be done manually: + + \list + \li Layout the scroll bar (with the \l {Item::}{x} and \l {Item::}{y} or + \l {Item::}{anchors} property, for example). + \li Set the \l size and \l position properties to determine the size and position + of the scroll bar in relation to the scrolled item. + \li Set the \l active property to determine when the scroll bar will be + visible. + \endlist + + \sa ScrollIndicator, ScrollView, {Customizing ScrollBar}, {Indicator Controls} +*/ + +static const QQuickItemPrivate::ChangeTypes changeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed; +static const QQuickItemPrivate::ChangeTypes horizontalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitHeight; +static const QQuickItemPrivate::ChangeTypes verticalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitWidth; + +QQuickScrollBarPrivate::VisualArea QQuickScrollBarPrivate::visualArea() const +{ + qreal visualPos = position; + + if (minimumSize > size && size != 1.0) + visualPos = position / (1.0 - size) * (1.0 - minimumSize); + + qreal visualSize = qBound<qreal>(0, qMax(size, minimumSize) + qMin<qreal>(0, visualPos), + qMax(0.0, 1.0 - visualPos)); + + visualPos = qBound<qreal>(0, visualPos, 1.0 - visualSize); + + return VisualArea(visualPos, visualSize); +} + +qreal QQuickScrollBarPrivate::logicalPosition(qreal position) const +{ + if (minimumSize > size && minimumSize != 1.0) + return position * (1.0 - size) / (1.0 - minimumSize); + return position; +} + +qreal QQuickScrollBarPrivate::snapPosition(qreal position) const +{ + const qreal effectiveStep = stepSize * (1.0 - size); + if (qFuzzyIsNull(effectiveStep)) + return position; + + return qRound(position / effectiveStep) * effectiveStep; +} + +qreal QQuickScrollBarPrivate::positionAt(const QPointF &point) const +{ + Q_Q(const QQuickScrollBar); + if (orientation == Qt::Horizontal) + return logicalPosition(point.x() - q->leftPadding()) / q->availableWidth(); + else + return logicalPosition(point.y() - q->topPadding()) / q->availableHeight(); +} + +void QQuickScrollBarPrivate::setInteractive(bool enabled) +{ + Q_Q(QQuickScrollBar); + if (interactive == enabled) + return; + + interactive = enabled; + if (interactive) { + q->setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + q->setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + q->setCursor(Qt::ArrowCursor); +#endif + } else { + q->setAcceptedMouseButtons(Qt::NoButton); +#if QT_CONFIG(quicktemplates2_multitouch) + q->setAcceptTouchEvents(false); +#endif +#if QT_CONFIG(cursor) + q->unsetCursor(); +#endif + q->ungrabMouse(); + } + emit q->interactiveChanged(); +} + +void QQuickScrollBarPrivate::updateActive() +{ + Q_Q(QQuickScrollBar); +#if QT_CONFIG(quicktemplates2_hover) + bool hover = hovered; +#else + bool hover = false; +#endif + q->setActive(moving || (interactive && (pressed || hover))); +} + +void QQuickScrollBarPrivate::resizeContent() +{ + Q_Q(QQuickScrollBar); + if (!contentItem) + return; + + // - negative overshoot (pos < 0): clamp the pos to 0, and deduct the overshoot from the size + // - positive overshoot (pos + size > 1): clamp the size to 1-pos + const VisualArea visual = visualArea(); + + if (orientation == Qt::Horizontal) { + contentItem->setPosition(QPointF(q->leftPadding() + visual.position * q->availableWidth(), q->topPadding())); + contentItem->setSize(QSizeF(q->availableWidth() * visual.size, q->availableHeight())); + } else { + contentItem->setPosition(QPointF(q->leftPadding(), q->topPadding() + visual.position * q->availableHeight())); + contentItem->setSize(QSizeF(q->availableWidth(), q->availableHeight() * visual.size)); + } +} + +void QQuickScrollBarPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickScrollBar); + QQuickControlPrivate::itemImplicitWidthChanged(item); + QQuickIndicatorButton *indicatorButton = q->decreaseVisual(); + if (!indicatorButton || item != indicatorButton->indicator()) { + indicatorButton = q->increaseVisual(); + if (!indicatorButton || item != indicatorButton->indicator()) + return; + } + if (indicatorButton) + emit indicatorButton->implicitIndicatorWidthChanged(); +} + +void QQuickScrollBarPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickScrollBar); + QQuickControlPrivate::itemImplicitHeightChanged(item); + QQuickIndicatorButton *indicatorButton = q->decreaseVisual(); + if (!indicatorButton || item != indicatorButton->indicator()) { + indicatorButton = q->increaseVisual(); + if (!indicatorButton || item != indicatorButton->indicator()) + return; + } + if (indicatorButton) + emit indicatorButton->implicitIndicatorHeightChanged(); +} + +void QQuickScrollBarPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickScrollBar); + QQuickControlPrivate::handlePress(point); + if (QQuickIndicatorButton *indicatorButton = q->decreaseVisual()) { + QQuickItem *decreaseArrow = indicatorButton->indicator(); + if (decreaseArrow && decreaseArrow->contains(q->mapToItem(decreaseArrow, point + QPointF(0.5, 0.5)))) { + indicatorButton->setPressed(true); + q->decrease(); + return; + } + } + + if (QQuickIndicatorButton *increaseObject = q->increaseVisual()) { + QQuickItem *increaseArrow = increaseObject->indicator(); + if (increaseArrow && increaseArrow->contains(q->mapToItem(increaseArrow, point + QPointF(0.5, 0.5)))) { + increaseObject->setPressed(true); + q->increase(); + return; + } + } + + offset = positionAt(point) - position; + qreal sz = qMax(size, logicalPosition(minimumSize)); + if (offset < 0 || offset > sz) + offset = sz / 2; + q->setPressed(true); +} + +void QQuickScrollBarPrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickScrollBar); + QQuickControlPrivate::handleMove(point); + + /* + * handleMove() will be called as soon as you hold the mouse button down *anywhere* on the + * ScrollBar, including the increase/decrease button indicator areas. So without the following + * early return, it would move the scrollbar handle to one of its extremeties. That would + * ruin the behavior we would like when clicking e.g. the "increase button": To step the + * scrollbar gently. + */ + if (!pressed) + return; + qreal pos = qBound<qreal>(0.0, positionAt(point) - offset, 1.0 - size); + if (snapMode == QQuickScrollBar::SnapAlways) + pos = snapPosition(pos); + q->setPosition(pos); +} + +void QQuickScrollBarPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickScrollBar); + QQuickControlPrivate::handleRelease(point); + + if (orientation == Qt::Vertical) { + if (point.y() < q->topPadding() || point.y() >= (q->height() - q->bottomPadding())) + return; + } else /* orientation == Qt::Horizontal */{ + if (point.x() < q->leftPadding() || point.x() >= (q->width() - q->rightPadding())) + return; + } + qreal pos = qBound<qreal>(0.0, positionAt(point) - offset, 1.0 - size); + if (snapMode != QQuickScrollBar::NoSnap) + pos = snapPosition(pos); + q->setPosition(pos); + offset = 0.0; + q->setPressed(false); +} + +void QQuickScrollBarPrivate::handleUngrab() +{ + Q_Q(QQuickScrollBar); + QQuickControlPrivate::handleUngrab(); + offset = 0.0; + q->setPressed(false); +} + +void QQuickScrollBarPrivate::visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea) +{ + Q_Q(QQuickScrollBar); + if (!qFuzzyCompare(newVisualArea.size, oldVisualArea.size)) + emit q->visualSizeChanged(); + if (!qFuzzyCompare(newVisualArea.position, oldVisualArea.position)) + emit q->visualPositionChanged(); +} + +void QQuickScrollBarPrivate::updateHover(const QPointF &pos, std::optional<bool> newHoverState) +{ + Q_Q(QQuickScrollBar); + auto updateHoverOnButton = [&](QQuickIndicatorButton *sbButton) { + if (sbButton) { + bool hovered = newHoverState.value_or(false); + if (!newHoverState.has_value()) { + if (QQuickItem *indicator = sbButton->indicator()) + hovered = indicator->contains(q->mapToItem(indicator, pos)); + } + sbButton->setHovered(hovered); + } + }; + updateHoverOnButton(q->decreaseVisual()); + updateHoverOnButton(q->increaseVisual()); +} + +QQuickScrollBar::QQuickScrollBar(QQuickItem *parent) + : QQuickControl(*(new QQuickScrollBarPrivate), parent) +{ + Q_D(QQuickScrollBar); + d->decreaseVisual = new QQuickIndicatorButton(this); + d->increaseVisual = new QQuickIndicatorButton(this); + setKeepMouseGrab(true); + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif +} + +QQuickScrollBarAttached *QQuickScrollBar::qmlAttachedProperties(QObject *object) +{ + return new QQuickScrollBarAttached(object); +} + +/*! + \qmlproperty real QtQuick.Controls::ScrollBar::size + + This property holds the size of the scroll bar, scaled to \c {0.0 - 1.0}. + + \sa {Flickable::visibleArea.heightRatio}{Flickable::visibleArea} + + This property is automatically set when the scroll bar is + \l {Attaching ScrollBar to a Flickable}{attached to a flickable}. + + \sa minimumSize, visualSize +*/ +qreal QQuickScrollBar::size() const +{ + Q_D(const QQuickScrollBar); + return d->size; +} + +void QQuickScrollBar::setSize(qreal size) +{ + Q_D(QQuickScrollBar); + if (!qt_is_finite(size) || qFuzzyCompare(d->size, size)) + return; + + auto oldVisualArea = d->visualArea(); + d->size = qBound(0.0, size, 1.0); + if (isComponentComplete()) + d->resizeContent(); + emit sizeChanged(); + d->visualAreaChange(d->visualArea(), oldVisualArea); +} + +/*! + \qmlproperty real QtQuick.Controls::ScrollBar::position + + This property holds the position of the scroll bar, scaled to \c {0.0 - 1.0}. + + \sa {Flickable::visibleArea.yPosition}{Flickable::visibleArea} + + This property is automatically set when the scroll bar is + \l {Attaching ScrollBar to a Flickable}{attached to a flickable}. + + \sa visualPosition +*/ +qreal QQuickScrollBar::position() const +{ + Q_D(const QQuickScrollBar); + return d->position; +} + +void QQuickScrollBar::setPosition(qreal position) +{ + Q_D(QQuickScrollBar); + if (!qt_is_finite(position) || qFuzzyCompare(d->position, position)) + return; + + auto oldVisualArea = d->visualArea(); + d->position = position; + if (isComponentComplete()) + d->resizeContent(); + emit positionChanged(); + d->visualAreaChange(d->visualArea(), oldVisualArea); +} + +/*! + \qmlproperty real QtQuick.Controls::ScrollBar::stepSize + + This property holds the step size. The default value is \c 0.0. + + \sa snapMode, increase(), decrease() +*/ +qreal QQuickScrollBar::stepSize() const +{ + Q_D(const QQuickScrollBar); + return d->stepSize; +} + +void QQuickScrollBar::setStepSize(qreal step) +{ + Q_D(QQuickScrollBar); + if (!qt_is_finite(step) || qFuzzyCompare(d->stepSize, step)) + return; + + d->stepSize = step; + emit stepSizeChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::ScrollBar::active + + This property holds whether the scroll bar is active, i.e. when it's \l pressed + or the attached Flickable is \l {Flickable::moving}{moving}. + + It is possible to keep \l {Binding the Active State of Horizontal and Vertical Scroll Bars} + {both horizontal and vertical bars visible} while scrolling in either direction. + + This property is automatically set when the scroll bar is + \l {Attaching ScrollBar to a Flickable}{attached to a flickable}. +*/ +bool QQuickScrollBar::isActive() const +{ + Q_D(const QQuickScrollBar); + return d->active; +} + +void QQuickScrollBar::setActive(bool active) +{ + Q_D(QQuickScrollBar); + if (d->active == active) + return; + + d->active = active; + emit activeChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::ScrollBar::pressed + + This property holds whether the scroll bar is pressed. +*/ +bool QQuickScrollBar::isPressed() const +{ + Q_D(const QQuickScrollBar); + return d->pressed; +} + +void QQuickScrollBar::setPressed(bool pressed) +{ + Q_D(QQuickScrollBar); + if (!pressed) { + if (QQuickIndicatorButton *button = decreaseVisual()) + button->setPressed(false); + if (QQuickIndicatorButton *button = increaseVisual()) + button->setPressed(false); + } + if (d->pressed == pressed) + return; + + d->pressed = pressed; + setAccessibleProperty("pressed", pressed); + d->updateActive(); + emit pressedChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::ScrollBar::orientation + + This property holds the orientation of the scroll bar. + + Possible values: + \value Qt.Horizontal Horizontal + \value Qt.Vertical Vertical (default) + + This property is automatically set when the scroll bar is + \l {Attaching ScrollBar to a Flickable}{attached to a flickable}. + + \sa horizontal, vertical +*/ +Qt::Orientation QQuickScrollBar::orientation() const +{ + Q_D(const QQuickScrollBar); + return d->orientation; +} + +void QQuickScrollBar::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickScrollBar); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + if (isComponentComplete()) + d->resizeContent(); + emit orientationChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty enumeration QtQuick.Controls::ScrollBar::snapMode + + This property holds the snap mode. + + Possible values: + \value ScrollBar.NoSnap The scrollbar does not snap (default). + \value ScrollBar.SnapAlways The scrollbar snaps while dragged. + \value ScrollBar.SnapOnRelease The scrollbar does not snap while being dragged, but only after released. + + In the following table, the various modes are illustrated with animations. + The movement and the \l stepSize (\c 0.25) are identical in each animation. + + \table + \header + \row \li \b Value \li \b Example + \row \li \c ScrollBar.NoSnap \li \image qtquickcontrols2-scrollbar-nosnap.gif + \row \li \c ScrollBar.SnapAlways \li \image qtquickcontrols2-scrollbar-snapalways.gif + \row \li \c ScrollBar.SnapOnRelease \li \image qtquickcontrols2-scrollbar-snaponrelease.gif + \endtable + + \sa stepSize +*/ +QQuickScrollBar::SnapMode QQuickScrollBar::snapMode() const +{ + Q_D(const QQuickScrollBar); + return d->snapMode; +} + +void QQuickScrollBar::setSnapMode(SnapMode mode) +{ + Q_D(QQuickScrollBar); + if (d->snapMode == mode) + return; + + d->snapMode = mode; + emit snapModeChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::ScrollBar::interactive + + This property holds whether the scroll bar is interactive. The default value is \c true. + + A non-interactive scroll bar is visually and behaviorally similar to \l ScrollIndicator. + This property is useful for switching between typical mouse- and touch-orientated UIs + with interactive and non-interactive scroll bars, respectively. +*/ +bool QQuickScrollBar::isInteractive() const +{ + Q_D(const QQuickScrollBar); + return d->interactive; +} + +void QQuickScrollBar::setInteractive(bool interactive) +{ + Q_D(QQuickScrollBar); + d->explicitInteractive = true; + d->setInteractive(interactive); +} + +void QQuickScrollBar::resetInteractive() +{ + Q_D(QQuickScrollBar); + d->explicitInteractive = false; + d->setInteractive(true); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty enumeration QtQuick.Controls::ScrollBar::policy + + This property holds the policy of the scroll bar. The default policy is \c ScrollBar.AsNeeded. + + Possible values: + \value ScrollBar.AsNeeded The scroll bar is only shown when the content is too large to fit. + \value ScrollBar.AlwaysOff The scroll bar is never shown. + \value ScrollBar.AlwaysOn The scroll bar is always shown. + + The following example keeps the vertical scroll bar always visible: + + \snippet qtquickcontrols2-scrollbar-policy.qml 1 +*/ +QQuickScrollBar::Policy QQuickScrollBar::policy() const +{ + Q_D(const QQuickScrollBar); + return d->policy; +} + +void QQuickScrollBar::setPolicy(Policy policy) +{ + Q_D(QQuickScrollBar); + if (d->policy == policy) + return; + + d->policy = policy; + emit policyChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::ScrollBar::horizontal + \readonly + + This property holds whether the scroll bar is horizontal. + + \sa orientation +*/ +bool QQuickScrollBar::isHorizontal() const +{ + Q_D(const QQuickScrollBar); + return d->orientation == Qt::Horizontal; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::ScrollBar::vertical + \readonly + + This property holds whether the scroll bar is vertical. + + \sa orientation +*/ +bool QQuickScrollBar::isVertical() const +{ + Q_D(const QQuickScrollBar); + return d->orientation == Qt::Vertical; +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty real QtQuick.Controls::ScrollBar::minimumSize + + This property holds the minimum size of the scroll bar, scaled to \c {0.0 - 1.0}. + + \sa size, visualSize, visualPosition +*/ +qreal QQuickScrollBar::minimumSize() const +{ + Q_D(const QQuickScrollBar); + return d->minimumSize; +} + +void QQuickScrollBar::setMinimumSize(qreal minimumSize) +{ + Q_D(QQuickScrollBar); + if (!qt_is_finite(minimumSize) || qFuzzyCompare(d->minimumSize, minimumSize)) + return; + + auto oldVisualArea = d->visualArea(); + d->minimumSize = qBound(0.0, minimumSize, 1.0); + if (isComponentComplete()) + d->resizeContent(); + emit minimumSizeChanged(); + d->visualAreaChange(d->visualArea(), oldVisualArea); +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty real QtQuick.Controls::ScrollBar::visualSize + + This property holds the effective visual size of the scroll bar, + which may be limited by the \l {minimumSize}{minimum size}. + + \sa size, minimumSize +*/ +qreal QQuickScrollBar::visualSize() const +{ + Q_D(const QQuickScrollBar); + return d->visualArea().size; +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty real QtQuick.Controls::ScrollBar::visualPosition + + This property holds the effective visual position of the scroll bar, + which may be limited by the \l {minimumSize}{minimum size}. + + \sa position, minimumSize +*/ +qreal QQuickScrollBar::visualPosition() const +{ + Q_D(const QQuickScrollBar); + return d->visualArea().position; +} + +QQuickIndicatorButton *QQuickScrollBar::decreaseVisual() +{ + Q_D(QQuickScrollBar); + return d->decreaseVisual; +} + +QQuickIndicatorButton *QQuickScrollBar::increaseVisual() +{ + Q_D(QQuickScrollBar); + return d->increaseVisual; +} + +/*! + \qmlmethod void QtQuick.Controls::ScrollBar::increase() + + Increases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0. + + \sa stepSize +*/ +void QQuickScrollBar::increase() +{ + Q_D(QQuickScrollBar); + qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + bool wasActive = d->active; + setActive(true); + setPosition(qMin<qreal>(1.0 - d->size, d->position + step)); + setActive(wasActive); +} + +/*! + \qmlmethod void QtQuick.Controls::ScrollBar::decrease() + + Decreases the position by \l stepSize or \c 0.1 if stepSize is \c 0.0. + + \sa stepSize +*/ +void QQuickScrollBar::decrease() +{ + Q_D(QQuickScrollBar); + qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + bool wasActive = d->active; + setActive(true); + setPosition(qMax<qreal>(0.0, d->position - step)); + setActive(wasActive); +} + +void QQuickScrollBar::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickScrollBar); + QQuickControl::mousePressEvent(event); + d->handleMove(event->position()); +} + +#if QT_CONFIG(quicktemplates2_hover) +void QQuickScrollBar::hoverChange() +{ + Q_D(QQuickScrollBar); + d->updateActive(); +} + +void QQuickScrollBar::hoverEnterEvent(QHoverEvent *event) +{ + Q_D(QQuickScrollBar); + QQuickControl::hoverEnterEvent(event); + d->updateHover(event->position()); + event->ignore(); +} + +void QQuickScrollBar::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QQuickScrollBar); + QQuickControl::hoverMoveEvent(event); + d->updateHover(event->position()); + event->ignore(); +} + +void QQuickScrollBar::hoverLeaveEvent(QHoverEvent *event) +{ + Q_D(QQuickScrollBar); + QQuickControl::hoverLeaveEvent(event); + + d->updateHover(QPoint(), false); //position is not needed when we force it to unhover + event->ignore(); +} +#endif + +void QQuickScrollBar::classBegin() +{ + Q_D(QQuickScrollBar); + QQuickControl::classBegin(); + + QQmlContext *context = qmlContext(this); + if (context) { + QQmlEngine::setContextForObject(d->decreaseVisual, context); + QQmlEngine::setContextForObject(d->increaseVisual, context); + } +} + +void QQuickScrollBar::componentComplete() +{ + Q_D(QQuickScrollBar); + QQuickIndicatorButtonPrivate::get(d->decreaseVisual)->executeIndicator(true); + QQuickIndicatorButtonPrivate::get(d->increaseVisual)->executeIndicator(true); + + QQuickControl::componentComplete(); +} + +#if QT_CONFIG(accessibility) +void QQuickScrollBar::accessibilityActiveChanged(bool active) +{ + QQuickControl::accessibilityActiveChanged(active); + + Q_D(QQuickScrollBar); + if (active) { + setAccessibleProperty("pressed", d->pressed); + + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this)) { + connect(accessibleAttached, &QQuickAccessibleAttached::increaseAction, this, &QQuickScrollBar::increase); + connect(accessibleAttached, &QQuickAccessibleAttached::decreaseAction, this, &QQuickScrollBar::decrease); + } + } else { + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this)) { + disconnect(accessibleAttached, &QQuickAccessibleAttached::increaseAction, this, &QQuickScrollBar::increase); + disconnect(accessibleAttached, &QQuickAccessibleAttached::decreaseAction, this, &QQuickScrollBar::decrease); + } + } +} + +QAccessible::Role QQuickScrollBar::accessibleRole() const +{ + return QAccessible::ScrollBar; +} +#endif + +void QQuickScrollBarAttachedPrivate::setFlickable(QQuickFlickable *item) +{ + if (flickable) { + // NOTE: Use removeItemChangeListener(Geometry) instead of updateOrRemoveGeometryChangeListener(Size). + // The latter doesn't remove the listener but only resets its types. Thus, it leaves behind a dangling + // pointer on destruction. + QQuickItemPrivate::get(flickable)->removeItemChangeListener(this, QQuickItemPrivate::Geometry); + QQuickItemPrivate::get(flickable)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed); + if (horizontal) + cleanupHorizontal(); + if (vertical) + cleanupVertical(); + } + + flickable = item; + + if (item) { + // Don't know how to combine these calls into one, and as long as they're separate calls, + // the remove* calls above need to be separate too, otherwise they will have no effect. + QQuickItemPrivate::get(item)->updateOrAddGeometryChangeListener(this, QQuickGeometryChange::Size); + QQuickItemPrivate::get(item)->updateOrAddItemChangeListener(this, QQuickItemPrivate::Destroyed); + if (horizontal) + initHorizontal(); + if (vertical) + initVertical(); + } +} + +void QQuickScrollBarAttachedPrivate::initHorizontal() +{ + Q_ASSERT(flickable && horizontal); + + connect(flickable, &QQuickFlickable::movingHorizontallyChanged, this, &QQuickScrollBarAttachedPrivate::activateHorizontal); + + // TODO: export QQuickFlickableVisibleArea + QObject *area = flickable->property("visibleArea").value<QObject *>(); + QObject::connect(area, SIGNAL(widthRatioChanged(qreal)), horizontal, SLOT(setSize(qreal))); + QObject::connect(area, SIGNAL(xPositionChanged(qreal)), horizontal, SLOT(setPosition(qreal))); + + // ensure that the ScrollBar is stacked above the Flickable in a ScrollView + QQuickItem *parent = horizontal->parentItem(); + if (parent && parent == flickable->parentItem()) + horizontal->stackAfter(flickable); + + // If a scroll bar was previously hidden (due to e.g. setting a new contentItem + // on a ScrollView), we need to make sure that we un-hide it. + // We don't bother checking if the item is actually the old one, because + // if it's not, all of the things the function does (setting parent, visibility, etc.) + // should be no-ops anyway. + if (auto control = qobject_cast<QQuickControl*>(q_func()->parent())) + QQuickControlPrivate::unhideOldItem(control, horizontal); + + layoutHorizontal(); + horizontal->setSize(area->property("widthRatio").toReal()); + horizontal->setPosition(area->property("xPosition").toReal()); +} + +void QQuickScrollBarAttachedPrivate::initVertical() +{ + Q_ASSERT(flickable && vertical); + + connect(flickable, &QQuickFlickable::movingVerticallyChanged, this, &QQuickScrollBarAttachedPrivate::activateVertical); + + // TODO: export QQuickFlickableVisibleArea + QObject *area = flickable->property("visibleArea").value<QObject *>(); + QObject::connect(area, SIGNAL(heightRatioChanged(qreal)), vertical, SLOT(setSize(qreal))); + QObject::connect(area, SIGNAL(yPositionChanged(qreal)), vertical, SLOT(setPosition(qreal))); + + // ensure that the ScrollBar is stacked above the Flickable in a ScrollView + QQuickItem *parent = vertical->parentItem(); + if (parent && parent == flickable->parentItem()) + vertical->stackAfter(flickable); + + if (auto control = qobject_cast<QQuickControl*>(q_func()->parent())) + QQuickControlPrivate::unhideOldItem(control, vertical); + + layoutVertical(); + vertical->setSize(area->property("heightRatio").toReal()); + vertical->setPosition(area->property("yPosition").toReal()); +} + +void QQuickScrollBarAttachedPrivate::cleanupHorizontal() +{ + Q_ASSERT(flickable && horizontal); + + QQuickControlPrivate::hideOldItem(horizontal); + // ScrollBar.qml has a binding to visible and ScrollView.qml has a binding to parent. + // If we just set visible to false and parent to null, these bindings will overwrite + // them upon component completion as part of the binding evaluation. + // That's why we remove the binding completely. + const QQmlProperty visibleProperty(horizontal, QStringLiteral("visible")); + const QQmlProperty parentProperty(horizontal, QStringLiteral("parent")); + QQmlPropertyPrivate::removeBinding(visibleProperty); + QQmlPropertyPrivate::removeBinding(parentProperty); + + disconnect(flickable, &QQuickFlickable::movingHorizontallyChanged, this, &QQuickScrollBarAttachedPrivate::activateHorizontal); + + // TODO: export QQuickFlickableVisibleArea + QObject *area = flickable->property("visibleArea").value<QObject *>(); + QObject::disconnect(area, SIGNAL(widthRatioChanged(qreal)), horizontal, SLOT(setSize(qreal))); + QObject::disconnect(area, SIGNAL(xPositionChanged(qreal)), horizontal, SLOT(setPosition(qreal))); +} + +void QQuickScrollBarAttachedPrivate::cleanupVertical() +{ + Q_ASSERT(flickable && vertical); + + QQuickControlPrivate::hideOldItem(vertical); + const QQmlProperty visibleProperty(vertical, QStringLiteral("visible")); + const QQmlProperty parentProperty(vertical, QStringLiteral("parent")); + QQmlPropertyPrivate::removeBinding(visibleProperty); + QQmlPropertyPrivate::removeBinding(parentProperty); + + disconnect(flickable, &QQuickFlickable::movingVerticallyChanged, this, &QQuickScrollBarAttachedPrivate::activateVertical); + + // TODO: export QQuickFlickableVisibleArea + QObject *area = flickable->property("visibleArea").value<QObject *>(); + QObject::disconnect(area, SIGNAL(heightRatioChanged(qreal)), vertical, SLOT(setSize(qreal))); + QObject::disconnect(area, SIGNAL(yPositionChanged(qreal)), vertical, SLOT(setPosition(qreal))); +} + +void QQuickScrollBarAttachedPrivate::activateHorizontal() +{ + QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(horizontal); + p->moving = flickable->isMovingHorizontally(); + p->updateActive(); +} + +void QQuickScrollBarAttachedPrivate::activateVertical() +{ + QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(vertical); + p->moving = flickable->isMovingVertically(); + p->updateActive(); +} + +// TODO: QQuickFlickable::maxXYExtent() +class QQuickFriendlyFlickable : public QQuickFlickable +{ + friend class QQuickScrollBarAttachedPrivate; +}; + +void QQuickScrollBarAttachedPrivate::scrollHorizontal() +{ + if (!flickable) + return; + + QQuickFriendlyFlickable *f = reinterpret_cast<QQuickFriendlyFlickable *>(flickable); + + const qreal viewwidth = f->width(); + const qreal maxxextent = -f->maxXExtent() + f->minXExtent(); + const qreal cx = horizontal->position() * (maxxextent + viewwidth) - f->minXExtent(); + + if (!qIsNaN(cx) && !qFuzzyCompare(cx, flickable->contentX())) + flickable->setContentX(cx); +} + +void QQuickScrollBarAttachedPrivate::scrollVertical() +{ + if (!flickable) + return; + + QQuickFriendlyFlickable *f = reinterpret_cast<QQuickFriendlyFlickable *>(flickable); + + const qreal viewheight = f->height(); + const qreal maxyextent = -f->maxYExtent() + f->minYExtent(); + const qreal cy = vertical->position() * (maxyextent + viewheight) - f->minYExtent(); + + if (!qIsNaN(cy) && !qFuzzyCompare(cy, flickable->contentY())) + flickable->setContentY(cy); +} + +void QQuickScrollBarAttachedPrivate::mirrorVertical() +{ + layoutVertical(true); +} + +void QQuickScrollBarAttachedPrivate::layoutHorizontal(bool move) +{ + Q_ASSERT(horizontal && flickable); + if (horizontal->parentItem() != flickable) + return; + horizontal->setWidth(flickable->width()); + if (move) + horizontal->setY(flickable->height() - horizontal->height()); +} + +void QQuickScrollBarAttachedPrivate::layoutVertical(bool move) +{ + Q_ASSERT(vertical && flickable); + if (vertical->parentItem() != flickable) + return; + vertical->setHeight(flickable->height()); + if (move) + vertical->setX(vertical->isMirrored() ? 0 : flickable->width() - vertical->width()); +} + +void QQuickScrollBarAttachedPrivate::itemGeometryChanged(QQuickItem *item, const QQuickGeometryChange change, const QRectF &diff) +{ + Q_UNUSED(item); + Q_UNUSED(change); + if (horizontal && horizontal->height() > 0) { +#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry + bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), diff.height() - horizontal->height()); +#else + bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), item->height() - diff.height() - horizontal->height()); +#endif + if (flickable) + layoutHorizontal(move); + } + if (vertical && vertical->width() > 0) { +#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry + bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), diff.width() - vertical->width()); +#else + bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), item->width() - diff.width() - vertical->width()); +#endif + if (flickable) + layoutVertical(move); + } +} + +void QQuickScrollBarAttachedPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + if (item == vertical && flickable) + layoutVertical(true); +} + +void QQuickScrollBarAttachedPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + if (item == horizontal && flickable) + layoutHorizontal(true); +} + +void QQuickScrollBarAttachedPrivate::itemDestroyed(QQuickItem *item) +{ + if (item == flickable) + flickable = nullptr; + if (item == horizontal) + horizontal = nullptr; + if (item == vertical) + vertical = nullptr; +} + +QQuickScrollBarAttached::QQuickScrollBarAttached(QObject *parent) + : QObject(*(new QQuickScrollBarAttachedPrivate), parent) +{ + Q_D(QQuickScrollBarAttached); + d->setFlickable(qobject_cast<QQuickFlickable *>(parent)); + + if (parent && !d->flickable && !qobject_cast<QQuickScrollView *>(parent)) + qmlWarning(parent) << "ScrollBar must be attached to a Flickable or ScrollView"; +} + +QQuickScrollBarAttached::~QQuickScrollBarAttached() +{ + Q_D(QQuickScrollBarAttached); + if (d->horizontal) { + QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, horizontalChangeTypes); + d->horizontal = nullptr; + } + if (d->vertical) { + QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, verticalChangeTypes); + d->vertical = nullptr; + } + d->setFlickable(nullptr); +} + +/*! + \qmlattachedproperty ScrollBar QtQuick.Controls::ScrollBar::horizontal + + This property attaches a horizontal scroll bar to a \l Flickable. + + \code + Flickable { + contentWidth: 2000 + ScrollBar.horizontal: ScrollBar { } + } + \endcode + + \sa {Attaching ScrollBar to a Flickable} +*/ +QQuickScrollBar *QQuickScrollBarAttached::horizontal() const +{ + Q_D(const QQuickScrollBarAttached); + return d->horizontal; +} + +void QQuickScrollBarAttached::setHorizontal(QQuickScrollBar *horizontal) +{ + Q_D(QQuickScrollBarAttached); + if (d->horizontal == horizontal) + return; + + if (d->horizontal) { + QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, horizontalChangeTypes); + QObjectPrivate::disconnect(d->horizontal, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollHorizontal); + + if (d->flickable) + d->cleanupHorizontal(); + } + + d->horizontal = horizontal; + + if (horizontal) { + if (!horizontal->parentItem()) + horizontal->setParentItem(qobject_cast<QQuickItem *>(parent())); + horizontal->setOrientation(Qt::Horizontal); + + QQuickItemPrivate::get(horizontal)->addItemChangeListener(d, horizontalChangeTypes); + QObjectPrivate::connect(horizontal, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollHorizontal); + + if (d->flickable) + d->initHorizontal(); + } + emit horizontalChanged(); +} + +/*! + \qmlattachedproperty ScrollBar QtQuick.Controls::ScrollBar::vertical + + This property attaches a vertical scroll bar to a \l Flickable. + + \code + Flickable { + contentHeight: 2000 + ScrollBar.vertical: ScrollBar { } + } + \endcode + + \sa {Attaching ScrollBar to a Flickable} +*/ +QQuickScrollBar *QQuickScrollBarAttached::vertical() const +{ + Q_D(const QQuickScrollBarAttached); + return d->vertical; +} + +void QQuickScrollBarAttached::setVertical(QQuickScrollBar *vertical) +{ + Q_D(QQuickScrollBarAttached); + if (d->vertical == vertical) + return; + + if (d->vertical) { + QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, verticalChangeTypes); + QObjectPrivate::disconnect(d->vertical, &QQuickScrollBar::mirroredChanged, d, &QQuickScrollBarAttachedPrivate::mirrorVertical); + QObjectPrivate::disconnect(d->vertical, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollVertical); + + if (d->flickable) + d->cleanupVertical(); + } + + d->vertical = vertical; + + if (vertical) { + if (!vertical->parentItem()) + vertical->setParentItem(qobject_cast<QQuickItem *>(parent())); + vertical->setOrientation(Qt::Vertical); + + QQuickItemPrivate::get(vertical)->addItemChangeListener(d, verticalChangeTypes); + QObjectPrivate::connect(vertical, &QQuickScrollBar::mirroredChanged, d, &QQuickScrollBarAttachedPrivate::mirrorVertical); + QObjectPrivate::connect(vertical, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollVertical); + + if (d->flickable) + d->initVertical(); + } + emit verticalChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquickscrollbar_p.cpp" diff --git a/src/quicktemplates2/qquickscrollbar_p.h b/src/quicktemplates2/qquickscrollbar_p.h new file mode 100644 index 0000000000..b7de290b24 --- /dev/null +++ b/src/quicktemplates2/qquickscrollbar_p.h @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSCROLLBAR_P_H +#define QQUICKSCROLLBAR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQuickTemplates2/private/qquickindicatorbutton_p.h> +QT_BEGIN_NAMESPACE + +class QQuickScrollBarAttached; +class QQuickScrollBarPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollBar : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY sizeChanged FINAL) + Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + // 2.2 (Qt 5.9) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL REVISION(2, 2)) + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive RESET resetInteractive NOTIFY interactiveChanged FINAL REVISION(2, 2)) + Q_PROPERTY(Policy policy READ policy WRITE setPolicy NOTIFY policyChanged FINAL REVISION(2, 2)) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool horizontal READ isHorizontal NOTIFY orientationChanged FINAL REVISION(2, 3)) + Q_PROPERTY(bool vertical READ isVertical NOTIFY orientationChanged FINAL REVISION(2, 3)) + // 2.4 (Qt 5.11) + Q_PROPERTY(qreal minimumSize READ minimumSize WRITE setMinimumSize NOTIFY minimumSizeChanged FINAL REVISION(2, 4)) + Q_PROPERTY(qreal visualSize READ visualSize NOTIFY visualSizeChanged FINAL REVISION(2, 4)) + Q_PROPERTY(qreal visualPosition READ visualPosition NOTIFY visualPositionChanged FINAL REVISION(2, 4)) + + Q_PROPERTY(QQuickIndicatorButton *__decreaseVisual READ decreaseVisual CONSTANT FINAL) + Q_PROPERTY(QQuickIndicatorButton *__increaseVisual READ increaseVisual CONSTANT FINAL) + + QML_NAMED_ELEMENT(ScrollBar) + QML_ATTACHED(QQuickScrollBarAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickScrollBar(QQuickItem *parent = nullptr); + + static QQuickScrollBarAttached *qmlAttachedProperties(QObject *object); + + qreal size() const; + qreal position() const; + + qreal stepSize() const; + void setStepSize(qreal step); + + bool isActive() const; + void setActive(bool active); + + bool isPressed() const; + void setPressed(bool pressed); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + // 2.2 (Qt 5.9) + enum SnapMode { + NoSnap, + SnapAlways, + SnapOnRelease + }; + Q_ENUM(SnapMode) + + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + bool isInteractive() const; + void setInteractive(bool interactive); + void resetInteractive(); + + enum Policy { + AsNeeded = Qt::ScrollBarAsNeeded, + AlwaysOff = Qt::ScrollBarAlwaysOff, + AlwaysOn = Qt::ScrollBarAlwaysOn + }; + Q_ENUM(Policy) + + Policy policy() const; + void setPolicy(Policy policy); + + // 2.3 (Qt 5.10) + bool isHorizontal() const; + bool isVertical() const; + + // 2.4 (Qt 5.11) + qreal minimumSize() const; + void setMinimumSize(qreal minimumSize); + + qreal visualSize() const; + qreal visualPosition() const; + + QQuickIndicatorButton *decreaseVisual(); + QQuickIndicatorButton *increaseVisual(); + +public Q_SLOTS: + void increase(); + void decrease(); + void setSize(qreal size); + void setPosition(qreal position); + +Q_SIGNALS: + void sizeChanged(); + void positionChanged(); + void stepSizeChanged(); + void activeChanged(); + void pressedChanged(); + void orientationChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void snapModeChanged(); + Q_REVISION(2, 2) void interactiveChanged(); + Q_REVISION(2, 2) void policyChanged(); + // 2.4 (Qt 5.11) + Q_REVISION(2, 4) void minimumSizeChanged(); + Q_REVISION(2, 4) void visualSizeChanged(); + Q_REVISION(2, 4) void visualPositionChanged(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +#if QT_CONFIG(quicktemplates2_hover) + void hoverChange() override; + void hoverEnterEvent(QHoverEvent *event) override; + void hoverMoveEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; +#endif + + void classBegin() override; + void componentComplete() override; + +#if QT_CONFIG(accessibility) + void accessibilityActiveChanged(bool active) override; + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickScrollBar) + Q_DECLARE_PRIVATE(QQuickScrollBar) +}; + +class QQuickScrollBarAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollBarAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickScrollBar *horizontal READ horizontal WRITE setHorizontal NOTIFY horizontalChanged FINAL) + Q_PROPERTY(QQuickScrollBar *vertical READ vertical WRITE setVertical NOTIFY verticalChanged FINAL) + +public: + explicit QQuickScrollBarAttached(QObject *parent = nullptr); + ~QQuickScrollBarAttached(); + + QQuickScrollBar *horizontal() const; + void setHorizontal(QQuickScrollBar *horizontal); + + QQuickScrollBar *vertical() const; + void setVertical(QQuickScrollBar *vertical); + +Q_SIGNALS: + void horizontalChanged(); + void verticalChanged(); + +private: + Q_DISABLE_COPY(QQuickScrollBarAttached) + Q_DECLARE_PRIVATE(QQuickScrollBarAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickScrollBar) +QML_DECLARE_TYPEINFO(QQuickScrollBar, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKSCROLLBAR_P_H diff --git a/src/quicktemplates2/qquickscrollbar_p_p.h b/src/quicktemplates2/qquickscrollbar_p_p.h new file mode 100644 index 0000000000..70da611f0e --- /dev/null +++ b/src/quicktemplates2/qquickscrollbar_p_p.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSCROLLBAR_P_P_H +#define QQUICKSCROLLBAR_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickscrollbar_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickFlickable; +class QQuickIndicatorButton; + +class QQuickScrollBarPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickScrollBar) + +public: + static QQuickScrollBarPrivate *get(QQuickScrollBar *bar) + { + return bar->d_func(); + } + + struct VisualArea + { + VisualArea(qreal pos, qreal sz) + : position(pos), size(sz) { } + qreal position = 0; + qreal size = 0; + }; + VisualArea visualArea() const; + + qreal logicalPosition(qreal position) const; + + qreal snapPosition(qreal position) const; + qreal positionAt(const QPointF &point) const; + void setInteractive(bool interactive); + void updateActive(); + void resizeContent() override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + void handleUngrab() override; + + void visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea); + + void updateHover(const QPointF &pos, std::optional<bool> newHoverState = {}); + + QQuickIndicatorButton *decreaseVisual = nullptr; + QQuickIndicatorButton *increaseVisual = nullptr; + qreal size = 0; + qreal position = 0; + qreal stepSize = 0; + qreal offset = 0; + qreal minimumSize = 0; + bool active = false; + bool pressed = false; + bool moving = false; + bool interactive = true; + bool explicitInteractive = false; + Qt::Orientation orientation = Qt::Vertical; + QQuickScrollBar::SnapMode snapMode = QQuickScrollBar::NoSnap; + QQuickScrollBar::Policy policy = QQuickScrollBar::AsNeeded; +}; + +class QQuickScrollBarAttachedPrivate : public QObjectPrivate, public QQuickItemChangeListener +{ + Q_DECLARE_PUBLIC(QQuickScrollBarAttached) + +public: + static QQuickScrollBarAttachedPrivate *get(QQuickScrollBarAttached *attached) + { + return attached->d_func(); + } + + void setFlickable(QQuickFlickable *flickable); + + void initHorizontal(); + void initVertical(); + void cleanupHorizontal(); + void cleanupVertical(); + void activateHorizontal(); + void activateVertical(); + void scrollHorizontal(); + void scrollVertical(); + void mirrorVertical(); + + void layoutHorizontal(bool move = true); + void layoutVertical(bool move = true); + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; + + QQuickFlickable *flickable = nullptr; + QQuickScrollBar *horizontal = nullptr; + QQuickScrollBar *vertical = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKSCROLLBAR_P_P_H diff --git a/src/quicktemplates2/qquickscrollindicator.cpp b/src/quicktemplates2/qquickscrollindicator.cpp new file mode 100644 index 0000000000..1e7efc6bde --- /dev/null +++ b/src/quicktemplates2/qquickscrollindicator.cpp @@ -0,0 +1,667 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickscrollindicator_p.h" +#include "qquickcontrol_p_p.h" + +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickflickable_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ScrollIndicator + \inherits Control +//! \instantiates QQuickScrollIndicator + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-indicators + \brief Vertical or horizontal non-interactive scroll indicator. + + \image qtquickcontrols2-scrollindicator.gif + + ScrollIndicator is a non-interactive indicator that indicates the current scroll + position. A scroll indicator can be either \l vertical or \l horizontal, and can + be attached to any \l Flickable, such as \l ListView and \l GridView. + + \code + Flickable { + // ... + ScrollIndicator.vertical: ScrollIndicator { } + } + \endcode + + \section1 Attaching ScrollIndicator to a Flickable + + \note When ScrollIndicator is attached \l {ScrollIndicator::vertical}{vertically} + or \l {ScrollIndicator::horizontal}{horizontally} to a Flickable, its geometry and + the following properties are automatically set and updated as appropriate: + + \list + \li \l orientation + \li \l position + \li \l size + \li \l active + \endlist + + An attached ScrollIndicator re-parents itself to the target Flickable. A vertically + attached ScrollIndicator resizes itself to the height of the Flickable, and positions + itself to either side of it based on the \l {Control::mirrored}{layout direction}. + A horizontally attached ScrollIndicator resizes itself to the width of the Flickable, + and positions itself to the bottom. The automatic geometry management can be disabled + by specifying another parent for the attached ScrollIndicator. This can be useful, for + example, if the ScrollIndicator should be placed outside a clipping Flickable. This is + demonstrated by the following example: + + \code + Flickable { + id: flickable + clip: true + // ... + ScrollIndicator.vertical: ScrollIndicator { + parent: flickable.parent + anchors.top: flickable.top + anchors.left: flickable.right + anchors.bottom: flickable.bottom + } + } + \endcode + + \section1 Binding the Active State of Horizontal and Vertical Scroll Indicators + + Horizontal and vertical scroll indicators do not share the \l active state with + each other by default. In order to keep both indicators visible whilst scrolling + to either direction, establish a two-way binding between the active states as + presented by the following example: + + \snippet qtquickcontrols2-scrollindicator-active.qml 1 + + \section1 Non-attached Scroll Indicators + + It is possible to create an instance of ScrollIndicator without using the + attached property API. This is useful when the behavior of the attached + scoll indicator is not sufficient or a \l Flickable is not in use. In the + following example, horizontal and vertical scroll indicators are used to + indicate how far the user has scrolled over the text (using \l MouseArea + instead of \l Flickable): + + \snippet qtquickcontrols2-scrollindicator-non-attached.qml 1 + + \image qtquickcontrols2-scrollindicator-non-attached.png + + \sa ScrollBar, {Customizing ScrollIndicator}, {Indicator Controls} +*/ + +static const QQuickItemPrivate::ChangeTypes changeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed; +static const QQuickItemPrivate::ChangeTypes horizontalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitHeight; +static const QQuickItemPrivate::ChangeTypes verticalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitWidth; + +class QQuickScrollIndicatorPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickScrollIndicator) + +public: + struct VisualArea + { + VisualArea(qreal pos, qreal sz) + : position(pos), size(sz) { } + qreal position = 0; + qreal size = 0; + }; + VisualArea visualArea() const; + void visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea); + + void resizeContent() override; + + qreal size = 0; + qreal minimumSize = 0; + qreal position = 0; + bool active = false; + Qt::Orientation orientation = Qt::Vertical; +}; + +QQuickScrollIndicatorPrivate::VisualArea QQuickScrollIndicatorPrivate::visualArea() const +{ + qreal visualPos = position; + if (minimumSize > size) + visualPos = position / (1.0 - size) * (1.0 - minimumSize); + + qreal visualSize = qBound<qreal>(0, qMax(size, minimumSize) + qMin<qreal>(0, visualPos), 1.0 - visualPos); + + visualPos = qBound<qreal>(0, visualPos, 1.0 - visualSize); + + return VisualArea(visualPos, visualSize); +} + +void QQuickScrollIndicatorPrivate::visualAreaChange(const VisualArea &newVisualArea, const VisualArea &oldVisualArea) +{ + Q_Q(QQuickScrollIndicator); + if (!qFuzzyCompare(newVisualArea.size, oldVisualArea.size)) + emit q->visualSizeChanged(); + if (!qFuzzyCompare(newVisualArea.position, oldVisualArea.position)) + emit q->visualPositionChanged(); +} + +void QQuickScrollIndicatorPrivate::resizeContent() +{ + Q_Q(QQuickScrollIndicator); + if (!contentItem) + return; + + // - negative overshoot (pos < 0): clamp the pos to 0, and deduct the overshoot from the size + // - positive overshoot (pos + size > 1): clamp the size to 1-pos + const VisualArea visual = visualArea(); + + if (orientation == Qt::Horizontal) { + contentItem->setPosition(QPointF(q->leftPadding() + visual.position * q->availableWidth(), q->topPadding())); + contentItem->setSize(QSizeF(q->availableWidth() * visual.size, q->availableHeight())); + } else { + contentItem->setPosition(QPointF(q->leftPadding(), q->topPadding() + visual.position * q->availableHeight())); + contentItem->setSize(QSizeF(q->availableWidth(), q->availableHeight() * visual.size)); + } +} + +QQuickScrollIndicator::QQuickScrollIndicator(QQuickItem *parent) + : QQuickControl(*(new QQuickScrollIndicatorPrivate), parent) +{ +} + +QQuickScrollIndicatorAttached *QQuickScrollIndicator::qmlAttachedProperties(QObject *object) +{ + return new QQuickScrollIndicatorAttached(object); +} + +/*! + \qmlproperty real QtQuick.Controls::ScrollIndicator::size + + This property holds the size of the indicator, scaled to \c {0.0 - 1.0}. + + \sa {Flickable::visibleArea.heightRatio}{Flickable::visibleArea} + + This property is automatically set when the scroll indicator is + \l {Attaching ScrollIndicator to a Flickable}{attached to a flickable}. + + \sa minimumSize, visualSize +*/ +qreal QQuickScrollIndicator::size() const +{ + Q_D(const QQuickScrollIndicator); + return d->size; +} + +void QQuickScrollIndicator::setSize(qreal size) +{ + Q_D(QQuickScrollIndicator); + if (qFuzzyCompare(d->size, size)) + return; + + auto oldVisualArea = d->visualArea(); + d->size = size; + if (isComponentComplete()) + d->resizeContent(); + emit sizeChanged(); + d->visualAreaChange(d->visualArea(), oldVisualArea); +} + +/*! + \qmlproperty real QtQuick.Controls::ScrollIndicator::position + + This property holds the position of the indicator, scaled to \c {0.0 - 1.0}. + + This property is automatically set when the scroll indicator is + \l {Attaching ScrollIndicator to a Flickable}{attached to a flickable}. + + \sa {Flickable::visibleArea.yPosition}{Flickable::visibleArea}, visualPosition +*/ +qreal QQuickScrollIndicator::position() const +{ + Q_D(const QQuickScrollIndicator); + return d->position; +} + +void QQuickScrollIndicator::setPosition(qreal position) +{ + Q_D(QQuickScrollIndicator); + if (qFuzzyCompare(d->position, position)) + return; + + auto oldVisualArea = d->visualArea(); + d->position = position; + if (isComponentComplete()) + d->resizeContent(); + emit positionChanged(); + d->visualAreaChange(d->visualArea(), oldVisualArea); +} + +/*! + \qmlproperty bool QtQuick.Controls::ScrollIndicator::active + + This property holds whether the indicator is active, that is, when the + attached Flickable is \l {Flickable::moving}{moving}. + + It is possible to keep \l {Binding the Active State of Horizontal and Vertical Scroll Indicators} + {both horizontal and vertical indicators visible} while scrolling in either direction. + + This property is automatically set when the scroll indicator is + \l {Attaching ScrollIndicator to a Flickable}{attached to a flickable}. +*/ +bool QQuickScrollIndicator::isActive() const +{ + Q_D(const QQuickScrollIndicator); + return d->active; +} + +void QQuickScrollIndicator::setActive(bool active) +{ + Q_D(QQuickScrollIndicator); + if (d->active == active) + return; + + d->active = active; + emit activeChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::ScrollIndicator::orientation + + This property holds the orientation of the indicator. + + Possible values: + \value Qt.Horizontal Horizontal + \value Qt.Vertical Vertical (default) + + This property is automatically set when the scroll indicator is + \l {Attaching ScrollIndicator to a Flickable}{attached to a flickable}. + + \sa horizontal, vertical +*/ +Qt::Orientation QQuickScrollIndicator::orientation() const +{ + Q_D(const QQuickScrollIndicator); + return d->orientation; +} + +void QQuickScrollIndicator::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickScrollIndicator); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + if (isComponentComplete()) + d->resizeContent(); + emit orientationChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::ScrollIndicator::horizontal + \readonly + + This property holds whether the scroll indicator is horizontal. + + \sa orientation +*/ +bool QQuickScrollIndicator::isHorizontal() const +{ + Q_D(const QQuickScrollIndicator); + return d->orientation == Qt::Horizontal; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::ScrollIndicator::vertical + \readonly + + This property holds whether the scroll indicator is vertical. + + \sa orientation +*/ +bool QQuickScrollIndicator::isVertical() const +{ + Q_D(const QQuickScrollIndicator); + return d->orientation == Qt::Vertical; +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty real QtQuick.Controls::ScrollIndicator::minimumSize + + This property holds the minimum size of the indicator, scaled to \c {0.0 - 1.0}. + + \sa size, visualSize, visualPosition +*/ +qreal QQuickScrollIndicator::minimumSize() const +{ + Q_D(const QQuickScrollIndicator); + return d->minimumSize; +} + +void QQuickScrollIndicator::setMinimumSize(qreal minimumSize) +{ + Q_D(QQuickScrollIndicator); + if (qFuzzyCompare(d->minimumSize, minimumSize)) + return; + + auto oldVisualArea = d->visualArea(); + d->minimumSize = minimumSize; + if (isComponentComplete()) + d->resizeContent(); + emit minimumSizeChanged(); + d->visualAreaChange(d->visualArea(), oldVisualArea); +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty real QtQuick.Controls::ScrollIndicator::visualSize + + This property holds the effective visual size of the indicator, + which may be limited by the \l {minimumSize}{minimum size}. + + \sa size, minimumSize +*/ +qreal QQuickScrollIndicator::visualSize() const +{ + Q_D(const QQuickScrollIndicator); + return d->visualArea().size; +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty real QtQuick.Controls::ScrollIndicator::visualPosition + + This property holds the effective visual position of the indicator, + which may be limited by the \l {minimumSize}{minimum size}. + + \sa position, minimumSize +*/ +qreal QQuickScrollIndicator::visualPosition() const +{ + Q_D(const QQuickScrollIndicator); + return d->visualArea().position; +} + +class QQuickScrollIndicatorAttachedPrivate : public QObjectPrivate, public QQuickItemChangeListener +{ +public: + void activateHorizontal(); + void activateVertical(); + + void layoutHorizontal(bool move = true); + void layoutVertical(bool move = true); + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; + + QQuickFlickable *flickable = nullptr; + QQuickScrollIndicator *horizontal = nullptr; + QQuickScrollIndicator *vertical = nullptr; +}; + +void QQuickScrollIndicatorAttachedPrivate::activateHorizontal() +{ + horizontal->setActive(flickable->isMovingHorizontally()); +} + +void QQuickScrollIndicatorAttachedPrivate::activateVertical() +{ + vertical->setActive(flickable->isMovingVertically()); +} + +void QQuickScrollIndicatorAttachedPrivate::layoutHorizontal(bool move) +{ + Q_ASSERT(horizontal && flickable); + if (horizontal->parentItem() != flickable) + return; + horizontal->setWidth(flickable->width()); + if (move) + horizontal->setY(flickable->height() - horizontal->height()); +} + +void QQuickScrollIndicatorAttachedPrivate::layoutVertical(bool move) +{ + Q_ASSERT(vertical && flickable); + if (vertical->parentItem() != flickable) + return; + vertical->setHeight(flickable->height()); + if (move && !QQuickItemPrivate::get(vertical)->isMirrored()) + vertical->setX(flickable->width() - vertical->width()); +} + +void QQuickScrollIndicatorAttachedPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + Q_UNUSED(item); + Q_UNUSED(change); + if (horizontal && horizontal->height() > 0) { +#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry + bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), diff.height() - horizontal->height()); +#else + bool move = qFuzzyIsNull(horizontal->y()) || qFuzzyCompare(horizontal->y(), item->height() - diff.height() - horizontal->height()); +#endif + layoutHorizontal(move); + } + if (vertical && vertical->width() > 0) { +#ifdef QT_QUICK_NEW_GEOMETRY_CHANGED_HANDLING // TODO: correct/rename diff to oldGeometry + bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), diff.width() - vertical->width()); +#else + bool move = qFuzzyIsNull(vertical->x()) || qFuzzyCompare(vertical->x(), item->width() - diff.width() - vertical->width()); +#endif + layoutVertical(move); + } +} + +void QQuickScrollIndicatorAttachedPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + if (item == vertical) + layoutVertical(true); +} + +void QQuickScrollIndicatorAttachedPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + if (item == horizontal) + layoutHorizontal(true); +} + +void QQuickScrollIndicatorAttachedPrivate::itemDestroyed(QQuickItem *item) +{ + if (item == horizontal) + horizontal = nullptr; + if (item == vertical) + vertical = nullptr; +} + +QQuickScrollIndicatorAttached::QQuickScrollIndicatorAttached(QObject *parent) + : QObject(*(new QQuickScrollIndicatorAttachedPrivate), parent) +{ + Q_D(QQuickScrollIndicatorAttached); + d->flickable = qobject_cast<QQuickFlickable *>(parent); + if (d->flickable) + QQuickItemPrivate::get(d->flickable)->updateOrAddGeometryChangeListener(d, QQuickGeometryChange::Size); + else if (parent) + qmlWarning(parent) << "ScrollIndicator must be attached to a Flickable"; +} + +QQuickScrollIndicatorAttached::~QQuickScrollIndicatorAttached() +{ + Q_D(QQuickScrollIndicatorAttached); + if (d->flickable) { + if (d->horizontal) + QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, horizontalChangeTypes); + if (d->vertical) + QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d,verticalChangeTypes); + // NOTE: Use removeItemChangeListener(Geometry) instead of updateOrRemoveGeometryChangeListener(Size). + // The latter doesn't remove the listener but only resets its types. Thus, it leaves behind a dangling + // pointer on destruction. + QQuickItemPrivate::get(d->flickable)->removeItemChangeListener(d, QQuickItemPrivate::Geometry); + } +} + +/*! + \qmlattachedproperty ScrollIndicator QtQuick.Controls::ScrollIndicator::horizontal + + This property attaches a horizontal scroll indicator to a \l Flickable. + + \code + Flickable { + contentWidth: 2000 + ScrollIndicator.horizontal: ScrollIndicator { } + } + \endcode + + \sa {Attaching ScrollIndicator to a Flickable} +*/ +QQuickScrollIndicator *QQuickScrollIndicatorAttached::horizontal() const +{ + Q_D(const QQuickScrollIndicatorAttached); + return d->horizontal; +} + +void QQuickScrollIndicatorAttached::setHorizontal(QQuickScrollIndicator *horizontal) +{ + Q_D(QQuickScrollIndicatorAttached); + if (d->horizontal == horizontal) + return; + + if (d->horizontal && d->flickable) { + QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, horizontalChangeTypes); + QObjectPrivate::disconnect(d->flickable, &QQuickFlickable::movingHorizontallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateHorizontal); + + // TODO: export QQuickFlickableVisibleArea + QObject *area = d->flickable->property("visibleArea").value<QObject *>(); + disconnect(area, SIGNAL(widthRatioChanged(qreal)), d->horizontal, SLOT(setSize(qreal))); + disconnect(area, SIGNAL(xPositionChanged(qreal)), d->horizontal, SLOT(setPosition(qreal))); + } + + d->horizontal = horizontal; + + if (horizontal && d->flickable) { + if (!horizontal->parentItem()) + horizontal->setParentItem(d->flickable); + horizontal->setOrientation(Qt::Horizontal); + + QQuickItemPrivate::get(horizontal)->addItemChangeListener(d, horizontalChangeTypes); + QObjectPrivate::connect(d->flickable, &QQuickFlickable::movingHorizontallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateHorizontal); + + // TODO: export QQuickFlickableVisibleArea + QObject *area = d->flickable->property("visibleArea").value<QObject *>(); + connect(area, SIGNAL(widthRatioChanged(qreal)), horizontal, SLOT(setSize(qreal))); + connect(area, SIGNAL(xPositionChanged(qreal)), horizontal, SLOT(setPosition(qreal))); + + d->layoutHorizontal(); + horizontal->setSize(area->property("widthRatio").toReal()); + horizontal->setPosition(area->property("xPosition").toReal()); + } + emit horizontalChanged(); +} + +/*! + \qmlattachedproperty ScrollIndicator QtQuick.Controls::ScrollIndicator::vertical + + This property attaches a vertical scroll indicator to a \l Flickable. + + \code + Flickable { + contentHeight: 2000 + ScrollIndicator.vertical: ScrollIndicator { } + } + \endcode + + \sa {Attaching ScrollIndicator to a Flickable} +*/ +QQuickScrollIndicator *QQuickScrollIndicatorAttached::vertical() const +{ + Q_D(const QQuickScrollIndicatorAttached); + return d->vertical; +} + +void QQuickScrollIndicatorAttached::setVertical(QQuickScrollIndicator *vertical) +{ + Q_D(QQuickScrollIndicatorAttached); + if (d->vertical == vertical) + return; + + if (d->vertical && d->flickable) { + QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, verticalChangeTypes); + QObjectPrivate::disconnect(d->flickable, &QQuickFlickable::movingVerticallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateVertical); + + // TODO: export QQuickFlickableVisibleArea + QObject *area = d->flickable->property("visibleArea").value<QObject *>(); + disconnect(area, SIGNAL(heightRatioChanged(qreal)), d->vertical, SLOT(setSize(qreal))); + disconnect(area, SIGNAL(yPositionChanged(qreal)), d->vertical, SLOT(setPosition(qreal))); + } + + d->vertical = vertical; + + if (vertical && d->flickable) { + if (!vertical->parentItem()) + vertical->setParentItem(d->flickable); + vertical->setOrientation(Qt::Vertical); + + QQuickItemPrivate::get(vertical)->addItemChangeListener(d, verticalChangeTypes); + QObjectPrivate::connect(d->flickable, &QQuickFlickable::movingVerticallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateVertical); + + // TODO: export QQuickFlickableVisibleArea + QObject *area = d->flickable->property("visibleArea").value<QObject *>(); + connect(area, SIGNAL(heightRatioChanged(qreal)), vertical, SLOT(setSize(qreal))); + connect(area, SIGNAL(yPositionChanged(qreal)), vertical, SLOT(setPosition(qreal))); + + d->layoutVertical(); + vertical->setSize(area->property("heightRatio").toReal()); + vertical->setPosition(area->property("yPosition").toReal()); + } + emit verticalChanged(); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickScrollIndicator::touchEvent(QTouchEvent *event) +{ + event->ignore(); // QTBUG-61785 +} +#endif + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickScrollIndicator::accessibleRole() const +{ + return QAccessible::Indicator; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickscrollindicator_p.cpp" diff --git a/src/quicktemplates2/qquickscrollindicator_p.h b/src/quicktemplates2/qquickscrollindicator_p.h new file mode 100644 index 0000000000..dbad6ffe43 --- /dev/null +++ b/src/quicktemplates2/qquickscrollindicator_p.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSCROLLINDICATOR_P_H +#define QQUICKSCROLLINDICATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickFlickable; +class QQuickScrollIndicatorAttached; +class QQuickScrollIndicatorPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollIndicator : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY sizeChanged FINAL) + Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged FINAL) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool horizontal READ isHorizontal NOTIFY orientationChanged FINAL REVISION(2, 3)) + Q_PROPERTY(bool vertical READ isVertical NOTIFY orientationChanged FINAL REVISION(2, 3)) + // 2.4 (Qt 5.11) + Q_PROPERTY(qreal minimumSize READ minimumSize WRITE setMinimumSize NOTIFY minimumSizeChanged FINAL REVISION(2, 4)) + Q_PROPERTY(qreal visualSize READ visualSize NOTIFY visualSizeChanged FINAL REVISION(2, 4)) + Q_PROPERTY(qreal visualPosition READ visualPosition NOTIFY visualPositionChanged FINAL REVISION(2, 4)) + QML_NAMED_ELEMENT(ScrollIndicator) + QML_ATTACHED(QQuickScrollIndicatorAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickScrollIndicator(QQuickItem *parent = nullptr); + + static QQuickScrollIndicatorAttached *qmlAttachedProperties(QObject *object); + + qreal size() const; + qreal position() const; + + bool isActive() const; + void setActive(bool active); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + // 2.3 (Qt 5.10) + bool isHorizontal() const; + bool isVertical() const; + + // 2.4 (Qt 5.11) + qreal minimumSize() const; + void setMinimumSize(qreal minimumSize); + + qreal visualSize() const; + qreal visualPosition() const; + +public Q_SLOTS: + void setSize(qreal size); + void setPosition(qreal position); + +Q_SIGNALS: + void sizeChanged(); + void positionChanged(); + void activeChanged(); + void orientationChanged(); + // 2.4 (Qt 5.11) + Q_REVISION(2, 4) void minimumSizeChanged(); + Q_REVISION(2, 4) void visualSizeChanged(); + Q_REVISION(2, 4) void visualPositionChanged(); + +protected: +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickScrollIndicator) + Q_DECLARE_PRIVATE(QQuickScrollIndicator) +}; + +class QQuickScrollIndicatorAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollIndicatorAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickScrollIndicator *horizontal READ horizontal WRITE setHorizontal NOTIFY horizontalChanged FINAL) + Q_PROPERTY(QQuickScrollIndicator *vertical READ vertical WRITE setVertical NOTIFY verticalChanged FINAL) + +public: + explicit QQuickScrollIndicatorAttached(QObject *parent = nullptr); + ~QQuickScrollIndicatorAttached(); + + QQuickScrollIndicator *horizontal() const; + void setHorizontal(QQuickScrollIndicator *horizontal); + + QQuickScrollIndicator *vertical() const; + void setVertical(QQuickScrollIndicator *vertical); + +Q_SIGNALS: + void horizontalChanged(); + void verticalChanged(); + +private: + Q_DISABLE_COPY(QQuickScrollIndicatorAttached) + Q_DECLARE_PRIVATE(QQuickScrollIndicatorAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickScrollIndicator) +QML_DECLARE_TYPEINFO(QQuickScrollIndicator, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKSCROLLINDICATOR_P_H diff --git a/src/quicktemplates2/qquickscrollview.cpp b/src/quicktemplates2/qquickscrollview.cpp new file mode 100644 index 0000000000..1f5adbb7ff --- /dev/null +++ b/src/quicktemplates2/qquickscrollview.cpp @@ -0,0 +1,623 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickscrollview_p.h" +#include "qquickpane_p_p.h" +#include "qquickscrollbar_p_p.h" + +#include <QtQuick/private/qquickflickable_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ScrollView + \inherits Pane +//! \instantiates QQuickScrollView + \inqmlmodule QtQuick.Controls + \since 5.9 + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Scrollable view. + + ScrollView provides scrolling for user-defined content. It can be used to + either replace a \l Flickable, or to decorate an existing one. + + \image qtquickcontrols2-scrollview.png + + The first example demonstrates the simplest usage of ScrollView. + + \snippet qtquickcontrols2-scrollview.qml file + + The second example illustrates using an existing \l Flickable, that is, + a \l ListView. + + \snippet qtquickcontrols2-scrollview-listview.qml file + + \note As of Qt-6.0, ScrollView automatically clips its contents if you + don't use a Flickable as a child. If this is not wanted, you can + set your own Flickable as a child, and control the \l {Item::}{clip} + property on the Flickable explicitly. + + \section2 Sizing + + As with Flickable, there are several things to keep in mind when using + ScrollView: + \list + \li If only a single item is used within a ScrollView, the content size is + automatically calculated based on the implicit size of its contained item. + However, if more than one item is used (or an implicit size is not + provided), the \l {QtQuick.Controls::Pane::}{contentWidth} and + \l {QtQuick.Controls::Pane::}{contentHeight} properties must + be set to the combined size of its contained items. + \li If the content size is less than or equal to the size of the ScrollView, + it will not be scrollable. + \li If you want the ScrollView to only scroll vertically, you can bind + \l {QtQuick.Controls::Pane::}{contentWidth} to + \l {QtQuick.Controls::Control::}{availableWidth} + (and vice versa for contentHeight). This will let the contents fill + out all the available space horizontally inside the ScrollView, taking + any padding or scroll bars into account. + \endlist + + \section2 Scroll Bars + + The horizontal and vertical scroll bars can be accessed and customized using + the \l {ScrollBar::horizontal}{ScrollBar.horizontal} and \l {ScrollBar::vertical} + {ScrollBar.vertical} attached properties. The following example adjusts the scroll + bar policies so that the horizontal scroll bar is always off, and the vertical + scroll bar is always on. + + \snippet qtquickcontrols2-scrollview-policy.qml file + + \section2 Touch vs. Mouse Interaction + + On touch, ScrollView enables flicking and makes the scroll bars non-interactive. + + \image qtquickcontrols2-scrollindicator.gif + + When interacted with a mouse device, flicking is disabled and the scroll bars + are interactive. + + \image qtquickcontrols2-scrollbar.gif + + Scroll bars can be made interactive on touch, or non-interactive when interacted + with a mouse device, by setting the \l {ScrollBar::}{interactive} property explicitly + to \c true or \c false, respectively. + + \snippet qtquickcontrols2-scrollview-interactive.qml file + + \sa ScrollBar, ScrollIndicator, {Customizing ScrollView}, {Container Controls}, + {Focus Management in Qt Quick Controls} +*/ + +class QQuickScrollViewPrivate : public QQuickPanePrivate +{ + Q_DECLARE_PUBLIC(QQuickScrollView) + +public: + QQmlListProperty<QObject> contentData() override; + QQmlListProperty<QQuickItem> contentChildren() override; + QList<QQuickItem *> contentChildItems() const override; + + QQuickItem *getContentItem() override; + + QQuickFlickable *ensureFlickable(bool content); + bool setFlickable(QQuickFlickable *flickable, bool content); + + void flickableContentWidthChanged(); + void flickableContentHeightChanged(); + + qreal getContentWidth() const override; + qreal getContentHeight() const override; + + QQuickScrollBar *verticalScrollBar() const; + QQuickScrollBar *horizontalScrollBar() const; + + void setScrollBarsInteractive(bool interactive); + + static void contentData_append(QQmlListProperty<QObject> *prop, QObject *obj); + static qsizetype contentData_count(QQmlListProperty<QObject> *prop); + static QObject *contentData_at(QQmlListProperty<QObject> *prop, qsizetype index); + static void contentData_clear(QQmlListProperty<QObject> *prop); + + static void contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *obj); + static qsizetype contentChildren_count(QQmlListProperty<QQuickItem> *prop); + static QQuickItem *contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index); + static void contentChildren_clear(QQmlListProperty<QQuickItem> *prop); + + void itemImplicitWidthChanged(QQuickItem *item) override; + + bool wasTouched = false; + QQuickFlickable *flickable = nullptr; + bool flickableHasExplicitContentWidth = true; + bool flickableHasExplicitContentHeight = true; +}; + +QList<QQuickItem *> QQuickScrollViewPrivate::contentChildItems() const +{ + if (!flickable) + return QList<QQuickItem *>(); + + return flickable->contentItem()->childItems(); +} + +QQuickItem *QQuickScrollViewPrivate::getContentItem() +{ + if (!contentItem) + executeContentItem(); + return ensureFlickable(false); +} + +QQuickFlickable *QQuickScrollViewPrivate::ensureFlickable(bool content) +{ + Q_Q(QQuickScrollView); + if (!flickable) { + flickableHasExplicitContentWidth = false; + flickableHasExplicitContentHeight = false; + auto flickable = new QQuickFlickable(q); + // We almost always want to clip the flickable so that flickable + // contents doesn't show up outside the scrollview. The only time + // this is not really needed, is when the scrollview covers the whole + // window and the scrollbars are transient. But for that corner case, if this + // optimization is needed, the user can simply create his own flickable + // child inside the scrollview, and control clipping on it explicit. + flickable->setClip(true); + flickable->setPixelAligned(true); + setFlickable(flickable, content); + } + return flickable; +} + +bool QQuickScrollViewPrivate::setFlickable(QQuickFlickable *item, bool content) +{ + Q_Q(QQuickScrollView); + if (item == flickable) + return false; + + QQuickScrollBarAttached *attached = qobject_cast<QQuickScrollBarAttached *>(qmlAttachedPropertiesObject<QQuickScrollBar>(q, false)); + + if (flickable) { + flickable->removeEventFilter(q); + + if (attached) + QQuickScrollBarAttachedPrivate::get(attached)->setFlickable(nullptr); + + QObjectPrivate::disconnect(flickable->contentItem(), &QQuickItem::childrenChanged, this, &QQuickPanePrivate::contentChildrenChange); + QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickScrollViewPrivate::flickableContentWidthChanged); + QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentHeightChanged, this, &QQuickScrollViewPrivate::flickableContentHeightChanged); + } + + flickable = item; + if (content) + q->setContentItem(flickable); + + if (flickable) { + flickable->installEventFilter(q); + if (hasContentWidth) + flickable->setContentWidth(contentWidth); + else + flickableContentWidthChanged(); + if (hasContentHeight) + flickable->setContentHeight(contentHeight); + else + flickableContentHeightChanged(); + + if (attached) + QQuickScrollBarAttachedPrivate::get(attached)->setFlickable(flickable); + + QObjectPrivate::connect(flickable->contentItem(), &QQuickItem::childrenChanged, this, &QQuickPanePrivate::contentChildrenChange); + QObjectPrivate::connect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickScrollViewPrivate::flickableContentWidthChanged); + QObjectPrivate::connect(flickable, &QQuickFlickable::contentHeightChanged, this, &QQuickScrollViewPrivate::flickableContentHeightChanged); + } + + return true; +} + +void QQuickScrollViewPrivate::flickableContentWidthChanged() +{ + Q_Q(QQuickScrollView); + if (!flickable || !componentComplete) + return; + + const qreal cw = flickable->contentWidth(); + if (qFuzzyCompare(cw, implicitContentWidth)) + return; + + flickableHasExplicitContentWidth = true; + implicitContentWidth = cw; + emit q->implicitContentWidthChanged(); +} + +void QQuickScrollViewPrivate::flickableContentHeightChanged() +{ + Q_Q(QQuickScrollView); + if (!flickable || !componentComplete) + return; + + const qreal ch = flickable->contentHeight(); + if (qFuzzyCompare(ch, implicitContentHeight)) + return; + + flickableHasExplicitContentHeight = true; + implicitContentHeight = ch; + emit q->implicitContentHeightChanged(); +} + +qreal QQuickScrollViewPrivate::getContentWidth() const +{ + if (flickable && flickableHasExplicitContentWidth) + return flickable->contentWidth(); + + // The scrollview wraps a flickable created by us, and nobody searched for it and + // modified its contentWidth. In that case, since the application does not control + // this flickable, we fall back to calculate the content width based on the child + // items inside it. + return QQuickPanePrivate::getContentWidth(); +} + +qreal QQuickScrollViewPrivate::getContentHeight() const +{ + if (flickable && flickableHasExplicitContentHeight) + return flickable->contentHeight(); + + // The scrollview wraps a flickable created by us, and nobody searched for it and + // modified its contentHeight. In that case, since the application does not control + // this flickable, we fall back to calculate the content height based on the child + // items inside it. + return QQuickPanePrivate::getContentHeight(); +} + +QQuickScrollBar *QQuickScrollViewPrivate::verticalScrollBar() const +{ + Q_Q(const QQuickScrollView); + QQuickScrollBarAttached *attached = qobject_cast<QQuickScrollBarAttached *>(qmlAttachedPropertiesObject<QQuickScrollBar>(q, false)); + if (!attached) + return nullptr; + return attached->vertical(); +} + +QQuickScrollBar *QQuickScrollViewPrivate::horizontalScrollBar() const +{ + Q_Q(const QQuickScrollView); + QQuickScrollBarAttached *attached = qobject_cast<QQuickScrollBarAttached *>(qmlAttachedPropertiesObject<QQuickScrollBar>(q, false)); + if (!attached) + return nullptr; + return attached->horizontal(); +} + +void QQuickScrollViewPrivate::setScrollBarsInteractive(bool interactive) +{ + QQuickScrollBar *hbar = horizontalScrollBar(); + if (hbar) { + QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(hbar); + if (!p->explicitInteractive) + p->setInteractive(interactive); + } + + QQuickScrollBar *vbar = verticalScrollBar(); + if (vbar) { + QQuickScrollBarPrivate *p = QQuickScrollBarPrivate::get(vbar); + if (!p->explicitInteractive) + p->setInteractive(interactive); + } +} + +void QQuickScrollViewPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) +{ + QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); + if (!p->flickable && p->setFlickable(qobject_cast<QQuickFlickable *>(obj), true)) + return; + + QQuickFlickable *flickable = p->ensureFlickable(true); + Q_ASSERT(flickable); + QQmlListProperty<QObject> data = flickable->flickableData(); + data.append(&data, obj); +} + +qsizetype QQuickScrollViewPrivate::contentData_count(QQmlListProperty<QObject> *prop) +{ + QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); + if (!p->flickable) + return 0; + + QQmlListProperty<QObject> data = p->flickable->flickableData(); + return data.count(&data); +} + +QObject *QQuickScrollViewPrivate::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index) +{ + QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); + if (!p->flickable) + return nullptr; + + QQmlListProperty<QObject> data = p->flickable->flickableData(); + return data.at(&data, index); +} + +void QQuickScrollViewPrivate::contentData_clear(QQmlListProperty<QObject> *prop) +{ + QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); + if (!p->flickable) + return; + + QQmlListProperty<QObject> data = p->flickable->flickableData(); + return data.clear(&data); +} + +void QQuickScrollViewPrivate::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item) +{ + QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); + if (!p->flickable) + p->setFlickable(qobject_cast<QQuickFlickable *>(item), true); + + QQuickFlickable *flickable = p->ensureFlickable(true); + Q_ASSERT(flickable); + QQmlListProperty<QQuickItem> children = flickable->flickableChildren(); + children.append(&children, item); +} + +qsizetype QQuickScrollViewPrivate::contentChildren_count(QQmlListProperty<QQuickItem> *prop) +{ + QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); + if (!p->flickable) + return 0; + + QQmlListProperty<QQuickItem> children = p->flickable->flickableChildren(); + return children.count(&children); +} + +QQuickItem *QQuickScrollViewPrivate::contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index) +{ + QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); + if (!p->flickable) + return nullptr; + + QQmlListProperty<QQuickItem> children = p->flickable->flickableChildren(); + return children.at(&children, index); +} + +void QQuickScrollViewPrivate::contentChildren_clear(QQmlListProperty<QQuickItem> *prop) +{ + QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); + if (!p->flickable) + return; + + QQmlListProperty<QQuickItem> children = p->flickable->flickableChildren(); + children.clear(&children); +} + +void QQuickScrollViewPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + // a special case for width<->height dependent content (wrapping text) in ScrollView + if (contentWidth < 0 && !componentComplete) + return; + + QQuickPanePrivate::itemImplicitWidthChanged(item); +} + +QQuickScrollView::QQuickScrollView(QQuickItem *parent) + : QQuickPane(*(new QQuickScrollViewPrivate), parent) +{ + Q_D(QQuickScrollView); + d->contentWidth = -1; + d->contentHeight = -1; + + setFiltersChildMouseEvents(true); + setWheelEnabled(true); +} + +/*! + \qmlproperty list<Object> QtQuick.Controls::ScrollView::contentData + \qmldefault + + This property holds the list of content data. + + The list contains all objects that have been declared in QML as children of the view. + + \note Unlike \c contentChildren, \c contentData does include non-visual QML objects. + + \sa Item::data, contentChildren +*/ +QQmlListProperty<QObject> QQuickScrollViewPrivate::contentData() +{ + Q_Q(QQuickScrollView); + return QQmlListProperty<QObject>(q, this, + QQuickScrollViewPrivate::contentData_append, + QQuickScrollViewPrivate::contentData_count, + QQuickScrollViewPrivate::contentData_at, + QQuickScrollViewPrivate::contentData_clear); +} + +/*! + \qmlproperty list<Item> QtQuick.Controls::ScrollView::contentChildren + + This property holds the list of content children. + + The list contains all items that have been declared in QML as children of the view. + + \note Unlike \c contentData, \c contentChildren does not include non-visual QML objects. + + \sa Item::children, contentData +*/ +QQmlListProperty<QQuickItem> QQuickScrollViewPrivate::contentChildren() +{ + Q_Q(QQuickScrollView); + return QQmlListProperty<QQuickItem>(q, this, + QQuickScrollViewPrivate::contentChildren_append, + QQuickScrollViewPrivate::contentChildren_count, + QQuickScrollViewPrivate::contentChildren_at, + QQuickScrollViewPrivate::contentChildren_clear); +} + +bool QQuickScrollView::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + Q_D(QQuickScrollView); + switch (event->type()) { + case QEvent::TouchBegin: + d->wasTouched = true; + d->setScrollBarsInteractive(false); + return false; + + case QEvent::TouchEnd: + d->wasTouched = false; + return false; + + case QEvent::MouseButtonPress: + // NOTE: Flickable does not handle touch events, only synthesized mouse events + if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized) { + d->wasTouched = false; + d->setScrollBarsInteractive(true); + return false; + } + return !d->wasTouched && item == d->flickable; + + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized) + return item == d->flickable; + break; + + case QEvent::HoverEnter: + case QEvent::HoverMove: + if (d->wasTouched && (item == d->verticalScrollBar() || item == d->horizontalScrollBar())) + d->setScrollBarsInteractive(true); + break; + + default: + break; + } + + return false; +} + +bool QQuickScrollView::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QQuickScrollView); + if (event->type() == QEvent::Wheel) { + d->setScrollBarsInteractive(true); + if (!d->wheelEnabled) + return true; + } + return QQuickPane::eventFilter(object, event); +} + +void QQuickScrollView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickScrollView); + QQuickPane::keyPressEvent(event); + switch (event->key()) { + case Qt::Key_Up: + if (QQuickScrollBar *vbar = d->verticalScrollBar()) { + vbar->decrease(); + event->accept(); + } + break; + case Qt::Key_Down: + if (QQuickScrollBar *vbar = d->verticalScrollBar()) { + vbar->increase(); + event->accept(); + } + break; + case Qt::Key_Left: + if (QQuickScrollBar *hbar = d->horizontalScrollBar()) { + hbar->decrease(); + event->accept(); + } + break; + case Qt::Key_Right: + if (QQuickScrollBar *hbar = d->horizontalScrollBar()) { + hbar->increase(); + event->accept(); + } + break; + default: + event->ignore(); + break; + } +} + +void QQuickScrollView::componentComplete() +{ + Q_D(QQuickScrollView); + QQuickPane::componentComplete(); + if (!d->contentItem) + d->ensureFlickable(true); +} + +void QQuickScrollView::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickScrollView); + if (newItem != d->flickable) { + // The new flickable was not created by us. In that case, we always + // assume/require that it has an explicit content size assigned. + d->flickableHasExplicitContentWidth = true; + d->flickableHasExplicitContentHeight = true; + auto newItemAsFlickable = qobject_cast<QQuickFlickable *>(newItem); + if (newItem && !newItemAsFlickable) + qmlWarning(this) << "ScrollView only supports Flickable types as its contentItem"; + d->setFlickable(newItemAsFlickable, false); + } + QQuickPane::contentItemChange(newItem, oldItem); +} + +void QQuickScrollView::contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize) +{ + Q_D(QQuickScrollView); + QQuickPane::contentSizeChange(newSize, oldSize); + if (d->flickable) { + // Only set the content size on the flickable if the flickable doesn't + // have an explicit assignment from before. Otherwise we can end up overwriting + // assignments done to those properties by the application. The + // exception is if the application has assigned a content size + // directly to the scrollview, which will then win even if the + // application has assigned something else to the flickable. + if (d->hasContentWidth || !d->flickableHasExplicitContentWidth) + d->flickable->setContentWidth(newSize.width()); + if (d->hasContentHeight || !d->flickableHasExplicitContentHeight) + d->flickable->setContentHeight(newSize.height()); + } +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickScrollView::accessibleRole() const +{ + return QAccessible::Pane; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickscrollview_p.cpp" diff --git a/src/quicktemplates2/qquickscrollview_p.h b/src/quicktemplates2/qquickscrollview_p.h new file mode 100644 index 0000000000..7eea46ddae --- /dev/null +++ b/src/quicktemplates2/qquickscrollview_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSCROLLVIEW_P_H +#define QQUICKSCROLLVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpane_p.h> +#include <QtQml/qqmllist.h> + +QT_BEGIN_NAMESPACE + +class QQuickScrollViewPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollView : public QQuickPane +{ + Q_OBJECT + QML_NAMED_ELEMENT(ScrollView) + QML_ADDED_IN_VERSION(2, 2) + +public: + explicit QQuickScrollView(QQuickItem *parent = nullptr); + +protected: + bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; + bool eventFilter(QObject *object, QEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + + void componentComplete() override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + void contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize) override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickScrollView) + Q_DECLARE_PRIVATE(QQuickScrollView) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickScrollView) + +#endif // QQUICKSCROLLVIEW_P_H diff --git a/src/quicktemplates2/qquickselectionrectangle.cpp b/src/quicktemplates2/qquickselectionrectangle.cpp new file mode 100644 index 0000000000..10573eda7c --- /dev/null +++ b/src/quicktemplates2/qquickselectionrectangle.cpp @@ -0,0 +1,574 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickselectionrectangle_p.h" +#include "qquickselectionrectangle_p_p.h" + +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickdraghandler_p.h> +#include <QtQuick/private/qquickhoverhandler_p.h> + +#include <QtQuick/private/qquicktableview_p_p.h> + +#include "qquickscrollview_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype SelectionRectangle + \inherits Control +//! \instantiates QQuickSelectionRectangle + \inqmlmodule QtQuick.Controls + \since 6.2 + \ingroup utilities + \brief Used to select table cells inside a TableView. + + \image qtquickcontrols2-selectionrectangle.png + + SelectionRectangle is used for selecting table cells in a TableView. It lets + the user start a selection by doing a pointer drag inside the viewport, or by + doing a long press on top of a cell. + + For a SelectionRectangle to be able to select cells, TableView must have + an ItemSelectionModel assigned. The ItemSelectionModel will store any + selections done on the model, and can be used for querying + which cells that the user has selected. + + The following example shows how you can make a SelectionRectangle target + a TableView: + + \snippet qtquickcontrols2-selectionrectangle.qml 0 + + \note A SelectionRectangle itself is not shown as part of a selection. Only the + delegates (like topLeftHandle and bottomRightHandle) are used. + You should also consider \l {Selecting items}{rendering the TableView delegate as selected}. + + \sa TableView, TableView::selectionModel, ItemSelectionModel +*/ + +/*! + \qmlproperty Item QtQuick.Controls::SelectionRectangle::target + + This property holds the TableView on which the + SelectionRectangle should act. +*/ + +/*! + \qmlproperty bool QtQuick.Controls::SelectionRectangle::active + \readonly + + This property is \c true while the user is performing a + selection. The selection will be active from the time the + the user starts to select, and until the selection is + removed again, for example from tapping inside the viewport. +*/ + +/*! + \qmlproperty bool QtQuick.Controls::SelectionRectangle::dragging + \readonly + + This property is \c true whenever the user is doing a pointer drag or + a handle drag to adjust the selection rectangle. +*/ + +/*! + \qmlproperty Component QtQuick.Controls::SelectionRectangle::topLeftHandle + + This property holds the delegate that will be shown on the center of the + top-left corner of the selection rectangle. When a handle is + provided, the user can drag it to adjust the selection. + + Set this property to \c null if you don't want a selection handle on the top-left. + + \sa bottomRightHandle +*/ + +/*! + \qmlproperty Component QtQuick.Controls::SelectionRectangle::bottomRightHandle + + This property holds the delegate that will be shown on the center of the + top-left corner of the selection rectangle. When a handle is + provided, the user can drag it to adjust the selection. + + Set this property to \c null if you don't want a selection handle on the bottom-right. + + \sa topLeftHandle +*/ + +/*! + \qmlproperty enumeration QtQuick.Controls::SelectionRectangle::selectionMode + + This property holds when a selection should start. + + \value SelectionRectangle.Drag A selection will start by doing a pointer drag inside the viewport + \value SelectionRectangle.PressAndHold A selection will start by doing a press and hold on top a cell + \value SelectionRectangle.Auto SelectionRectangle will choose which mode to use based on the target + and the platform. This normally means \c PressAndHold on touch based platforms, and \c Drag on desktop. + However, \c Drag will only be used if it doesn't conflict with flicking. This means that + TableView will need to be configured with \c interactive set to \c false, or placed + inside a ScrollView (where flicking, by default, is off for mouse events), for \c Drag to be chosen. + + The default value is \c Auto. +*/ + +/*! + \qmlattachedproperty SelectionRectangle QtQuick::SelectionRectangle::control + + This attached property holds the SelectionRectangle that manages the delegate instance. + It is attached to each handle instance. +*/ + +/*! + \qmlattachedproperty bool QtQuick::SelectionRectangle::dragging + + This attached property will be \c true if the user is dragging on the handle. + It is attached to each handle instance. +*/ + +QQuickSelectionRectanglePrivate::QQuickSelectionRectanglePrivate() + : QQuickControlPrivate() +{ + m_tapHandler = new QQuickTapHandler(); + m_dragHandler = new QQuickDragHandler(); + m_dragHandler->setTarget(nullptr); + + QObject::connect(&m_scrollTimer, &QTimer::timeout, [&]{ + if (m_topLeftHandle && m_draggedHandle == m_topLeftHandle) + m_selectable->setSelectionStartPos(m_scrollToPoint); + else + m_selectable->setSelectionEndPos(m_scrollToPoint); + updateHandles(); + const QSizeF dist = m_selectable->scrollTowardsSelectionPoint(m_scrollToPoint, m_scrollSpeed); + m_scrollToPoint.rx() += dist.width() > 0 ? m_scrollSpeed.width() : -m_scrollSpeed.width(); + m_scrollToPoint.ry() += dist.height() > 0 ? m_scrollSpeed.height() : -m_scrollSpeed.height(); + m_scrollSpeed = QSizeF(qAbs(dist.width() * 0.007), qAbs(dist.height() * 0.007)); + }); + + QObject::connect(m_tapHandler, &QQuickTapHandler::tapped, [=](QEventPoint) { + m_selectable->clearSelection(); + updateActiveState(false); + }); + + QObject::connect(m_tapHandler, &QQuickTapHandler::longPressed, [=]() { + if (handleUnderPos(m_tapHandler->point().pressPosition()) != nullptr) { + // Don't allow press'n'hold to start a new + // selection if it started on top of a handle. + return; + } + if (!m_alwaysAcceptPressAndHold) { + if (m_selectionMode == QQuickSelectionRectangle::Auto) { + // In Auto mode, we only accept press and hold from touch + if (m_tapHandler->point().device()->pointerType() != QPointingDevice::PointerType::Finger) + return; + } else if (m_selectionMode != QQuickSelectionRectangle::PressAndHold) { + return; + } + } + + const QPointF pos = m_tapHandler->point().position(); + m_selectable->clearSelection(); + m_selectable->setSelectionStartPos(pos); + m_selectable->setSelectionEndPos(pos); + updateHandles(); + updateActiveState(true); + }); + + QObject::connect(m_dragHandler, &QQuickDragHandler::activeChanged, [=]() { + const QPointF startPos = m_dragHandler->centroid().pressPosition(); + const QPointF dragPos = m_dragHandler->centroid().position(); + if (m_dragHandler->active()) { + m_selectable->clearSelection(); + m_selectable->setSelectionStartPos(startPos); + m_selectable->setSelectionEndPos(dragPos); + m_draggedHandle = nullptr; + updateHandles(); + updateActiveState(true); + updateDraggingState(true); + } else { + m_scrollTimer.stop(); + m_selectable->normalizeSelection(); + updateDraggingState(false); + } + }); + + QObject::connect(m_dragHandler, &QQuickDragHandler::centroidChanged, [=]() { + if (!m_dragging) + return; + const QPointF pos = m_dragHandler->centroid().position(); + m_selectable->setSelectionEndPos(pos); + updateHandles(); + scrollTowardsPos(pos); + }); +} + +void QQuickSelectionRectanglePrivate::scrollTowardsPos(const QPointF &pos) +{ + m_scrollToPoint = pos; + if (m_scrollTimer.isActive()) + return; + + const QSizeF dist = m_selectable->scrollTowardsSelectionPoint(m_scrollToPoint, m_scrollSpeed); + if (!dist.isNull()) + m_scrollTimer.start(1); +} + +QQuickItem *QQuickSelectionRectanglePrivate::handleUnderPos(const QPointF &pos) +{ + const auto handlerTarget = m_selectable->selectionPointerHandlerTarget(); + if (m_topLeftHandle) { + const QPointF localPos = m_topLeftHandle->mapFromItem(handlerTarget, pos); + if (m_topLeftHandle->contains(localPos)) + return m_topLeftHandle; + } + + if (m_bottomRightHandle) { + const QPointF localPos = m_bottomRightHandle->mapFromItem(handlerTarget, pos); + if (m_bottomRightHandle->contains(localPos)) + return m_bottomRightHandle; + } + + return nullptr; +} + +void QQuickSelectionRectanglePrivate::updateDraggingState(bool dragging) +{ + if (dragging != m_dragging) { + m_dragging = dragging; + emit q_func()->draggingChanged(); + } + + if (auto attached = getAttachedObject(m_draggedHandle)) + attached->setDragging(dragging); +} + +void QQuickSelectionRectanglePrivate::updateActiveState(bool active) +{ + if (active == m_active) + return; + + m_active = active; + emit q_func()->activeChanged(); +} + +QQuickItem *QQuickSelectionRectanglePrivate::createHandle(QQmlComponent *delegate, Qt::Corner corner) +{ + Q_Q(QQuickSelectionRectangle); + + // Incubate the handle + QObject *obj = delegate->beginCreate(QQmlEngine::contextForObject(q)); + QQuickItem *handleItem = qobject_cast<QQuickItem*>(obj); + const auto handlerTarget = m_selectable->selectionPointerHandlerTarget(); + handleItem->setParentItem(handlerTarget); + if (auto attached = getAttachedObject(handleItem)) + attached->setControl(q); + delegate->completeCreate(); + if (handleItem->z() == 0) + handleItem->setZ(100); + + // Add pointer handlers to it + QQuickDragHandler *dragHandler = new QQuickDragHandler(); + dragHandler->setTarget(nullptr); + dragHandler->setParent(handleItem); + QQuickItemPrivate::get(handleItem)->addPointerHandler(dragHandler); + + QQuickHoverHandler *hoverHandler = new QQuickHoverHandler(); + hoverHandler->setTarget(nullptr); + hoverHandler->setParent(handleItem); + hoverHandler->setCursorShape(Qt::SizeFDiagCursor); + QQuickItemPrivate::get(handleItem)->addPointerHandler(hoverHandler); + + QObject::connect(dragHandler, &QQuickDragHandler::activeChanged, [=]() { + if (dragHandler->active()) { + const QPointF localPos = dragHandler->centroid().position(); + const QPointF pos = handleItem->mapToItem(handleItem->parentItem(), localPos); + if (corner == Qt::TopLeftCorner) + m_selectable->setSelectionStartPos(pos); + else + m_selectable->setSelectionEndPos(pos); + + m_draggedHandle = handleItem; + updateHandles(); + updateDraggingState(true); + QGuiApplication::setOverrideCursor(Qt::SizeFDiagCursor); + } else { + m_scrollTimer.stop(); + m_selectable->normalizeSelection(); + updateDraggingState(false); + QGuiApplication::restoreOverrideCursor(); + } + }); + + QObject::connect(dragHandler, &QQuickDragHandler::centroidChanged, [=]() { + if (!m_dragging) + return; + + const QPointF localPos = dragHandler->centroid().position(); + const QPointF pos = handleItem->mapToItem(handleItem->parentItem(), localPos); + if (corner == Qt::TopLeftCorner) + m_selectable->setSelectionStartPos(pos); + else + m_selectable->setSelectionEndPos(pos); + + updateHandles(); + scrollTowardsPos(pos); + }); + + return handleItem; +} + +void QQuickSelectionRectanglePrivate::updateHandles() +{ + if (!m_selectable) + return; + + const QRectF rect = m_selectable->selectionRectangle().normalized(); + + if (!m_topLeftHandle && m_topLeftHandleDelegate) + m_topLeftHandle = createHandle(m_topLeftHandleDelegate, Qt::TopLeftCorner); + + if (!m_bottomRightHandle && m_bottomRightHandleDelegate) + m_bottomRightHandle = createHandle(m_bottomRightHandleDelegate, Qt::BottomRightCorner); + + if (m_topLeftHandle) { + m_topLeftHandle->setX(rect.x() - (m_topLeftHandle->width() / 2)); + m_topLeftHandle->setY(rect.y() - (m_topLeftHandle->height() / 2)); + } + + if (m_bottomRightHandle) { + m_bottomRightHandle->setX(rect.x() + rect.width() - (m_bottomRightHandle->width() / 2)); + m_bottomRightHandle->setY(rect.y() + rect.height() - (m_bottomRightHandle->height() / 2)); + } +} + +void QQuickSelectionRectanglePrivate::connectToTarget() +{ + // To support QuickSelectionRectangle::Auto, we need to listen for changes to the target + if (const auto flickable = qobject_cast<QQuickFlickable *>(m_target)) { + connect(flickable, &QQuickFlickable::interactiveChanged, this, &QQuickSelectionRectanglePrivate::updateSelectionMode); + } +} + +void QQuickSelectionRectanglePrivate::updateSelectionMode() +{ + Q_Q(QQuickSelectionRectangle); + + const bool enabled = q->isEnabled(); + m_tapHandler->setEnabled(enabled); + + if (m_selectionMode == QQuickSelectionRectangle::Auto) { + if (qobject_cast<QQuickScrollView *>(m_target->parentItem())) { + // ScrollView allows flicking with touch, but not with mouse. So we do + // the same here: you can drag to select with a mouse, but not with touch. + m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse); + m_dragHandler->setEnabled(enabled); + } else if (const auto flickable = qobject_cast<QQuickFlickable *>(m_target)) { + m_dragHandler->setEnabled(enabled && !flickable->isInteractive()); + } else { + m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse); + m_dragHandler->setEnabled(enabled); + } + } else if (m_selectionMode == QQuickSelectionRectangle::Drag) { + m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::AllDevices); + m_dragHandler->setEnabled(enabled); + } else { + m_dragHandler->setEnabled(false); + } + + // If you can't select using a drag, we always accept a PressAndHold + m_alwaysAcceptPressAndHold = !m_dragHandler->enabled(); +} + +QQuickSelectionRectangleAttached *QQuickSelectionRectanglePrivate::getAttachedObject(const QObject *object) const +{ + QObject *attachedObject = qmlAttachedPropertiesObject<QQuickSelectionRectangle>(object); + return static_cast<QQuickSelectionRectangleAttached *>(attachedObject); +} + +// -------------------------------------------------------- + +QQuickSelectionRectangle::QQuickSelectionRectangle(QQuickItem *parent) + : QQuickControl(*(new QQuickSelectionRectanglePrivate), parent) +{ + Q_D(QQuickSelectionRectangle); + + QObject::connect(this, &QQuickItem::enabledChanged, [=]() { + d->m_scrollTimer.stop(); + d->updateSelectionMode(); + d->updateDraggingState(false); + d->updateActiveState(false); + }); +} + +QQuickItem *QQuickSelectionRectangle::target() const +{ + return d_func()->m_target; +} + +void QQuickSelectionRectangle::setTarget(QQuickItem *target) +{ + Q_D(QQuickSelectionRectangle); + if (d->m_target == target) + return; + + if (d->m_selectable) { + d->m_scrollTimer.stop(); + d->m_tapHandler->setParent(nullptr); + d->m_dragHandler->setParent(nullptr); + d->m_target->disconnect(this); + } + + d->m_target = target; + d->m_selectable = nullptr; + + if (d->m_target) { + d->m_selectable = dynamic_cast<QQuickSelectable *>(QObjectPrivate::get(d->m_target.data())); + if (!d->m_selectable) + qmlWarning(this) << "the assigned target is not supported by the control"; + } + + if (d->m_selectable) { + const auto handlerTarget = d->m_selectable->selectionPointerHandlerTarget(); + d->m_dragHandler->setParent(handlerTarget); + d->m_tapHandler->setParent(handlerTarget); + QQuickItemPrivate::get(handlerTarget)->addPointerHandler(d->m_tapHandler); + QQuickItemPrivate::get(handlerTarget)->addPointerHandler(d->m_dragHandler); + d->connectToTarget(); + d->updateSelectionMode(); + } + + emit targetChanged(); +} + +bool QQuickSelectionRectangle::active() +{ + return d_func()->m_active; +} + +bool QQuickSelectionRectangle::dragging() +{ + return d_func()->m_dragging; +} + +QQuickSelectionRectangle::SelectionMode QQuickSelectionRectangle::selectionMode() const +{ + return d_func()->m_selectionMode; +} + +void QQuickSelectionRectangle::setSelectionMode(QQuickSelectionRectangle::SelectionMode selectionMode) +{ + Q_D(QQuickSelectionRectangle); + if (d->m_selectionMode == selectionMode) + return; + + d->m_selectionMode = selectionMode; + + if (d->m_target) + d->updateSelectionMode(); + + emit selectionModeChanged(); +} + +QQmlComponent *QQuickSelectionRectangle::topLeftHandle() const +{ + return d_func()->m_topLeftHandleDelegate; +} + +void QQuickSelectionRectangle::setTopLeftHandle(QQmlComponent *topLeftHandle) +{ + Q_D(QQuickSelectionRectangle); + if (d->m_topLeftHandleDelegate == topLeftHandle) + return; + + d->m_topLeftHandleDelegate = topLeftHandle; + emit topLeftHandleChanged(); +} + +QQmlComponent *QQuickSelectionRectangle::bottomRightHandle() const +{ + return d_func()->m_bottomRightHandleDelegate; +} + +void QQuickSelectionRectangle::setBottomRightHandle(QQmlComponent *bottomRightHandle) +{ + Q_D(QQuickSelectionRectangle); + if (d->m_bottomRightHandleDelegate == bottomRightHandle) + return; + + d->m_bottomRightHandleDelegate = bottomRightHandle; + emit bottomRightHandleChanged(); +} + +QQuickSelectionRectangleAttached *QQuickSelectionRectangle::qmlAttachedProperties(QObject *obj) +{ + return new QQuickSelectionRectangleAttached(obj); +} + +QQuickSelectionRectangleAttached::QQuickSelectionRectangleAttached(QObject *parent) + : QObject(parent) +{ +} + +QQuickSelectionRectangle *QQuickSelectionRectangleAttached::control() const +{ + return m_control; +} + +void QQuickSelectionRectangleAttached::setControl(QQuickSelectionRectangle *control) +{ + if (m_control == control) + return; + + m_control = control; + emit controlChanged(); +} + +bool QQuickSelectionRectangleAttached::dragging() const +{ + return m_dragging; +} + +void QQuickSelectionRectangleAttached::setDragging(bool dragging) +{ + if (m_dragging == dragging) + return; + + m_dragging = dragging; + emit draggingChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquickselectionrectangle_p.cpp" diff --git a/src/quicktemplates2/qquickselectionrectangle_p.h b/src/quicktemplates2/qquickselectionrectangle_p.h new file mode 100644 index 0000000000..57d7706440 --- /dev/null +++ b/src/quicktemplates2/qquickselectionrectangle_p.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSELECTIONRECTANGLE_P_H +#define QQUICKSELECTIONRECTANGLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/qquickitem.h> +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSelectionRectanglePrivate; +class QQuickSelectable; +class QQuickSelectionRectangleAttached; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSelectionRectangle : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(SelectionMode selectionMode READ selectionMode WRITE setSelectionMode NOTIFY selectionModeChanged FINAL) + Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged FINAL) + Q_PROPERTY(QQmlComponent *topLeftHandle READ topLeftHandle WRITE setTopLeftHandle NOTIFY topLeftHandleChanged FINAL) + Q_PROPERTY(QQmlComponent *bottomRightHandle READ bottomRightHandle WRITE setBottomRightHandle NOTIFY bottomRightHandleChanged FINAL) + Q_PROPERTY(bool active READ active NOTIFY activeChanged FINAL) + Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged FINAL) + + QML_NAMED_ELEMENT(SelectionRectangle) + QML_ATTACHED(QQuickSelectionRectangleAttached) + QML_ADDED_IN_VERSION(6, 2) + +public: + enum SelectionMode { + Drag, + PressAndHold, + Auto + }; + Q_ENUM(SelectionMode) + + explicit QQuickSelectionRectangle(QQuickItem *parent = nullptr); + + QQuickItem *target() const; + void setTarget(QQuickItem *target); + + bool active(); + bool dragging(); + + SelectionMode selectionMode() const; + void setSelectionMode(SelectionMode selectionMode); + + QQmlComponent *topLeftHandle() const; + void setTopLeftHandle(QQmlComponent *topLeftHandle); + QQmlComponent *bottomRightHandle() const; + void setBottomRightHandle(QQmlComponent *bottomRightHandle); + + static QQuickSelectionRectangleAttached *qmlAttachedProperties(QObject *obj); + +Q_SIGNALS: + void targetChanged(); + void activeChanged(); + void draggingChanged(); + void topLeftHandleChanged(); + void bottomRightHandleChanged(); + void selectionModeChanged(); + +private: + Q_DISABLE_COPY(QQuickSelectionRectangle) + Q_DECLARE_PRIVATE(QQuickSelectionRectangle) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSelectionRectangleAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickSelectionRectangle *control READ control NOTIFY controlChanged FINAL) + Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged FINAL) + +public: + QQuickSelectionRectangleAttached(QObject *parent); + + QQuickSelectionRectangle *control() const; + void setControl(QQuickSelectionRectangle *control); + + bool dragging() const; + void setDragging(bool dragging); + +Q_SIGNALS: + void controlChanged(); + void draggingChanged(); + +private: + QPointer<QQuickSelectionRectangle> m_control; + bool m_dragging = false; + + friend class QQuickSelectionRectanglePrivate; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSelectionRectangle) + +#endif // QQUICKSELECTIONRECTANGLE_P_H diff --git a/src/quicktemplates2/qquickselectionrectangle_p_p.h b/src/quicktemplates2/qquickselectionrectangle_p_p.h new file mode 100644 index 0000000000..a42e9e78bc --- /dev/null +++ b/src/quicktemplates2/qquickselectionrectangle_p_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSELECTIONRECTANGLE_P_P_H +#define QQUICKSELECTIONRECTANGLE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickselectionrectangle_p.h" + +#include <QtCore/qpointer.h> +#include <QtCore/qtimer.h> + +#include <QtQuick/private/qquickselectable_p.h> +#include <QtQuick/private/qquicktaphandler_p.h> +#include <QtQuick/private/qquickdraghandler_p.h> + +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSelectionRectanglePrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickSelectionRectangle) + +public: + QQuickSelectionRectanglePrivate(); + + void updateDraggingState(bool isDragging); + void updateActiveState(bool isActive); + void updateHandles(); + void updateSelectionMode(); + void connectToTarget(); + void scrollTowardsPos(const QPointF &pos); + QQuickItem *handleUnderPos(const QPointF &pos); + + QQuickItem *createHandle(QQmlComponent *delegate, Qt::Corner corner); + + QQuickSelectionRectangleAttached *getAttachedObject(const QObject *object) const; + +public: + QPointer<QQuickItem> m_target; + + QQmlComponent *m_topLeftHandleDelegate = nullptr; + QQmlComponent *m_bottomRightHandleDelegate = nullptr; + QPointer<QQuickItem> m_topLeftHandle; + QPointer<QQuickItem> m_bottomRightHandle; + QPointer<QQuickItem> m_draggedHandle = nullptr; + + QQuickSelectable *m_selectable = nullptr; + + QQuickTapHandler *m_tapHandler = nullptr; + QQuickDragHandler *m_dragHandler = nullptr; + + QTimer m_scrollTimer; + QPointF m_scrollToPoint; + QSizeF m_scrollSpeed = QSizeF(1, 1); + + QQuickSelectionRectangle::SelectionMode m_selectionMode = QQuickSelectionRectangle::Auto; + bool m_alwaysAcceptPressAndHold = false; + + bool m_enabled = true; + bool m_active = false; + bool m_dragging = false; +}; + +QT_END_NAMESPACE + +#endif // QQUICKSELECTIONRECTANGLE_P_P_H diff --git a/src/quicktemplates2/qquickshortcutcontext.cpp b/src/quicktemplates2/qquickshortcutcontext.cpp new file mode 100644 index 0000000000..9237017dfa --- /dev/null +++ b/src/quicktemplates2/qquickshortcutcontext.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickshortcutcontext_p_p.h" +#include "qquickoverlay_p_p.h" +#include "qquicktooltip_p.h" +#include "qquickmenu_p.h" +#include "qquickmenu_p_p.h" +#include "qquickpopup_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtGui/qguiapplication.h> +#include <QtQuick/qquickrendercontrol.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcContextMatcher, "qt.quick.controls.shortcutcontext.matcher") + +static bool isBlockedByPopup(QQuickItem *item) +{ + if (!item || !item->window()) + return false; + + QQuickOverlay *overlay = QQuickOverlay::overlay(item->window()); + const auto popups = QQuickOverlayPrivate::get(overlay)->stackingOrderPopups(); + for (QQuickPopup *popup : popups) { + if (qobject_cast<QQuickToolTip *>(popup)) + continue; // ignore tooltips (QTBUG-60492) + if (popup->isModal() || popup->closePolicy() & QQuickPopup::CloseOnEscape) { + return item != popup->popupItem() && !popup->popupItem()->isAncestorOf(item); + } + } + + return false; +} + +bool QQuickShortcutContext::matcher(QObject *obj, Qt::ShortcutContext context) +{ + QQuickItem *item = nullptr; + switch (context) { + case Qt::ApplicationShortcut: + return true; + case Qt::WindowShortcut: + while (obj && !obj->isWindowType()) { + item = qobject_cast<QQuickItem *>(obj); + if (item && item->window()) { + obj = item->window(); + break; + } else if (QQuickPopup *popup = qobject_cast<QQuickPopup *>(obj)) { + obj = popup->window(); + item = popup->popupItem(); + + if (!obj) { + // The popup has no associated window (yet). However, sub-menus, + // unlike top-level menus, will not have an associated window + // until their parent menu is opened. So, check if this is a sub-menu + // so that actions within it can grab shortcuts. + if (auto *menu = qobject_cast<QQuickMenu *>(popup)) { + auto parentMenu = QQuickMenuPrivate::get(menu)->parentMenu; + while (!obj && parentMenu) + obj = parentMenu->window(); + } + } + break; + } + obj = obj->parent(); + } + if (QWindow *renderWindow = QQuickRenderControl::renderWindowFor(qobject_cast<QQuickWindow *>(obj))) + obj = renderWindow; + qCDebug(lcContextMatcher) << "obj" << obj << "focusWindow" << QGuiApplication::focusWindow() + << "!isBlockedByPopup(item)" << !isBlockedByPopup(item); + return obj && obj == QGuiApplication::focusWindow() && !isBlockedByPopup(item); + default: + return false; + } +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickshortcutcontext_p_p.h b/src/quicktemplates2/qquickshortcutcontext_p_p.h new file mode 100644 index 0000000000..44e63f1e0e --- /dev/null +++ b/src/quicktemplates2/qquickshortcutcontext_p_p.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSHORTCUTCONTEXT_P_P_H +#define QQUICKSHORTCUTCONTEXT_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qnamespace.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QObject; + +struct Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickShortcutContext +{ + static bool matcher(QObject *object, Qt::ShortcutContext context); +}; + +QT_END_NAMESPACE + +#endif // QQUICKSHORTCUTCONTEXT_P_P_H diff --git a/src/quicktemplates2/qquickslider.cpp b/src/quicktemplates2/qquickslider.cpp new file mode 100644 index 0000000000..1aa3e43ff1 --- /dev/null +++ b/src/quicktemplates2/qquickslider.cpp @@ -0,0 +1,895 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickslider_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtQuick/private/qquickwindow_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Slider + \inherits Control +//! \instantiates QQuickSlider + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-input + \brief Used to select a value by sliding a handle along a track. + + \image qtquickcontrols2-slider.gif + + Slider is used to select a value by sliding a handle along a track. + + In the example below, custom \l from, \l value, and \l to values are set: + + \code + Slider { + from: 1 + value: 25 + to: 100 + } + \endcode + + The \l position property is expressed as a fraction of the control's size, + in the range \c {0.0 - 1.0}. The \l visualPosition property is + the same, except that it is reversed in a + \l {Right-to-left User Interfaces}{right-to-left} application. The + visualPosition is useful for positioning the handle when styling Slider. + In the example above, \l visualPosition will be \c 0.24 in a left-to-right + application, and \c 0.76 in a right-to-left application. + + For a slider that allows the user to select a range by providing two + handles, see \l RangeSlider. + + \sa {Customizing Slider}, {Input Controls} +*/ + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlsignal QtQuick.Controls::Slider::moved() + + This signal is emitted when the slider has been interactively moved + by the user by either touch, mouse, wheel, or keys. +*/ + +class QQuickSliderPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickSlider) + +public: + qreal snapPosition(qreal position) const; + qreal positionAt(const QPointF &point) const; + void setPosition(qreal position); + void updatePosition(); + + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + void handleUngrab() override; + + void cancelHandle(); + void executeHandle(bool complete = false); + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + qreal from = 0; + qreal to = 1; + qreal value = 0; + qreal position = 0; + qreal stepSize = 0; + qreal touchDragThreshold = -1; // in QQuickWindowPrivate::dragOverThreshold, '-1' implies using styleHints::startDragDistance() + bool live = true; + bool pressed = false; + QPointF pressPoint; + Qt::Orientation orientation = Qt::Horizontal; + QQuickSlider::SnapMode snapMode = QQuickSlider::NoSnap; + QQuickDeferredPointer<QQuickItem> handle; +}; + +qreal QQuickSliderPrivate::snapPosition(qreal position) const +{ + const qreal range = to - from; + if (qFuzzyIsNull(range)) + return position; + + const qreal effectiveStep = stepSize / range; + if (qFuzzyIsNull(effectiveStep)) + return position; + + return qRound(position / effectiveStep) * effectiveStep; +} + +qreal QQuickSliderPrivate::positionAt(const QPointF &point) const +{ + Q_Q(const QQuickSlider); + qreal pos = 0.0; + if (orientation == Qt::Horizontal) { + const qreal hw = handle ? handle->width() : 0; + const qreal offset = hw / 2; + const qreal extent = q->availableWidth() - hw; + if (!qFuzzyIsNull(extent)) { + if (q->isMirrored()) + pos = (q->width() - point.x() - q->rightPadding() - offset) / extent; + else + pos = (point.x() - q->leftPadding() - offset) / extent; + } + } else { + const qreal hh = handle ? handle->height() : 0; + const qreal offset = hh / 2; + const qreal extent = q->availableHeight() - hh; + if (!qFuzzyIsNull(extent)) + pos = (q->height() - point.y() - q->bottomPadding() - offset) / extent; + } + return qBound<qreal>(0.0, pos, 1.0); +} + +void QQuickSliderPrivate::setPosition(qreal pos) +{ + Q_Q(QQuickSlider); + pos = qBound<qreal>(0.0, pos, 1.0); + if (qFuzzyCompare(position, pos)) + return; + + position = pos; + emit q->positionChanged(); + emit q->visualPositionChanged(); +} + +void QQuickSliderPrivate::updatePosition() +{ + qreal pos = 0; + if (!qFuzzyCompare(from, to)) + pos = (value - from) / (to - from); + setPosition(pos); +} + +void QQuickSliderPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickSlider); + QQuickControlPrivate::handlePress(point); + pressPoint = point; + q->setPressed(true); +} + +void QQuickSliderPrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickSlider); + QQuickControlPrivate::handleMove(point); + const qreal oldPos = position; + qreal pos = positionAt(point); + if (snapMode == QQuickSlider::SnapAlways) + pos = snapPosition(pos); + if (live) + q->setValue(q->valueAt(pos)); + if (!live || snapMode == QQuickSlider::NoSnap || snapMode == QQuickSlider::SnapOnRelease) + setPosition(pos); + if (!qFuzzyCompare(pos, oldPos)) + emit q->moved(); +} + +void QQuickSliderPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickSlider); + QQuickControlPrivate::handleRelease(point); + pressPoint = QPointF(); + const qreal oldPos = position; + qreal pos = positionAt(point); + if (snapMode != QQuickSlider::NoSnap) + pos = snapPosition(pos); + qreal val = q->valueAt(pos); + if (!qFuzzyCompare(val, value)) + q->setValue(val); + else if (snapMode != QQuickSlider::NoSnap) + setPosition(pos); + if (!qFuzzyCompare(pos, oldPos)) + emit q->moved(); + q->setKeepMouseGrab(false); + q->setKeepTouchGrab(false); + q->setPressed(false); +} + +void QQuickSliderPrivate::handleUngrab() +{ + Q_Q(QQuickSlider); + QQuickControlPrivate::handleUngrab(); + pressPoint = QPointF(); + q->setPressed(false); +} + +static inline QString handleName() { return QStringLiteral("handle"); } + +void QQuickSliderPrivate::cancelHandle() +{ + Q_Q(QQuickSlider); + quickCancelDeferred(q, handleName()); +} + +void QQuickSliderPrivate::executeHandle(bool complete) +{ + Q_Q(QQuickSlider); + if (handle.wasExecuted()) + return; + + if (!handle || complete) + quickBeginDeferred(q, handleName(), handle); + if (complete) + quickCompleteDeferred(q, handleName(), handle); +} + +void QQuickSliderPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickSlider); + QQuickControlPrivate::itemImplicitWidthChanged(item); + if (item == handle) + emit q->implicitHandleWidthChanged(); +} + +void QQuickSliderPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickSlider); + QQuickControlPrivate::itemImplicitHeightChanged(item); + if (item == handle) + emit q->implicitHandleHeightChanged(); +} + +QQuickSlider::QQuickSlider(QQuickItem *parent) + : QQuickControl(*(new QQuickSliderPrivate), parent) +{ + setActiveFocusOnTab(true); +#ifdef Q_OS_MACOS + setFocusPolicy(Qt::TabFocus); +#else + setFocusPolicy(Qt::StrongFocus); +#endif + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif +} + +QQuickSlider::~QQuickSlider() +{ + Q_D(QQuickSlider); + d->removeImplicitSizeListener(d->handle); +} + +/*! + \qmlproperty real QtQuick.Controls::Slider::from + + This property holds the starting value for the range. The default value is \c 0.0. + + \sa to, value +*/ +qreal QQuickSlider::from() const +{ + Q_D(const QQuickSlider); + return d->from; +} + +void QQuickSlider::setFrom(qreal from) +{ + Q_D(QQuickSlider); + if (qFuzzyCompare(d->from, from)) + return; + + d->from = from; + emit fromChanged(); + if (isComponentComplete()) { + setValue(d->value); + d->updatePosition(); + } +} + +/*! + \qmlproperty real QtQuick.Controls::Slider::to + + This property holds the end value for the range. The default value is \c 1.0. + + \sa from, value +*/ +qreal QQuickSlider::to() const +{ + Q_D(const QQuickSlider); + return d->to; +} + +void QQuickSlider::setTo(qreal to) +{ + Q_D(QQuickSlider); + if (qFuzzyCompare(d->to, to)) + return; + + d->to = to; + emit toChanged(); + if (isComponentComplete()) { + setValue(d->value); + d->updatePosition(); + } +} + +/*! + \qmlproperty real QtQuick.Controls::Slider::value + + This property holds the value in the range \c from - \c to. The default value is \c 0.0. + + \sa position +*/ +qreal QQuickSlider::value() const +{ + Q_D(const QQuickSlider); + return d->value; +} + +void QQuickSlider::setValue(qreal value) +{ + Q_D(QQuickSlider); + if (isComponentComplete()) + value = d->from > d->to ? qBound(d->to, value, d->from) : qBound(d->from, value, d->to); + + if (qFuzzyCompare(d->value, value)) + return; + + d->value = value; + d->updatePosition(); + emit valueChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::Slider::position + \readonly + + This property holds the logical position of the handle. + + The position is expressed as a fraction of the control's size, in the range + \c {0.0 - 1.0}. For visualizing a slider, the right-to-left aware + \l visualPosition should be used instead. + + \sa value, visualPosition, valueAt() +*/ +qreal QQuickSlider::position() const +{ + Q_D(const QQuickSlider); + return d->position; +} + +/*! + \qmlproperty real QtQuick.Controls::Slider::visualPosition + \readonly + + This property holds the visual position of the handle. + + The position is expressed as a fraction of the control's size, in the range + \c {0.0 - 1.0}. When the control is \l {Control::mirrored}{mirrored}, the + value is equal to \c {1.0 - position}. This makes the value suitable for + visualizing the slider, taking right-to-left support into account. + + \sa position +*/ +qreal QQuickSlider::visualPosition() const +{ + Q_D(const QQuickSlider); + if (d->orientation == Qt::Vertical || isMirrored()) + return 1.0 - d->position; + return d->position; +} + +/*! + \qmlproperty real QtQuick.Controls::Slider::stepSize + + This property holds the step size. The default value is \c 0.0. + + \sa snapMode, increase(), decrease() +*/ +qreal QQuickSlider::stepSize() const +{ + Q_D(const QQuickSlider); + return d->stepSize; +} + +void QQuickSlider::setStepSize(qreal step) +{ + Q_D(QQuickSlider); + if (qFuzzyCompare(d->stepSize, step)) + return; + + d->stepSize = step; + emit stepSizeChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Slider::snapMode + + This property holds the snap mode. + + The snap mode determines how the slider handle behaves with + regards to the \l stepSize. + + Possible values: + \value Slider.NoSnap The slider does not snap (default). + \value Slider.SnapAlways The slider snaps while the handle is dragged. + \value Slider.SnapOnRelease The slider does not snap while being dragged, but only after the handle is released. + + In the following table, the various modes are illustrated with animations. + The movement of the mouse cursor and the \l stepSize (\c 0.2) are identical + in each animation. + + \table + \header + \row \li \b Value \li \b Example + \row \li \c Slider.NoSnap \li \image qtquickcontrols2-slider-nosnap.gif + \row \li \c Slider.SnapAlways \li \image qtquickcontrols2-slider-snapalways.gif + \row \li \c Slider.SnapOnRelease \li \image qtquickcontrols2-slider-snaponrelease.gif + \endtable + + \sa stepSize +*/ +QQuickSlider::SnapMode QQuickSlider::snapMode() const +{ + Q_D(const QQuickSlider); + return d->snapMode; +} + +void QQuickSlider::setSnapMode(SnapMode mode) +{ + Q_D(QQuickSlider); + if (d->snapMode == mode) + return; + + d->snapMode = mode; + emit snapModeChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Slider::pressed + + This property holds whether the slider is pressed by either touch, mouse, + or keys. +*/ +bool QQuickSlider::isPressed() const +{ + Q_D(const QQuickSlider); + return d->pressed; +} + +void QQuickSlider::setPressed(bool pressed) +{ + Q_D(QQuickSlider); + if (d->pressed == pressed) + return; + + d->pressed = pressed; + setAccessibleProperty("pressed", pressed); + emit pressedChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::Slider::horizontal + \readonly + + This property holds whether the slider is horizontal. + + \sa orientation +*/ +bool QQuickSlider::isHorizontal() const +{ + Q_D(const QQuickSlider); + return d->orientation == Qt::Horizontal; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::Slider::vertical + \readonly + + This property holds whether the slider is vertical. + + \sa orientation +*/ +bool QQuickSlider::isVertical() const +{ + Q_D(const QQuickSlider); + return d->orientation == Qt::Vertical; +} + +/*! + \qmlproperty enumeration QtQuick.Controls::Slider::orientation + + This property holds the orientation. + + Possible values: + \value Qt.Horizontal Horizontal (default) + \value Qt.Vertical Vertical + + \sa horizontal, vertical +*/ +Qt::Orientation QQuickSlider::orientation() const +{ + Q_D(const QQuickSlider); + return d->orientation; +} + +void QQuickSlider::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickSlider); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + emit orientationChanged(); +} + +/*! + \qmlproperty Item QtQuick.Controls::Slider::handle + + This property holds the handle item. + + \sa {Customizing Slider} +*/ +QQuickItem *QQuickSlider::handle() const +{ + QQuickSliderPrivate *d = const_cast<QQuickSliderPrivate *>(d_func()); + if (!d->handle) + d->executeHandle(); + return d->handle; +} + +void QQuickSlider::setHandle(QQuickItem *handle) +{ + Q_D(QQuickSlider); + if (d->handle == handle) + return; + + if (!d->handle.isExecuting()) + d->cancelHandle(); + + const qreal oldImplicitHandleWidth = implicitHandleWidth(); + const qreal oldImplicitHandleHeight = implicitHandleHeight(); + + d->removeImplicitSizeListener(d->handle); + QQuickControlPrivate::hideOldItem(d->handle); + d->handle = handle; + + if (handle) { + if (!handle->parentItem()) + handle->setParentItem(this); + d->addImplicitSizeListener(handle); + } + + if (!qFuzzyCompare(oldImplicitHandleWidth, implicitHandleWidth())) + emit implicitHandleWidthChanged(); + if (!qFuzzyCompare(oldImplicitHandleHeight, implicitHandleHeight())) + emit implicitHandleHeightChanged(); + if (!d->handle.isExecuting()) + emit handleChanged(); +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlmethod real QtQuick.Controls::Slider::valueAt(real position) + + Returns the value for the given \a position. + + \sa value, position +*/ +qreal QQuickSlider::valueAt(qreal position) const +{ + Q_D(const QQuickSlider); + const qreal value = (d->to - d->from) * position; + if (qFuzzyIsNull(d->stepSize)) + return d->from + value; + return d->from + qRound(value / d->stepSize) * d->stepSize; +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::Slider::live + + This property holds whether the slider provides live updates for the \l value + property while the handle is dragged. + + The default value is \c true. + + \sa value, valueAt() +*/ +bool QQuickSlider::live() const +{ + Q_D(const QQuickSlider); + return d->live; +} + +void QQuickSlider::setLive(bool live) +{ + Q_D(QQuickSlider); + if (d->live == live) + return; + + d->live = live; + emit liveChanged(); +} + +/*! + \qmlmethod void QtQuick.Controls::Slider::increase() + + Increases the value by \l stepSize or \c 0.1 if stepSize is not defined. + + \sa stepSize +*/ +void QQuickSlider::increase() +{ + Q_D(QQuickSlider); + qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + setValue(d->value + step); +} + +/*! + \qmlmethod void QtQuick.Controls::Slider::decrease() + + Decreases the value by \l stepSize or \c 0.1 if stepSize is not defined. + + \sa stepSize +*/ +void QQuickSlider::decrease() +{ + Q_D(QQuickSlider); + qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + setValue(d->value - step); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty qreal QtQuick.Controls::Slider::touchDragThreshold + + This property holds the threshold (in logical pixels) at which a touch drag event will be initiated. + The mouse drag threshold won't be affected. + The default value is \c Qt.styleHints.startDragDistance. + + \sa QStyleHints +*/ +qreal QQuickSlider::touchDragThreshold() const +{ + Q_D(const QQuickSlider); + return d->touchDragThreshold; +} + +void QQuickSlider::setTouchDragThreshold(qreal touchDragThreshold) +{ + Q_D(QQuickSlider); + if (d->touchDragThreshold == touchDragThreshold) + return; + + d->touchDragThreshold = touchDragThreshold; + emit touchDragThresholdChanged(); +} + +void QQuickSlider::resetTouchDragThreshold() +{ + setTouchDragThreshold(-1); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Slider::implicitHandleWidth + \readonly + + This property holds the implicit handle width. + + The value is equal to \c {handle ? handle.implicitWidth : 0}. + + This is typically used, together with \l {Control::}{implicitContentWidth} and + \l {Control::}{implicitBackgroundWidth}, to calculate the \l {Item::}{implicitWidth}. + + \sa implicitHandleHeight +*/ +qreal QQuickSlider::implicitHandleWidth() const +{ + Q_D(const QQuickSlider); + if (!d->handle) + return 0; + return d->handle->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::Slider::implicitHandleHeight + \readonly + + This property holds the implicit handle height. + + The value is equal to \c {handle ? handle.implicitHeight : 0}. + + This is typically used, together with \l {Control::}{implicitContentHeight} and + \l {Control::}{implicitBackgroundHeight}, to calculate the \l {Item::}{implicitHeight}. + + \sa implicitHandleWidth +*/ +qreal QQuickSlider::implicitHandleHeight() const +{ + Q_D(const QQuickSlider); + if (!d->handle) + return 0; + return d->handle->implicitHeight(); +} + +void QQuickSlider::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickSlider); + QQuickControl::keyPressEvent(event); + + const qreal oldValue = d->value; + if (d->orientation == Qt::Horizontal) { + if (event->key() == Qt::Key_Left) { + setPressed(true); + if (isMirrored()) + increase(); + else + decrease(); + event->accept(); + } else if (event->key() == Qt::Key_Right) { + setPressed(true); + if (isMirrored()) + decrease(); + else + increase(); + event->accept(); + } + } else { + if (event->key() == Qt::Key_Up) { + setPressed(true); + increase(); + event->accept(); + } else if (event->key() == Qt::Key_Down) { + setPressed(true); + decrease(); + event->accept(); + } + } + if (!qFuzzyCompare(d->value, oldValue)) + emit moved(); +} + +void QQuickSlider::keyReleaseEvent(QKeyEvent *event) +{ + QQuickControl::keyReleaseEvent(event); + setPressed(false); +} + +void QQuickSlider::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickSlider); + QQuickControl::mousePressEvent(event); + d->handleMove(event->position()); + setKeepMouseGrab(true); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickSlider::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickSlider); + switch (event->type()) { + case QEvent::TouchUpdate: + for (const QTouchEvent::TouchPoint &point : event->points()) { + if (!d->acceptTouch(point)) + continue; + + switch (point.state()) { + case QEventPoint::Pressed: + d->handlePress(point.position()); + break; + case QEventPoint::Updated: + if (!keepTouchGrab()) { + if (d->orientation == Qt::Horizontal) + setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.position().x() - d->pressPoint.x(), Qt::XAxis, &point, qRound(d->touchDragThreshold))); + else + setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.position().y() - d->pressPoint.y(), Qt::YAxis, &point, qRound(d->touchDragThreshold))); + } + if (keepTouchGrab()) + d->handleMove(point.position()); + break; + case QEventPoint::Released: + d->handleRelease(point.position()); + break; + default: + break; + } + } + break; + + default: + QQuickControl::touchEvent(event); + break; + } +} +#endif + +#if QT_CONFIG(wheelevent) +void QQuickSlider::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickSlider); + QQuickControl::wheelEvent(event); + if (d->wheelEnabled) { + const qreal oldValue = d->value; + const QPointF angle = event->angleDelta(); + const qreal delta = (qFuzzyIsNull(angle.y()) ? angle.x() : (event->inverted() ? -angle.y() : angle.y())) / int(QWheelEvent::DefaultDeltasPerStep); + const qreal step = qFuzzyIsNull(d->stepSize) ? 0.1 : d->stepSize; + setValue(oldValue + step * delta); + const bool wasMoved = !qFuzzyCompare(d->value, oldValue); + if (wasMoved) + emit moved(); + } +} +#endif + +void QQuickSlider::mirrorChange() +{ + QQuickControl::mirrorChange(); + emit visualPositionChanged(); +} + +void QQuickSlider::componentComplete() +{ + Q_D(QQuickSlider); + d->executeHandle(true); + QQuickControl::componentComplete(); + setValue(d->value); + d->updatePosition(); +} + +#if QT_CONFIG(accessibility) +void QQuickSlider::accessibilityActiveChanged(bool active) +{ + QQuickControl::accessibilityActiveChanged(active); + + Q_D(QQuickSlider); + if (active) + setAccessibleProperty("pressed", d->pressed); +} + +QAccessible::Role QQuickSlider::accessibleRole() const +{ + return QAccessible::Slider; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickslider_p.cpp" diff --git a/src/quicktemplates2/qquickslider_p.h b/src/quicktemplates2/qquickslider_p.h new file mode 100644 index 0000000000..ac03f7f996 --- /dev/null +++ b/src/quicktemplates2/qquickslider_p.h @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSLIDER_P_H +#define QQUICKSLIDER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSliderPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSlider : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) + Q_PROPERTY(qreal to READ to WRITE setTo NOTIFY toChanged FINAL) + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY(qreal position READ position NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal visualPosition READ visualPosition NOTIFY visualPositionChanged FINAL) + Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + Q_PROPERTY(QQuickItem *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) + Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged FINAL REVISION(2, 2)) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool horizontal READ isHorizontal NOTIFY orientationChanged FINAL REVISION(2, 3)) + Q_PROPERTY(bool vertical READ isVertical NOTIFY orientationChanged FINAL REVISION(2, 3)) + // 2.5 (Qt 5.12) + Q_PROPERTY(qreal touchDragThreshold READ touchDragThreshold WRITE setTouchDragThreshold RESET resetTouchDragThreshold NOTIFY touchDragThresholdChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitHandleWidth READ implicitHandleWidth NOTIFY implicitHandleWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitHandleHeight READ implicitHandleHeight NOTIFY implicitHandleHeightChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background,handle") + QML_NAMED_ELEMENT(Slider) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickSlider(QQuickItem *parent = nullptr); + ~QQuickSlider(); + + qreal from() const; + void setFrom(qreal from); + + qreal to() const; + void setTo(qreal to); + + qreal value() const; + void setValue(qreal value); + + qreal position() const; + qreal visualPosition() const; + + qreal stepSize() const; + void setStepSize(qreal step); + + enum SnapMode { + NoSnap, + SnapAlways, + SnapOnRelease + }; + Q_ENUM(SnapMode) + + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + bool isPressed() const; + void setPressed(bool pressed); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + QQuickItem *handle() const; + void setHandle(QQuickItem *handle); + + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) Q_INVOKABLE qreal valueAt(qreal position) const; + + // 2.2 (Qt 5.9) + bool live() const; + void setLive(bool live); + + // 2.3 (Qt 5.10) + bool isHorizontal() const; + bool isVertical() const; + + // 2.5 (Qt 5.12) + qreal touchDragThreshold() const; + void setTouchDragThreshold(qreal touchDragThreshold); + void resetTouchDragThreshold(); + + qreal implicitHandleWidth() const; + qreal implicitHandleHeight() const; + +public Q_SLOTS: + void increase(); + void decrease(); + +Q_SIGNALS: + void fromChanged(); + void toChanged(); + void valueChanged(); + void positionChanged(); + void visualPositionChanged(); + void stepSizeChanged(); + void snapModeChanged(); + void pressedChanged(); + void orientationChanged(); + void handleChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void moved(); + Q_REVISION(2, 2) void liveChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void touchDragThresholdChanged(); + Q_REVISION(2, 5) void implicitHandleWidthChanged(); + Q_REVISION(2, 5) void implicitHandleHeightChanged(); + +protected: + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + + void mirrorChange() override; + void componentComplete() override; + +#if QT_CONFIG(accessibility) + void accessibilityActiveChanged(bool active) override; + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickSlider) + Q_DECLARE_PRIVATE(QQuickSlider) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSlider) + +#endif // QQUICKSLIDER_P_H diff --git a/src/quicktemplates2/qquickspinbox.cpp b/src/quicktemplates2/qquickspinbox.cpp new file mode 100644 index 0000000000..f0d34c3659 --- /dev/null +++ b/src/quicktemplates2/qquickspinbox.cpp @@ -0,0 +1,1064 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickspinbox_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickindicatorbutton_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtGui/qguiapplication.h> +#include <QtGui/qstylehints.h> + +#include <QtQml/qqmlinfo.h> +#include <QtQml/private/qqmllocale_p.h> +#include <QtQml/private/qqmlengine_p.h> +#include <QtQuick/private/qquicktextinput_p.h> + +QT_BEGIN_NAMESPACE + +// copied from qabstractbutton.cpp +static const int AUTO_REPEAT_DELAY = 300; +static const int AUTO_REPEAT_INTERVAL = 100; + +/*! + \qmltype SpinBox + \inherits Control +//! \instantiates QQuickSpinBox + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup input + \ingroup qtquickcontrols2-focusscopes + \brief Allows the user to select from a set of preset values. + + \image qtquickcontrols2-spinbox.png + + SpinBox allows the user to choose an integer value by clicking the up + or down indicator buttons, or by pressing up or down on the keyboard. + Optionally, SpinBox can be also made \l editable, so the user can enter + a text value in the input field. + + By default, SpinBox provides discrete values in the range of \c [0-99] + with a \l stepSize of \c 1. + + \snippet qtquickcontrols2-spinbox.qml 1 + + \section2 Custom Values + + \image qtquickcontrols2-spinbox-textual.png + + Even though SpinBox works on integer values, it can be customized to + accept arbitrary input values. The following snippet demonstrates how + \l validator, \l textFromValue and \l valueFromText can be used to + customize the default behavior. + + \snippet qtquickcontrols2-spinbox-textual.qml 1 + + In the same manner, SpinBox can be customized to accept floating point + numbers: + + \image qtquickcontrols2-spinbox-double.png + + \snippet qtquickcontrols2-spinbox-double.qml 1 + + \sa Tumbler, {Customizing SpinBox}, {Focus Management in Qt Quick Controls} +*/ + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlsignal QtQuick.Controls::SpinBox::valueModified() + + This signal is emitted when the spin box value has been interactively + modified by the user by either touch, mouse, wheel, or keys. + In the case of interaction via keyboard, the signal is only emitted + when the text has been accepted; meaning when the enter or return keys + are pressed, or the input field loses focus. +*/ + +class QQuickSpinBoxPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickSpinBox) + +public: + int boundValue(int value, bool wrap) const; + void updateValue(); + bool setValue(int value, bool wrap, bool modified); + bool stepBy(int steps, bool modified); + void increase(bool modified); + void decrease(bool modified); + + int effectiveStepSize() const; + + void updateDisplayText(bool modified = false); + void setDisplayText(const QString &displayText, bool modified = false); + + bool upEnabled() const; + void updateUpEnabled(); + bool downEnabled() const; + void updateDownEnabled(); + void updateHover(const QPointF &pos); + + void startRepeatDelay(); + void startPressRepeat(); + void stopPressRepeat(); + + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + void handleUngrab() override; + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::SpinBox); } + + bool editable = false; + bool wrap = false; + int from = 0; + int to = 99; + int value = 0; + int stepSize = 1; + int delayTimer = 0; + int repeatTimer = 0; + QString displayText; + QQuickIndicatorButton *up = nullptr; + QQuickIndicatorButton *down = nullptr; + QValidator *validator = nullptr; + mutable QJSValue textFromValue; + mutable QJSValue valueFromText; + Qt::InputMethodHints inputMethodHints = Qt::ImhDigitsOnly; +}; + +int QQuickSpinBoxPrivate::boundValue(int value, bool wrap) const +{ + bool inverted = from > to; + if (!wrap) + return inverted ? qBound(to, value, from) : qBound(from, value, to); + + int f = inverted ? to : from; + int t = inverted ? from : to; + if (value < f) + value = t; + else if (value > t) + value = f; + + return value; +} + +void QQuickSpinBoxPrivate::updateValue() +{ + Q_Q(QQuickSpinBox); + if (contentItem) { + QVariant text = contentItem->property("text"); + if (text.isValid()) { + int val = 0; + QQmlEngine *engine = qmlEngine(q); + if (engine && valueFromText.isCallable()) { + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + QJSValue loc = QJSValuePrivate::fromReturnedValue(QQmlLocale::wrap(v4, locale)); + val = valueFromText.call(QJSValueList() << text.toString() << loc).toInt(); + } else { + val = locale.toInt(text.toString()); + } + setValue(val, /* allowWrap = */ false, /* modified = */ true); + } + } +} + +// modified indicates if the value was modified by the user and not programatically +// this is then passed on to updateDisplayText to indicate that the user has modified +// the value so it may need to trigger an update of the contentItem's text too + +bool QQuickSpinBoxPrivate::setValue(int newValue, bool allowWrap, bool modified) +{ + Q_Q(QQuickSpinBox); + int correctedValue = newValue; + if (q->isComponentComplete()) + correctedValue = boundValue(newValue, allowWrap); + + if (!modified && newValue == correctedValue && newValue == value) + return false; + + const bool emitSignals = (value != correctedValue); + value = correctedValue; + + updateDisplayText(modified); + updateUpEnabled(); + updateDownEnabled(); + + // Only emit the signals if the corrected value is not the same as the + // original value to avoid unnecessary updates + if (emitSignals) { + emit q->valueChanged(); + if (modified) + emit q->valueModified(); + } + return true; +} + +bool QQuickSpinBoxPrivate::stepBy(int steps, bool modified) +{ + return setValue(value + steps, wrap, modified); +} + +void QQuickSpinBoxPrivate::increase(bool modified) +{ + setValue(value + effectiveStepSize(), wrap, modified); +} + +void QQuickSpinBoxPrivate::decrease(bool modified) +{ + setValue(value - effectiveStepSize(), wrap, modified); +} + +int QQuickSpinBoxPrivate::effectiveStepSize() const +{ + return from > to ? -1 * stepSize : stepSize; +} + +void QQuickSpinBoxPrivate::updateDisplayText(bool modified) +{ + Q_Q(QQuickSpinBox); + QString text; + QQmlEngine *engine = qmlEngine(q); + if (engine && textFromValue.isCallable()) { + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + QJSValue loc = QJSValuePrivate::fromReturnedValue(QQmlLocale::wrap(v4, locale)); + text = textFromValue.call(QJSValueList() << value << loc).toString(); + } else { + text = locale.toString(value); + } + setDisplayText(text, modified); +} + +void QQuickSpinBoxPrivate::setDisplayText(const QString &text, bool modified) +{ + Q_Q(QQuickSpinBox); + + if (!modified && displayText == text) + return; + + displayText = text; + emit q->displayTextChanged(); +} + +bool QQuickSpinBoxPrivate::upEnabled() const +{ + const QQuickItem *upIndicator = up->indicator(); + return upIndicator && upIndicator->isEnabled(); +} + +void QQuickSpinBoxPrivate::updateUpEnabled() +{ + QQuickItem *upIndicator = up->indicator(); + if (!upIndicator) + return; + + upIndicator->setEnabled(wrap || (from < to ? value < to : value > to)); +} + +bool QQuickSpinBoxPrivate::downEnabled() const +{ + const QQuickItem *downIndicator = down->indicator(); + return downIndicator && downIndicator->isEnabled(); +} + +void QQuickSpinBoxPrivate::updateDownEnabled() +{ + QQuickItem *downIndicator = down->indicator(); + if (!downIndicator) + return; + + downIndicator->setEnabled(wrap || (from < to ? value > from : value < from)); +} + +void QQuickSpinBoxPrivate::updateHover(const QPointF &pos) +{ + Q_Q(QQuickSpinBox); + QQuickItem *ui = up->indicator(); + QQuickItem *di = down->indicator(); + up->setHovered(ui && ui->isEnabled() && ui->contains(q->mapToItem(ui, pos))); + down->setHovered(di && di->isEnabled() && di->contains(q->mapToItem(di, pos))); +} + +void QQuickSpinBoxPrivate::startRepeatDelay() +{ + Q_Q(QQuickSpinBox); + stopPressRepeat(); + delayTimer = q->startTimer(AUTO_REPEAT_DELAY); +} + +void QQuickSpinBoxPrivate::startPressRepeat() +{ + Q_Q(QQuickSpinBox); + stopPressRepeat(); + repeatTimer = q->startTimer(AUTO_REPEAT_INTERVAL); +} + +void QQuickSpinBoxPrivate::stopPressRepeat() +{ + Q_Q(QQuickSpinBox); + if (delayTimer > 0) { + q->killTimer(delayTimer); + delayTimer = 0; + } + if (repeatTimer > 0) { + q->killTimer(repeatTimer); + repeatTimer = 0; + } +} + +void QQuickSpinBoxPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickSpinBox); + QQuickControlPrivate::handlePress(point); + QQuickItem *ui = up->indicator(); + QQuickItem *di = down->indicator(); + up->setPressed(ui && ui->isEnabled() && ui->contains(ui->mapFromItem(q, point))); + down->setPressed(di && di->isEnabled() && di->contains(di->mapFromItem(q, point))); + + bool pressed = up->isPressed() || down->isPressed(); + q->setAccessibleProperty("pressed", pressed); + if (pressed) + startRepeatDelay(); +} + +void QQuickSpinBoxPrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickSpinBox); + QQuickControlPrivate::handleMove(point); + QQuickItem *ui = up->indicator(); + QQuickItem *di = down->indicator(); + up->setHovered(ui && ui->isEnabled() && ui->contains(ui->mapFromItem(q, point))); + up->setPressed(up->isHovered()); + down->setHovered(di && di->isEnabled() && di->contains(di->mapFromItem(q, point))); + down->setPressed(down->isHovered()); + + bool pressed = up->isPressed() || down->isPressed(); + q->setAccessibleProperty("pressed", pressed); + if (!pressed) + stopPressRepeat(); +} + +void QQuickSpinBoxPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickSpinBox); + QQuickControlPrivate::handleRelease(point); + QQuickItem *ui = up->indicator(); + QQuickItem *di = down->indicator(); + + int oldValue = value; + if (up->isPressed()) { + up->setPressed(false); + if (repeatTimer <= 0 && ui && ui->contains(ui->mapFromItem(q, point))) + q->increase(); + } else if (down->isPressed()) { + down->setPressed(false); + if (repeatTimer <= 0 && di && di->contains(di->mapFromItem(q, point))) + q->decrease(); + } + if (value != oldValue) + emit q->valueModified(); + + q->setAccessibleProperty("pressed", false); + stopPressRepeat(); +} + +void QQuickSpinBoxPrivate::handleUngrab() +{ + Q_Q(QQuickSpinBox); + QQuickControlPrivate::handleUngrab(); + up->setPressed(false); + down->setPressed(false); + + q->setAccessibleProperty("pressed", false); + stopPressRepeat(); +} + +void QQuickSpinBoxPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + QQuickControlPrivate::itemImplicitWidthChanged(item); + if (item == up->indicator()) + emit up->implicitIndicatorWidthChanged(); + else if (item == down->indicator()) + emit down->implicitIndicatorWidthChanged(); +} + +void QQuickSpinBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + QQuickControlPrivate::itemImplicitHeightChanged(item); + if (item == up->indicator()) + emit up->implicitIndicatorHeightChanged(); + else if (item == down->indicator()) + emit down->implicitIndicatorHeightChanged(); +} + +QQuickSpinBox::QQuickSpinBox(QQuickItem *parent) + : QQuickControl(*(new QQuickSpinBoxPrivate), parent) +{ + Q_D(QQuickSpinBox); + d->up = new QQuickIndicatorButton(this); + d->down = new QQuickIndicatorButton(this); + + setFlag(ItemIsFocusScope); + setFiltersChildMouseEvents(true); + setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif +} + +QQuickSpinBox::~QQuickSpinBox() +{ + Q_D(QQuickSpinBox); + d->removeImplicitSizeListener(d->up->indicator()); + d->removeImplicitSizeListener(d->down->indicator()); +} + +/*! + \qmlproperty int QtQuick.Controls::SpinBox::from + + This property holds the starting value for the range. The default value is \c 0. + + \sa to, value +*/ +int QQuickSpinBox::from() const +{ + Q_D(const QQuickSpinBox); + return d->from; +} + +void QQuickSpinBox::setFrom(int from) +{ + Q_D(QQuickSpinBox); + if (d->from == from) + return; + + d->from = from; + emit fromChanged(); + if (isComponentComplete()) { + if (!d->setValue(d->value, /* allowWrap = */ false, /* modified = */ false)) { + d->updateUpEnabled(); + d->updateDownEnabled(); + } + } +} + +/*! + \qmlproperty int QtQuick.Controls::SpinBox::to + + This property holds the end value for the range. The default value is \c 99. + + \sa from, value +*/ +int QQuickSpinBox::to() const +{ + Q_D(const QQuickSpinBox); + return d->to; +} + +void QQuickSpinBox::setTo(int to) +{ + Q_D(QQuickSpinBox); + if (d->to == to) + return; + + d->to = to; + emit toChanged(); + if (isComponentComplete()) { + if (!d->setValue(d->value, /* allowWrap = */false, /* modified = */ false)) { + d->updateUpEnabled(); + d->updateDownEnabled(); + } + } +} + +/*! + \qmlproperty int QtQuick.Controls::SpinBox::value + + This property holds the value in the range \c from - \c to. The default value is \c 0. +*/ +int QQuickSpinBox::value() const +{ + Q_D(const QQuickSpinBox); + return d->value; +} + +void QQuickSpinBox::setValue(int value) +{ + Q_D(QQuickSpinBox); + d->setValue(value, /* allowWrap = */ false, /* modified = */ false); +} + +/*! + \qmlproperty int QtQuick.Controls::SpinBox::stepSize + + This property holds the step size. The default value is \c 1. + + \sa increase(), decrease() +*/ +int QQuickSpinBox::stepSize() const +{ + Q_D(const QQuickSpinBox); + return d->stepSize; +} + +void QQuickSpinBox::setStepSize(int step) +{ + Q_D(QQuickSpinBox); + if (d->stepSize == step) + return; + + d->stepSize = step; + emit stepSizeChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::SpinBox::editable + + This property holds whether the spinbox is editable. The default value is \c false. + + \sa validator +*/ +bool QQuickSpinBox::isEditable() const +{ + Q_D(const QQuickSpinBox); + return d->editable; +} + +void QQuickSpinBox::setEditable(bool editable) +{ + Q_D(QQuickSpinBox); + if (d->editable == editable) + return; + +#if QT_CONFIG(cursor) + if (d->contentItem) { + if (editable) + d->contentItem->setCursor(Qt::IBeamCursor); + else + d->contentItem->unsetCursor(); + } +#endif + + d->editable = editable; + setAccessibleProperty("editable", editable); + emit editableChanged(); +} + +/*! + \qmlproperty Validator QtQuick.Controls::SpinBox::validator + + This property holds the input text validator for editable spinboxes. By + default, SpinBox uses \l IntValidator to accept input of integer numbers. + + \code + SpinBox { + id: control + validator: IntValidator { + locale: control.locale.name + bottom: Math.min(control.from, control.to) + top: Math.max(control.from, control.to) + } + } + \endcode + + \sa editable, textFromValue, valueFromText, {Control::locale}{locale}, + {Validating Input Text} +*/ +QValidator *QQuickSpinBox::validator() const +{ + Q_D(const QQuickSpinBox); + return d->validator; +} + +void QQuickSpinBox::setValidator(QValidator *validator) +{ + Q_D(QQuickSpinBox); + if (d->validator == validator) + return; + + d->validator = validator; + emit validatorChanged(); +} + +/*! + \qmlproperty function QtQuick.Controls::SpinBox::textFromValue + + This property holds a callback function that is called whenever + an integer value needs to be converted to display text. + + The default function can be overridden to display custom text for a given + value. This applies to both editable and non-editable spinboxes; + for example, when using the up and down buttons or a mouse wheel to + increment and decrement the value, the new value is converted to display + text using this function. + + The callback function signature is \c {string function(value, locale)}. + The function can have one or two arguments, where the first argument + is the value to be converted, and the optional second argument is the + locale that should be used for the conversion, if applicable. + + The default implementation does the conversion using + \l {QtQml::Number::toLocaleString()}{Number.toLocaleString}(): + + \code + textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 0); } + \endcode + + \note When applying a custom \c textFromValue implementation for editable + spinboxes, a matching \l valueFromText implementation must be provided + to be able to convert the custom text back to an integer value. + + \sa valueFromText, validator, {Control::locale}{locale} +*/ +QJSValue QQuickSpinBox::textFromValue() const +{ + Q_D(const QQuickSpinBox); + if (!d->textFromValue.isCallable()) { + QQmlEngine *engine = qmlEngine(this); + if (engine) + d->textFromValue = engine->evaluate(QStringLiteral("(function(value, locale) { return Number(value).toLocaleString(locale, 'f', 0); })")); + } + return d->textFromValue; +} + +void QQuickSpinBox::setTextFromValue(const QJSValue &callback) +{ + Q_D(QQuickSpinBox); + if (!callback.isCallable()) { + qmlWarning(this) << "textFromValue must be a callable function"; + return; + } + d->textFromValue = callback; + emit textFromValueChanged(); +} + +/*! + \qmlproperty function QtQuick.Controls::SpinBox::valueFromText + + This property holds a callback function that is called whenever + input text needs to be converted to an integer value. + + This function only needs to be overridden when \l textFromValue + is overridden for an editable spinbox. + + The callback function signature is \c {int function(text, locale)}. + The function can have one or two arguments, where the first argument + is the text to be converted, and the optional second argument is the + locale that should be used for the conversion, if applicable. + + The default implementation does the conversion using \l {QtQml::Locale}{Number.fromLocaleString()}: + + \code + valueFromText: function(text, locale) { return Number.fromLocaleString(locale, text); } + \endcode + + \note When applying a custom \l textFromValue implementation for editable + spinboxes, a matching \c valueFromText implementation must be provided + to be able to convert the custom text back to an integer value. + + \sa textFromValue, validator, {Control::locale}{locale} +*/ +QJSValue QQuickSpinBox::valueFromText() const +{ + Q_D(const QQuickSpinBox); + if (!d->valueFromText.isCallable()) { + QQmlEngine *engine = qmlEngine(this); + if (engine) + d->valueFromText = engine->evaluate(QStringLiteral("(function(text, locale) { return Number.fromLocaleString(locale, text); })")); + } + return d->valueFromText; +} + +void QQuickSpinBox::setValueFromText(const QJSValue &callback) +{ + Q_D(QQuickSpinBox); + if (!callback.isCallable()) { + qmlWarning(this) << "valueFromText must be a callable function"; + return; + } + d->valueFromText = callback; + emit valueFromTextChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::SpinBox::up.pressed + \qmlproperty Item QtQuick.Controls::SpinBox::up.indicator + \qmlproperty bool QtQuick.Controls::SpinBox::up.hovered + \qmlproperty real QtQuick.Controls::SpinBox::up.implicitIndicatorWidth + \qmlproperty real QtQuick.Controls::SpinBox::up.implicitIndicatorHeight + + These properties hold the up indicator item and whether it is pressed or + hovered. The \c up.hovered property was introduced in QtQuick.Controls 2.1, + and the \c up.implicitIndicatorWidth and \c up.implicitIndicatorHeight + properties were introduced in QtQuick.Controls 2.5. + + \sa increase() +*/ +QQuickIndicatorButton *QQuickSpinBox::up() const +{ + Q_D(const QQuickSpinBox); + return d->up; +} + +/*! + \qmlproperty bool QtQuick.Controls::SpinBox::down.pressed + \qmlproperty Item QtQuick.Controls::SpinBox::down.indicator + \qmlproperty bool QtQuick.Controls::SpinBox::down.hovered + \qmlproperty real QtQuick.Controls::SpinBox::down.implicitIndicatorWidth + \qmlproperty real QtQuick.Controls::SpinBox::down.implicitIndicatorHeight + + These properties hold the down indicator item and whether it is pressed or + hovered. The \c down.hovered property was introduced in QtQuick.Controls 2.1, + and the \c down.implicitIndicatorWidth and \c down.implicitIndicatorHeight + properties were introduced in QtQuick.Controls 2.5. + + \sa decrease() +*/ +QQuickIndicatorButton *QQuickSpinBox::down() const +{ + Q_D(const QQuickSpinBox); + return d->down; +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty flags QtQuick.Controls::SpinBox::inputMethodHints + + This property provides hints to the input method about the expected content + of the spin box and how it should operate. + + The default value is \c Qt.ImhDigitsOnly. + + \include inputmethodhints.qdocinc +*/ +Qt::InputMethodHints QQuickSpinBox::inputMethodHints() const +{ + Q_D(const QQuickSpinBox); + return d->inputMethodHints; +} + +void QQuickSpinBox::setInputMethodHints(Qt::InputMethodHints hints) +{ + Q_D(QQuickSpinBox); + if (d->inputMethodHints == hints) + return; + + d->inputMethodHints = hints; + emit inputMethodHintsChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty bool QtQuick.Controls::SpinBox::inputMethodComposing + \readonly + + This property holds whether an editable spin box has partial text input from an input method. + + While it is composing, an input method may rely on mouse or key events from the spin box to + edit or commit the partial text. This property can be used to determine when to disable event + handlers that may interfere with the correct operation of an input method. +*/ +bool QQuickSpinBox::isInputMethodComposing() const +{ + Q_D(const QQuickSpinBox); + return d->contentItem && d->contentItem->property("inputMethodComposing").toBool(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::SpinBox::wrap + + This property holds whether the spinbox wraps. The default value is \c false. + + If wrap is \c true, stepping past \l to changes the value to \l from and vice versa. +*/ +bool QQuickSpinBox::wrap() const +{ + Q_D(const QQuickSpinBox); + return d->wrap; +} + +void QQuickSpinBox::setWrap(bool wrap) +{ + Q_D(QQuickSpinBox); + if (d->wrap == wrap) + return; + + d->wrap = wrap; + if (d->value == d->from || d->value == d->to) { + d->updateUpEnabled(); + d->updateDownEnabled(); + } + emit wrapChanged(); +} + +/*! + \since QtQuick.Controls 2.4 (Qt 5.11) + \qmlproperty string QtQuick.Controls::SpinBox::displayText + \readonly + + This property holds the textual value of the spinbox. + + The value of the property is based on \l textFromValue and \l {Control::} + {locale}, and equal to: + \badcode + var text = spinBox.textFromValue(spinBox.value, spinBox.locale) + \endcode + + \sa textFromValue +*/ +QString QQuickSpinBox::displayText() const +{ + Q_D(const QQuickSpinBox); + return d->displayText; +} + +/*! + \qmlmethod void QtQuick.Controls::SpinBox::increase() + + Increases the value by \l stepSize, or \c 1 if stepSize is not defined. + + \sa stepSize +*/ +void QQuickSpinBox::increase() +{ + Q_D(QQuickSpinBox); + d->increase(false); +} + +/*! + \qmlmethod void QtQuick.Controls::SpinBox::decrease() + + Decreases the value by \l stepSize, or \c 1 if stepSize is not defined. + + \sa stepSize +*/ +void QQuickSpinBox::decrease() +{ + Q_D(QQuickSpinBox); + d->decrease(false); +} + +void QQuickSpinBox::focusInEvent(QFocusEvent *event) +{ + Q_D(QQuickSpinBox); + QQuickControl::focusInEvent(event); + + // When an editable SpinBox gets focus, it must pass on the focus to its editor. + if (d->editable && d->contentItem && !d->contentItem->hasActiveFocus()) + d->contentItem->forceActiveFocus(event->reason()); +} + +void QQuickSpinBox::hoverEnterEvent(QHoverEvent *event) +{ + Q_D(QQuickSpinBox); + QQuickControl::hoverEnterEvent(event); + d->updateHover(event->position()); + event->ignore(); +} + +void QQuickSpinBox::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QQuickSpinBox); + QQuickControl::hoverMoveEvent(event); + d->updateHover(event->position()); + event->ignore(); +} + +void QQuickSpinBox::hoverLeaveEvent(QHoverEvent *event) +{ + Q_D(QQuickSpinBox); + QQuickControl::hoverLeaveEvent(event); + d->down->setHovered(false); + d->up->setHovered(false); + event->ignore(); +} + +void QQuickSpinBox::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickSpinBox); + QQuickControl::keyPressEvent(event); + + switch (event->key()) { + case Qt::Key_Up: + if (d->upEnabled()) { + d->increase(true); + d->up->setPressed(true); + event->accept(); + } + break; + + case Qt::Key_Down: + if (d->downEnabled()) { + d->decrease(true); + d->down->setPressed(true); + event->accept(); + } + break; + + default: + break; + } + + setAccessibleProperty("pressed", d->up->isPressed() || d->down->isPressed()); +} + +void QQuickSpinBox::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QQuickSpinBox); + QQuickControl::keyReleaseEvent(event); + + if (d->editable && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + d->updateValue(); + + d->up->setPressed(false); + d->down->setPressed(false); + setAccessibleProperty("pressed", false); +} + +void QQuickSpinBox::timerEvent(QTimerEvent *event) +{ + Q_D(QQuickSpinBox); + QQuickControl::timerEvent(event); + if (event->timerId() == d->delayTimer) { + d->startPressRepeat(); + } else if (event->timerId() == d->repeatTimer) { + if (d->up->isPressed()) + d->increase(true); + else if (d->down->isPressed()) + d->decrease(true); + } +} + +#if QT_CONFIG(wheelevent) +void QQuickSpinBox::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickSpinBox); + QQuickControl::wheelEvent(event); + if (d->wheelEnabled) { + const QPointF angle = event->angleDelta(); + const qreal delta = (qFuzzyIsNull(angle.y()) ? angle.x() : angle.y()) / int(QWheelEvent::DefaultDeltasPerStep); + d->stepBy(qRound(d->effectiveStepSize() * delta), true); + } +} +#endif + +void QQuickSpinBox::classBegin() +{ + Q_D(QQuickSpinBox); + QQuickControl::classBegin(); + + QQmlContext *context = qmlContext(this); + if (context) { + QQmlEngine::setContextForObject(d->up, context); + QQmlEngine::setContextForObject(d->down, context); + } +} + +void QQuickSpinBox::componentComplete() +{ + Q_D(QQuickSpinBox); + QQuickIndicatorButtonPrivate::get(d->up)->executeIndicator(true); + QQuickIndicatorButtonPrivate::get(d->down)->executeIndicator(true); + + QQuickControl::componentComplete(); + if (!d->setValue(d->value, /* allowWrap = */ false, /* modified = */ false)) { + d->updateDisplayText(); + d->updateUpEnabled(); + d->updateDownEnabled(); + } +} + +void QQuickSpinBox::itemChange(ItemChange change, const ItemChangeData &value) +{ + Q_D(QQuickSpinBox); + QQuickControl::itemChange(change, value); + if (d->editable && change == ItemActiveFocusHasChanged && !value.boolValue) + d->updateValue(); +} + +void QQuickSpinBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickSpinBox); + if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) + disconnect(oldInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickSpinBox::inputMethodComposingChanged); + + if (newItem) { + newItem->setActiveFocusOnTab(true); + if (d->activeFocus) + newItem->forceActiveFocus(d->focusReason); +#if QT_CONFIG(cursor) + if (d->editable) + newItem->setCursor(Qt::IBeamCursor); +#endif + + if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) + connect(newInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickSpinBox::inputMethodComposingChanged); + } +} + +void QQuickSpinBox::localeChange(const QLocale &newLocale, const QLocale &oldLocale) +{ + Q_D(QQuickSpinBox); + QQuickControl::localeChange(newLocale, oldLocale); + d->updateDisplayText(); +} + +QFont QQuickSpinBox::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::SpinBox); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickSpinBox::accessibleRole() const +{ + return QAccessible::SpinBox; +} + +void QQuickSpinBox::accessibilityActiveChanged(bool active) +{ + Q_D(QQuickSpinBox); + QQuickControl::accessibilityActiveChanged(active); + + if (active) + setAccessibleProperty("editable", d->editable); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquickspinbox_p.cpp" diff --git a/src/quicktemplates2/qquickspinbox_p.h b/src/quicktemplates2/qquickspinbox_p.h new file mode 100644 index 0000000000..9e64e96e32 --- /dev/null +++ b/src/quicktemplates2/qquickspinbox_p.h @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSPINBOX_P_H +#define QQUICKSPINBOX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtQml/qjsvalue.h> + +QT_BEGIN_NAMESPACE + +class QValidator; +class QQuickSpinBoxPrivate; +class QQuickIndicatorButton; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSpinBox : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(int from READ from WRITE setFrom NOTIFY fromChanged FINAL) + Q_PROPERTY(int to READ to WRITE setTo NOTIFY toChanged FINAL) + Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY(int stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) + Q_PROPERTY(bool editable READ isEditable WRITE setEditable NOTIFY editableChanged FINAL) + Q_PROPERTY(QValidator *validator READ validator WRITE setValidator NOTIFY validatorChanged FINAL) + Q_PROPERTY(QJSValue textFromValue READ textFromValue WRITE setTextFromValue NOTIFY textFromValueChanged FINAL) + Q_PROPERTY(QJSValue valueFromText READ valueFromText WRITE setValueFromText NOTIFY valueFromTextChanged FINAL) + Q_PROPERTY(QQuickIndicatorButton *up READ up CONSTANT FINAL) + Q_PROPERTY(QQuickIndicatorButton *down READ down CONSTANT FINAL) + // 2.2 (Qt 5.9) + Q_PROPERTY(Qt::InputMethodHints inputMethodHints READ inputMethodHints WRITE setInputMethodHints NOTIFY inputMethodHintsChanged FINAL REVISION(2, 2)) + Q_PROPERTY(bool inputMethodComposing READ isInputMethodComposing NOTIFY inputMethodComposingChanged FINAL REVISION(2, 2)) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool wrap READ wrap WRITE setWrap NOTIFY wrapChanged FINAL REVISION(2, 3)) + // 2.4 (Qt 5.11) + Q_PROPERTY(QString displayText READ displayText NOTIFY displayTextChanged FINAL REVISION(2, 4)) + QML_NAMED_ELEMENT(SpinBox) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickSpinBox(QQuickItem *parent = nullptr); + ~QQuickSpinBox(); + + int from() const; + void setFrom(int from); + + int to() const; + void setTo(int to); + + int value() const; + void setValue(int value); + + int stepSize() const; + void setStepSize(int step); + + bool isEditable() const; + void setEditable(bool editable); + + QValidator *validator() const; + void setValidator(QValidator *validator); + + QJSValue textFromValue() const; + void setTextFromValue(const QJSValue &callback); + + QJSValue valueFromText() const; + void setValueFromText(const QJSValue &callback); + + QQuickIndicatorButton *up() const; + QQuickIndicatorButton *down() const; + + // 2.2 (Qt 5.9) + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints(Qt::InputMethodHints hints); + + bool isInputMethodComposing() const; + + // 2.3 (Qt 5.10) + bool wrap() const; + void setWrap(bool wrap); + + // 2.4 (Qt 5.11) + QString displayText() const; + +public Q_SLOTS: + void increase(); + void decrease(); + +Q_SIGNALS: + void fromChanged(); + void toChanged(); + void valueChanged(); + void stepSizeChanged(); + void editableChanged(); + void validatorChanged(); + void textFromValueChanged(); + void valueFromTextChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void valueModified(); + Q_REVISION(2, 2) void inputMethodHintsChanged(); + Q_REVISION(2, 2) void inputMethodComposingChanged(); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void wrapChanged(); + // 2.4 (Qt 5.11) + Q_REVISION(2, 4) void displayTextChanged(); + +protected: + void focusInEvent(QFocusEvent *event) override; + void hoverEnterEvent(QHoverEvent *event) override; + void hoverMoveEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void timerEvent(QTimerEvent *event) override; +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + + void classBegin() override; + void componentComplete() override; + void itemChange(ItemChange change, const ItemChangeData &value) override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + void localeChange(const QLocale &newLocale, const QLocale &oldLocale) override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; + void accessibilityActiveChanged(bool active) override; +#endif + +private: + Q_DISABLE_COPY(QQuickSpinBox) + Q_DECLARE_PRIVATE(QQuickSpinBox) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSpinBox) + +#endif // QQUICKSPINBOX_P_H diff --git a/src/quicktemplates2/qquicksplitview.cpp b/src/quicktemplates2/qquicksplitview.cpp new file mode 100644 index 0000000000..24e42d8258 --- /dev/null +++ b/src/quicktemplates2/qquicksplitview.cpp @@ -0,0 +1,2141 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicksplitview_p.h" +#include "qquicksplitview_p_p.h" +#include "qquickcontentitem_p.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qcborarray.h> +#include <QtCore/qcbormap.h> +#include <QtCore/qcborvalue.h> +#include <QtQml/QQmlInfo> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype SplitView + \inherits Container +//! \instantiates QQuickSplitView + \inqmlmodule QtQuick.Controls + \since 5.13 + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Lays out items with a draggable splitter between each item. + + SplitView is a control that lays out items horizontally or vertically with + a draggable splitter between each item. + + SplitView supports the following attached properties on items it manages: + + \list + \li \l{minimumWidth}{SplitView.minimumWidth} + \li \l{minimumHeight}{SplitView.minimumHeight} + \li \l{preferredWidth}{SplitView.preferredWidth} + \li \l{preferredHeight}{SplitView.preferredHeight} + \li \l{maximumWidth}{SplitView.maximumWidth} + \li \l{maximumHeight}{SplitView.maximumHeight} + \li \l{fillWidth}{SplitView.fillWidth} (true for only one child) + \li \l{fillHeight}{SplitView.fillHeight} (true for only one child) + \endlist + + In addition, each handle has the following read-only attached properties: + + \list + \li \l{SplitHandle::hovered}{SplitHandle.hovered} + \li \l{SplitHandle::pressed}{SplitHandle.pressed} + \endlist + + \note Handles should be purely visual and not handle events, as it can + interfere with their hovered and pressed states. + + The preferred size of items in a SplitView can be specified via + \l{Item::}{implicitWidth} and \l{Item::}{implicitHeight} or + \c SplitView.preferredWidth and \c SplitView.preferredHeight: + + \code + SplitView { + anchors.fill: parent + + Item { + SplitView.preferredWidth: 50 + } + + // ... + } + \endcode + + For a horizontal SplitView, it's not necessary to specify the preferred + height of each item, as they will be resized to the height of the view. + This applies in reverse for vertical views. + + When a split handle is dragged, the \c SplitView.preferredWidth or + \c SplitView.preferredHeight property is overwritten, depending on the + \l orientation of the view. + + To limit the size of items in a horizontal view, use the following + properties: + + \code + SplitView { + anchors.fill: parent + + Item { + SplitView.minimumWidth: 25 + SplitView.preferredWidth: 50 + SplitView.maximumWidth: 100 + } + + // ... + } + \endcode + + To limit the size of items in a vertical view, use the following + properties: + + \code + SplitView { + anchors.fill: parent + orientation: Qt.Vertical + + Item { + SplitView.minimumHeight: 25 + SplitView.preferredHeight: 50 + SplitView.maximumHeight: 100 + } + + // ... + } + \endcode + + There will always be one item (the fill item) in the SplitView that has + \c SplitView.fillWidth set to \c true (or \c SplitView.fillHeight, if + \l orientation is \c Qt.Vertical). This means that the item will get all + leftover space when other items have been laid out. By default, the last + visible child of the SplitView will have this set, but it can be changed by + explicitly setting \c fillWidth to \c true on another item. + + A handle can belong to the item either on the left or top side, or on the + right or bottom side: + + \list + \li If the fill item is to the right: the handle belongs to the left + item. + \li If the fill item is on the left: the handle belongs to the right + item. + \endlist + + To create a SplitView with three items, and let the center item get + superfluous space, one could do the following: + + \code + SplitView { + anchors.fill: parent + orientation: Qt.Horizontal + + Rectangle { + implicitWidth: 200 + SplitView.maximumWidth: 400 + color: "lightblue" + Label { + text: "View 1" + anchors.centerIn: parent + } + } + Rectangle { + id: centerItem + SplitView.minimumWidth: 50 + SplitView.fillWidth: true + color: "lightgray" + Label { + text: "View 2" + anchors.centerIn: parent + } + } + Rectangle { + implicitWidth: 200 + color: "lightgreen" + Label { + text: "View 3" + anchors.centerIn: parent + } + } + } + \endcode + + \section1 Serializing SplitView's State + + The main purpose of SplitView is to allow users to easily configure the + size of various UI elements. In addition, the user's preferred sizes should + be remembered across sessions. To achieve this, the values of the \c + SplitView.preferredWidth and \c SplitView.preferredHeight properties can be + serialized using the \l saveState() and \l restoreState() functions: + + \qml + import QtQuick.Controls + import Qt.labs.settings + + ApplicationWindow { + // ... + + Component.onCompleted: splitView.restoreState(settings.splitView) + Component.onDestruction: settings.splitView = splitView.saveState() + + Settings { + id: settings + property var splitView + } + + SplitView { + id: splitView + // ... + } + } + \endqml + + Alternatively, the \l {Settings::}{value()} and \l {Settings::}{setValue()} + functions of \l Settings can be used: + + \qml + import QtQuick.Controls + import Qt.labs.settings + + ApplicationWindow { + // ... + + Component.onCompleted: splitView.restoreState(settings.value("ui/splitview")) + Component.onDestruction: settings.setValue("ui/splitview", splitView.saveState()) + + Settings { + id: settings + } + + SplitView { + id: splitView + // ... + } + } + \endqml + + \sa SplitHandle, {Customizing SplitView}, {Container Controls} +*/ + +Q_LOGGING_CATEGORY(qlcQQuickSplitView, "qt.quick.controls.splitview") +Q_LOGGING_CATEGORY(qlcQQuickSplitViewPointer, "qt.quick.controls.splitview.pointer") +Q_LOGGING_CATEGORY(qlcQQuickSplitViewState, "qt.quick.controls.splitview.state") + +void QQuickSplitViewPrivate::updateFillIndex() +{ + const int count = contentModel->count(); + const bool horizontal = isHorizontal(); + + qCDebug(qlcQQuickSplitView) << "looking for fillWidth/Height item amongst" << count << "items"; + + m_fillIndex = -1; + int i = 0; + int lastVisibleIndex = -1; + for (; i < count; ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (!item->isVisible()) + continue; + + lastVisibleIndex = i; + + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + if (!attached) + continue; + + if ((horizontal && attached->fillWidth()) || (!horizontal && attached->fillHeight())) { + m_fillIndex = i; + qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << m_fillIndex; + break; + } + } + + if (m_fillIndex == -1) { + // If there was no item with fillWidth/fillHeight set, m_fillIndex will be -1, + // and we'll set it to the last visible item. + // If there was an item with fillWidth/fillHeight set, we were already done and this will be skipped. + m_fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1; + qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << m_fillIndex; + } +} + +/* + Resizes split items according to their preferred size and any constraints. + + If a split item is being resized due to a split handle being dragged, + it will be resized accordingly. + + Items that aren't visible are skipped. +*/ +void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &usedHeight, int &indexBeingResizedDueToDrag) +{ + const int count = contentModel->count(); + const bool horizontal = isHorizontal(); + for (int index = 0; index < count; ++index) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); + if (!item->isVisible()) { + // The item is not visible, so skip it. + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": split item " << item + << " at index " << index << " is not visible; skipping it and its handles (if any)"; + continue; + } + + const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + const auto sizeData = effectiveSizeData(itemPrivate, attached); + + const bool resizeLeftItem = m_fillIndex > m_pressedHandleIndex; + // True if any handle is pressed. + const bool isAHandlePressed = m_pressedHandleIndex != -1; + // True if this particular item is being resized as a result of a handle being dragged. + const bool isBeingResized = isAHandlePressed && ((resizeLeftItem && index == m_pressedHandleIndex) + || (!resizeLeftItem && index == m_nextVisibleIndexAfterPressedHandle)); + if (isBeingResized) { + indexBeingResizedDueToDrag = index; + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": dragging handle for item"; + } + + const qreal size = horizontal ? width : height; + qreal requestedSize = 0; + if (isBeingResized) { + // Don't let the mouse go past either edge of the SplitView. + const qreal clampedMousePos = horizontal + ? qBound(qreal(0.0), m_mousePos.x(), qreal(width)) + : qBound(qreal(0.0), m_mousePos.y(), qreal(height)); + + // We also need to ensure that the item's edge doesn't go too far + // out and hence give the item more space than is available. + const int firstIndex = resizeLeftItem ? m_nextVisibleIndexAfterPressedHandle : 0; + const int lastIndex = resizeLeftItem ? contentModel->count() - 1 : m_pressedHandleIndex; + const qreal accumulated = accumulatedSize(firstIndex, lastIndex); + + const qreal mousePosRelativeToLeftHandleEdge = horizontal + ? m_pressPos.x() - m_handlePosBeforePress.x() + : m_pressPos.y() - m_handlePosBeforePress.y(); + + const QQuickItem *pressedHandleItem = m_handleItems.at(m_pressedHandleIndex); + const qreal pressedHandleSize = horizontal ? pressedHandleItem->width() : pressedHandleItem->height(); + + if (resizeLeftItem) { + // The handle shouldn't cross other handles, so use the right edge of + // the first handle to the left as the left edge. + qreal leftEdge = 0; + if (m_pressedHandleIndex - 1 >= 0) { + const QQuickItem *leftHandle = m_handleItems.at(m_pressedHandleIndex - 1); + leftEdge = horizontal + ? leftHandle->x() + leftHandle->width() + : leftHandle->y() + leftHandle->height(); + } + + // The mouse can be clicked anywhere in the handle, and if we don't account for + // its position within the handle, the handle will jump when dragged. + const qreal pressedHandlePos = clampedMousePos - mousePosRelativeToLeftHandleEdge; + + const qreal rightStop = size - accumulated - pressedHandleSize; + qreal leftStop = qMax(leftEdge, pressedHandlePos); + // qBound() doesn't care if min is greater than max, but we do. + if (leftStop > rightStop) + leftStop = rightStop; + const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); + const qreal newItemSize = newHandlePos - leftEdge; + + // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth + // will be used on the next layout (when it's no longer being resized). + if (!attached) { + // Force the attached object to be created since we rely on it. + attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); + } + + /* + Users could conceivably respond to size changes in items by setting attached + SplitView properties: + + onWidthChanged: if (width < 10) secondItem.SplitView.preferredWidth = 100 + + We handle this by doing another layout after the current layout if the + attached/implicit size properties are set during this layout. However, we also + need to set preferredWidth/Height here (for reasons mentioned in the comment above), + but we don't want this to count as a request for a delayed layout, so we guard against it. + */ + m_ignoreNextLayoutRequest = true; + + if (horizontal) + attached->setPreferredWidth(newItemSize); + else + attached->setPreferredHeight(newItemSize); + + // We still need to use requestedWidth in the setWidth() call below, + // because sizeData has already been calculated and now contains an old + // effectivePreferredWidth value. + requestedSize = newItemSize; + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item + << " (clampedMousePos=" << clampedMousePos + << " pressedHandlePos=" << pressedHandlePos + << " accumulated=" << accumulated + << " leftEdge=" << leftEdge + << " leftStop=" << leftStop + << " rightStop=" << rightStop + << " newHandlePos=" << newHandlePos + << " newItemSize=" << newItemSize << ")"; + } else { // Resizing the item on the right. + // The handle shouldn't cross other handles, so use the left edge of + // the first handle to the right as the right edge. + qreal rightEdge = size; + if (m_nextVisibleIndexAfterPressedHandle < m_handleItems.size()) { + const QQuickItem *rightHandle = m_handleItems.at(m_nextVisibleIndexAfterPressedHandle); + rightEdge = horizontal ? rightHandle->x() : rightHandle->y(); + } + + // The mouse can be clicked anywhere in the handle, and if we don't account for + // its position within the handle, the handle will jump when dragged. + const qreal pressedHandlePos = clampedMousePos - mousePosRelativeToLeftHandleEdge; + + const qreal leftStop = accumulated - pressedHandleSize; + qreal rightStop = qMin(rightEdge - pressedHandleSize, pressedHandlePos); + // qBound() doesn't care if min is greater than max, but we do. + if (rightStop < leftStop) + rightStop = leftStop; + const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); + const qreal newItemSize = rightEdge - (newHandlePos + pressedHandleSize); + + // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth + // will be used on the next layout (when it's no longer being resized). + if (!attached) { + // Force the attached object to be created since we rely on it. + attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); + } + + m_ignoreNextLayoutRequest = true; + + if (horizontal) + attached->setPreferredWidth(newItemSize); + else + attached->setPreferredHeight(newItemSize); + + // We still need to use requestedSize in the setWidth()/setHeight() call below, + // because sizeData has already been calculated and now contains an old + // effectivePreferredWidth/Height value. + requestedSize = newItemSize; + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item + << " (clampedMousePos=" << clampedMousePos + << " pressedHandlePos=" << pressedHandlePos + << " accumulated=" << accumulated + << " leftEdge=" << rightEdge + << " leftStop=" << leftStop + << " rightStop=" << rightStop + << " newHandlePos=" << newHandlePos + << " newItemSize=" << newItemSize << ")"; + } + } else if (index != m_fillIndex) { + // No handle is being dragged and we're not the fill item, + // so set our preferred size as we normally would. + requestedSize = horizontal + ? sizeData.effectivePreferredWidth : sizeData.effectivePreferredHeight; + } + + if (index != m_fillIndex) { + if (horizontal) { + item->setWidth(qBound( + sizeData.effectiveMinimumWidth, + requestedSize, + sizeData.effectiveMaximumWidth)); + item->setHeight(height); + } else { + item->setWidth(width); + item->setHeight(qBound( + sizeData.effectiveMinimumHeight, + requestedSize, + sizeData.effectiveMaximumHeight)); + } + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized split item " << item + << " (effective" + << " minW=" << sizeData.effectiveMinimumWidth + << ", minH=" << sizeData.effectiveMinimumHeight + << ", prfW=" << sizeData.effectivePreferredWidth + << ", prfH=" << sizeData.effectivePreferredHeight + << ", maxW=" << sizeData.effectiveMaximumWidth + << ", maxH=" << sizeData.effectiveMaximumHeight << ")"; + + // Keep track of how much space has been used so far. + if (horizontal) + usedWidth += item->width(); + else + usedHeight += item->height(); + } else if (indexBeingResizedDueToDrag != m_fillIndex) { + // The fill item is resized afterwards, outside of the loop. + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": skipping fill item as we resize it last"; + } + + // Also account for the size of the handle for this item (if any). + // We do this for the fill item too, which is why it's outside of the check above. + if (index < count - 1 && m_handle) { + QQuickItem *handleItem = m_handleItems.at(index); + // The handle for an item that's not visible will usually already be skipped + // with the item visibility check higher up, but if the view looks like this + // [ visible ] | [ visible (fill) ] | [ hidden ] + // ^ + // hidden + // and we're iterating over the second item (which is visible but has no handle), + // we need to add an extra check for it to avoid it still taking up space. + if (handleItem->isVisible()) { + if (horizontal) { + qCDebug(qlcQQuickSplitView).nospace() << " - " << index + << ": handle takes up " << handleItem->width() << " width"; + usedWidth += handleItem->width(); + } else { + qCDebug(qlcQQuickSplitView).nospace() << " - " << index + << ": handle takes up " << handleItem->height() << " height"; + usedHeight += handleItem->height(); + } + } else { + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": handle is not visible; skipping it"; + } + } + } +} + +/* + Resizes the fill item by giving it the remaining space + after all other items have been resized. + + Items that aren't visible are skipped. +*/ +void QQuickSplitViewPrivate::layoutResizeFillItem(QQuickItem *fillItem, + qreal &usedWidth, qreal &usedHeight, int indexBeingResizedDueToDrag) +{ + // Only bother resizing if it it's visible. Also, if it's being resized due to a drag, + // then we've already set its size in layoutResizeSplitItems(), so no need to do it here. + if (!fillItem->isVisible() || indexBeingResizedDueToDrag == m_fillIndex) { + qCDebug(qlcQQuickSplitView).nospace() << m_fillIndex << ": - fill item " << fillItem + << " is not visible or was already resized due to a drag;" + << " skipping it and its handles (if any)"; + return; + } + + const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(fillItem); + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false)); + const auto fillSizeData = effectiveSizeData(fillItemPrivate, attached); + if (isHorizontal()) { + fillItem->setWidth(qBound( + fillSizeData.effectiveMinimumWidth, + width - usedWidth, + fillSizeData.effectiveMaximumWidth)); + fillItem->setHeight(height); + } else { + fillItem->setWidth(width); + fillItem->setHeight(qBound( + fillSizeData.effectiveMinimumHeight, + height - usedHeight, + fillSizeData.effectiveMaximumHeight)); + } + + qCDebug(qlcQQuickSplitView).nospace() << " - " << m_fillIndex + << ": resized split fill item " << fillItem << " (effective" + << " minW=" << fillSizeData.effectiveMinimumWidth + << ", minH=" << fillSizeData.effectiveMinimumHeight + << ", maxW=" << fillSizeData.effectiveMaximumWidth + << ", maxH=" << fillSizeData.effectiveMaximumHeight << ")"; +} + +/* + Positions items by laying them out in a row or column. + + Items that aren't visible are skipped. +*/ +void QQuickSplitViewPrivate::layoutPositionItems(const QQuickItem *fillItem) +{ + const bool horizontal = isHorizontal(); + const int count = contentModel->count(); + qreal usedWidth = 0; + qreal usedHeight = 0; + + for (int i = 0; i < count; ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (!item->isVisible()) { + qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": split item " << item + << " is not visible; skipping it and its handles (if any)"; + continue; + } + + // Position the item. + if (horizontal) { + item->setX(usedWidth); + item->setY(0); + } else { + item->setX(0); + item->setY(usedHeight); + } + + // Keep track of how much space has been used so far. + if (horizontal) + usedWidth += item->width(); + else + usedHeight += item->height(); + + if (Q_UNLIKELY(qlcQQuickSplitView().isDebugEnabled())) { + const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(fillItem); + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false)); + const auto sizeData = effectiveSizeData(fillItemPrivate, attached); + qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": positioned " + << (i == m_fillIndex ? "fill item " : "item ") << item << " (effective" + << " minW=" << sizeData.effectiveMinimumWidth + << ", minH=" << sizeData.effectiveMinimumHeight + << ", prfW=" << sizeData.effectivePreferredWidth + << ", prfH=" << sizeData.effectivePreferredHeight + << ", maxW=" << sizeData.effectiveMaximumWidth + << ", maxH=" << sizeData.effectiveMaximumHeight << ")"; + } + + // Position the handle for this item (if any). + if (i < count - 1 && m_handle) { + // Position the handle. + QQuickItem *handleItem = m_handleItems.at(i); + handleItem->setX(horizontal ? usedWidth : 0); + handleItem->setY(horizontal ? 0 : usedHeight); + + if (horizontal) + usedWidth += handleItem->width(); + else + usedHeight += handleItem->height(); + + qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": positioned handle " << handleItem; + } + } +} + +void QQuickSplitViewPrivate::requestLayout() +{ + Q_Q(QQuickSplitView); + q->polish(); +} + +void QQuickSplitViewPrivate::layout() +{ + if (!componentComplete) + return; + + if (m_layingOut) + return; + + const int count = contentModel->count(); + if (count <= 0) + return; + + Q_ASSERT_X(m_fillIndex < count, Q_FUNC_INFO, qPrintable( + QString::fromLatin1("m_fillIndex is %1 but our count is %2").arg(m_fillIndex).arg(count))); + + Q_ASSERT_X(!m_handle || m_handleItems.size() == count - 1, Q_FUNC_INFO, qPrintable(QString::fromLatin1( + "Expected %1 handle items, but there are %2").arg(count - 1).arg(m_handleItems.size()))); + + // We allow mouse events to instantly trigger layouts, whereas with e.g. + // attached properties being set, we require a delayed layout. + // To prevent recursive calls during mouse events, we need this guard. + QBoolBlocker guard(m_layingOut, true); + + const bool horizontal = isHorizontal(); + qCDebug(qlcQQuickSplitView) << "laying out" << count << "split items" + << (horizontal ? "horizontally" : "vertically") << "in SplitView" << q_func(); + + qreal usedWidth = 0; + qreal usedHeight = 0; + int indexBeingResizedDueToDrag = -1; + + qCDebug(qlcQQuickSplitView) << " resizing:"; + + // First, resize the items. We need to do this first because otherwise fill + // items would take up all of the remaining space as soon as they are encountered. + layoutResizeSplitItems(usedWidth, usedHeight, indexBeingResizedDueToDrag); + + qCDebug(qlcQQuickSplitView).nospace() + << " - (remaining width=" << width - usedWidth + << " remaining height=" << height - usedHeight << ")"; + + // Give the fill item the remaining space. + QQuickItem *fillItem = qobject_cast<QQuickItem*>(contentModel->object(m_fillIndex)); + layoutResizeFillItem(fillItem, usedWidth, usedHeight, indexBeingResizedDueToDrag); + + qCDebug(qlcQQuickSplitView) << " positioning:"; + + // Position the items. + layoutPositionItems(fillItem); + + qCDebug(qlcQQuickSplitView).nospace() << "finished layouting"; +} + +void QQuickSplitViewPrivate::createHandles() +{ + Q_ASSERT(m_handle); + // A handle only makes sense if there are two items on either side. + if (contentModel->count() <= 1) + return; + + // Create new handle items if there aren't enough. + const int count = contentModel->count() - 1; + qCDebug(qlcQQuickSplitView) << "creating" << count << "handles"; + m_handleItems.reserve(count); + for (int i = 0; i < count; ++i) + createHandleItem(i); +} + +void QQuickSplitViewPrivate::createHandleItem(int index) +{ + Q_Q(QQuickSplitView); + if (contentModel->count() <= 1) + return; + + qCDebug(qlcQQuickSplitView) << "- creating handle for split item at index" << index + << "from handle component" << m_handle; + + // If we don't use the correct context, it won't be possible to refer to + // the control's id from within the delegate. + QQmlContext *creationContext = m_handle->creationContext(); + // The component might not have been created in QML, in which case + // the creation context will be null and we have to create it ourselves. + if (!creationContext) + creationContext = qmlContext(q); + QQmlContext *context = new QQmlContext(creationContext, q); + context->setContextObject(q); + QQuickItem *handleItem = qobject_cast<QQuickItem*>(m_handle->beginCreate(context)); + if (handleItem) { + qCDebug(qlcQQuickSplitView) << "- successfully created handle item" << handleItem << "for split item at index" << index; + + // Insert the item to our list of items *before* its parent is set to us, + // so that we can avoid it being added as a content item by checking + // if it is in the list in isContent(). + m_handleItems.insert(index, handleItem); + + handleItem->setParentItem(q); + // Handles must have priority for press events, so we need to set this. + handleItem->setAcceptedMouseButtons(Qt::LeftButton); + handleItem->setKeepMouseGrab(true); +#if QT_CONFIG(cursor) + updateCursorHandle(handleItem); +#endif + m_handle->completeCreate(); + resizeHandle(handleItem); + } +} + +void QQuickSplitViewPrivate::removeExcessHandles() +{ + int excess = m_handleItems.size() - qMax(0, contentModel->count() - 1); + qCDebug(qlcQQuickSplitView) << "removing" << excess << "excess handles from the end of our list"; + for (; excess > 0; --excess) { + QQuickItem *handleItem = m_handleItems.takeLast(); + delete handleItem; + } +} + +qreal QQuickSplitViewPrivate::accumulatedSize(int firstIndex, int lastIndex) const +{ + qreal size = 0.0; + const bool horizontal = isHorizontal(); + for (int i = firstIndex; i <= lastIndex; ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (item->isVisible()) { + if (i != m_fillIndex) { + size += horizontal ? item->width() : item->height(); + } else { + // If the fill item has a minimum size specified, we must respect it. + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + if (attached) { + const QQuickSplitViewAttachedPrivate *attachedPrivate + = QQuickSplitViewAttachedPrivate::get(attached); + if (horizontal && attachedPrivate->m_isMinimumWidthSet) + size += attachedPrivate->m_minimumWidth; + else if (!horizontal && attachedPrivate->m_isMinimumHeightSet) + size += attachedPrivate->m_minimumHeight; + } + } + } + + // Only add the handle's width if there's actually a handle for this split item index. + if (i < lastIndex || lastIndex < contentModel->count() - 1) { + const QQuickItem *handleItem = m_handleItems.at(i); + if (handleItem->isVisible()) + size += horizontal ? handleItem->width() : handleItem->height(); + } + } + return size; +} + +qreal effectiveMinimumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate) +{ + return attachedPrivate && attachedPrivate->m_isMinimumWidthSet ? attachedPrivate->m_minimumWidth : 0; +} + +qreal effectiveMinimumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate) +{ + return attachedPrivate && attachedPrivate->m_isMinimumHeightSet ? attachedPrivate->m_minimumHeight : 0; +} + +qreal effectivePreferredWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate, + const QQuickItemPrivate *itemPrivate) +{ + return attachedPrivate && attachedPrivate->m_isPreferredWidthSet + ? attachedPrivate->m_preferredWidth : itemPrivate->implicitWidth; +} + +qreal effectivePreferredHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate, + const QQuickItemPrivate *itemPrivate) +{ + return attachedPrivate && attachedPrivate->m_isPreferredHeightSet + ? attachedPrivate->m_preferredHeight : itemPrivate->implicitHeight; +} + +qreal effectiveMaximumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate) +{ + return attachedPrivate && attachedPrivate->m_isMaximumWidthSet + ? attachedPrivate->m_maximumWidth : std::numeric_limits<qreal>::infinity(); +} + +qreal effectiveMaximumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate) +{ + return attachedPrivate && attachedPrivate->m_isMaximumHeightSet + ? attachedPrivate->m_maximumHeight : std::numeric_limits<qreal>::infinity(); +} + +// We don't just take an index, because the item and attached properties object +// will both be used outside of this function by calling code, so save some +// time by not accessing them twice. +QQuickSplitViewPrivate::EffectiveSizeData QQuickSplitViewPrivate::effectiveSizeData( + const QQuickItemPrivate *itemPrivate, const QQuickSplitViewAttached *attached) const +{ + EffectiveSizeData data; + const QQuickSplitViewAttachedPrivate *attachedPrivate = attached ? QQuickSplitViewAttachedPrivate::get(attached) : nullptr; + data.effectiveMinimumWidth = effectiveMinimumWidth(attachedPrivate); + data.effectiveMinimumHeight = effectiveMinimumHeight(attachedPrivate); + data.effectivePreferredWidth = effectivePreferredWidth(attachedPrivate, itemPrivate); + data.effectivePreferredHeight = effectivePreferredHeight(attachedPrivate, itemPrivate); + data.effectiveMaximumWidth = effectiveMaximumWidth(attachedPrivate); + data.effectiveMaximumHeight = effectiveMaximumHeight(attachedPrivate); + return data; +} + +int QQuickSplitViewPrivate::handleIndexForSplitIndex(int splitIndex) const +{ + // If it's the first and only item in the view, it doesn't have a handle, + // so return -1: splitIndex (0) - 1. + // If it's the last item in the view, it doesn't have a handle, so use + // the handle for the previous item. + return splitIndex == contentModel->count() - 1 ? splitIndex - 1 : splitIndex; +} + +void QQuickSplitViewPrivate::destroyHandles() +{ + qCDebug(qlcQQuickSplitView) << "destroying" << m_handleItems.size() << "handles"; + qDeleteAll(m_handleItems); + m_handleItems.clear(); +} + +void QQuickSplitViewPrivate::resizeHandle(QQuickItem *handleItem) +{ + const bool horizontal = isHorizontal(); + handleItem->setWidth(horizontal ? handleItem->implicitWidth() : width); + handleItem->setHeight(horizontal ? height : handleItem->implicitHeight()); +} + +void QQuickSplitViewPrivate::resizeHandles() +{ + for (QQuickItem *handleItem : m_handleItems) + resizeHandle(handleItem); +} + +#if QT_CONFIG(cursor) +void QQuickSplitViewPrivate::updateCursorHandle(QQuickItem *handleItem) +{ + handleItem->setCursor(isHorizontal() ? Qt::SplitHCursor : Qt::SplitVCursor); +} +#endif + +void QQuickSplitViewPrivate::updateHandleVisibilities() +{ + // If this is the first item that is visible, we won't have any + // handles yet, because we don't create a handle if we only have one item. + if (m_handleItems.isEmpty()) + return; + + // If the visibility/children change makes any item the last (right/bottom-most) + // visible item, we don't want to display a handle for it either: + // [ visible (fill) ] | [ hidden ] | [ hidden ] + // ^ ^ + // hidden hidden + const int count = contentModel->count(); + int lastVisibleItemIndex = -1; + for (int i = count - 1; i >= 0; --i) { + const QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (item->isVisible()) { + lastVisibleItemIndex = i; + break; + } + } + + for (int i = 0; i < count - 1; ++i) { + const QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + QQuickItem *handleItem = m_handleItems.at(i); + if (i != lastVisibleItemIndex) + handleItem->setVisible(item->isVisible()); + else + handleItem->setVisible(false); + qCDebug(qlcQQuickSplitView) << "set visible property of handle" << handleItem << "at index" + << i << "to" << handleItem->isVisible(); + } +} + +void QQuickSplitViewPrivate::updateHoveredHandle(QQuickItem *hoveredItem) +{ + qCDebug(qlcQQuickSplitViewPointer) << "updating hovered handle after" << hoveredItem << "was hovered"; + + const int oldHoveredHandleIndex = m_hoveredHandleIndex; + m_hoveredHandleIndex = m_handleItems.indexOf(hoveredItem); + if (m_hoveredHandleIndex == oldHoveredHandleIndex) + return; + + // First, clear the hovered flag of any previously-hovered handle. + if (oldHoveredHandleIndex != -1) { + QQuickItem *oldHoveredHandle = m_handleItems.at(oldHoveredHandleIndex); + QQuickSplitHandleAttached *oldHoveredHandleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(oldHoveredHandle, true)); + QQuickSplitHandleAttachedPrivate::get(oldHoveredHandleAttached)->setHovered(false); + qCDebug(qlcQQuickSplitViewPointer) << "handle item at index" << oldHoveredHandleIndex << "is no longer hovered"; + } + + if (m_hoveredHandleIndex != -1) { + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(hoveredItem, true)); + QQuickSplitHandleAttachedPrivate::get(handleAttached)->setHovered(true); + qCDebug(qlcQQuickSplitViewPointer) << "handle item at index" << m_hoveredHandleIndex << "is now hovered"; + } else { + qCDebug(qlcQQuickSplitViewPointer) << "either there is no hovered item or" << hoveredItem << "is not a handle"; + } +} + +void QQuickSplitViewPrivate::setResizing(bool resizing) +{ + Q_Q(QQuickSplitView); + if (resizing == m_resizing) + return; + + m_resizing = resizing; + emit q->resizingChanged(); +} + +bool QQuickSplitViewPrivate::isHorizontal() const +{ + return m_orientation == Qt::Horizontal; +} + +QQuickItem *QQuickSplitViewPrivate::getContentItem() +{ + Q_Q(QQuickSplitView); + if (QQuickItem *item = QQuickContainerPrivate::getContentItem()) + return item; + + return new QQuickContentItem(q); +} + +void QQuickSplitViewPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickSplitView); + QQuickContainerPrivate::handlePress(point); + + QQuickItem *pressedItem = q->childAt(point.x(), point.y()); + const int pressedHandleIndex = m_handleItems.indexOf(pressedItem); + if (pressedHandleIndex != -1) { + m_pressedHandleIndex = pressedHandleIndex; + m_pressPos = point; + m_mousePos = point; + + const QQuickItem *leftOrTopItem = qobject_cast<QQuickItem*>(contentModel->object(m_pressedHandleIndex)); + // Find the first item to the right/bottom of this one that is visible. + QQuickItem *rightOrBottomItem = nullptr; + m_nextVisibleIndexAfterPressedHandle = -1; + for (int i = m_pressedHandleIndex + 1; i < contentModel->count(); ++i) { + auto nextItem = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (nextItem->isVisible()) { + rightOrBottomItem = nextItem; + m_nextVisibleIndexAfterPressedHandle = i; + break; + } + } + Q_ASSERT_X(rightOrBottomItem, Q_FUNC_INFO, qPrintable(QString::fromLatin1( + "Failed to find a visible item to the right/bottom of the one that was pressed at index %1; this shouldn't happen") + .arg(m_pressedHandleIndex))); + + const bool isHorizontal = m_orientation == Qt::Horizontal; + m_leftOrTopItemSizeBeforePress = isHorizontal ? leftOrTopItem->width() : leftOrTopItem->height(); + m_rightOrBottomItemSizeBeforePress = isHorizontal ? rightOrBottomItem->width() : rightOrBottomItem->height(); + m_handlePosBeforePress = pressedItem->position(); + + + // Force the attached object to be created since we rely on it. + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(pressedItem, true)); + QQuickSplitHandleAttachedPrivate::get(handleAttached)->setPressed(true); + + setResizing(true); + + qCDebug(qlcQQuickSplitViewPointer).nospace() << "handled press -" + << " left/top index=" << m_pressedHandleIndex << "," + << " size before press=" << m_leftOrTopItemSizeBeforePress << "," + << " item=" << leftOrTopItem + << " right/bottom index=" << m_nextVisibleIndexAfterPressedHandle << "," + << " size before press=" << m_rightOrBottomItemSizeBeforePress + << " item=" << rightOrBottomItem; + } +} + +void QQuickSplitViewPrivate::handleMove(const QPointF &point) +{ + QQuickContainerPrivate::handleMove(point); + + if (m_pressedHandleIndex != -1) { + m_mousePos = point; + // Don't request layouts for input events because we want + // resizing to be as responsive and smooth as possible. + updatePolish(); + } +} + +void QQuickSplitViewPrivate::handleRelease(const QPointF &point) +{ + QQuickContainerPrivate::handleRelease(point); + + if (m_pressedHandleIndex != -1) { + QQuickItem *pressedHandle = m_handleItems.at(m_pressedHandleIndex); + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(pressedHandle, true)); + QQuickSplitHandleAttachedPrivate::get(handleAttached)->setPressed(false); + } + + setResizing(false); + + m_pressedHandleIndex = -1; + m_pressPos = QPointF(); + m_mousePos = QPointF(); + m_handlePosBeforePress = QPointF(); + m_leftOrTopItemSizeBeforePress = 0.0; + m_rightOrBottomItemSizeBeforePress = 0.0; +} + +void QQuickSplitViewPrivate::itemVisibilityChanged(QQuickItem *item) +{ + const int itemIndex = contentModel->indexOf(item, nullptr); + Q_ASSERT(itemIndex != -1); + + qCDebug(qlcQQuickSplitView) << "visible property of split item" + << item << "at index" << itemIndex << "changed to" << item->isVisible(); + + // The visibility of an item just changed, so we need to update the visibility + // of the corresponding handle (if one exists). + + const int handleIndex = handleIndexForSplitIndex(itemIndex); + if (handleIndex != -1) { + QQuickItem *handleItem = m_handleItems.at(handleIndex); + handleItem->setVisible(item->isVisible()); + + qCDebug(qlcQQuickSplitView) << "set visible property of handle item" + << handleItem << "at index" << handleIndex << "to" << item->isVisible(); + } + + updateHandleVisibilities(); + updateFillIndex(); + requestLayout(); +} + +void QQuickSplitViewPrivate::itemImplicitWidthChanged(QQuickItem *) +{ + requestLayout(); +} + +void QQuickSplitViewPrivate::itemImplicitHeightChanged(QQuickItem *) +{ + requestLayout(); +} + +void QQuickSplitViewPrivate::updatePolish() +{ + layout(); +} + +QQuickSplitViewPrivate *QQuickSplitViewPrivate::get(QQuickSplitView *splitView) +{ + return splitView->d_func(); +} + +QQuickSplitView::QQuickSplitView(QQuickItem *parent) + : QQuickContainer(*(new QQuickSplitViewPrivate), parent) +{ + Q_D(QQuickSplitView); + d->changeTypes |= QQuickItemPrivate::Visibility; + + setFiltersChildMouseEvents(true); +} + +QQuickSplitView::QQuickSplitView(QQuickSplitViewPrivate &dd, QQuickItem *parent) + : QQuickContainer(dd, parent) +{ + Q_D(QQuickSplitView); + d->changeTypes |= QQuickItemPrivate::Visibility; + + setFiltersChildMouseEvents(true); +} + +QQuickSplitView::~QQuickSplitView() +{ + Q_D(QQuickSplitView); + for (int i = 0; i < d->contentModel->count(); ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(i)); + d->removeImplicitSizeListener(item); + } +} + +/*! + \qmlproperty enumeration QtQuick.Controls::SplitView::orientation + + This property holds the orientation of the SplitView. + + The orientation determines how the split items are laid out: + + Possible values: + \value Qt.Horizontal The items are laid out horizontally (default). + \value Qt.Vertical The items are laid out vertically. +*/ +Qt::Orientation QQuickSplitView::orientation() const +{ + Q_D(const QQuickSplitView); + return d->m_orientation; +} + +void QQuickSplitView::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickSplitView); + if (orientation == d->m_orientation) + return; + + d->m_orientation = orientation; + d->resizeHandles(); +#if QT_CONFIG(cursor) + for (QQuickItem *handleItem : d->m_handleItems) + d->updateCursorHandle(handleItem); +#endif + d->requestLayout(); + emit orientationChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::SplitView::resizing + \readonly + + This property is \c true when the user is resizing + split items by dragging on the splitter handles. +*/ +bool QQuickSplitView::isResizing() const +{ + Q_D(const QQuickSplitView); + return d->m_resizing; +} + +/*! + \qmlproperty Component QtQuick.Controls::SplitView::handle + + This property holds the handle component. + + An instance of this component will be instantiated \c {count - 1} + times, as long as \c count is greater than than \c {1}. + + The following table explains how each handle will be resized + depending on the orientation of the split view: + + \table + \header + \li Orientation + \li Handle Width + \li Handle Height + \row + \li \c Qt.Horizontal + \li \c implicitWidth + \li The \c height of the SplitView. + \row + \li \c Qt.Vertical + \li The \c width of the SplitView. + \li \c implicitHeight + \endtable + + To change the size of the handle for mouse and touch events without + changing its visual size, use a \l {Item::}{containmentMask}: + + \snippet qtquickcontrols2-splitview-handle-containmentmask.qml 1 + + \sa {Customizing SplitView} +*/ +QQmlComponent *QQuickSplitView::handle() +{ + Q_D(const QQuickSplitView); + return d->m_handle; +} + +void QQuickSplitView::setHandle(QQmlComponent *handle) +{ + Q_D(QQuickSplitView); + if (handle == d->m_handle) + return; + + qCDebug(qlcQQuickSplitView) << "setting handle" << handle; + + if (d->m_handle) + d->destroyHandles(); + + d->m_handle = handle; + + if (d->m_handle) { + d->createHandles(); + d->updateHandleVisibilities(); + } + + d->requestLayout(); + + emit handleChanged(); +} + +bool QQuickSplitView::isContent(QQuickItem *item) const +{ + Q_D(const QQuickSplitView); + if (!qmlContext(item)) + return false; + + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return false; + + return !d->m_handleItems.contains(item); +} + +QQuickSplitViewAttached *QQuickSplitView::qmlAttachedProperties(QObject *object) +{ + return new QQuickSplitViewAttached(object); +} + +/*! + \qmlmethod var QtQuick.Controls::SplitView::saveState() + + Saves the preferred sizes of split items into a byte array and returns it. + + \sa {Serializing SplitView's State}, restoreState() +*/ +QVariant QQuickSplitView::saveState() +{ + Q_D(QQuickSplitView); + qCDebug(qlcQQuickSplitViewState) << "saving state for split items in" << this; + + // Save the preferred sizes of each split item. + QCborArray cborArray; + for (int i = 0; i < d->contentModel->count(); ++i) { + const QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(i)); + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + // Don't serialise stuff if we don't need to. If a split item was given a preferred + // size in QML or it was dragged, it will have an attached object and either + // m_isPreferredWidthSet or m_isPreferredHeightSet (or both) will be true, + // so items without these can be skipped. We write the index of each item + // that has data so that we know which item to set it on when restoring. + if (!attached) + continue; + + const QQuickSplitViewAttachedPrivate *attachedPrivate = QQuickSplitViewAttachedPrivate::get(attached); + if (!attachedPrivate->m_isPreferredWidthSet && !attachedPrivate->m_isPreferredHeightSet) + continue; + + QCborMap cborMap; + cborMap[QLatin1String("index")] = i; + if (attachedPrivate->m_isPreferredWidthSet) { + cborMap[QLatin1String("preferredWidth")] = static_cast<double>(attachedPrivate->m_preferredWidth); + + qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredWidth of " + << attachedPrivate->m_preferredWidth << " for split item " << item << " at index " << i; + } + if (attachedPrivate->m_isPreferredHeightSet) { + cborMap[QLatin1String("preferredHeight")] = static_cast<double>(attachedPrivate->m_preferredHeight); + + qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredHeight of " + << attachedPrivate->m_preferredHeight << " for split item " << item << " at index " << i; + } + + cborArray.append(cborMap); + } + + const QByteArray byteArray = cborArray.toCborValue().toCbor(); + qCDebug(qlcQQuickSplitViewState) << "the resulting byte array is:" << byteArray; + return QVariant(byteArray); +} + +/*! + \qmlmethod bool QtQuick.Controls::SplitView::restoreState(state) + + Reads the preferred sizes from \a state and applies them to the split items. + + Returns \c true if the state was successfully restored, otherwise \c false. + + \sa {Serializing SplitView's State}, saveState() +*/ +bool QQuickSplitView::restoreState(const QVariant &state) +{ + const QByteArray cborByteArray = state.toByteArray(); + Q_D(QQuickSplitView); + if (cborByteArray.isEmpty()) + return false; + + QCborParserError parserError; + const QCborValue cborValue(QCborValue::fromCbor(cborByteArray, &parserError)); + if (parserError.error != QCborError::NoError) { + qmlWarning(this) << "Error reading SplitView state:" << parserError.errorString(); + return false; + } + + qCDebug(qlcQQuickSplitViewState) << "restoring state for split items of" << this + << "from the following string:" << state; + + const QCborArray cborArray(cborValue.toArray()); + const int ourCount = d->contentModel->count(); + // This could conceivably happen if items were removed from the SplitView since the state was last saved. + if (cborArray.size() > ourCount) { + qmlWarning(this) << "Error reading SplitView state: expected " + << ourCount << " or less split items but got " << cborArray.size(); + return false; + } + + for (auto it = cborArray.constBegin(); it != cborArray.constEnd(); ++it) { + QCborMap cborMap(it->toMap()); + const int splitItemIndex = cborMap.value(QLatin1String("index")).toInteger(); + const bool isPreferredWidthSet = cborMap.contains(QLatin1String("preferredWidth")); + const bool isPreferredHeightSet = cborMap.contains(QLatin1String("preferredHeight")); + + QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(splitItemIndex)); + // If the split item does not have a preferred size specified in QML, it could still have + // been resized via dragging before it was saved. In this case, it won't have an + // attached object upon application startup, so we create it. + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); + if (isPreferredWidthSet) { + const qreal preferredWidth = cborMap.value(QLatin1String("preferredWidth")).toDouble(); + attached->setPreferredWidth(preferredWidth); + } + if (isPreferredHeightSet) { + const qreal preferredHeight = cborMap.value(QLatin1String("preferredHeight")).toDouble(); + attached->setPreferredHeight(preferredHeight); + } + + const QQuickSplitViewAttachedPrivate *attachedPrivate = QQuickSplitViewAttachedPrivate::get(attached); + qCDebug(qlcQQuickSplitViewState).nospace() + << "- restored the following state for split item " << item << " at index " << splitItemIndex + << ": preferredWidthSet=" << attachedPrivate->m_isPreferredWidthSet + << " preferredWidth=" << attachedPrivate->m_preferredWidth + << " preferredHeightSet=" << attachedPrivate->m_isPreferredHeightSet + << " preferredHeight=" << attachedPrivate->m_preferredHeight; + } + + return true; +} + +void QQuickSplitView::componentComplete() +{ + Q_D(QQuickSplitView); + QQuickControl::componentComplete(); + d->resizeHandles(); + d->updateFillIndex(); + d->updatePolish(); +} + +void QQuickSplitView::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QQuickSplitView); + QQuickContainer::hoverMoveEvent(event); + + QQuickItem *hoveredItem = childAt(event->position().toPoint().x(), event->position().toPoint().y()); + d->updateHoveredHandle(hoveredItem); +} + +void QQuickSplitView::hoverLeaveEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + Q_D(QQuickSplitView); + // If SplitView is no longer hovered (e.g. visible set to false), clear handle hovered value + d->updateHoveredHandle(nullptr); +} + +bool QQuickSplitView::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + Q_D(QQuickSplitView); + qCDebug(qlcQQuickSplitViewPointer) << "childMouseEventFilter called with" << item << event; + + if (Q_LIKELY(event->isPointerEvent())) { + auto *pointerEvent = static_cast<QPointerEvent *>(event); + const auto &eventPoint = pointerEvent->points().first(); + const QPointF point = mapFromItem(item, eventPoint.position()); + + switch (event->type()) { + case QEvent::MouseButtonPress: + d->handlePress(point); + // Keep the mouse grab if this item belongs to the handle, + // otherwise this event can be stolen e.g. Flickable if we're inside it. + if (d->m_pressedHandleIndex != -1) + item->setKeepMouseGrab(true); + break; + case QEvent::MouseButtonRelease: + d->handleRelease(point); + break; + case QEvent::MouseMove: + d->handleMove(point); + break; + case QEvent::TouchBegin: + if (pointerEvent->pointCount() == 1) { + d->handlePress(point); + // We filter the event on behalf of item, but we want the item + // to be the exclusive grabber so that we can continue to filter + // touch events for it. + if (d->m_pressedHandleIndex != -1) { + item->setKeepTouchGrab(true); + pointerEvent->setExclusiveGrabber(eventPoint, item); + } + } + break; + case QEvent::TouchEnd: + if (pointerEvent->pointCount() == 1) + d->handleRelease(point); + break; + case QEvent::TouchUpdate: + if (pointerEvent->pointCount() == 1) + d->handleMove(point); + break; + default: + break; + } + } + + // If this event belongs to the handle, filter it. (d->m_pressedHandleIndex != -1) means that + // we press or move the handle, so we don't need to propagate it further. + if (d->m_pressedHandleIndex != -1) + return true; + + return QQuickContainer::childMouseEventFilter(item, event); +} + +void QQuickSplitView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickSplitView); + QQuickControl::geometryChange(newGeometry, oldGeometry); + d->resizeHandles(); + d->requestLayout(); +} + +void QQuickSplitView::itemAdded(int index, QQuickItem *item) +{ + Q_D(QQuickSplitView); + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + const int count = d->contentModel->count(); + qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " added at index " << index + << "; there are now " << count << " items"; + + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + if (attached) + QQuickSplitViewAttachedPrivate::get(attached)->setView(this); + + // Only need to add handles if we have more than one split item. + if (count > 1) { + // If the item was added at the end, it shouldn't get a handle; + // the handle always goes to the split item on the left. + d->createHandleItem(index < count - 1 ? index : index - 1); + } + + d->addImplicitSizeListener(item); + + d->updateHandleVisibilities(); + d->updateFillIndex(); + d->requestLayout(); +} + +void QQuickSplitView::itemMoved(int index, QQuickItem *item) +{ + Q_D(QQuickSplitView); + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + qCDebug(qlcQQuickSplitView) << "split item" << item << "moved to index" << index; + + d->updateHandleVisibilities(); + d->updateFillIndex(); + d->requestLayout(); +} + +void QQuickSplitView::itemRemoved(int index, QQuickItem *item) +{ + Q_D(QQuickSplitView); + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " removed from index " << index + << "; there are now " << d->contentModel->count() << " items"; + + // Clear hovered/pressed handle if there are any. + if (d->m_hoveredHandleIndex != -1 || d->m_pressedHandleIndex != -1) { + const int handleIndex = d->m_hoveredHandleIndex != -1 ? d->m_hoveredHandleIndex : d->m_pressedHandleIndex; + QQuickItem *itemHandle = d->m_handleItems.at(handleIndex); + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(itemHandle, false)); + if (handleAttached) { + auto handleAttachedPrivate = QQuickSplitHandleAttachedPrivate::get(handleAttached); + handleAttachedPrivate->setHovered(false); + handleAttachedPrivate->setPressed(false); + } + + d->m_hoveredHandleIndex = -1; + d->m_pressedHandleIndex = -1; + } + + // Unset any attached properties since the item is no longer owned by us. + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + if (attached) + QQuickSplitViewAttachedPrivate::get(attached)->setView(this); + + d->removeImplicitSizeListener(item); + + d->removeExcessHandles(); + d->updateHandleVisibilities(); + d->updateFillIndex(); + d->requestLayout(); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickSplitView::accessibleRole() const +{ + return QAccessible::Pane; +} +#endif + +QQuickSplitViewAttached::QQuickSplitViewAttached(QObject *parent) + : QObject(*(new QQuickSplitViewAttachedPrivate), parent) +{ + Q_D(QQuickSplitViewAttached); + QQuickItem *item = qobject_cast<QQuickItem *>(parent); + if (!item) { + qmlWarning(parent) << "SplitView: attached properties can only be used on Items"; + return; + } + + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + d->m_splitItem = item; + + // Child items get added to SplitView's contentItem, so we have to ensure + // that exists first before trying to set m_splitView. + // Apparently, in some cases it's normal for the parent item + // to not exist until shortly after this constructor has run. + if (!item->parentItem()) + return; + + // This will get hit when attached SplitView properties are imperatively set + // on an item that previously had none set, for example. + QQuickSplitView *splitView = qobject_cast<QQuickSplitView*>(item->parentItem()->parentItem()); + if (!splitView) { + qmlWarning(parent) << "SplitView: attached properties must be accessed through a direct child of SplitView"; + return; + } + + d->setView(splitView); +} + +/*! + \qmlattachedproperty SplitView QtQuick.Controls::SplitView::view + + This attached property holds the split view of the item it is + attached to, or \c null if the item is not in a split view. +*/ +QQuickSplitView *QQuickSplitViewAttached::view() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_splitView; +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::minimumWidth + + This attached property controls the minimum width of the split item. + The \l preferredWidth is bound within the \l minimumWidth and + \l maximumWidth. A split item cannot be dragged to be smaller than + its \c minimumWidth. + + The default value is \c 0. To reset this property to its default value, + set it to \c undefined. + + \sa maximumWidth, preferredWidth, fillWidth, minimumHeight +*/ +qreal QQuickSplitViewAttached::minimumWidth() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_minimumWidth; +} + +void QQuickSplitViewAttached::setMinimumWidth(qreal width) +{ + Q_D(QQuickSplitViewAttached); + d->m_isMinimumWidthSet = true; + if (qFuzzyCompare(width, d->m_minimumWidth)) + return; + + d->m_minimumWidth = width; + d->requestLayoutView(); + emit minimumWidthChanged(); +} + +void QQuickSplitViewAttached::resetMinimumWidth() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectiveMinimumWidth = effectiveMinimumWidth(d); + + d->m_isMinimumWidthSet = false; + d->m_minimumWidth = -1; + + const qreal newEffectiveMinimumWidth = effectiveMinimumWidth(d); + if (qFuzzyCompare(newEffectiveMinimumWidth, oldEffectiveMinimumWidth)) + return; + + d->requestLayoutView(); + emit minimumWidthChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::minimumHeight + + This attached property controls the minimum height of the split item. + The \l preferredHeight is bound within the \l minimumHeight and + \l maximumHeight. A split item cannot be dragged to be smaller than + its \c minimumHeight. + + The default value is \c 0. To reset this property to its default value, + set it to \c undefined. + + \sa maximumHeight, preferredHeight, fillHeight, minimumWidth +*/ +qreal QQuickSplitViewAttached::minimumHeight() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_minimumHeight; +} + +void QQuickSplitViewAttached::setMinimumHeight(qreal height) +{ + Q_D(QQuickSplitViewAttached); + d->m_isMinimumHeightSet = true; + if (qFuzzyCompare(height, d->m_minimumHeight)) + return; + + d->m_minimumHeight = height; + d->requestLayoutView(); + emit minimumHeightChanged(); +} + +void QQuickSplitViewAttached::resetMinimumHeight() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectiveMinimumHeight = effectiveMinimumHeight(d); + + d->m_isMinimumHeightSet = false; + d->m_minimumHeight = -1; + + const qreal newEffectiveMinimumHeight = effectiveMinimumHeight(d); + if (qFuzzyCompare(newEffectiveMinimumHeight, oldEffectiveMinimumHeight)) + return; + + d->requestLayoutView(); + emit minimumHeightChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::preferredWidth + + This attached property controls the preferred width of the split item. The + preferred width will be used as the size of the item, and will be bound + within the \l minimumWidth and \l maximumWidth. If the preferred width + is not set, the item's \l {Item::}{implicitWidth} will be used. + + When a split item is resized, the preferredWidth will be set in order + to keep track of the new size. + + By default, this property is not set, and therefore + \l {Item::}{implicitWidth} will be used instead. To reset this property to + its default value, set it to \c undefined. + + \note Do not set the \l{Item::}{width} property of a split item, as it will be + overwritten upon each layout of the SplitView. + + \sa minimumWidth, maximumWidth, fillWidth, preferredHeight +*/ +qreal QQuickSplitViewAttached::preferredWidth() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_preferredWidth; +} + +void QQuickSplitViewAttached::setPreferredWidth(qreal width) +{ + Q_D(QQuickSplitViewAttached); + d->m_isPreferredWidthSet = true; + // Make sure that we clear this flag now, before we emit the change signals + // which could cause another setter to be called. + auto splitViewPrivate = d->m_splitView ? QQuickSplitViewPrivate::get(d->m_splitView) : nullptr; + const bool ignoreNextLayoutRequest = splitViewPrivate && splitViewPrivate->m_ignoreNextLayoutRequest; + if (splitViewPrivate) + splitViewPrivate->m_ignoreNextLayoutRequest = false; + + if (qFuzzyCompare(width, d->m_preferredWidth)) + return; + + d->m_preferredWidth = width; + + if (!ignoreNextLayoutRequest) { + // We are currently in the middle of performing a layout, and the user (not our internal code) + // changed the preferred width of one of the split items, so request another layout. + d->requestLayoutView(); + } + + emit preferredWidthChanged(); +} + +void QQuickSplitViewAttached::resetPreferredWidth() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectivePreferredWidth = effectivePreferredWidth( + d, QQuickItemPrivate::get(d->m_splitItem)); + + d->m_isPreferredWidthSet = false; + d->m_preferredWidth = -1; + + const qreal newEffectivePreferredWidth = effectivePreferredWidth( + d, QQuickItemPrivate::get(d->m_splitItem)); + if (qFuzzyCompare(newEffectivePreferredWidth, oldEffectivePreferredWidth)) + return; + + d->requestLayoutView(); + emit preferredWidthChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::preferredHeight + + This attached property controls the preferred height of the split item. The + preferred height will be used as the size of the item, and will be bound + within the \l minimumHeight and \l maximumHeight. If the preferred height + is not set, the item's \l{Item::}{implicitHeight} will be used. + + When a split item is resized, the preferredHeight will be set in order + to keep track of the new size. + + By default, this property is not set, and therefore + \l{Item::}{implicitHeight} will be used instead. To reset this property to + its default value, set it to \c undefined. + + \note Do not set the \l{Item::}{height} property of a split item, as it will be + overwritten upon each layout of the SplitView. + + \sa minimumHeight, maximumHeight, fillHeight, preferredWidth +*/ +qreal QQuickSplitViewAttached::preferredHeight() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_preferredHeight; +} + +void QQuickSplitViewAttached::setPreferredHeight(qreal height) +{ + Q_D(QQuickSplitViewAttached); + d->m_isPreferredHeightSet = true; + // Make sure that we clear this flag now, before we emit the change signals + // which could cause another setter to be called. + auto splitViewPrivate = d->m_splitView ? QQuickSplitViewPrivate::get(d->m_splitView) : nullptr; + const bool ignoreNextLayoutRequest = splitViewPrivate && splitViewPrivate->m_ignoreNextLayoutRequest; + if (splitViewPrivate) + splitViewPrivate->m_ignoreNextLayoutRequest = false; + + if (qFuzzyCompare(height, d->m_preferredHeight)) + return; + + d->m_preferredHeight = height; + + if (!ignoreNextLayoutRequest) { + // We are currently in the middle of performing a layout, and the user (not our internal code) + // changed the preferred height of one of the split items, so request another layout. + d->requestLayoutView(); + } + + emit preferredHeightChanged(); +} + +void QQuickSplitViewAttached::resetPreferredHeight() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectivePreferredHeight = effectivePreferredHeight( + d, QQuickItemPrivate::get(d->m_splitItem)); + + d->m_isPreferredHeightSet = false; + d->m_preferredHeight = -1; + + const qreal newEffectivePreferredHeight = effectivePreferredHeight( + d, QQuickItemPrivate::get(d->m_splitItem)); + if (qFuzzyCompare(newEffectivePreferredHeight, oldEffectivePreferredHeight)) + return; + + d->requestLayoutView(); + emit preferredHeightChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::maximumWidth + + This attached property controls the maximum width of the split item. + The \l preferredWidth is bound within the \l minimumWidth and + \l maximumWidth. A split item cannot be dragged to be larger than + its \c maximumWidth. + + The default value is \c Infinity. To reset this property to its default + value, set it to \c undefined. + + \sa minimumWidth, preferredWidth, fillWidth, maximumHeight +*/ +qreal QQuickSplitViewAttached::maximumWidth() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_maximumWidth; +} + +void QQuickSplitViewAttached::setMaximumWidth(qreal width) +{ + Q_D(QQuickSplitViewAttached); + d->m_isMaximumWidthSet = true; + if (qFuzzyCompare(width, d->m_maximumWidth)) + return; + + d->m_maximumWidth = width; + d->requestLayoutView(); + emit maximumWidthChanged(); +} + +void QQuickSplitViewAttached::resetMaximumWidth() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectiveMaximumWidth = effectiveMaximumWidth(d); + + d->m_isMaximumWidthSet = false; + d->m_maximumWidth = -1; + + const qreal newEffectiveMaximumWidth = effectiveMaximumWidth(d); + if (qFuzzyCompare(newEffectiveMaximumWidth, oldEffectiveMaximumWidth)) + return; + + d->requestLayoutView(); + emit maximumWidthChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::maximumHeight + + This attached property controls the maximum height of the split item. + The \l preferredHeight is bound within the \l minimumHeight and + \l maximumHeight. A split item cannot be dragged to be larger than + its \c maximumHeight. + + The default value is \c Infinity. To reset this property to its default + value, set it to \c undefined. + + \sa minimumHeight, preferredHeight, fillHeight, maximumWidth +*/ +qreal QQuickSplitViewAttached::maximumHeight() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_maximumHeight; +} + +void QQuickSplitViewAttached::setMaximumHeight(qreal height) +{ + Q_D(QQuickSplitViewAttached); + d->m_isMaximumHeightSet = true; + if (qFuzzyCompare(height, d->m_maximumHeight)) + return; + + d->m_maximumHeight = height; + d->requestLayoutView(); + emit maximumHeightChanged(); +} + +void QQuickSplitViewAttached::resetMaximumHeight() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectiveMaximumHeight = effectiveMaximumHeight(d); + + d->m_isMaximumHeightSet = false; + d->m_maximumHeight = -1; + + const qreal newEffectiveMaximumHeight = effectiveMaximumHeight(d); + if (qFuzzyCompare(newEffectiveMaximumHeight, oldEffectiveMaximumHeight)) + return; + + d->requestLayoutView(); + emit maximumHeightChanged(); +} + +/*! + \qmlattachedproperty bool QtQuick.Controls::SplitView::fillWidth + + This attached property controls whether the item takes the remaining space + in the split view after all other items have been laid out. + + By default, the last visible child of the split view will have this set, + but it can be changed by explicitly setting \c fillWidth to \c true on + another item. + + The width of a split item with \c fillWidth set to \c true is still + restricted within its \l minimumWidth and \l maximumWidth. + + \sa minimumWidth, preferredWidth, maximumWidth, fillHeight +*/ +bool QQuickSplitViewAttached::fillWidth() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_fillWidth; +} + +void QQuickSplitViewAttached::setFillWidth(bool fill) +{ + Q_D(QQuickSplitViewAttached); + d->m_isFillWidthSet = true; + if (fill == d->m_fillWidth) + return; + + d->m_fillWidth = fill; + if (d->m_splitView && d->m_splitView->orientation() == Qt::Horizontal) + QQuickSplitViewPrivate::get(d->m_splitView)->updateFillIndex(); + d->requestLayoutView(); + emit fillWidthChanged(); +} + +/*! + \qmlattachedproperty bool QtQuick.Controls::SplitView::fillHeight + + This attached property controls whether the item takes the remaining space + in the split view after all other items have been laid out. + + By default, the last visible child of the split view will have this set, + but it can be changed by explicitly setting \c fillHeight to \c true on + another item. + + The height of a split item with \c fillHeight set to \c true is still + restricted within its \l minimumHeight and \l maximumHeight. + + \sa minimumHeight, preferredHeight, maximumHeight, fillWidth +*/ +bool QQuickSplitViewAttached::fillHeight() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_fillHeight; +} + +void QQuickSplitViewAttached::setFillHeight(bool fill) +{ + Q_D(QQuickSplitViewAttached); + d->m_isFillHeightSet = true; + if (fill == d->m_fillHeight) + return; + + d->m_fillHeight = fill; + if (d->m_splitView && d->m_splitView->orientation() == Qt::Vertical) + QQuickSplitViewPrivate::get(d->m_splitView)->updateFillIndex(); + d->requestLayoutView(); + emit fillHeightChanged(); +} + +QQuickSplitViewAttachedPrivate::QQuickSplitViewAttachedPrivate() + : m_fillWidth(false) + , m_fillHeight(false) + , m_isFillWidthSet(false) + , m_isFillHeightSet(false) + , m_isMinimumWidthSet(false) + , m_isMinimumHeightSet(false) + , m_isPreferredWidthSet(false) + , m_isPreferredHeightSet(false) + , m_isMaximumWidthSet(false) + , m_isMaximumHeightSet(false) + , m_minimumWidth(0) + , m_minimumHeight(0) + , m_preferredWidth(-1) + , m_preferredHeight(-1) + , m_maximumWidth(std::numeric_limits<qreal>::infinity()) + , m_maximumHeight(std::numeric_limits<qreal>::infinity()) +{ +} + +void QQuickSplitViewAttachedPrivate::setView(QQuickSplitView *newView) +{ + Q_Q(QQuickSplitViewAttached); + if (newView == m_splitView) + return; + + m_splitView = newView; + qCDebug(qlcQQuickSplitView) << "set SplitView" << newView << "on attached object" << this; + emit q->viewChanged(); +} + +void QQuickSplitViewAttachedPrivate::requestLayoutView() +{ + if (m_splitView) + QQuickSplitViewPrivate::get(m_splitView)->requestLayout(); +} + +QQuickSplitViewAttachedPrivate *QQuickSplitViewAttachedPrivate::get(QQuickSplitViewAttached *attached) +{ + return attached->d_func(); +} + +const QQuickSplitViewAttachedPrivate *QQuickSplitViewAttachedPrivate::get(const QQuickSplitViewAttached *attached) +{ + return attached->d_func(); +} + +QQuickSplitHandleAttachedPrivate::QQuickSplitHandleAttachedPrivate() + : m_hovered(false) + , m_pressed(false) +{ +} + +void QQuickSplitHandleAttachedPrivate::setHovered(bool hovered) +{ + Q_Q(QQuickSplitHandleAttached); + if (hovered == m_hovered) + return; + + m_hovered = hovered; + emit q->hoveredChanged(); +} + +void QQuickSplitHandleAttachedPrivate::setPressed(bool pressed) +{ + Q_Q(QQuickSplitHandleAttached); + if (pressed == m_pressed) + return; + + m_pressed = pressed; + emit q->pressedChanged(); +} + +QQuickSplitHandleAttachedPrivate *QQuickSplitHandleAttachedPrivate::get(QQuickSplitHandleAttached *attached) +{ + return attached->d_func(); +} + +const QQuickSplitHandleAttachedPrivate *QQuickSplitHandleAttachedPrivate::get(const QQuickSplitHandleAttached *attached) +{ + return attached->d_func(); +} + +QQuickSplitHandleAttached::QQuickSplitHandleAttached(QObject *parent) + : QObject(*(new QQuickSplitHandleAttachedPrivate), parent) +{ +} + +/*! + \qmltype SplitHandle + \inherits QtObject +//! \instantiates QQuickSplitHandleAttached + \inqmlmodule QtQuick.Controls + \since 5.13 + \brief Provides attached properties for SplitView handles. + + SplitHandle provides attached properties for \l SplitView handles. + + For split items themselves, use the attached \l SplitView properties. + + \sa SplitView +*/ + +/*! + \qmlattachedproperty bool QtQuick.Controls::SplitHandle::hovered + + This attached property holds whether the split handle is hovered. + + \sa pressed +*/ +bool QQuickSplitHandleAttached::isHovered() const +{ + Q_D(const QQuickSplitHandleAttached); + return d->m_hovered; +} + +/*! + \qmlattachedproperty bool QtQuick.Controls::SplitHandle::pressed + + This attached property holds whether the split handle is pressed. + + \sa hovered +*/ +bool QQuickSplitHandleAttached::isPressed() const +{ + Q_D(const QQuickSplitHandleAttached); + return d->m_pressed; +} + +QQuickSplitHandleAttached *QQuickSplitHandleAttached::qmlAttachedProperties(QObject *object) +{ + return new QQuickSplitHandleAttached(object); +} + +QT_END_NAMESPACE + +#include "moc_qquicksplitview_p.cpp" diff --git a/src/quicktemplates2/qquicksplitview_p.h b/src/quicktemplates2/qquicksplitview_p.h new file mode 100644 index 0000000000..5d8f8a3175 --- /dev/null +++ b/src/quicktemplates2/qquicksplitview_p.h @@ -0,0 +1,227 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSPLITVIEW_P_H +#define QQUICKSPLITVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p.h> +#include <QtQml/qqmllist.h> + +QT_BEGIN_NAMESPACE + +class QQuickSplitViewPrivate; +class QQuickSplitViewAttached; +class QQuickSplitViewAttachedPrivate; +class QQuickSplitHandleAttached; +class QQuickSplitHandleAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitView : public QQuickContainer +{ + Q_OBJECT + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + Q_PROPERTY(bool resizing READ isResizing NOTIFY resizingChanged) + Q_PROPERTY(QQmlComponent *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) + QML_NAMED_ELEMENT(SplitView) + QML_ATTACHED(QQuickSplitViewAttached) + QML_ADDED_IN_VERSION(2, 13) + +public: + explicit QQuickSplitView(QQuickItem *parent = nullptr); + ~QQuickSplitView() override; + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + bool isResizing() const; + + QQmlComponent *handle(); + void setHandle(QQmlComponent *handle); + + bool isContent(QQuickItem *item) const override; + + static QQuickSplitViewAttached *qmlAttachedProperties(QObject *object); + + // Based on the same code in QMainWindow. + enum VersionMarkers { + VersionMarker = 0xff + }; + Q_INVOKABLE QVariant saveState(); + Q_INVOKABLE bool restoreState(const QVariant &state); + +Q_SIGNALS: + void orientationChanged(); + void resizingChanged(); + void handleChanged(); + +protected: + QQuickSplitView(QQuickSplitViewPrivate &dd, QQuickItem *parent); + + void componentComplete() override; + void hoverMoveEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; + bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + void itemAdded(int index, QQuickItem *item) override; + void itemMoved(int index, QQuickItem *item) override; + void itemRemoved(int index, QQuickItem *item) override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickSplitView) + Q_DECLARE_PRIVATE(QQuickSplitView) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitViewAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickSplitView *view READ view NOTIFY viewChanged FINAL) + Q_PROPERTY(qreal minimumWidth READ minimumWidth WRITE setMinimumWidth + RESET resetMinimumWidth NOTIFY minimumWidthChanged FINAL) + Q_PROPERTY(qreal minimumHeight READ minimumHeight WRITE setMinimumHeight + RESET resetMinimumHeight NOTIFY minimumHeightChanged FINAL) + Q_PROPERTY(qreal preferredWidth READ preferredWidth WRITE setPreferredWidth + RESET resetPreferredWidth NOTIFY preferredWidthChanged FINAL) + Q_PROPERTY(qreal preferredHeight READ preferredHeight WRITE setPreferredHeight + RESET resetPreferredHeight NOTIFY preferredHeightChanged FINAL) + Q_PROPERTY(qreal maximumWidth READ maximumWidth WRITE setMaximumWidth + RESET resetMaximumWidth NOTIFY maximumWidthChanged FINAL) + Q_PROPERTY(qreal maximumHeight READ maximumHeight WRITE setMaximumHeight + RESET resetMaximumHeight NOTIFY maximumHeightChanged FINAL) + Q_PROPERTY(bool fillHeight READ fillHeight WRITE setFillHeight NOTIFY fillHeightChanged FINAL) + Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged FINAL) + +public: + explicit QQuickSplitViewAttached(QObject *parent = nullptr); + + QQuickSplitView *view() const; + + qreal minimumWidth() const; + void setMinimumWidth(qreal width); + void resetMinimumWidth(); + + qreal minimumHeight() const; + void setMinimumHeight(qreal height); + void resetMinimumHeight(); + + qreal preferredWidth() const; + void setPreferredWidth(qreal width); + void resetPreferredWidth(); + + qreal preferredHeight() const; + void setPreferredHeight(qreal height); + void resetPreferredHeight(); + + qreal maximumWidth() const; + void setMaximumWidth(qreal width); + void resetMaximumWidth(); + + qreal maximumHeight() const; + void setMaximumHeight(qreal height); + void resetMaximumHeight(); + + bool fillWidth() const; + void setFillWidth(bool fill); + + bool fillHeight() const; + void setFillHeight(bool fill); + +Q_SIGNALS: + void viewChanged(); + void minimumWidthChanged(); + void minimumHeightChanged(); + void preferredWidthChanged(); + void preferredHeightChanged(); + void maximumWidthChanged(); + void maximumHeightChanged(); + void fillWidthChanged(); + void fillHeightChanged(); + +private: + Q_DISABLE_COPY(QQuickSplitViewAttached) + Q_DECLARE_PRIVATE(QQuickSplitViewAttached) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitHandleAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) + QML_NAMED_ELEMENT(SplitHandle) + QML_ATTACHED(QQuickSplitHandleAttached) + QML_UNCREATABLE("") + QML_ADDED_IN_VERSION(2, 13) + +public: + explicit QQuickSplitHandleAttached(QObject *parent = nullptr); + + bool isHovered() const; + bool isPressed() const; + + static QQuickSplitHandleAttached *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void hoveredChanged(); + void pressedChanged(); + +private: + Q_DISABLE_COPY(QQuickSplitHandleAttached) + Q_DECLARE_PRIVATE(QQuickSplitHandleAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSplitView) +QML_DECLARE_TYPEINFO(QQuickSplitView, QML_HAS_ATTACHED_PROPERTIES) + +QML_DECLARE_TYPE(QQuickSplitHandleAttached) +QML_DECLARE_TYPEINFO(QQuickSplitHandleAttached, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKSPLITVIEW_P_H diff --git a/src/quicktemplates2/qquicksplitview_p_p.h b/src/quicktemplates2/qquicksplitview_p_p.h new file mode 100644 index 0000000000..fdf27f5002 --- /dev/null +++ b/src/quicktemplates2/qquicksplitview_p_p.h @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSPLITVIEW_P_P_H +#define QQUICKSPLITVIEW_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSplitView; +class QQuickSplitViewAttached; +class QQuickSplitHandleAttached; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitViewPrivate : public QQuickContainerPrivate +{ + Q_DECLARE_PUBLIC(QQuickSplitView) + +public: + void updateFillIndex(); + void layoutResizeSplitItems(qreal &usedWidth, qreal &usedHeight, int &indexBeingResizedDueToDrag); + void layoutResizeFillItem(QQuickItem *fillItem, qreal &usedWidth, qreal &usedHeight, int indexBeingResizedDueToDrag); + void layoutPositionItems(const QQuickItem *fillItem); + void requestLayout(); + void layout(); + void createHandles(); + void createHandleItem(int index); + void removeExcessHandles(); + void destroyHandles(); + void resizeHandle(QQuickItem *handleItem); + void resizeHandles(); +#if QT_CONFIG(cursor) + void updateCursorHandle(QQuickItem *handleItem); +#endif + void updateHandleVisibilities(); + void updateHoveredHandle(QQuickItem *hoveredItem); + void setResizing(bool resizing); + + bool isHorizontal() const; + qreal accumulatedSize(int firstIndex, int lastIndex) const; + + struct EffectiveSizeData { + qreal effectiveMinimumWidth; + qreal effectiveMinimumHeight; + qreal effectivePreferredWidth; + qreal effectivePreferredHeight; + qreal effectiveMaximumWidth; + qreal effectiveMaximumHeight; + }; + + EffectiveSizeData effectiveSizeData(const QQuickItemPrivate *itemPrivate, + const QQuickSplitViewAttached *attached) const; + + int handleIndexForSplitIndex(int splitIndex) const; + + QQuickItem *getContentItem() override; + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + + void itemVisibilityChanged(QQuickItem *item) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + void updatePolish() override; + + static QQuickSplitViewPrivate *get(QQuickSplitView *splitView); + + Qt::Orientation m_orientation = Qt::Horizontal; + QQmlComponent *m_handle = nullptr; + QList<QQuickItem*> m_handleItems; + int m_hoveredHandleIndex = -1; + int m_pressedHandleIndex = -1; + int m_nextVisibleIndexAfterPressedHandle = -1; + QPointF m_pressPos; + QPointF m_mousePos; + QPointF m_handlePosBeforePress; + qreal m_leftOrTopItemSizeBeforePress = 0.0; + qreal m_rightOrBottomItemSizeBeforePress = 0.0; + int m_fillIndex = -1; + bool m_layingOut = false; + bool m_ignoreNextLayoutRequest = false; + bool m_resizing = false; +}; + +class QQuickSplitViewAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickSplitViewAttached) + +public: + QQuickSplitViewAttachedPrivate(); + + void setView(QQuickSplitView *newView); + void requestLayoutView(); + + static QQuickSplitViewAttachedPrivate *get(QQuickSplitViewAttached *attached); + static const QQuickSplitViewAttachedPrivate *get(const QQuickSplitViewAttached *attached); + + QQuickItem *m_splitItem = nullptr; + QQuickSplitView *m_splitView = nullptr; + + unsigned m_fillWidth : 1; + unsigned m_fillHeight : 1; + unsigned m_isFillWidthSet : 1; + unsigned m_isFillHeightSet : 1; + unsigned m_isMinimumWidthSet : 1; + unsigned m_isMinimumHeightSet : 1; + unsigned m_isPreferredWidthSet : 1; + unsigned m_isPreferredHeightSet : 1; + unsigned m_isMaximumWidthSet : 1; + unsigned m_isMaximumHeightSet : 1; + qreal m_minimumWidth; + qreal m_minimumHeight; + qreal m_preferredWidth; + qreal m_preferredHeight; + qreal m_maximumWidth; + qreal m_maximumHeight; +}; + +class QQuickSplitHandleAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickSplitHandleAttached) + +public: + QQuickSplitHandleAttachedPrivate(); + + void setHovered(bool hovered); + void setPressed(bool pressed); + + static QQuickSplitHandleAttachedPrivate *get(QQuickSplitHandleAttached *attached); + static const QQuickSplitHandleAttachedPrivate *get(const QQuickSplitHandleAttached *attached); + + unsigned m_hovered : 1; + unsigned m_pressed : 1; +}; + +QT_END_NAMESPACE + +#endif // QQUICKSPLITVIEW_P_P_H diff --git a/src/quicktemplates2/qquickstackelement.cpp b/src/quicktemplates2/qquickstackelement.cpp new file mode 100644 index 0000000000..cf644a16fa --- /dev/null +++ b/src/quicktemplates2/qquickstackelement.cpp @@ -0,0 +1,341 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickstackelement_p_p.h" +#include "qquickstackview_p_p.h" + +#include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlincubator.h> +#include <QtQml/private/qv4qobjectwrapper_p.h> +#include <QtQml/private/qqmlcomponent_p.h> +#include <QtQml/private/qqmlengine_p.h> +#include <QtQml/private/qqmlincubator_p.h> + +QT_BEGIN_NAMESPACE + +static QQuickStackViewAttached *attachedStackObject(QQuickStackElement *element) +{ + QQuickStackViewAttached *attached = qobject_cast<QQuickStackViewAttached *>(qmlAttachedPropertiesObject<QQuickStackView>(element->item, false)); + if (attached) + QQuickStackViewAttachedPrivate::get(attached)->element = element; + return attached; +} + +class QQuickStackIncubator : public QQmlIncubator +{ +public: + QQuickStackIncubator(QQuickStackElement *element) + : QQmlIncubator(Synchronous), + element(element) + { + } + +protected: + void setInitialState(QObject *object) override + { + auto privIncubator = QQmlIncubatorPrivate::get(this); + element->incubate(object, privIncubator->requiredProperties()); + } + +private: + QQuickStackElement *element; +}; + +QQuickStackElement::QQuickStackElement() + : QQuickItemViewTransitionableItem(nullptr) +{ +} + +QQuickStackElement::~QQuickStackElement() +{ + if (item) + QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed); + + if (ownComponent) + delete component; + + QQuickStackViewAttached *attached = attachedStackObject(this); + if (item) { + if (ownItem) { + item->setParentItem(nullptr); + item->deleteLater(); + item = nullptr; + } else { + setVisible(false); + if (!widthValid) + item->resetWidth(); + if (!heightValid) + item->resetHeight(); + if (item->parentItem() != originalParent) { + item->setParentItem(originalParent); + } else { + if (attached) + QQuickStackViewAttachedPrivate::get(attached)->itemParentChanged(item, nullptr); + } + } + } + + if (attached) + emit attached->removed(); + + delete context; +} + +QQuickStackElement *QQuickStackElement::fromString(const QString &str, QQuickStackView *view, QString *error) +{ + QUrl url(str); + if (!url.isValid()) { + *error = QStringLiteral("invalid url: ") + str; + return nullptr; + } + + if (url.isRelative()) + url = qmlContext(view)->resolvedUrl(url); + + QQuickStackElement *element = new QQuickStackElement; + element->component = new QQmlComponent(qmlEngine(view), url, view); + element->ownComponent = true; + return element; +} + +QQuickStackElement *QQuickStackElement::fromObject(QObject *object, QQuickStackView *view, QString *error) +{ + Q_UNUSED(view); + QQmlComponent *component = qobject_cast<QQmlComponent *>(object); + QQuickItem *item = qobject_cast<QQuickItem *>(object); + if (!component && !item) { + *error = QQmlMetaType::prettyTypeName(object) + QStringLiteral(" is not supported. Must be Item or Component."); + return nullptr; + } + + QQuickStackElement *element = new QQuickStackElement; + element->component = qobject_cast<QQmlComponent *>(object); + element->item = qobject_cast<QQuickItem *>(object); + if (element->item) + element->originalParent = element->item->parentItem(); + return element; +} + +bool QQuickStackElement::load(QQuickStackView *parent) +{ + setView(parent); + if (!item) { + ownItem = true; + + if (component->isLoading()) { + QObject::connect(component, &QQmlComponent::statusChanged, [this](QQmlComponent::Status status) { + if (status == QQmlComponent::Ready) + load(view); + else if (status == QQmlComponent::Error) + QQuickStackViewPrivate::get(view)->warn(component->errorString().trimmed()); + }); + return true; + } + + QQmlContext *creationContext = component->creationContext(); + if (!creationContext) + creationContext = qmlContext(parent); + context = new QQmlContext(creationContext, parent); + context->setContextObject(parent); + + QQuickStackIncubator incubator(this); + component->create(incubator, context); + if (component->isError()) + QQuickStackViewPrivate::get(parent)->warn(component->errorString().trimmed()); + } else { + RequiredProperties noRequiredProperties {}; + initialize(noRequiredProperties); + } + return item; +} + +void QQuickStackElement::incubate(QObject *object, RequiredProperties &requiredProperties) +{ + item = qmlobject_cast<QQuickItem *>(object); + if (item) { + QQmlEngine::setObjectOwnership(item, QQmlEngine::CppOwnership); + item->setParent(view); + initialize(requiredProperties); + } +} + +void QQuickStackElement::initialize(RequiredProperties &requiredProperties) +{ + if (!item || init) + return; + + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!(widthValid = p->widthValid())) + item->setWidth(view->width()); + if (!(heightValid = p->heightValid())) + item->setHeight(view->height()); + item->setParentItem(view); + + if (!properties.isUndefined()) { + QQmlEngine *engine = qmlEngine(view); + Q_ASSERT(engine); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + Q_ASSERT(v4); + QV4::Scope scope(v4); + QV4::ScopedValue ipv(scope, properties.value()); + QV4::Scoped<QV4::QmlContext> qmlContext(scope, qmlCallingContext.value()); + QV4::ScopedValue qmlObject(scope, QV4::QObjectWrapper::wrap(v4, item)); + QQmlComponentPrivate::setInitialProperties(v4, qmlContext, qmlObject, ipv, requiredProperties, item); + properties.clear(); + } + + if (!requiredProperties.empty()) { + QString error; + for (const auto &property: requiredProperties) { + error += QLatin1String("Property %1 was marked as required but not set.\n") + .arg(property.propertyName); + } + QQuickStackViewPrivate::get(view)->warn(error); + item = nullptr; + } else { + p->addItemChangeListener(this, QQuickItemPrivate::Destroyed); + } + + init = true; +} + +void QQuickStackElement::setIndex(int value) +{ + if (index == value) + return; + + index = value; + QQuickStackViewAttached *attached = attachedStackObject(this); + if (attached) + emit attached->indexChanged(); +} + +void QQuickStackElement::setView(QQuickStackView *value) +{ + if (view == value) + return; + + view = value; + QQuickStackViewAttached *attached = attachedStackObject(this); + if (attached) + emit attached->viewChanged(); +} + +void QQuickStackElement::setStatus(QQuickStackView::Status value) +{ + if (status == value) + return; + + status = value; + QQuickStackViewAttached *attached = attachedStackObject(this); + if (!attached) + return; + + switch (value) { + case QQuickStackView::Inactive: + emit attached->deactivated(); + break; + case QQuickStackView::Deactivating: + emit attached->deactivating(); + break; + case QQuickStackView::Activating: + emit attached->activating(); + break; + case QQuickStackView::Active: + emit attached->activated(); + break; + default: + Q_UNREACHABLE(); + break; + } + + emit attached->statusChanged(); +} + +void QQuickStackElement::setVisible(bool visible) +{ + QQuickStackViewAttached *attached = attachedStackObject(this); + if (!item || (attached && QQuickStackViewAttachedPrivate::get(attached)->explicitVisible)) + return; + + item->setVisible(visible); +} + +void QQuickStackElement::transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget) +{ + if (transitioner) + transitioner->transitionNextReposition(this, type, asTarget); +} + +bool QQuickStackElement::prepareTransition(QQuickItemViewTransitioner *transitioner, const QRectF &viewBounds) +{ + if (transitioner) { + if (item) { + QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; + // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors + if (anchors && (anchors->fill() || anchors->centerIn())) + qmlWarning(item) << "StackView has detected conflicting anchors. Transitions may not execute properly."; + } + + // TODO: add force argument to QQuickItemViewTransitionableItem::prepareTransition()? + nextTransitionToSet = true; + nextTransitionFromSet = true; + nextTransitionFrom += QPointF(1, 1); + return QQuickItemViewTransitionableItem::prepareTransition(transitioner, index, viewBounds); + } + return false; +} + +void QQuickStackElement::startTransition(QQuickItemViewTransitioner *transitioner, QQuickStackView::Status status) +{ + setStatus(status); + if (transitioner) + QQuickItemViewTransitionableItem::startTransition(transitioner, index); +} + +void QQuickStackElement::completeTransition(QQuickTransition *quickTransition) +{ + QQuickItemViewTransitionableItem::completeTransition(quickTransition); +} + +void QQuickStackElement::itemDestroyed(QQuickItem *) +{ + item = nullptr; +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickstackelement_p_p.h b/src/quicktemplates2/qquickstackelement_p_p.h new file mode 100644 index 0000000000..1c7cd5632b --- /dev/null +++ b/src/quicktemplates2/qquickstackelement_p_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSTACKELEMENT_P_P_H +#define QQUICKSTACKELEMENT_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickstackview_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#include <QtQuick/private/qquickitemviewtransition_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQml/private/qv4persistent_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlContext; +class QQmlComponent; +struct QQuickStackTransition; +class RequiredProperties; + +class QQuickStackElement : public QQuickItemViewTransitionableItem, public QQuickItemChangeListener +{ + QQuickStackElement(); + +public: + ~QQuickStackElement(); + + static QQuickStackElement *fromString(const QString &str, QQuickStackView *view, QString *error); + static QQuickStackElement *fromObject(QObject *object, QQuickStackView *view, QString *error); + + bool load(QQuickStackView *parent); + void incubate(QObject *object, RequiredProperties &requiredProperties); + void initialize(RequiredProperties &requiredProperties); + + void setIndex(int index); + void setView(QQuickStackView *view); + void setStatus(QQuickStackView::Status status); + void setVisible(bool visible); + + void transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget); + bool prepareTransition(QQuickItemViewTransitioner *transitioner, const QRectF &viewBounds); + void startTransition(QQuickItemViewTransitioner *transitioner, QQuickStackView::Status status); + void completeTransition(QQuickTransition *quickTransition); + + void itemDestroyed(QQuickItem *item) override; + + int index = -1; + bool init = false; + bool removal = false; + bool ownItem = false; + bool ownComponent = false; + bool widthValid = false; + bool heightValid = false; + QQmlContext *context = nullptr; + QQmlComponent *component = nullptr; + QQuickStackView *view = nullptr; + QPointer<QQuickItem> originalParent; + QQuickStackView::Status status = QQuickStackView::Inactive; + QV4::PersistentValue properties; + QV4::PersistentValue qmlCallingContext; +}; + +QT_END_NAMESPACE + +#endif // QQUICKSTACKELEMENT_P_P_H diff --git a/src/quicktemplates2/qquickstacktransition.cpp b/src/quicktemplates2/qquickstacktransition.cpp new file mode 100644 index 0000000000..a0192d1be1 --- /dev/null +++ b/src/quicktemplates2/qquickstacktransition.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickstacktransition_p_p.h" +#include "qquickstackelement_p_p.h" +#include "qquickstackview_p_p.h" + +QT_BEGIN_NAMESPACE + +static QQuickStackTransition exitTransition(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view) +{ + QQuickStackTransition st; + st.status = QQuickStackView::Deactivating; + st.element = element; + + const QQuickItemViewTransitioner *transitioner = QQuickStackViewPrivate::get(view)->transitioner; + + switch (operation) { + case QQuickStackView::PushTransition: + st.type = QQuickItemViewTransitioner::AddTransition; + if (transitioner) + st.transition = transitioner->addDisplacedTransition; + break; + case QQuickStackView::ReplaceTransition: + st.type = QQuickItemViewTransitioner::MoveTransition; + if (transitioner) + st.transition = transitioner->moveDisplacedTransition; + break; + case QQuickStackView::PopTransition: + st.target = true; + st.type = QQuickItemViewTransitioner::RemoveTransition; + st.viewBounds = view->boundingRect(); + if (transitioner) + st.transition = transitioner->removeTransition; + break; + default: + Q_UNREACHABLE(); + break; + } + + return st; +} + +static QQuickStackTransition enterTransition(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view) +{ + QQuickStackTransition st; + st.status = QQuickStackView::Activating; + st.element = element; + + const QQuickItemViewTransitioner *transitioner = QQuickStackViewPrivate::get(view)->transitioner; + + switch (operation) { + case QQuickStackView::PushTransition: + st.target = true; + st.type = QQuickItemViewTransitioner::AddTransition; + st.viewBounds = view->boundingRect(); + if (transitioner) + st.transition = transitioner->addTransition; + break; + case QQuickStackView::ReplaceTransition: + st.target = true; + st.type = QQuickItemViewTransitioner::MoveTransition; + st.viewBounds = view->boundingRect(); + if (transitioner) + st.transition = transitioner->moveTransition; + break; + case QQuickStackView::PopTransition: + st.type = QQuickItemViewTransitioner::RemoveTransition; + if (transitioner) + st.transition = transitioner->removeDisplacedTransition; + break; + default: + Q_UNREACHABLE(); + break; + } + + return st; +} + +static QQuickStackView::Operation operationTransition(QQuickStackView::Operation operation, QQuickStackView::Operation transition) +{ + if (operation == QQuickStackView::Immediate || operation == QQuickStackView::Transition) + return transition; + return operation; +} + +QQuickStackTransition QQuickStackTransition::popExit(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view) +{ + return exitTransition(operationTransition(operation, QQuickStackView::PopTransition), element, view); +} + +QQuickStackTransition QQuickStackTransition::popEnter(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view) +{ + return enterTransition(operationTransition(operation, QQuickStackView::PopTransition), element, view); +} + +QQuickStackTransition QQuickStackTransition::pushExit(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view) +{ + return exitTransition(operationTransition(operation, QQuickStackView::PushTransition), element, view); +} + +QQuickStackTransition QQuickStackTransition::pushEnter(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view) +{ + return enterTransition(operationTransition(operation, QQuickStackView::PushTransition), element, view); +} + +QQuickStackTransition QQuickStackTransition::replaceExit(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view) +{ + return exitTransition(operationTransition(operation, QQuickStackView::ReplaceTransition), element, view); +} + +QQuickStackTransition QQuickStackTransition::replaceEnter(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view) +{ + return enterTransition(operationTransition(operation, QQuickStackView::ReplaceTransition), element, view); +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickstacktransition_p_p.h b/src/quicktemplates2/qquickstacktransition_p_p.h new file mode 100644 index 0000000000..3a08fb4a68 --- /dev/null +++ b/src/quicktemplates2/qquickstacktransition_p_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSTACKTRANSITION_P_P_H +#define QQUICKSTACKTRANSITION_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickstackview_p.h> +#include <QtQuick/private/qquickitemviewtransition_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickStackElement; + +struct QQuickStackTransition +{ + static QQuickStackTransition popExit(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view); + static QQuickStackTransition popEnter(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view); + + static QQuickStackTransition pushExit(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view); + static QQuickStackTransition pushEnter(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view); + + static QQuickStackTransition replaceExit(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view); + static QQuickStackTransition replaceEnter(QQuickStackView::Operation operation, QQuickStackElement *element, QQuickStackView *view); + + bool target = false; + QQuickStackView::Status status = QQuickStackView::Inactive; + QQuickItemViewTransitioner::TransitionType type = QQuickItemViewTransitioner::NoTransition; + QRectF viewBounds; + QQuickStackElement *element = nullptr; + QQuickTransition *transition = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKSTACKTRANSITION_P_P_H diff --git a/src/quicktemplates2/qquickstackview.cpp b/src/quicktemplates2/qquickstackview.cpp new file mode 100644 index 0000000000..016b45a7bc --- /dev/null +++ b/src/quicktemplates2/qquickstackview.cpp @@ -0,0 +1,1404 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickstackview_p.h" +#include "qquickstackview_p_p.h" +#include "qquickstackelement_p_p.h" +#include "qquickstacktransition_p_p.h" + +#include <QtCore/qscopedvaluerollback.h> +#include <QtQml/qjsvalue.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlinfo.h> + +#include <private/qv4qobjectwrapper_p.h> +#include <private/qqmlengine_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype StackView + \inherits Control +//! \instantiates QQuickStackView + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-navigation + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Provides a stack-based navigation model. + + \image qtquickcontrols2-stackview-wireframe.png + + StackView can be used with a set of inter-linked information pages. For + example, an email application with separate views to list the latest emails, + view a specific email, and list/view the attachments. The email list view + is pushed onto the stack as users open an email, and popped out as they + choose to go back. + + The following snippet demonstrates a simple use case, where the \c mainView + is pushed onto and popped out of the stack on relevant button click: + + \qml + ApplicationWindow { + title: qsTr("Hello World") + width: 640 + height: 480 + visible: true + + StackView { + id: stack + initialItem: mainView + anchors.fill: parent + } + + Component { + id: mainView + + Row { + spacing: 10 + + Button { + text: "Push" + onClicked: stack.push(mainView) + } + Button { + text: "Pop" + enabled: stack.depth > 1 + onClicked: stack.pop() + + } + Text { + text: stack.depth + } + } + } + } + \endqml + + \section1 Using StackView in an Application + + Using StackView in an application is as simple as adding it as a child to + a Window. The stack is usually anchored to the edges of the window, except + at the top or bottom where it might be anchored to a status bar, or some + other similar UI component. The stack can then be used by invoking its + navigation methods. The first item to show in the StackView is the one + that was assigned to \l initialItem, or the topmost item if \l initialItem + is not set. + + \section1 Basic Navigation + + StackView supports three primary navigation operations: push(), pop(), and + replace(). These correspond to classic stack operations where "push" adds + an item to the top of a stack, "pop" removes the top item from the + stack, and "replace" is like a pop followed by a push, which replaces the + topmost item with the new item. The topmost item in the stack + corresponds to the one that is \l{StackView::currentItem}{currently} + visible on screen. Logically, "push" navigates forward or deeper into the + application UI, "pop" navigates backward, and "replace" replaces the + \l currentItem. + + \section2 Pushing Items + + In the following animation, three \l Label controls are pushed onto a + stack view with the \l push() function: + + \image qtquickcontrols2-stackview-push.gif + + The stack now contains the following items: \c [A, B, C]. + + \note When the stack is empty, a push() operation will not have a + transition animation because there is nothing to transition from (typically + on application start-up). + + \section2 Popping Items + + Continuing on from the example above, the topmost item on the stack is + removed with a call to \l pop(): + + \image qtquickcontrols2-stackview-pop.gif + + The stack now contains the following items: \c [A, B]. + + \note A pop() operation on a stack with depth 1 or 0 does nothing. In such + cases, the stack can be emptied using the \l clear() method. + + \section3 Unwinding Items via Pop + + Sometimes, it is necessary to go back more than a single step in the stack. + For example, to return to a "main" item or some kind of section item in the + application. In such cases, it is possible to specify an item as a + parameter for pop(). This is called an "unwind" operation, where the stack + unwinds till the specified item. If the item is not found, stack unwinds + until it is left with one item, which becomes the \l currentItem. To + explicitly unwind to the bottom of the stack, it is recommended to use + \l{pop()}{pop(null)}, although any non-existent item will do. + + In the following animation, we unwind the stack to the first item by + calling \c pop(null): + + \image qtquickcontrols2-stackview-unwind.gif + + The stack now contains a single item: \c [A]. + + \section2 Replacing Items + + In the following animation, we \l replace the topmost item with \c D: + + \image qtquickcontrols2-stackview-replace.gif + + The stack now contains the following items: \c [A, B, D]. + + \section1 Deep Linking + + \e{Deep linking} means launching an application into a particular state. For + example, a newspaper application could be launched into showing a + particular article, bypassing the topmost item. In terms of StackView, deep linking means the ability to modify + the state of the stack, so much so that it is possible to push a set of + items to the top of the stack, or to completely reset the stack to a given + state. + + The API for deep linking in StackView is the same as for basic navigation. + Pushing an array instead of a single item adds all the items in that array + to the stack. The transition animation, however, is applied only for the + last item in the array. The normal semantics of push() apply for deep + linking, that is, it adds whatever is pushed onto the stack. + + \note Only the last item of the array is loaded. The rest of the items are + loaded only when needed, either on subsequent calls to pop or on request to + get an item using get(). + + This gives us the following result, given the stack [A, B, C]: + + \list + \li \l{push()}{push([D, E, F])} => [A, B, C, D, E, F] - "push" transition + animation between C and F + \li \l{replace()}{replace([D, E, F])} => [A, B, D, E, F] - "replace" + transition animation between C and F + \li \l{clear()} followed by \l{push()}{push([D, E, F])} => [D, E, F] - no + transition animation for pushing items as the stack was empty. + \endlist + + \section1 Finding Items + + An Item for which the application does not have a reference can be found + by calling find(). The method needs a callback function, which is invoked + for each item in the stack (starting at the top) until a match is found. + If the callback returns \c true, find() stops and returns the matching + item, otherwise \c null is returned. + + The code below searches the stack for an item named "order_id" and unwinds + to that item. + + \badcode + stackView.pop(stackView.find(function(item) { + return item.name == "order_id"; + })); + \endcode + + You can also get to an item in the stack using \l {get()}{get(index)}. + + \badcode + previousItem = stackView.get(myItem.StackView.index - 1)); + \endcode + + \section1 Transitions + + For each push or pop operation, different transition animations are applied + to entering and exiting items. These animations define how the entering item + should animate in, and the exiting item should animate out. The animations + can be customized by assigning different \l{Transition}s for the + \l pushEnter, \l pushExit, \l popEnter, \l popExit, \l replaceEnter, and + \l replaceExit properties of StackView. + + \note The transition animations affect each others' transitional behavior. + Customizing the animation for one and leaving the other may give unexpected + results. + + The following snippet defines a simple fade transition for push and pop + operations: + + \qml + StackView { + id: stackview + anchors.fill: parent + + pushEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + pushExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + popEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + popExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + } + \endqml + + \note Using anchors on the items added to a StackView is not supported. + Typically push, pop, and replace transitions animate the position, + which is not possible when anchors are applied. Notice that this + only applies to the root of the item. Using anchors for its children + works as expected. + + \section1 Item Ownership + + StackView only takes ownership of items that it creates itself. This means + that any item pushed onto a StackView will never be destroyed by the + StackView; only items that StackView creates from \l {Component}{Components} + or \l [QML] {url}{URLs} are destroyed by the StackView. To illustrate this, + the messages in the example below will only be printed when the StackView + is destroyed, not when the items are popped off the stack: + + \qml + Component { + id: itemComponent + + Item { + Component.onDestruction: print("Destroying second item") + } + } + + StackView { + initialItem: Item { + Component.onDestruction: print("Destroying initial item") + } + + Component.onCompleted: push(itemComponent.createObject(window)) + } + \endqml + + However, both of the items created from the URL and Component in the + following example will be destroyed by the StackView when they are popped + off of it: + + \qml + Component { + id: itemComponent + + Item { + Component.onDestruction: print("Destroying second item") + } + } + + StackView { + initialItem: "Item1.qml" + + Component.onCompleted: push(itemComponent) + } + \endqml + + \section1 Size + + StackView does not inherit an implicit size from items that are pushed onto + it. This means that using it as the \l {Popup::}{contentItem} of a + \l Dialog, for example, will not work as expected: + + \code + Dialog { + StackView { + initialItem: Rectangle { + width: 200 + height: 200 + color: "salmon" + } + } + } + \endcode + + There are several ways to ensure that StackView has a size in this + situation: + + \list + \li Set \l[QtQuick]{Item::}{implicitWidth} and + \l[QtQuick]{Item::}{implicitHeight} on the StackView itself. + \li Set \l[QtQuick]{Item::}{implicitWidth} and + \l[QtQuick]{Item::}{implicitHeight} on the \l Rectangle. + \li Set \l {Popup::}{contentWidth} and \l {Popup::}{contentHeight} on + the Dialog. + \li Give the Dialog a size. + \endlist + + \sa {Customizing StackView}, {Navigating with StackView}, {Navigation Controls}, + {Container Controls}, {Focus Management in Qt Quick Controls} +*/ + +QQuickStackView::QQuickStackView(QQuickItem *parent) + : QQuickControl(*(new QQuickStackViewPrivate), parent) +{ + setFlag(ItemIsFocusScope); +} + +QQuickStackView::~QQuickStackView() +{ + Q_D(QQuickStackView); + if (d->transitioner) { + d->transitioner->setChangeListener(nullptr); + delete d->transitioner; + } + qDeleteAll(d->removing); + qDeleteAll(d->removed); + qDeleteAll(d->elements); +} + +QQuickStackViewAttached *QQuickStackView::qmlAttachedProperties(QObject *object) +{ + return new QQuickStackViewAttached(object); +} + +/*! + \qmlproperty bool QtQuick.Controls::StackView::busy + \readonly + This property holds whether a transition is running. +*/ +bool QQuickStackView::isBusy() const +{ + Q_D(const QQuickStackView); + return d->busy; +} + +/*! + \qmlproperty int QtQuick.Controls::StackView::depth + \readonly + This property holds the number of items currently pushed onto the stack. +*/ +int QQuickStackView::depth() const +{ + Q_D(const QQuickStackView); + return d->elements.count(); +} + +/*! + \qmlproperty Item QtQuick.Controls::StackView::currentItem + \readonly + This property holds the current top-most item in the stack. +*/ +QQuickItem *QQuickStackView::currentItem() const +{ + Q_D(const QQuickStackView); + return d->currentItem; +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::get(index, behavior) + + Returns the item at position \a index in the stack, or \c null if the index + is out of bounds. + + Supported \a behavior values: + \value StackView.DontLoad The item is not forced to load (and \c null is returned if not yet loaded). + \value StackView.ForceLoad The item is forced to load. +*/ +QQuickItem *QQuickStackView::get(int index, LoadBehavior behavior) +{ + Q_D(QQuickStackView); + QQuickStackElement *element = d->elements.value(index); + if (element) { + if (behavior == ForceLoad) + element->load(this); + return element->item; + } + return nullptr; +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::find(callback, behavior) + + Search for a specific item inside the stack. The \a callback function is called + for each item in the stack (with the item and index as arguments) until the callback + function returns \c true. The return value is the item found. For example: + + \code + stackView.find(function(item, index) { + return item.isTheOne + }) + \endcode + + Supported \a behavior values: + \value StackView.DontLoad Unloaded items are skipped (the callback function is not called for them). + \value StackView.ForceLoad Unloaded items are forced to load. +*/ +QQuickItem *QQuickStackView::find(const QJSValue &callback, LoadBehavior behavior) +{ + Q_D(QQuickStackView); + QJSValue func(callback); + QQmlEngine *engine = qmlEngine(this); + if (!engine || !func.isCallable()) // TODO: warning? + return nullptr; + + for (int i = d->elements.count() - 1; i >= 0; --i) { + QQuickStackElement *element = d->elements.at(i); + if (behavior == ForceLoad) + element->load(this); + if (element->item) { + QJSValue rv = func.call(QJSValueList() << engine->newQObject(element->item) << i); + if (rv.toBool()) + return element->item; + } + } + + return nullptr; +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::push(item, properties, operation) + + Pushes an \a item onto the stack using an optional \a operation, and + optionally applies a set of \a properties on the item. The item can be + an \l Item, \l Component, or a \l [QML] url. Returns the item that became + current. + + StackView creates an instance automatically if the pushed item is a \l Component, + or a \l [QML] url, and the instance will be destroyed when it is popped + off the stack. See \l {Item Ownership} for more information. + + The optional \a properties argument specifies a map of initial + property values for the pushed item. For dynamically created items, these values + are applied before the creation is finalized. This is more efficient than setting + property values after creation, particularly where large sets of property values + are defined, and also allows property bindings to be set up (using \l{Qt::binding} + {Qt.binding()}) before the item is created. + + Pushing a single item: + \code + stackView.push(rect) + + // or with properties: + stackView.push(rect, {"color": "red"}) + \endcode + + Multiple items can be pushed at the same time either by passing them as + additional arguments, or as an array. The last item becomes the current + item. Each item can be followed by a set of properties to apply. + + Passing a variable amount of arguments: + \code + stackView.push(rect1, rect2, rect3) + + // or with properties: + stackView.push(rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"}) + \endcode + + Pushing an array of items: + \code + stackView.push([rect1, rect2, rect3]) + + // or with properties: + stackView.push([rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"}]) + \endcode + + An \a operation can be optionally specified as the last argument. Supported + operations: + + \value StackView.Immediate An immediate operation without transitions. + \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1). + \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1). + \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1). + + If no operation is provided, \c PushTransition will be used. + + \note Items that already exist in the stack are not pushed. + + \sa initialItem, {Pushing Items} +*/ +void QQuickStackView::push(QQmlV4Function *args) +{ + Q_D(QQuickStackView); + const QString operationName = QStringLiteral("push"); + if (d->modifyingElements) { + d->warnOfInterruption(operationName); + return; + } + + QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); + QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); + if (args->length() <= 0) { + d->warn(QStringLiteral("missing arguments")); + args->setReturnValue(QV4::Encode::null()); + return; + } + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + Operation operation = d->elements.isEmpty() ? Immediate : PushTransition; + QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + + QStringList errors; + QList<QQuickStackElement *> elements = d->parseElements(0, args, &errors); + // Remove any items that are already in the stack, as they can't be in two places at once. + for (int i = 0; i < elements.size(); ) { + QQuickStackElement *element = elements.at(i); + if (element->item && d->findElement(element->item)) + elements.removeAt(i); + else + ++i; + } + + if (!errors.isEmpty() || elements.isEmpty()) { + if (!errors.isEmpty()) { + for (const QString &error : qAsConst(errors)) + d->warn(error); + } else { + d->warn(QStringLiteral("nothing to push")); + } + args->setReturnValue(QV4::Encode::null()); + return; + } + + QQuickStackElement *exit = nullptr; + if (!d->elements.isEmpty()) + exit = d->elements.top(); + + int oldDepth = d->elements.count(); + if (d->pushElements(elements)) { + d->depthChange(d->elements.count(), oldDepth); + QQuickStackElement *enter = d->elements.top(); + d->startTransition(QQuickStackTransition::pushEnter(operation, enter, this), + QQuickStackTransition::pushExit(operation, exit, this), + operation == Immediate); + d->setCurrentItem(enter); + } + + if (d->currentItem) { + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, d->currentItem)); + args->setReturnValue(rv->asReturnedValue()); + } else { + args->setReturnValue(QV4::Encode::null()); + } +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::pop(item, operation) + + Pops one or more items off the stack. Returns the last item removed from the stack. + + If the \a item argument is specified, all items down to (but not + including) \a item will be popped. If \a item is \c null, all + items down to (but not including) the first item is popped. + If not specified, only the current item is popped. + + \note A pop() operation on a stack with depth 1 or 0 does nothing. In such + cases, the stack can be emptied using the \l clear() method. + + \include qquickstackview.qdocinc pop-ownership + + An \a operation can be optionally specified as the last argument. Supported + operations: + + \value StackView.Immediate An immediate operation without transitions. + \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1). + \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1). + \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1). + + If no operation is provided, \c PopTransition will be used. + + Examples: + \code + stackView.pop() + stackView.pop(someItem, StackView.Immediate) + stackView.pop(StackView.Immediate) + stackView.pop(null) + \endcode + + \sa clear(), {Popping Items}, {Unwinding Items via Pop} +*/ +void QQuickStackView::pop(QQmlV4Function *args) +{ + Q_D(QQuickStackView); + const QString operationName = QStringLiteral("pop"); + if (d->modifyingElements) { + d->warnOfInterruption(operationName); + args->setReturnValue(QV4::Encode::null()); + return; + } + + QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); + QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); + int argc = args->length(); + if (d->elements.count() <= 1 || argc > 2) { + if (argc > 2) + d->warn(QStringLiteral("too many arguments")); + args->setReturnValue(QV4::Encode::null()); + return; + } + + int oldDepth = d->elements.count(); + QQuickStackElement *exit = d->elements.pop(); + QQuickStackElement *enter = d->elements.top(); + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + if (argc > 0) { + QV4::ScopedValue value(scope, (*args)[0]); + if (value->isNull()) { + enter = d->elements.value(0); + } else if (const QV4::QObjectWrapper *o = value->as<QV4::QObjectWrapper>()) { + QQuickItem *item = qobject_cast<QQuickItem *>(o->object()); + enter = d->findElement(item); + if (!enter) { + if (item != d->currentItem) + d->warn(QStringLiteral("unknown argument: ") + value->toQString()); // TODO: safe? + args->setReturnValue(QV4::Encode::null()); + d->elements.push(exit); // restore + return; + } + } + } + + Operation operation = PopTransition; + if (argc > 0) { + QV4::ScopedValue lastArg(scope, (*args)[argc - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + } + + QQuickItem *previousItem = nullptr; + + if (d->popElements(enter)) { + if (exit) { + exit->removal = true; + d->removing.insert(exit); + previousItem = exit->item; + } + d->depthChange(d->elements.count(), oldDepth); + d->startTransition(QQuickStackTransition::popExit(operation, exit, this), + QQuickStackTransition::popEnter(operation, enter, this), + operation == Immediate); + d->setCurrentItem(enter); + } + + if (previousItem) { + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, previousItem)); + args->setReturnValue(rv->asReturnedValue()); + } else { + args->setReturnValue(QV4::Encode::null()); + } +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::replace(target, item, properties, operation) + + Replaces one or more items on the stack with the specified \a item and + optional \a operation, and optionally applies a set of \a properties on the + item. The item can be an \l Item, \l Component, or a \l [QML] url. + Returns the item that became current. + + \include qquickstackview.qdocinc pop-ownership + + If the \a target argument is specified, all items down to the \a target + item will be replaced. If \a target is \c null, all items in the stack + will be replaced. If not specified, only the top item will be replaced. + + StackView creates an instance automatically if the replacing item is a \l Component, + or a \l [QML] url. The optional \a properties argument specifies a map of initial + property values for the replacing item. For dynamically created items, these values + are applied before the creation is finalized. This is more efficient than setting + property values after creation, particularly where large sets of property values + are defined, and also allows property bindings to be set up (using \l{Qt::binding} + {Qt.binding()}) before the item is created. + + Replace the top item: + \code + stackView.replace(rect) + + // or with properties: + stackView.replace(rect, {"color": "red"}) + \endcode + + Multiple items can be replaced at the same time either by passing them as + additional arguments, or as an array. Each item can be followed by a set + of properties to apply. + + Passing a variable amount of arguments: + \code + stackView.replace(rect1, rect2, rect3) + + // or with properties: + stackView.replace(rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"}) + \endcode + + Replacing an array of items: + \code + stackView.replace([rect1, rect2, rect3]) + + // or with properties: + stackView.replace([rect1, {"color": "red"}, rect2, {"color": "green"}, rect3, {"color": "blue"}]) + \endcode + + An \a operation can be optionally specified as the last argument. Supported + operations: + + \value StackView.Immediate An immediate operation without transitions. + \value StackView.PushTransition An operation with push transitions (since QtQuick.Controls 2.1). + \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1). + \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1). + + If no operation is provided, \c ReplaceTransition will be used. + + The following example illustrates the use of push and pop transitions with replace(). + + \code + StackView { + id: stackView + + initialItem: Component { + id: page + + Page { + Row { + spacing: 20 + anchors.centerIn: parent + + Button { + text: "<" + onClicked: stackView.replace(page, StackView.PopTransition) + } + Button { + text: ">" + onClicked: stackView.replace(page, StackView.PushTransition) + } + } + } + } + } + \endcode + + \sa push(), {Replacing Items} +*/ +void QQuickStackView::replace(QQmlV4Function *args) +{ + Q_D(QQuickStackView); + const QString operationName = QStringLiteral("replace"); + if (d->modifyingElements) { + d->warnOfInterruption(operationName); + args->setReturnValue(QV4::Encode::null()); + return; + } + + QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); + QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); + if (args->length() <= 0) { + d->warn(QStringLiteral("missing arguments")); + args->setReturnValue(QV4::Encode::null()); + return; + } + + QV4::ExecutionEngine *v4 = args->v4engine(); + QV4::Scope scope(v4); + + Operation operation = d->elements.isEmpty() ? Immediate : ReplaceTransition; + QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); + if (lastArg->isInt32()) + operation = static_cast<Operation>(lastArg->toInt32()); + + QQuickStackElement *target = nullptr; + QV4::ScopedValue firstArg(scope, (*args)[0]); + if (firstArg->isNull()) + target = d->elements.value(0); + else if (!firstArg->isInt32()) + target = d->findElement(firstArg); + + QStringList errors; + QList<QQuickStackElement *> elements = d->parseElements(target ? 1 : 0, args, &errors); + if (!errors.isEmpty() || elements.isEmpty()) { + if (!errors.isEmpty()) { + for (const QString &error : qAsConst(errors)) + d->warn(error); + } else { + d->warn(QStringLiteral("nothing to push")); + } + args->setReturnValue(QV4::Encode::null()); + return; + } + + int oldDepth = d->elements.count(); + QQuickStackElement* exit = nullptr; + if (!d->elements.isEmpty()) + exit = d->elements.pop(); + + if (exit != target ? d->replaceElements(target, elements) : d->pushElements(elements)) { + d->depthChange(d->elements.count(), oldDepth); + if (exit) { + exit->removal = true; + d->removing.insert(exit); + } + QQuickStackElement *enter = d->elements.top(); + d->startTransition(QQuickStackTransition::replaceExit(operation, exit, this), + QQuickStackTransition::replaceEnter(operation, enter, this), + operation == Immediate); + d->setCurrentItem(enter); + } + + if (d->currentItem) { + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(v4, d->currentItem)); + args->setReturnValue(rv->asReturnedValue()); + } else { + args->setReturnValue(QV4::Encode::null()); + } +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::StackView::empty + \readonly + + This property holds whether the stack is empty. + + \sa depth +*/ +bool QQuickStackView::isEmpty() const +{ + Q_D(const QQuickStackView); + return d->elements.isEmpty(); +} + +/*! + \qmlmethod void QtQuick.Controls::StackView::clear(transition) + + Removes all items from the stack. + + \include qquickstackview.qdocinc pop-ownership + + Since QtQuick.Controls 2.3, a \a transition can be optionally specified. Supported transitions: + + \value StackView.Immediate Clear the stack immediately without any transition (default). + \value StackView.PushTransition Clear the stack with a push transition. + \value StackView.ReplaceTransition Clear the stack with a replace transition. + \value StackView.PopTransition Clear the stack with a pop transition. +*/ +void QQuickStackView::clear(Operation operation) +{ + Q_D(QQuickStackView); + if (d->elements.isEmpty()) + return; + + const QString operationName = QStringLiteral("clear"); + if (d->modifyingElements) { + d->warnOfInterruption(operationName); + return; + } + + QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); + QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); + if (operation != Immediate) { + QQuickStackElement *exit = d->elements.pop(); + exit->removal = true; + d->removing.insert(exit); + d->startTransition(QQuickStackTransition::popExit(operation, exit, this), + QQuickStackTransition::popEnter(operation, nullptr, this), false); + } + + int oldDepth = d->elements.count(); + d->setCurrentItem(nullptr); + qDeleteAll(d->elements); + d->elements.clear(); + d->depthChange(0, oldDepth); +} + +/*! + \qmlproperty var QtQuick.Controls::StackView::initialItem + + This property holds the initial item that should be shown when the StackView + is created. The initial item can be an \l Item, \l Component, or a \l [QML] url. + Specifying an initial item is equivalent to: + \code + Component.onCompleted: stackView.push(myInitialItem) + \endcode + + \sa push() +*/ +QJSValue QQuickStackView::initialItem() const +{ + Q_D(const QQuickStackView); + return d->initialItem; +} + +void QQuickStackView::setInitialItem(const QJSValue &item) +{ + Q_D(QQuickStackView); + d->initialItem = item; +} + +/*! + \qmlproperty Transition QtQuick.Controls::StackView::popEnter + + This property holds the transition that is applied to the item that + enters the stack when another item is popped off of it. + + \sa {Customizing StackView} +*/ +QQuickTransition *QQuickStackView::popEnter() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->removeDisplacedTransition; + return nullptr; +} + +void QQuickStackView::setPopEnter(QQuickTransition *enter) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->removeDisplacedTransition == enter) + return; + + d->transitioner->removeDisplacedTransition = enter; + emit popEnterChanged(); +} + +/*! + \qmlproperty Transition QtQuick.Controls::StackView::popExit + + This property holds the transition that is applied to the item that + exits the stack when the item is popped off of it. + + \sa {Customizing StackView} +*/ +QQuickTransition *QQuickStackView::popExit() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->removeTransition; + return nullptr; +} + +void QQuickStackView::setPopExit(QQuickTransition *exit) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->removeTransition == exit) + return; + + d->transitioner->removeTransition = exit; + emit popExitChanged(); +} + +/*! + \qmlproperty Transition QtQuick.Controls::StackView::pushEnter + + This property holds the transition that is applied to the item that + enters the stack when the item is pushed onto it. + + \sa {Customizing StackView} +*/ +QQuickTransition *QQuickStackView::pushEnter() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->addTransition; + return nullptr; +} + +void QQuickStackView::setPushEnter(QQuickTransition *enter) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->addTransition == enter) + return; + + d->transitioner->addTransition = enter; + emit pushEnterChanged(); +} + +/*! + \qmlproperty Transition QtQuick.Controls::StackView::pushExit + + This property holds the transition that is applied to the item that + exits the stack when another item is pushed onto it. + + \sa {Customizing StackView} +*/ +QQuickTransition *QQuickStackView::pushExit() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->addDisplacedTransition; + return nullptr; +} + +void QQuickStackView::setPushExit(QQuickTransition *exit) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->addDisplacedTransition == exit) + return; + + d->transitioner->addDisplacedTransition = exit; + emit pushExitChanged(); +} + +/*! + \qmlproperty Transition QtQuick.Controls::StackView::replaceEnter + + This property holds the transition that is applied to the item that + enters the stack when another item is replaced by it. + + \sa {Customizing StackView} +*/ +QQuickTransition *QQuickStackView::replaceEnter() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->moveTransition; + return nullptr; +} + +void QQuickStackView::setReplaceEnter(QQuickTransition *enter) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->moveTransition == enter) + return; + + d->transitioner->moveTransition = enter; + emit replaceEnterChanged(); +} + +/*! + \qmlproperty Transition QtQuick.Controls::StackView::replaceExit + + This property holds the transition that is applied to the item that + exits the stack when it is replaced by another item. + + \sa {Customizing StackView} +*/ +QQuickTransition *QQuickStackView::replaceExit() const +{ + Q_D(const QQuickStackView); + if (d->transitioner) + return d->transitioner->moveDisplacedTransition; + return nullptr; +} + +void QQuickStackView::setReplaceExit(QQuickTransition *exit) +{ + Q_D(QQuickStackView); + d->ensureTransitioner(); + if (d->transitioner->moveDisplacedTransition == exit) + return; + + d->transitioner->moveDisplacedTransition = exit; + emit replaceExitChanged(); +} + +void QQuickStackView::componentComplete() +{ + QQuickControl::componentComplete(); + + Q_D(QQuickStackView); + QScopedValueRollback<QString> operationNameRollback(d->operation, QStringLiteral("initialItem")); + QQuickStackElement *element = nullptr; + QString error; + int oldDepth = d->elements.count(); + if (QObject *o = d->initialItem.toQObject()) + element = QQuickStackElement::fromObject(o, this, &error); + else if (d->initialItem.isString()) + element = QQuickStackElement::fromString(d->initialItem.toString(), this, &error); + if (!error.isEmpty()) { + d->warn(error); + delete element; + } else if (d->pushElement(element)) { + d->depthChange(d->elements.count(), oldDepth); + d->setCurrentItem(element); + element->setStatus(QQuickStackView::Active); + } +} + +void QQuickStackView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QQuickControl::geometryChange(newGeometry, oldGeometry); + + Q_D(QQuickStackView); + for (QQuickStackElement *element : qAsConst(d->elements)) { + if (element->item) { + if (!element->widthValid) + element->item->setWidth(newGeometry.width()); + if (!element->heightValid) + element->item->setHeight(newGeometry.height()); + } + } +} + +bool QQuickStackView::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + // in order to block accidental user interaction while busy/transitioning, + // StackView filters out childrens' mouse events. therefore we block all + // press events. however, since push() may be called from signal handlers + // such as onPressed or onDoubleClicked, we must let the current mouse + // grabber item receive the respective mouse release event to avoid + // breaking its state (QTBUG-50305). + if (event->type() == QEvent::MouseButtonPress) + return true; + if (event->type() == QEvent::UngrabMouse) + return false; + QQuickWindow *window = item->window(); + return window && !window->mouseGrabberItem(); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickStackView::touchEvent(QTouchEvent *event) +{ + event->ignore(); // QTBUG-65084 +} +#endif + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickStackView::accessibleRole() const +{ + return QAccessible::LayeredPane; +} +#endif + +void QQuickStackViewAttachedPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent) +{ + Q_Q(QQuickStackViewAttached); + int oldIndex = element ? element->index : -1; + QQuickStackView *oldView = element ? element->view : nullptr; + QQuickStackView::Status oldStatus = element ? element->status : QQuickStackView::Inactive; + + QQuickStackView *newView = qobject_cast<QQuickStackView *>(parent); + element = newView ? QQuickStackViewPrivate::get(newView)->findElement(item) : nullptr; + + int newIndex = element ? element->index : -1; + QQuickStackView::Status newStatus = element ? element->status : QQuickStackView::Inactive; + + if (oldIndex != newIndex) + emit q->indexChanged(); + if (oldView != newView) + emit q->viewChanged(); + if (oldStatus != newStatus) + emit q->statusChanged(); +} + +QQuickStackViewAttached::QQuickStackViewAttached(QObject *parent) + : QObject(*(new QQuickStackViewAttachedPrivate), parent) +{ + Q_D(QQuickStackViewAttached); + QQuickItem *item = qobject_cast<QQuickItem *>(parent); + if (item) { + connect(item, &QQuickItem::visibleChanged, this, &QQuickStackViewAttached::visibleChanged); + QQuickItemPrivate::get(item)->addItemChangeListener(d, QQuickItemPrivate::Parent); + d->itemParentChanged(item, item->parentItem()); + } else if (parent) { + qmlWarning(parent) << "StackView must be attached to an Item"; + } +} + +QQuickStackViewAttached::~QQuickStackViewAttached() +{ + Q_D(QQuickStackViewAttached); + QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent()); + if (parentItem) + QQuickItemPrivate::get(parentItem)->removeItemChangeListener(d, QQuickItemPrivate::Parent); +} + +/*! + \qmlattachedproperty int QtQuick.Controls::StackView::index + \readonly + + This attached property holds the stack index of the item it's + attached to, or \c -1 if the item is not in a stack. +*/ +int QQuickStackViewAttached::index() const +{ + Q_D(const QQuickStackViewAttached); + return d->element ? d->element->index : -1; +} + +/*! + \qmlattachedproperty StackView QtQuick.Controls::StackView::view + \readonly + + This attached property holds the stack view of the item it's + attached to, or \c null if the item is not in a stack. +*/ +QQuickStackView *QQuickStackViewAttached::view() const +{ + Q_D(const QQuickStackViewAttached); + return d->element ? d->element->view : nullptr; +} + +/*! + \qmlattachedproperty enumeration QtQuick.Controls::StackView::status + \readonly + + This attached property holds the stack status of the item it's + attached to, or \c StackView.Inactive if the item is not in a stack. + + Available values: + \value StackView.Inactive The item is inactive (or not in a stack). + \value StackView.Deactivating The item is being deactivated (popped off). + \value StackView.Activating The item is being activated (becoming the current item). + \value StackView.Active The item is active, that is, the current item. +*/ +QQuickStackView::Status QQuickStackViewAttached::status() const +{ + Q_D(const QQuickStackViewAttached); + return d->element ? d->element->status : QQuickStackView::Inactive; +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlattachedproperty bool QtQuick.Controls::StackView::visible + + This attached property holds the visibility of the item it's attached to. + The value follows the value of \l Item::visible. + + By default, StackView shows incoming items when the enter transition begins, + and hides outgoing items when the exit transition ends. Setting this property + explicitly allows the default behavior to be overridden, making it possible + to keep items that are below the top-most item visible. + + \note The default transitions of most styles slide outgoing items outside the + view, and may also animate their opacity. In order to keep a full stack + of items visible, consider customizing the \l transitions so that the + items underneath can be seen. + + \image qtquickcontrols2-stackview-visible.png + + \snippet qtquickcontrols2-stackview-visible.qml 1 +*/ +bool QQuickStackViewAttached::isVisible() const +{ + const QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent()); + return parentItem && parentItem->isVisible(); +} + +void QQuickStackViewAttached::setVisible(bool visible) +{ + Q_D(QQuickStackViewAttached); + d->explicitVisible = true; + QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent()); + if (parentItem) + parentItem->setVisible(visible); +} + +void QQuickStackViewAttached::resetVisible() +{ + Q_D(QQuickStackViewAttached); + d->explicitVisible = false; + if (!d->element || !d->element->view) + return; + + QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent()); + if (parentItem) + parentItem->setVisible(parentItem == d->element->view->currentItem()); +} + +/*! + \qmlattachedsignal QtQuick.Controls::StackView::activated() + \since QtQuick.Controls 2.1 (Qt 5.8) + + This attached signal is emitted when the item it's attached to is activated in the stack. + + \sa status +*/ + +/*! + \qmlattachedsignal QtQuick.Controls::StackView::deactivated() + \since QtQuick.Controls 2.1 (Qt 5.8) + + This attached signal is emitted when the item it's attached to is deactivated in the stack. + + \sa status +*/ + +/*! + \qmlattachedsignal QtQuick.Controls::StackView::activating() + \since QtQuick.Controls 2.1 (Qt 5.8) + + This attached signal is emitted when the item it's attached to is in the process of being + activated in the stack. + + \sa status +*/ + +/*! + \qmlattachedsignal QtQuick.Controls::StackView::deactivating() + \since QtQuick.Controls 2.1 (Qt 5.8) + + This attached signal is emitted when the item it's attached to is in the process of being + dectivated in the stack. + + \sa status +*/ + +/*! + \qmlattachedsignal QtQuick.Controls::StackView::removed() + \since QtQuick.Controls 2.1 (Qt 5.8) + + This attached signal is emitted when the item it's attached to has been + removed from the stack. It can be used to safely destroy an Item that was + pushed onto the stack, for example: + + \code + Item { + StackView.onRemoved: destroy() // Will be destroyed sometime after this call. + } + \endcode + + \sa status +*/ + +QT_END_NAMESPACE + +#include "moc_qquickstackview_p.cpp" diff --git a/src/quicktemplates2/qquickstackview_p.cpp b/src/quicktemplates2/qquickstackview_p.cpp new file mode 100644 index 0000000000..402efa75d1 --- /dev/null +++ b/src/quicktemplates2/qquickstackview_p.cpp @@ -0,0 +1,360 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickstackview_p_p.h" +#include "qquickstackelement_p_p.h" +#include "qquickstacktransition_p_p.h" + +#include <QtQml/qqmlinfo.h> +#include <QtQml/qqmllist.h> +#include <QtQml/private/qv4qmlcontext_p.h> +#include <QtQml/private/qv4qobjectwrapper_p.h> +#include <QtQml/private/qv4variantobject_p.h> +#include <QtQml/private/qv4urlobject_p.h> +#include <QtQuick/private/qquickanimation_p.h> +#include <QtQuick/private/qquicktransition_p.h> + +QT_BEGIN_NAMESPACE + +void QQuickStackViewPrivate::warn(const QString &error) +{ + Q_Q(QQuickStackView); + if (operation.isEmpty()) + qmlWarning(q) << error; + else + qmlWarning(q) << operation << ": " << error; +} + +void QQuickStackViewPrivate::warnOfInterruption(const QString &attemptedOperation) +{ + Q_Q(QQuickStackView); + qmlWarning(q) << "cannot " << attemptedOperation << " while already in the process of completing a " << operation; +} + +void QQuickStackViewPrivate::setCurrentItem(QQuickStackElement *element) +{ + Q_Q(QQuickStackView); + QQuickItem *item = element ? element->item : nullptr; + if (currentItem == item) + return; + + currentItem = item; + if (element) + element->setVisible(true); + if (item) + item->setFocus(true); + emit q->currentItemChanged(); +} + +static bool initProperties(QQuickStackElement *element, const QV4::Value &props, QQmlV4Function *args) +{ + if (props.isObject()) { + const QV4::QObjectWrapper *wrapper = props.as<QV4::QObjectWrapper>(); + if (!wrapper) { + QV4::ExecutionEngine *v4 = args->v4engine(); + element->properties.set(v4, props); + element->qmlCallingContext.set(v4, v4->qmlContext()); + return true; + } + } + return false; +} + +QList<QQuickStackElement *> QQuickStackViewPrivate::parseElements(int from, QQmlV4Function *args, QStringList *errors) +{ + QV4::ExecutionEngine *v4 = args->v4engine(); + auto context = v4->callingQmlContext(); + QV4::Scope scope(v4); + + QList<QQuickStackElement *> elements; + + int argc = args->length(); + for (int i = from; i < argc; ++i) { + QV4::ScopedValue arg(scope, (*args)[i]); + if (QV4::ArrayObject *array = arg->as<QV4::ArrayObject>()) { + const uint len = uint(array->getLength()); + for (uint j = 0; j < len; ++j) { + QString error; + QV4::ScopedValue value(scope, array->get(j)); + QQuickStackElement *element = createElement(value, context, &error); + if (element) { + if (j < len - 1) { + QV4::ScopedValue props(scope, array->get(j + 1)); + if (initProperties(element, props, args)) + ++j; + } + elements += element; + } else if (!error.isEmpty()) { + *errors += error; + } + } + } else { + QString error; + QQuickStackElement *element = createElement(arg, context, &error); + if (element) { + if (i < argc - 1) { + QV4::ScopedValue props(scope, (*args)[i + 1]); + if (initProperties(element, props, args)) + ++i; + } + elements += element; + } else if (!error.isEmpty()) { + *errors += error; + } + } + } + return elements; +} + +QQuickStackElement *QQuickStackViewPrivate::findElement(QQuickItem *item) const +{ + if (item) { + for (QQuickStackElement *e : qAsConst(elements)) { + if (e->item == item) + return e; + } + } + return nullptr; +} + +QQuickStackElement *QQuickStackViewPrivate::findElement(const QV4::Value &value) const +{ + if (const QV4::QObjectWrapper *o = value.as<QV4::QObjectWrapper>()) + return findElement(qobject_cast<QQuickItem *>(o->object())); + return nullptr; +} + +static QUrl resolvedUrl(const QUrl &url, const QQmlRefPointer<QQmlContextData> &context) +{ + if (url.isRelative()) + return context->resolvedUrl(url).toString(); + return url; +} + +static QString resolvedUrl(const QString &str, const QQmlRefPointer<QQmlContextData> &context) +{ + QUrl url(str); + if (url.isRelative()) + return context->resolvedUrl(url).toString(); + return str; +} + +QQuickStackElement *QQuickStackViewPrivate::createElement(const QV4::Value &value, const QQmlRefPointer<QQmlContextData> &context, QString *error) +{ + Q_Q(QQuickStackView); + if (const QV4::String *s = value.as<QV4::String>()) + return QQuickStackElement::fromString(resolvedUrl(s->toQString(), context), q, error); + if (const QV4::QObjectWrapper *o = value.as<QV4::QObjectWrapper>()) + return QQuickStackElement::fromObject(o->object(), q, error); + if (const QV4::UrlObject *u = value.as<QV4::UrlObject>()) + return QQuickStackElement::fromString(resolvedUrl(u->href(), context), q, error); + + if (const QV4::Object *v = value.as<QV4::Object>()) { + const QVariant data = v->engine()->toVariant(value, QMetaType::fromType<QUrl>()); + if (data.typeId() == QMetaType::QUrl) { + return QQuickStackElement::fromString(resolvedUrl(data.toUrl(), context).toString(), q, + error); + } + } + + return nullptr; +} + +bool QQuickStackViewPrivate::pushElements(const QList<QQuickStackElement *> &elems) +{ + Q_Q(QQuickStackView); + if (!elems.isEmpty()) { + for (QQuickStackElement *e : elems) { + e->setIndex(elements.count()); + elements += e; + } + return elements.top()->load(q); + } + return false; +} + +bool QQuickStackViewPrivate::pushElement(QQuickStackElement *element) +{ + if (element) + return pushElements(QList<QQuickStackElement *>() << element); + return false; +} + +bool QQuickStackViewPrivate::popElements(QQuickStackElement *element) +{ + Q_Q(QQuickStackView); + while (elements.count() > 1 && elements.top() != element) { + delete elements.pop(); + if (!element) + break; + } + return elements.top()->load(q); +} + +bool QQuickStackViewPrivate::replaceElements(QQuickStackElement *target, const QList<QQuickStackElement *> &elems) +{ + if (target) { + while (!elements.isEmpty()) { + QQuickStackElement* top = elements.pop(); + delete top; + if (top == target) + break; + } + } + return pushElements(elems); +} + +void QQuickStackViewPrivate::ensureTransitioner() +{ + if (!transitioner) { + transitioner = new QQuickItemViewTransitioner; + transitioner->setChangeListener(this); + } +} + +void QQuickStackViewPrivate::startTransition(const QQuickStackTransition &first, const QQuickStackTransition &second, bool immediate) +{ + if (first.element) + first.element->transitionNextReposition(transitioner, first.type, first.target); + if (second.element) + second.element->transitionNextReposition(transitioner, second.type, second.target); + + if (first.element) { + // Let the check for immediate happen after prepareTransition() is + // called, because we need the prepared transition in both branches. + // Same for the second element. + if (!first.element->item || !first.element->prepareTransition(transitioner, first.viewBounds) || immediate) + completeTransition(first.element, first.transition, first.status); + else + first.element->startTransition(transitioner, first.status); + } + if (second.element) { + if (!second.element->item || !second.element->prepareTransition(transitioner, second.viewBounds) || immediate) + completeTransition(second.element, second.transition, second.status); + else + second.element->startTransition(transitioner, second.status); + } + + if (transitioner) { + setBusy(!transitioner->runningJobs.isEmpty()); + transitioner->resetTargetLists(); + } +} + +void QQuickStackViewPrivate::completeTransition(QQuickStackElement *element, QQuickTransition *transition, QQuickStackView::Status status) +{ + element->setStatus(status); + if (transition) { + if (element->prepared) { + // Here we force reading all the animations, even if the desired + // transition type is StackView.Immediate. After that we force + // all the animations to complete immediately, without waiting for + // the animation timer. + // This allows us to correctly restore all the properties affected + // by the push/pop animations. + ACTION_IF_DELETED(element, element->completeTransition(transition), return); + } else if (element->item) { + // At least try to move the item to its desired place. This, + // however, is only a partly correct solution, because a lot more + // properties can be affected by the transition + element->item->setPosition(element->nextTransitionTo); + } + } + viewItemTransitionFinished(element); +} + +void QQuickStackViewPrivate::viewItemTransitionFinished(QQuickItemViewTransitionableItem *transitionable) +{ + QQuickStackElement *element = static_cast<QQuickStackElement *>(transitionable); + if (element->status == QQuickStackView::Activating) { + element->setStatus(QQuickStackView::Active); + } else if (element->status == QQuickStackView::Deactivating) { + element->setStatus(QQuickStackView::Inactive); + QQuickStackElement *existingElement = element->item ? findElement(element->item) : nullptr; + // If a different element with the same item is found, + // do not call setVisible(false) since it needs to be visible. + if (!existingElement || element == existingElement) + element->setVisible(false); + if (element->removal || element->isPendingRemoval()) + removed += element; + } + + if (transitioner && transitioner->runningJobs.isEmpty()) { + // ~QQuickStackElement() emits QQuickStackViewAttached::removed(), which may be used + // to modify the stack. Set the status first and make a copy of the destroyable stack + // elements to exclude any modifications that may happen during qDeleteAll(). (QTBUG-62153) + setBusy(false); + QList<QQuickStackElement*> removedElements = removed; + removed.clear(); + + for (QQuickStackElement *removedElement : qAsConst(removedElements)) { + // If an element with the same item is found in the active stack list, + // forget about the item so that we don't hide it. + if (removedElement->item && findElement(removedElement->item)) { + QQuickItemPrivate::get(removedElement->item)->removeItemChangeListener(removedElement, QQuickItemPrivate::Destroyed); + removedElement->item = nullptr; + } + } + + qDeleteAll(removedElements); + } + + removing.remove(element); +} + +void QQuickStackViewPrivate::setBusy(bool b) +{ + Q_Q(QQuickStackView); + if (busy == b) + return; + + busy = b; + q->setFiltersChildMouseEvents(busy); + emit q->busyChanged(); +} + +void QQuickStackViewPrivate::depthChange(int newDepth, int oldDepth) +{ + Q_Q(QQuickStackView); + if (newDepth == oldDepth) + return; + + emit q->depthChanged(); + if (newDepth == 0 || oldDepth == 0) + emit q->emptyChanged(); +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickstackview_p.h b/src/quicktemplates2/qquickstackview_p.h new file mode 100644 index 0000000000..f4cb545833 --- /dev/null +++ b/src/quicktemplates2/qquickstackview_p.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSTACKVIEW_P_H +#define QQUICKSTACKVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlV4Function; +class QQuickTransition; +class QQuickStackElement; +class QQuickStackViewPrivate; +class QQuickStackViewAttached; +class QQuickStackViewAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickStackView : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(bool busy READ isBusy NOTIFY busyChanged FINAL) + Q_PROPERTY(int depth READ depth NOTIFY depthChanged FINAL) + Q_PROPERTY(QQuickItem *currentItem READ currentItem NOTIFY currentItemChanged FINAL) + Q_PROPERTY(QJSValue initialItem READ initialItem WRITE setInitialItem FINAL) + Q_PROPERTY(QQuickTransition *popEnter READ popEnter WRITE setPopEnter NOTIFY popEnterChanged FINAL) + Q_PROPERTY(QQuickTransition *popExit READ popExit WRITE setPopExit NOTIFY popExitChanged FINAL) + Q_PROPERTY(QQuickTransition *pushEnter READ pushEnter WRITE setPushEnter NOTIFY pushEnterChanged FINAL) + Q_PROPERTY(QQuickTransition *pushExit READ pushExit WRITE setPushExit NOTIFY pushExitChanged FINAL) + Q_PROPERTY(QQuickTransition *replaceEnter READ replaceEnter WRITE setReplaceEnter NOTIFY replaceEnterChanged FINAL) + Q_PROPERTY(QQuickTransition *replaceExit READ replaceExit WRITE setReplaceExit NOTIFY replaceExitChanged FINAL) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged FINAL REVISION(2, 3)) + QML_NAMED_ELEMENT(StackView) + QML_ATTACHED(QQuickStackViewAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickStackView(QQuickItem *parent = nullptr); + ~QQuickStackView(); + + static QQuickStackViewAttached *qmlAttachedProperties(QObject *object); + + bool isBusy() const; + int depth() const; + QQuickItem *currentItem() const; + + enum Status { + Inactive = 0, + Deactivating = 1, + Activating = 2, + Active = 3 + }; + Q_ENUM(Status) + + QJSValue initialItem() const; + void setInitialItem(const QJSValue &item); + + QQuickTransition *popEnter() const; + void setPopEnter(QQuickTransition *enter); + + QQuickTransition *popExit() const; + void setPopExit(QQuickTransition *exit); + + QQuickTransition *pushEnter() const; + void setPushEnter(QQuickTransition *enter); + + QQuickTransition *pushExit() const; + void setPushExit(QQuickTransition *exit); + + QQuickTransition *replaceEnter() const; + void setReplaceEnter(QQuickTransition *enter); + + QQuickTransition *replaceExit() const; + void setReplaceExit(QQuickTransition *exit); + + enum LoadBehavior { + DontLoad, + ForceLoad + }; + Q_ENUM(LoadBehavior) + + Q_INVOKABLE QQuickItem *get(int index, LoadBehavior behavior = DontLoad); + Q_INVOKABLE QQuickItem *find(const QJSValue &callback, LoadBehavior behavior = DontLoad); + + enum Operation { + Transition = -1, // ### Deprecated in Qt 6; remove in Qt 7. + Immediate = 0, + PushTransition = 1, + ReplaceTransition = 2, + PopTransition = 3, + }; + Q_ENUM(Operation) + + Q_INVOKABLE void push(QQmlV4Function *args); + Q_INVOKABLE void pop(QQmlV4Function *args); + Q_INVOKABLE void replace(QQmlV4Function *args); + + // 2.3 (Qt 5.10) + bool isEmpty() const; + +public Q_SLOTS: + void clear(Operation operation = Immediate); + +Q_SIGNALS: + void busyChanged(); + void depthChanged(); + void currentItemChanged(); + void popEnterChanged(); + void popExitChanged(); + void pushEnterChanged(); + void pushExitChanged(); + void replaceEnterChanged(); + void replaceExitChanged(); + // 2.3 (Qt 5.10) + Q_REVISION(2, 3) void emptyChanged(); + +protected: + void componentComplete() override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + bool childMouseEventFilter(QQuickItem *, QEvent *) override; + +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickStackView) + Q_DECLARE_PRIVATE(QQuickStackView) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickStackViewAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL) + Q_PROPERTY(QQuickStackView *view READ view NOTIFY viewChanged FINAL) + Q_PROPERTY(QQuickStackView::Status status READ status NOTIFY statusChanged FINAL) + // 2.2 (Qt 5.9) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible RESET resetVisible NOTIFY visibleChanged FINAL) // REVISION(2, 2) + +public: + explicit QQuickStackViewAttached(QObject *parent = nullptr); + ~QQuickStackViewAttached(); + + int index() const; + QQuickStackView *view() const; + QQuickStackView::Status status() const; + + // 2.2 (Qt 5.9) + bool isVisible() const; + void setVisible(bool visible); + void resetVisible(); + +Q_SIGNALS: + void indexChanged(); + void viewChanged(); + void statusChanged(); + // 2.1 (Qt 5.8) + /*Q_REVISION(2, 1)*/ void activated(); + /*Q_REVISION(2, 1)*/ void activating(); + /*Q_REVISION(2, 1)*/ void deactivated(); + /*Q_REVISION(2, 1)*/ void deactivating(); + /*Q_REVISION(2, 1)*/ void removed(); + // 2.2 (Qt 5.9) + /*Q_REVISION(2, 2)*/ void visibleChanged(); + +private: + Q_DISABLE_COPY(QQuickStackViewAttached) + Q_DECLARE_PRIVATE(QQuickStackViewAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickStackView) +QML_DECLARE_TYPEINFO(QQuickStackView, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKSTACKVIEW_P_H diff --git a/src/quicktemplates2/qquickstackview_p_p.h b/src/quicktemplates2/qquickstackview_p_p.h new file mode 100644 index 0000000000..0c64019db8 --- /dev/null +++ b/src/quicktemplates2/qquickstackview_p_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSTACKVIEW_P_P_H +#define QQUICKSTACKVIEW_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickstackview_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#include <QtQuick/private/qquickitemviewtransition_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQml/private/qv4value_p.h> +#include <QtQml/private/qqmlcontextdata_p.h> +#include <QtCore/qset.h> +#include <QtCore/qstack.h> + +QT_BEGIN_NAMESPACE + +class QQuickStackElement; +struct QQuickStackTransition; + +class QQuickStackViewPrivate : public QQuickControlPrivate, public QQuickItemViewTransitionChangeListener +{ + Q_DECLARE_PUBLIC(QQuickStackView) + +public: + static QQuickStackViewPrivate *get(QQuickStackView *view) + { + return view->d_func(); + } + + void warn(const QString &error); + void warnOfInterruption(const QString &attemptedOperation); + + void setCurrentItem(QQuickStackElement *element); + + QList<QQuickStackElement *> parseElements(int from, QQmlV4Function *args, QStringList *errors); + QQuickStackElement *findElement(QQuickItem *item) const; + QQuickStackElement *findElement(const QV4::Value &value) const; + QQuickStackElement *createElement(const QV4::Value &value, const QQmlRefPointer<QQmlContextData> &context, QString *error); + bool pushElements(const QList<QQuickStackElement *> &elements); + bool pushElement(QQuickStackElement *element); + bool popElements(QQuickStackElement *element); + bool replaceElements(QQuickStackElement *element, const QList<QQuickStackElement *> &elements); + + void ensureTransitioner(); + void startTransition(const QQuickStackTransition &first, const QQuickStackTransition &second, bool immediate); + void completeTransition(QQuickStackElement *element, QQuickTransition *transition, QQuickStackView::Status status); + + void viewItemTransitionFinished(QQuickItemViewTransitionableItem *item) override; + void setBusy(bool busy); + void depthChange(int newDepth, int oldDepth); + + bool busy = false; + bool modifyingElements = false; + QString operation; + QJSValue initialItem; + QQuickItem *currentItem = nullptr; + QSet<QQuickStackElement*> removing; + QList<QQuickStackElement*> removed; + QStack<QQuickStackElement *> elements; + QQuickItemViewTransitioner *transitioner = nullptr; +}; + +class QQuickStackViewAttachedPrivate : public QObjectPrivate, public QQuickItemChangeListener +{ + Q_DECLARE_PUBLIC(QQuickStackViewAttached) + +public: + static QQuickStackViewAttachedPrivate *get(QQuickStackViewAttached *attached) + { + return attached->d_func(); + } + + void itemParentChanged(QQuickItem *item, QQuickItem *parent) override; + + bool explicitVisible = false; + QQuickStackElement *element = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKSTACKVIEW_P_P_H diff --git a/src/quicktemplates2/qquickswipe_p.h b/src/quicktemplates2/qquickswipe_p.h new file mode 100644 index 0000000000..768d5638fd --- /dev/null +++ b/src/quicktemplates2/qquickswipe_p.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSWIPE_P_H +#define QQUICKSWIPE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtQuickTemplates2/private/qquickswipedelegate_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlComponent; +class QQuickItem; +class QQuickTransition; +class QQuickSwipePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipe : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(bool complete READ isComplete NOTIFY completeChanged FINAL) + Q_PROPERTY(QQmlComponent *left READ left WRITE setLeft NOTIFY leftChanged FINAL) + Q_PROPERTY(QQmlComponent *behind READ behind WRITE setBehind NOTIFY behindChanged FINAL) + Q_PROPERTY(QQmlComponent *right READ right WRITE setRight NOTIFY rightChanged FINAL) + Q_PROPERTY(QQuickItem *leftItem READ leftItem NOTIFY leftItemChanged FINAL) + Q_PROPERTY(QQuickItem *behindItem READ behindItem NOTIFY behindItemChanged FINAL) + Q_PROPERTY(QQuickItem *rightItem READ rightItem NOTIFY rightItemChanged FINAL) + // 2.2 (Qt 5.9) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged FINAL) // REVISION(2, 2) + Q_PROPERTY(QQuickTransition *transition READ transition WRITE setTransition NOTIFY transitionChanged FINAL) // REVISION(2, 2) + QML_ANONYMOUS + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickSwipe(QQuickSwipeDelegate *control); + + qreal position() const; + void setPosition(qreal position); + + bool isComplete() const; + void setComplete(bool complete); + + QQmlComponent *left() const; + void setLeft(QQmlComponent *left); + + QQmlComponent *behind() const; + void setBehind(QQmlComponent *behind); + + QQmlComponent *right() const; + void setRight(QQmlComponent *right); + + QQuickItem *leftItem() const; + void setLeftItem(QQuickItem *item); + + QQuickItem *behindItem() const; + void setBehindItem(QQuickItem *item); + + QQuickItem *rightItem() const; + void setRightItem(QQuickItem *item); + + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) Q_INVOKABLE void close(); + + // 2.2 (Qt 5.9) + bool isEnabled() const; + void setEnabled(bool enabled); + + QQuickTransition *transition() const; + void setTransition(QQuickTransition *transition); + + Q_REVISION(2, 2) Q_INVOKABLE void open(QQuickSwipeDelegate::Side side); + +Q_SIGNALS: + void positionChanged(); + void completeChanged(); + void leftChanged(); + void behindChanged(); + void rightChanged(); + void leftItemChanged(); + void behindItemChanged(); + void rightItemChanged(); + // 2.1 (Qt 5.8) + /*Q_REVISION(2, 1)*/ void completed(); + // 2.2 (Qt 5.9) + /*Q_REVISION(2, 2)*/ void opened(); + /*Q_REVISION(2, 2)*/ void closed(); + /*Q_REVISION(2, 2)*/ void enabledChanged(); + /*Q_REVISION(2, 2)*/ void transitionChanged(); + +private: + Q_DISABLE_COPY(QQuickSwipe) + Q_DECLARE_PRIVATE(QQuickSwipe) +}; + +QT_END_NAMESPACE + +#endif // QQUICKSWIPE_P_H diff --git a/src/quicktemplates2/qquickswipedelegate.cpp b/src/quicktemplates2/qquickswipedelegate.cpp new file mode 100644 index 0000000000..242dbc6654 --- /dev/null +++ b/src/quicktemplates2/qquickswipedelegate.cpp @@ -0,0 +1,1527 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickswipedelegate_p.h" +#include "qquickswipedelegate_p_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickitemdelegate_p_p.h" +#include "qquickvelocitycalculator_p_p.h" + +#include <QtGui/qstylehints.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/private/qeventpoint_p.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickanimation_p.h> +#include <QtQuick/private/qquicktransition_p.h> +#include <QtQuick/private/qquicktransitionmanager_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype SwipeDelegate + \inherits ItemDelegate +//! \instantiates QQuickSwipeDelegate + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-delegates + \brief Swipable item delegate. + + SwipeDelegate presents a view item that can be swiped left or right to + expose more options or information. It is used as a delegate in views such + as \l ListView. + + In the following example, SwipeDelegate is used in a \l ListView to allow + items to be removed from it by swiping to the left: + + \snippet qtquickcontrols2-swipedelegate.qml 1 + + SwipeDelegate inherits its API from \l ItemDelegate, which is inherited + from AbstractButton. For instance, you can set \l {AbstractButton::text}{text}, + and react to \l {AbstractButton::clicked}{clicks} using the AbstractButton + API. + + Information regarding the progress of a swipe, as well as the components + that should be shown upon swiping, are both available through the + \l {SwipeDelegate::}{swipe} grouped property object. For example, + \c swipe.position holds the position of the + swipe within the range \c -1.0 to \c 1.0. The \c swipe.left + property determines which item will be displayed when the control is swiped + to the right, and vice versa for \c swipe.right. The positioning of these + components is left to applications to decide. For example, without specifying + any position for \c swipe.left or \c swipe.right, the following will + occur: + + \image qtquickcontrols2-swipedelegate.gif + + If \c swipe.left and \c swipe.right are anchored to the left and + right of the \l {Control::}{background} item (respectively), they'll behave like this: + + \image qtquickcontrols2-swipedelegate-leading-trailing.gif + + When using \c swipe.left and \c swipe.right, the control cannot be + swiped past the left and right edges. To achieve this type of "wrapping" + behavior, set \c swipe.behind instead. This will result in the same + item being shown regardless of which direction the control is swiped. For + example, in the image below, we set \c swipe.behind and then swipe the + control repeatedly in both directions: + + \image qtquickcontrols2-swipedelegate-behind.gif + + \sa {Customizing SwipeDelegate}, {Delegate Controls}, {Qt Quick Controls 2 - Swipe to Remove}{Swipe to Remove Example} +*/ + +namespace { + typedef QQuickSwipeDelegateAttached Attached; + + Attached *attachedObject(QQuickItem *item) { + return qobject_cast<Attached*>(qmlAttachedPropertiesObject<QQuickSwipeDelegate>(item, false)); + } + + enum PositionAnimation { + DontAnimatePosition, + AnimatePosition + }; +} + +class QQuickSwipeTransitionManager : public QQuickTransitionManager +{ +public: + QQuickSwipeTransitionManager(QQuickSwipe *swipe); + + void transition(QQuickTransition *transition, qreal position); + +protected: + void finished() override; + +private: + QQuickSwipe *m_swipe = nullptr; +}; + +class QQuickSwipePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickSwipe) + +public: + QQuickSwipePrivate(QQuickSwipeDelegate *control) : control(control) { } + + static QQuickSwipePrivate *get(QQuickSwipe *swipe); + + QQuickItem *createDelegateItem(QQmlComponent *component); + QQuickItem *showRelevantItemForPosition(qreal position); + QQuickItem *createRelevantItemForDistance(qreal distance); + void reposition(PositionAnimation animationPolicy); + void createLeftItem(); + void createBehindItem(); + void createRightItem(); + void createAndShowLeftItem(); + void createAndShowBehindItem(); + void createAndShowRightItem(); + + void warnAboutMixingDelegates(); + void warnAboutSettingDelegatesWhileVisible(); + + bool hasDelegates() const; + + bool isTransitioning() const; + void beginTransition(qreal position); + void finishTransition(); + + QQuickSwipeDelegate *control = nullptr; + // Same range as position, but is set before press events so that we can + // keep track of which direction the user must swipe when using left and right delegates. + qreal positionBeforePress = 0; + qreal position = 0; + // A "less strict" version of complete that is true if complete was true + // before the last press event. + bool wasComplete = false; + bool complete = false; + bool enabled = true; + bool waitForTransition = false; + QQuickVelocityCalculator velocityCalculator; + QQmlComponent *left = nullptr; + QQmlComponent *behind = nullptr; + QQmlComponent *right = nullptr; + QQuickItem *leftItem = nullptr; + QQuickItem *behindItem = nullptr; + QQuickItem *rightItem = nullptr; + QQuickTransition *transition = nullptr; + QScopedPointer<QQuickSwipeTransitionManager> transitionManager; +}; + +QQuickSwipeTransitionManager::QQuickSwipeTransitionManager(QQuickSwipe *swipe) + : m_swipe(swipe) +{ +} + +void QQuickSwipeTransitionManager::transition(QQuickTransition *transition, qreal position) +{ + qmlExecuteDeferred(transition); + + QQmlProperty defaultTarget(m_swipe, QLatin1String("position")); + QQmlListProperty<QQuickAbstractAnimation> animations = transition->animations(); + const int count = animations.count(&animations); + for (int i = 0; i < count; ++i) { + QQuickAbstractAnimation *anim = animations.at(&animations, i); + anim->setDefaultTarget(defaultTarget); + } + + QList<QQuickStateAction> actions; + actions << QQuickStateAction(m_swipe, QLatin1String("position"), position); + QQuickTransitionManager::transition(actions, transition, m_swipe); +} + +void QQuickSwipeTransitionManager::finished() +{ + QQuickSwipePrivate::get(m_swipe)->finishTransition(); +} + +QQuickSwipePrivate *QQuickSwipePrivate::get(QQuickSwipe *swipe) +{ + return swipe->d_func(); +} + +QQuickItem *QQuickSwipePrivate::createDelegateItem(QQmlComponent *component) +{ + // If we don't use the correct context, it won't be possible to refer to + // the control's id from within the delegates. + QQmlContext *creationContext = component->creationContext(); + // The component might not have been created in QML, in which case + // the creation context will be null and we have to create it ourselves. + if (!creationContext) + creationContext = qmlContext(control); + QQmlContext *context = new QQmlContext(creationContext, control); + context->setContextObject(control); + QQuickItem *item = qobject_cast<QQuickItem*>(component->beginCreate(context)); + if (item) { + item->setParentItem(control); + component->completeCreate(); + } + return item; +} + +QQuickItem *QQuickSwipePrivate::showRelevantItemForPosition(qreal position) +{ + if (qFuzzyIsNull(position)) + return nullptr; + + if (behind) { + createAndShowBehindItem(); + return behindItem; + } + + if (right && position < 0.0) { + createAndShowRightItem(); + return rightItem; + } + + if (left && position > 0.0) { + createAndShowLeftItem(); + return leftItem; + } + + return nullptr; +} + +QQuickItem *QQuickSwipePrivate::createRelevantItemForDistance(qreal distance) +{ + if (qFuzzyIsNull(distance)) + return nullptr; + + if (behind) { + createBehindItem(); + return behindItem; + } + + // a) If the position before the press was 0.0, we know that *any* movement + // whose distance is negative will result in the right item being shown and + // vice versa. + // b) Once the control has been exposed (that is, swiped to the left or right, + // and hence the position is either -1.0 or 1.0), we must use the width of the + // relevant item to determine if the distance is larger than that item, + // in order to know whether or not to display it. + // c) If the control has been exposed, and the swipe is larger than the width + // of the relevant item from which the swipe started from, we must show the + // item on the other side (if any). + + if (right) { + if ((distance < 0.0 && positionBeforePress == 0.0) /* a) */ + || (rightItem && positionBeforePress == -1.0 && distance < rightItem->width()) /* b) */ + || (leftItem && positionBeforePress == 1.0 && qAbs(distance) > leftItem->width())) /* c) */ { + createRightItem(); + return rightItem; + } + } + + if (left) { + if ((distance > 0.0 && positionBeforePress == 0.0) /* a) */ + || (leftItem && positionBeforePress == 1.0 && qAbs(distance) < leftItem->width()) /* b) */ + || (rightItem && positionBeforePress == -1.0 && qAbs(distance) > rightItem->width())) /* c) */ { + createLeftItem(); + return leftItem; + } + } + + return nullptr; +} + +void QQuickSwipePrivate::reposition(PositionAnimation animationPolicy) +{ + QQuickItem *relevantItem = showRelevantItemForPosition(position); + const qreal relevantWidth = relevantItem ? relevantItem->width() : 0.0; + const qreal contentItemX = position * relevantWidth + control->leftPadding(); + + // "Behavior on x" relies on the property system to know when it should update, + // so we can prevent it from animating by setting the x position directly. + if (animationPolicy == AnimatePosition) { + if (QQuickItem *contentItem = control->contentItem()) + contentItem->setProperty("x", contentItemX); + if (QQuickItem *background = control->background()) + background->setProperty("x", position * relevantWidth); + } else { + if (QQuickItem *contentItem = control->contentItem()) + contentItem->setX(contentItemX); + if (QQuickItem *background = control->background()) + background->setX(position * relevantWidth); + } +} + +void QQuickSwipePrivate::createLeftItem() +{ + if (!leftItem) { + Q_Q(QQuickSwipe); + q->setLeftItem(createDelegateItem(left)); + if (!leftItem) + qmlWarning(control) << "Failed to create left item:" << left->errors(); + } +} + +void QQuickSwipePrivate::createBehindItem() +{ + if (!behindItem) { + Q_Q(QQuickSwipe); + q->setBehindItem(createDelegateItem(behind)); + if (!behindItem) + qmlWarning(control) << "Failed to create behind item:" << behind->errors(); + } +} + +void QQuickSwipePrivate::createRightItem() +{ + if (!rightItem) { + Q_Q(QQuickSwipe); + q->setRightItem(createDelegateItem(right)); + if (!rightItem) + qmlWarning(control) << "Failed to create right item:" << right->errors(); + } +} + +void QQuickSwipePrivate::createAndShowLeftItem() +{ + createLeftItem(); + + if (leftItem) + leftItem->setVisible(true); + + if (rightItem) + rightItem->setVisible(false); +} + +void QQuickSwipePrivate::createAndShowBehindItem() +{ + createBehindItem(); + + if (behindItem) + behindItem->setVisible(true); +} + +void QQuickSwipePrivate::createAndShowRightItem() +{ + createRightItem(); + + // This item may have already existed but was hidden. + if (rightItem) + rightItem->setVisible(true); + + // The left item isn't visible when the right item is visible, so save rendering effort by hiding it. + if (leftItem) + leftItem->setVisible(false); +} + +void QQuickSwipePrivate::warnAboutMixingDelegates() +{ + qmlWarning(control) << "cannot set both behind and left/right properties"; +} + +void QQuickSwipePrivate::warnAboutSettingDelegatesWhileVisible() +{ + qmlWarning(control) << "left/right/behind properties may only be set when swipe.position is 0"; +} + +bool QQuickSwipePrivate::hasDelegates() const +{ + return left || right || behind; +} + +bool QQuickSwipePrivate::isTransitioning() const +{ + return transitionManager && transitionManager->isRunning(); +} + +void QQuickSwipePrivate::beginTransition(qreal newPosition) +{ + if (waitForTransition) + return; + Q_Q(QQuickSwipe); + if (!transition) { + q->setPosition(newPosition); + finishTransition(); + return; + } + + if (!transitionManager) + transitionManager.reset(new QQuickSwipeTransitionManager(q)); + + transitionManager->transition(transition, newPosition); +} + +void QQuickSwipePrivate::finishTransition() +{ + Q_Q(QQuickSwipe); + waitForTransition = false; + q->setComplete(qFuzzyCompare(qAbs(position), qreal(1.0))); + if (complete) { + emit q->opened(); + } else { + if (qFuzzyIsNull(position)) + wasComplete = false; + emit q->closed(); + } +} + +QQuickSwipe::QQuickSwipe(QQuickSwipeDelegate *control) + : QObject(*(new QQuickSwipePrivate(control))) +{ +} + +QQmlComponent *QQuickSwipe::left() const +{ + Q_D(const QQuickSwipe); + return d->left; +} + +void QQuickSwipe::setLeft(QQmlComponent *left) +{ + Q_D(QQuickSwipe); + if (left == d->left) + return; + + if (d->behind) { + d->warnAboutMixingDelegates(); + return; + } + + if (!qFuzzyIsNull(d->position)) { + d->warnAboutSettingDelegatesWhileVisible(); + return; + } + + d->left = left; + + if (!d->left) { + delete d->leftItem; + d->leftItem = nullptr; + } + + d->control->setFiltersChildMouseEvents(d->hasDelegates()); + + emit leftChanged(); +} + +QQmlComponent *QQuickSwipe::behind() const +{ + Q_D(const QQuickSwipe); + return d->behind; +} + +void QQuickSwipe::setBehind(QQmlComponent *behind) +{ + Q_D(QQuickSwipe); + if (behind == d->behind) + return; + + if (d->left || d->right) { + d->warnAboutMixingDelegates(); + return; + } + + if (!qFuzzyIsNull(d->position)) { + d->warnAboutSettingDelegatesWhileVisible(); + return; + } + + d->behind = behind; + + if (!d->behind) { + delete d->behindItem; + d->behindItem = nullptr; + } + + d->control->setFiltersChildMouseEvents(d->hasDelegates()); + + emit behindChanged(); +} + +QQmlComponent *QQuickSwipe::right() const +{ + Q_D(const QQuickSwipe); + return d->right; +} + +void QQuickSwipe::setRight(QQmlComponent *right) +{ + Q_D(QQuickSwipe); + if (right == d->right) + return; + + if (d->behind) { + d->warnAboutMixingDelegates(); + return; + } + + if (!qFuzzyIsNull(d->position)) { + d->warnAboutSettingDelegatesWhileVisible(); + return; + } + + d->right = right; + + if (!d->right) { + delete d->rightItem; + d->rightItem = nullptr; + } + + d->control->setFiltersChildMouseEvents(d->hasDelegates()); + + emit rightChanged(); +} + +QQuickItem *QQuickSwipe::leftItem() const +{ + Q_D(const QQuickSwipe); + return d->leftItem; +} + +void QQuickSwipe::setLeftItem(QQuickItem *item) +{ + Q_D(QQuickSwipe); + if (item == d->leftItem) + return; + + delete d->leftItem; + d->leftItem = item; + + if (d->leftItem) { + d->leftItem->setParentItem(d->control); + + if (qFuzzyIsNull(d->leftItem->z())) + d->leftItem->setZ(-2); + } + + emit leftItemChanged(); +} + +QQuickItem *QQuickSwipe::behindItem() const +{ + Q_D(const QQuickSwipe); + return d->behindItem; +} + +void QQuickSwipe::setBehindItem(QQuickItem *item) +{ + Q_D(QQuickSwipe); + if (item == d->behindItem) + return; + + delete d->behindItem; + d->behindItem = item; + + if (d->behindItem) { + d->behindItem->setParentItem(d->control); + + if (qFuzzyIsNull(d->behindItem->z())) + d->behindItem->setZ(-2); + } + + emit behindItemChanged(); +} + +QQuickItem *QQuickSwipe::rightItem() const +{ + Q_D(const QQuickSwipe); + return d->rightItem; +} + +void QQuickSwipe::setRightItem(QQuickItem *item) +{ + Q_D(QQuickSwipe); + if (item == d->rightItem) + return; + + delete d->rightItem; + d->rightItem = item; + + if (d->rightItem) { + d->rightItem->setParentItem(d->control); + + if (qFuzzyIsNull(d->rightItem->z())) + d->rightItem->setZ(-2); + } + + emit rightItemChanged(); +} + +qreal QQuickSwipe::position() const +{ + Q_D(const QQuickSwipe); + return d->position; +} + +void QQuickSwipe::setPosition(qreal position) +{ + Q_D(QQuickSwipe); + const qreal adjustedPosition = qBound<qreal>(-1.0, position, 1.0); + if (adjustedPosition == d->position) + return; + + d->position = adjustedPosition; + d->reposition(AnimatePosition); + emit positionChanged(); +} + +bool QQuickSwipe::isComplete() const +{ + Q_D(const QQuickSwipe); + return d->complete; +} + +void QQuickSwipe::setComplete(bool complete) +{ + Q_D(QQuickSwipe); + if (complete == d->complete) + return; + + d->complete = complete; + emit completeChanged(); + if (d->complete) + emit completed(); +} + +bool QQuickSwipe::isEnabled() const +{ + Q_D(const QQuickSwipe); + return d->enabled; +} + +void QQuickSwipe::setEnabled(bool enabled) +{ + Q_D(QQuickSwipe); + if (enabled == d->enabled) + return; + + d->enabled = enabled; + emit enabledChanged(); +} + +QQuickTransition *QQuickSwipe::transition() const +{ + Q_D(const QQuickSwipe); + return d->transition; +} + +void QQuickSwipe::setTransition(QQuickTransition *transition) +{ + Q_D(QQuickSwipe); + if (transition == d->transition) + return; + + d->transition = transition; + emit transitionChanged(); +} + +void QQuickSwipe::open(QQuickSwipeDelegate::Side side) +{ + Q_D(QQuickSwipe); + if (qFuzzyCompare(qAbs(d->position), qreal(1.0))) + return; + + if ((side != QQuickSwipeDelegate::Left && side != QQuickSwipeDelegate::Right) + || (!d->left && !d->behind && side == QQuickSwipeDelegate::Left) + || (!d->right && !d->behind && side == QQuickSwipeDelegate::Right)) + return; + + d->beginTransition(side); + d->wasComplete = true; + d->velocityCalculator.reset(); + d->positionBeforePress = d->position; +} + +void QQuickSwipe::close() +{ + Q_D(QQuickSwipe); + if (qFuzzyIsNull(d->position)) + return; + + if (d->control->isPressed()) { + // We don't support closing when we're pressed; release() or clicked() should be used instead. + return; + } + + d->beginTransition(0.0); + d->waitForTransition = true; + d->wasComplete = false; + d->positionBeforePress = 0.0; + d->velocityCalculator.reset(); +} + +QQuickSwipeDelegatePrivate::QQuickSwipeDelegatePrivate(QQuickSwipeDelegate *control) + : swipe(control) +{ +} + +void QQuickSwipeDelegatePrivate::resizeBackground() +{ + if (!background) + return; + + resizingBackground = true; + + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + const bool extraAllocated = extra.isAllocated(); + // Don't check for or set the x here since it will just be overwritten by reposition(). + if (((!p->widthValid() || !extraAllocated || !extra->hasBackgroundWidth)) + || (extraAllocated && (extra->hasLeftInset || extra->hasRightInset))) { + background->setWidth(width - getLeftInset() - getRightInset()); + } + if (((!p->heightValid() || !extraAllocated || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y())) + || (extraAllocated && (extra->hasTopInset || extra->hasBottomInset))) { + background->setY(getTopInset()); + background->setHeight(height - getTopInset() - getBottomInset()); + } + + resizingBackground = false; +} + +bool QQuickSwipeDelegatePrivate::handleMousePressEvent(QQuickItem *item, QMouseEvent *event) +{ + Q_Q(QQuickSwipeDelegate); + const auto posInItem = item->mapToItem(q, event->position().toPoint()); + QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe); + // If the position is 0, we want to handle events ourselves - we don't want child items to steal them. + // This code will only get called when a child item has been created; + // events will go through the regular channels (mousePressEvent()) until then. + if (qFuzzyIsNull(swipePrivate->position)) { + q->mousePressEvent(event); + // The press point could be incorrect if the press happened over a child item, + // so we correct it after calling the base class' mousePressEvent(), rather + // than having to duplicate its code just so we can set the pressPoint. + setPressPoint(posInItem); + return true; + } + + // If the delegate is swiped open, send the event to the exposed item, + // in case it's an interactive child (like a Button). + if (swipePrivate->complete) + forwardMouseEvent(event, item, posInItem); + + // The position is non-zero, this press could be either for a delegate or the control itself + // (the control can be clicked to e.g. close the swipe). Either way, we must begin measuring + // mouse movement in case it turns into a swipe, in which case we grab the mouse. + swipePrivate->positionBeforePress = swipePrivate->position; + swipePrivate->velocityCalculator.startMeasuring(event->position().toPoint(), event->timestamp()); + setPressPoint(item->mapToItem(q, event->position().toPoint())); + + // When a delegate or any of its children uses the attached properties and signals, + // it declares that it wants mouse events. + const bool delivered = attachedObjectsSetPressed(item, event->scenePosition(), true); + if (delivered) + event->accept(); + return delivered; +} + +bool QQuickSwipeDelegatePrivate::handleMouseMoveEvent(QQuickItem *item, QMouseEvent *event) +{ + Q_Q(QQuickSwipeDelegate); + + if (holdTimer > 0) { + if (QLineF(pressPoint, event->position()).length() > QGuiApplication::styleHints()->startDragDistance()) + stopPressAndHold(); + } + + // The delegate can still be pressed when swipe.enabled is false, + // but the mouse moving shouldn't have any effect on swipe.position. + QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe); + if (!swipePrivate->enabled) + return false; + + // Protect against division by zero. + if (width == 0) + return false; + + // Don't bother reacting to events if we don't have any delegates. + if (!swipePrivate->left && !swipePrivate->right && !swipePrivate->behind) + return false; + + // Don't handle move events for the control if it wasn't pressed. + if (item == q && !pressed) + return false; + + const QPointF mappedEventPos = item->mapToItem(q, event->position().toPoint()); + const qreal distance = (mappedEventPos - pressPoint).x(); + if (!q->keepMouseGrab()) { + // Taken from QQuickDrawerPrivate::grabMouse; see comments there. + int threshold = qMax(20, QGuiApplication::styleHints()->startDragDistance() + 5); + const bool overThreshold = QQuickWindowPrivate::dragOverThreshold(distance, Qt::XAxis, event, threshold); + if (window && overThreshold) { + QQuickItem *grabber = q->window()->mouseGrabberItem(); + if (!grabber || !grabber->keepMouseGrab()) { + q->grabMouse(); + q->setKeepMouseGrab(true); + q->setPressed(true); + swipe.setComplete(false); + + attachedObjectsSetPressed(item, event->scenePosition(), false, true); + } + } + } + + if (q->keepMouseGrab()) { + // Ensure we don't try to calculate a position when the user tried to drag + // to the left when the left item is already exposed, and vice versa. + // The code below assumes that the drag is valid, so if we don't have this check, + // the wrong items are visible and the swiping wraps. + if (swipePrivate->behind + || ((swipePrivate->left || swipePrivate->right) + && (qFuzzyIsNull(swipePrivate->positionBeforePress) + || (swipePrivate->positionBeforePress == -1.0 && distance >= 0.0) + || (swipePrivate->positionBeforePress == 1.0 && distance <= 0.0)))) { + + // We must instantiate the items here so that we can calculate the + // position against the width of the relevant item. + QQuickItem *relevantItem = swipePrivate->createRelevantItemForDistance(distance); + // If there isn't any relevant item, the user may have swiped back to the 0 position, + // or they swiped back to a position that is equal to positionBeforePress. + const qreal normalizedDistance = relevantItem ? distance / relevantItem->width() : 0.0; + qreal position = 0; + + // If the control was exposed before the drag begun, the distance should be inverted. + // For example, if the control had been swiped to the right, the position would be 1.0. + // If the control was then swiped to the left by a distance of -20 pixels, the normalized + // distance might be -0.2, for example, which cannot be used as the position; the swipe + // started from the right, so we account for that by adding the position. + if (qFuzzyIsNull(normalizedDistance)) { + // There are two cases when the normalizedDistance can be 0, + // and we must distinguish between them: + // + // a) The swipe returns to the position that it was at before the press event. + // In this case, the distance will be 0. + // There would have been many position changes in the meantime, so we can't just + // ignore the move event; we have to set position to what it was before the press. + // + // b) If the position was at, 1.0, for example, and the control was then swiped + // to the left by the exact width of the left item, there won't be any relevant item + // (because the swipe's position would be at 0.0). In turn, the normalizedDistance + // would be 0 (because of the lack of a relevant item), but the distance will be non-zero. + position = qFuzzyIsNull(distance) ? swipePrivate->positionBeforePress : 0; + } else if (!swipePrivate->wasComplete) { + position = normalizedDistance; + } else { + position = distance > 0 ? normalizedDistance - 1.0 : normalizedDistance + 1.0; + } + + if (swipePrivate->isTransitioning()) + swipePrivate->transitionManager->cancel(); + swipe.setPosition(position); + } + } else { + // The swipe wasn't initiated. + if (event->position().toPoint().y() < 0 || event->position().toPoint().y() > height) { + // The mouse went outside the vertical bounds of the control, so + // we should no longer consider it pressed. + q->setPressed(false); + } + } + + event->accept(); + + return q->keepMouseGrab(); +} + +static const qreal exposeVelocityThreshold = 300.0; + +bool QQuickSwipeDelegatePrivate::handleMouseReleaseEvent(QQuickItem *item, QMouseEvent *event) +{ + Q_Q(QQuickSwipeDelegate); + QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe); + swipePrivate->velocityCalculator.stopMeasuring(event->position().toPoint(), event->timestamp()); + + const bool hadGrabbedMouse = q->keepMouseGrab(); + q->setKeepMouseGrab(false); + + // QQuickSwipe::close() doesn't allow closing while pressed, but now we're releasing. + // So set the pressed state false if this release _could_ result in closing, so that check can be bypassed. + if (!qIsNull(swipePrivate->position)) + q->setPressed(false); + + // Animations for the background and contentItem delegates are typically + // only enabled when !control.down, so that the animations aren't running + // when the user is swiping. To ensure that the animations are enabled + // *before* the positions of these delegates change (via the swipe.setPosition() calls below), + // we must cancel the press. QQuickAbstractButton::mouseUngrabEvent() does this + // for us, but by then it's too late. + if (hadGrabbedMouse) { + // TODO: this is copied from QQuickAbstractButton::mouseUngrabEvent(). + // Eventually it should be moved into a private helper so that we don't have to duplicate it. + q->setPressed(false); + stopPressRepeat(); + stopPressAndHold(); + emit q->canceled(); + } + + // Inform the given item that the mouse is released, in case it's an interactive child. + if (item != q && (swipePrivate->complete || swipePrivate->wasComplete)) + forwardMouseEvent(event, item, item->mapFromScene(event->scenePosition())); + + // The control can be exposed by either swiping past the halfway mark, or swiping fast enough. + const qreal swipeVelocity = swipePrivate->velocityCalculator.velocity().x(); + if (swipePrivate->position > 0.5 || + (swipePrivate->position > 0.0 && swipeVelocity > exposeVelocityThreshold)) { + swipePrivate->beginTransition(1.0); + swipePrivate->wasComplete = true; + } else if (swipePrivate->position < -0.5 || + (swipePrivate->position < 0.0 && swipeVelocity < -exposeVelocityThreshold)) { + swipePrivate->beginTransition(-1.0); + swipePrivate->wasComplete = true; + } else if (!swipePrivate->isTransitioning()) { + // The position is either <= 0.5 or >= -0.5, so the position should go to 0. + // However, if the position was already 0 or close to it, we were just clicked, + // and we don't need to start a transition. + if (!qFuzzyIsNull(swipePrivate->position)) + swipePrivate->beginTransition(0.0); + swipePrivate->wasComplete = false; + } + + // Inform any QQuickSwipeDelegateAttached objects that the mouse is released. + attachedObjectsSetPressed(item, event->scenePosition(), false); + + // Only consume child events if we had grabbed the mouse. + return hadGrabbedMouse; +} + +/*! \internal + Send a localized copy of \a event with \a localPos to the \a destination item. +*/ +void QQuickSwipeDelegatePrivate::forwardMouseEvent(QMouseEvent *event, QQuickItem *destination, QPointF localPos) +{ + Q_Q(QQuickSwipeDelegate); + QMutableSinglePointEvent localizedEvent(*event); + localizedEvent.mutablePoint().setPosition(localPos); + QGuiApplication::sendEvent(destination, &localizedEvent); + q->setPressed(!localizedEvent.isAccepted()); +} + +/*! \internal + For each QQuickSwipeDelegateAttached object on children of \a item: + if \a scenePos is in the attachee (the item to which it's attached), then + set its \a pressed state. Unless \a cancel is \c true, when the state + transitions from pressed to released, also emit \l QQuickSwipeDelegateAttached::clicked(). + Returns \c true if at least one relevant attached object was found. +*/ +bool QQuickSwipeDelegatePrivate::attachedObjectsSetPressed(QQuickItem *item, QPointF scenePos, bool pressed, bool cancel) +{ + bool found = false; + QVarLengthArray<QQuickItem *, 16> itemAndChildren; + itemAndChildren.append(item); + for (int i = 0; i < itemAndChildren.count(); ++i) { + auto item = itemAndChildren.at(i); + auto posInItem = item->mapFromScene(scenePos); + if (item->contains(posInItem)) { + if (Attached *attached = attachedObject(item)) { + const bool wasPressed = attached->isPressed(); + attached->setPressed(pressed); + if (wasPressed && !pressed && !cancel) + emit attached->clicked(); + found = true; + } + } + for (auto child : item->childItems()) + itemAndChildren.append(child); + } + return found; +} + +static void warnIfHorizontallyAnchored(QQuickItem *item, const QString &itemName) +{ + if (!item) + return; + + QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; + if (anchors && (anchors->fill() || anchors->centerIn() || anchors->left().item || anchors->right().item) + && !item->property("_q_QQuickSwipeDelegate_warned").toBool()) { + qmlWarning(item) << QString::fromLatin1("SwipeDelegate: cannot use horizontal anchors with %1; unable to layout the item.").arg(itemName); + item->setProperty("_q_QQuickSwipeDelegate_warned", true); + } +} + +void QQuickSwipeDelegatePrivate::resizeContent() +{ + warnIfHorizontallyAnchored(background, QStringLiteral("background")); + warnIfHorizontallyAnchored(contentItem, QStringLiteral("contentItem")); + + // If the background and contentItem are repositioned due to a swipe, + // we don't want to call QQuickControlPrivate's implementation of this function, + // as it repositions the contentItem to be visible. + // However, we still want to position the contentItem vertically + // and resize it (in case the control was resized while open). + QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&swipe); + if (!swipePrivate->complete) { + QQuickItemDelegatePrivate::resizeContent(); + } else if (contentItem) { + Q_Q(QQuickSwipeDelegate); + contentItem->setY(q->topPadding()); + contentItem->setWidth(q->availableWidth()); + contentItem->setHeight(q->availableHeight()); + } +} + +QPalette QQuickSwipeDelegatePrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::ListView); +} + +QQuickSwipeDelegate::QQuickSwipeDelegate(QQuickItem *parent) + : QQuickItemDelegate(*(new QQuickSwipeDelegatePrivate(this)), parent) +{ + // QQuickSwipeDelegate still depends on synthesized mouse events + setAcceptTouchEvents(false); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlmethod void QtQuick.Controls::SwipeDelegate::swipe.open(enumeration side) + + This method sets the \c position of the swipe so that it opens + from the specified \a side. + + Available values: + \value SwipeDelegate.Left The \c position is set to \c 1, which makes the swipe open + from the left. Either \c swipe.left or \c swipe.behind must + have been specified; otherwise the call is ignored. + \value SwipeDelegate.Right The \c position is set to \c -1, which makes the swipe open + from the right. Either \c swipe.right or \c swipe.behind must + have been specified; otherwise the call is ignored. + + Any animations defined for the \l {Item::}{x} position of \l {Control::}{contentItem} + and \l {Control::}{background} will be triggered. + + \sa swipe, swipe.close() +*/ + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlmethod void QtQuick.Controls::SwipeDelegate::swipe.close() + + This method sets the \c position of the swipe to \c 0. Any animations + defined for the \l {Item::}{x} position of \l {Control::}{contentItem} + and \l {Control::}{background} will be triggered. + + \sa swipe, swipe.open() +*/ + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.opened() + + This signal is emitted when the delegate has been swiped open + and the transition has finished. + + It is useful for performing some action upon completion of a swipe. + For example, it can be used to remove the delegate from the list + that it is in. + + \sa swipe, swipe.closed() +*/ + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.closed() + + This signal is emitted when the delegate has been swiped to closed + and the transition has finished. + + It is useful for performing some action upon cancellation of a swipe. + For example, it can be used to cancel the removal of the delegate from + the list that it is in. + + \sa swipe, swipe.opened() +*/ + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlsignal void QtQuick.Controls::SwipeDelegate::swipe.completed() + + This signal is emitted when \c swipe.complete becomes \c true. + + It is useful for performing some action upon completion of a swipe. + For example, it can be used to remove the delegate from the list + that it is in. + + \sa swipe +*/ + +/*! + \qmlproperty real QtQuick.Controls::SwipeDelegate::swipe.position + \qmlproperty bool QtQuick.Controls::SwipeDelegate::swipe.complete + \qmlproperty bool QtQuick.Controls::SwipeDelegate::swipe.enabled + \qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.left + \qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.behind + \qmlproperty Component QtQuick.Controls::SwipeDelegate::swipe.right + \qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.leftItem + \qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.behindItem + \qmlproperty Item QtQuick.Controls::SwipeDelegate::swipe.rightItem + \qmlproperty Transition QtQuick.Controls::SwipeDelegate::swipe.transition + + \table + \header + \li Name + \li Description + \row + \li position + \li This read-only property holds the position of the swipe relative to either + side of the control. When this value reaches either + \c -1.0 (left side) or \c 1.0 (right side) and the mouse button is + released, \c complete will be \c true. + \row + \li complete + \li This read-only property holds whether the control is fully exposed after + having been swiped to the left or right. + + When complete is \c true, any interactive items declared in \c left, + \c right, or \c behind will receive mouse events. + \row + \li enabled + \li This property determines whether or not the control can be swiped. + + This property was added in QtQuick.Controls 2.2. + \row + \li left + \li This property holds the left delegate. + + The left delegate sits behind both \l {Control::}{contentItem} and + \l {Control::}{background}. When the SwipeDelegate is swiped to the right, + this item will be gradually revealed. + + \include qquickswipedelegate-interaction.qdocinc + \row + \li behind + \li This property holds the delegate that is shown when the + SwipeDelegate is swiped to both the left and right. + + As with the \c left and \c right delegates, it sits behind both + \l {Control::}{contentItem} and \l {Control::}{background}. However, a + SwipeDelegate whose \c behind has been set can be continuously swiped + from either side, and will always show the same item. + + \include qquickswipedelegate-interaction.qdocinc + \row + \li right + \li This property holds the right delegate. + + The right delegate sits behind both \l {Control::}{contentItem} and + \l {Control::}{background}. When the SwipeDelegate is swiped to the left, + this item will be gradually revealed. + + \include qquickswipedelegate-interaction.qdocinc + \row + \li leftItem + \li This read-only property holds the item instantiated from the \c left component. + + If \c left has not been set, or the position hasn't changed since + creation of the SwipeDelegate, this property will be \c null. + \row + \li behindItem + \li This read-only property holds the item instantiated from the \c behind component. + + If \c behind has not been set, or the position hasn't changed since + creation of the SwipeDelegate, this property will be \c null. + \row + \li rightItem + \li This read-only property holds the item instantiated from the \c right component. + + If \c right has not been set, or the position hasn't changed since + creation of the SwipeDelegate, this property will be \c null. + \row + \li transition + \li This property holds the transition that is applied when a swipe is released, + or \l swipe.open() or \l swipe.close() is called. + + \snippet qtquickcontrols2-swipedelegate-transition.qml 1 + + This property was added in Qt Quick Controls 2.2. + \endtable + + \sa {Control::}{contentItem}, {Control::}{background}, swipe.open(), swipe.close() +*/ +QQuickSwipe *QQuickSwipeDelegate::swipe() const +{ + Q_D(const QQuickSwipeDelegate); + return const_cast<QQuickSwipe*>(&d->swipe); +} + +QQuickSwipeDelegateAttached *QQuickSwipeDelegate::qmlAttachedProperties(QObject *object) +{ + return new QQuickSwipeDelegateAttached(object); +} + +static bool isChildOrGrandchildOf(QQuickItem *child, QQuickItem *item) +{ + return item && (child == item || item->isAncestorOf(child)); +} + +bool QQuickSwipeDelegate::childMouseEventFilter(QQuickItem *child, QEvent *event) +{ + Q_D(QQuickSwipeDelegate); + // The contentItem is, by default, usually a non-interactive item like Text, and + // the same applies to the background. This means that simply stacking the left/right/behind + // items before these items won't allow us to get mouse events when the control is not currently exposed + // but has been previously. Therefore, we instead call setFiltersChildMouseEvents(true) in the constructor + // and filter out child events only when the child is the left/right/behind item. + const QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe); + if (!isChildOrGrandchildOf(child, swipePrivate->leftItem) && !isChildOrGrandchildOf(child, swipePrivate->behindItem) + && !isChildOrGrandchildOf(child, swipePrivate->rightItem)) { + return false; + } + + switch (event->type()) { + case QEvent::MouseButtonPress: { + return d->handleMousePressEvent(child, static_cast<QMouseEvent *>(event)); + } case QEvent::MouseMove: { + return d->handleMouseMoveEvent(child, static_cast<QMouseEvent *>(event)); + } case QEvent::MouseButtonRelease: { + // Make sure that the control gets release events if it has created child + // items that are stealing events from it. + QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); + QQuickItemDelegate::mouseReleaseEvent(mouseEvent); + return d->handleMouseReleaseEvent(child, mouseEvent); + } case QEvent::UngrabMouse: { + // If the mouse was pressed over e.g. rightItem and then dragged down, + // the ListView would eventually grab the mouse, at which point we must + // clear the pressed flag so that it doesn't stay pressed after the release. + Attached *attached = attachedObject(child); + if (attached) + attached->setPressed(false); + return false; + } default: + return false; + } +} + +// We only override this to set positionBeforePress; +// otherwise, it's the same as the base class implementation. +void QQuickSwipeDelegate::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickSwipeDelegate); + QQuickItemDelegate::mousePressEvent(event); + + QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe); + if (!swipePrivate->enabled) + return; + + swipePrivate->positionBeforePress = swipePrivate->position; + swipePrivate->velocityCalculator.startMeasuring(event->position().toPoint(), event->timestamp()); + + if (swipePrivate->complete) { + auto item = d->swipe.rightItem(); + if (item && item->contains(item->mapFromScene(event->scenePosition()))) { + d->pressedItem = item; + d->handleMousePressEvent(item, event); + } else { + item = d->swipe.leftItem(); + if (item && item->contains(item->mapFromScene(event->scenePosition()))) { + d->pressedItem = item; + d->handleMousePressEvent(item, event); + } + } + } +} + +void QQuickSwipeDelegate::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickSwipeDelegate); + if (filtersChildMouseEvents()) + d->handleMouseMoveEvent(this, event); + else + QQuickItemDelegate::mouseMoveEvent(event); + if (d->pressedItem) + d->handleMouseMoveEvent(d->pressedItem, event); +} + +void QQuickSwipeDelegate::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickSwipeDelegate); + if (!filtersChildMouseEvents() || !d->handleMouseReleaseEvent(this, event)) + QQuickItemDelegate::mouseReleaseEvent(event); + + if (d->pressedItem) { + if (d->pressedItem->acceptedMouseButtons()) + d->handleMouseReleaseEvent(d->pressedItem, event); + d->pressedItem = nullptr; + } +} + +void QQuickSwipeDelegate::mouseUngrabEvent() +{ + Q_D(QQuickSwipeDelegate); + setPressed(false); + + auto item = d->swipe.rightItem(); + if (item) { + if (auto control = qmlobject_cast<QQuickControl *>(item)) + QQuickControlPrivate::get(control)->handleUngrab(); + Attached *attached = attachedObject(item); + if (attached) + attached->setPressed(false); + } else { + item = d->swipe.leftItem(); + if (item) { + if (auto control = qmlobject_cast<QQuickControl *>(item)) + QQuickControlPrivate::get(control)->handleUngrab(); + Attached *attached = attachedObject(item); + if (attached) + attached->setPressed(false); + } + } + + d->pressedItem = nullptr; +} + +void QQuickSwipeDelegate::touchEvent(QTouchEvent *event) +{ + // Don't allow QQuickControl accept the touch event, because QQuickSwipeDelegate + // is still based on synthesized mouse events + event->ignore(); +} + +void QQuickSwipeDelegate::componentComplete() +{ + Q_D(QQuickSwipeDelegate); + QQuickItemDelegate::componentComplete(); + QQuickSwipePrivate::get(&d->swipe)->reposition(DontAnimatePosition); +} + +void QQuickSwipeDelegate::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickSwipeDelegate); + QQuickControl::geometryChange(newGeometry, oldGeometry); + + if (isComponentComplete() && !qFuzzyCompare(newGeometry.width(), oldGeometry.width())) { + QQuickSwipePrivate *swipePrivate = QQuickSwipePrivate::get(&d->swipe); + swipePrivate->reposition(DontAnimatePosition); + } +} + +QFont QQuickSwipeDelegate::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ListView); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickSwipeDelegate::accessibleRole() const +{ + return QAccessible::ListItem; +} +#endif + +class QQuickSwipeDelegateAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickSwipeDelegateAttached) + +public: + // True when left/right/behind is non-interactive and is pressed. + bool pressed = false; +}; + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlattachedsignal QtQuick.Controls::SwipeDelegate::clicked() + + This signal can be attached to a non-interactive item declared in + \c swipe.left, \c swipe.right, or \c swipe.behind, in order to react to + clicks. Items can only be clicked when \c swipe.complete is \c true. + + For interactive controls (such as \l Button) declared in these + items, use their respective \c clicked() signal instead. + + To respond to clicks on the SwipeDelegate itself, use its + \l {AbstractButton::}{clicked()} signal. + + \note See the documentation for \l pressed for information on + how to use the event-related properties correctly. + + \sa pressed +*/ + +QQuickSwipeDelegateAttached::QQuickSwipeDelegateAttached(QObject *object) + : QObject(*(new QQuickSwipeDelegateAttachedPrivate), object) +{ + QQuickItem *item = qobject_cast<QQuickItem *>(object); + if (item) { + // This allows us to be notified when an otherwise non-interactive item + // is pressed and clicked. The alternative is much more more complex: + // iterating through children that contain the event pos and finding + // the first one with an attached object. + item->setAcceptedMouseButtons(Qt::AllButtons); + } else { + qWarning() << "Attached properties of SwipeDelegate must be accessed through an Item"; + } +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlattachedproperty bool QtQuick.Controls::SwipeDelegate::pressed + \readonly + + This property can be attached to a non-interactive item declared in + \c swipe.left, \c swipe.right, or \c swipe.behind, in order to detect if it + is pressed. Items can only be pressed when \c swipe.complete is \c true. + + For example: + + \code + swipe.right: Label { + anchors.right: parent.right + height: parent.height + text: "Action" + color: "white" + padding: 12 + background: Rectangle { + color: SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato" + } + } + \endcode + + It is possible to have multiple items which individually receive mouse and + touch events. For example, to have two actions in the \c swipe.right item, + use the following code: + + \code + swipe.right: Row { + anchors.right: parent.right + height: parent.height + + Label { + id: moveLabel + text: qsTr("Move") + color: "white" + verticalAlignment: Label.AlignVCenter + padding: 12 + height: parent.height + + SwipeDelegate.onClicked: console.log("Moving...") + + background: Rectangle { + color: moveLabel.SwipeDelegate.pressed ? Qt.darker("#ffbf47", 1.1) : "#ffbf47" + } + } + Label { + id: deleteLabel + text: qsTr("Delete") + color: "white" + verticalAlignment: Label.AlignVCenter + padding: 12 + height: parent.height + + SwipeDelegate.onClicked: console.log("Deleting...") + + background: Rectangle { + color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato" + } + } + } + \endcode + + Note how the \c color assignment in each \l {Control::}{background} item + qualifies the attached property with the \c id of the label. This + is important; using the attached properties on an item causes that item + to accept events. Suppose we had left out the \c id in the previous example: + + \code + color: SwipeDelegate.pressed ? Qt.darker("tomato", 1.1) : "tomato" + \endcode + + The \l Rectangle background item is a child of the label, so it naturally + receives events before it. In practice, this means that the background + color will change, but the \c onClicked handler in the label will never + get called. + + For interactive controls (such as \l Button) declared in these + items, use their respective \c pressed property instead. + + For presses on the SwipeDelegate itself, use its + \l {AbstractButton::}{pressed} property. + + \sa clicked() +*/ +bool QQuickSwipeDelegateAttached::isPressed() const +{ + Q_D(const QQuickSwipeDelegateAttached); + return d->pressed; +} + +void QQuickSwipeDelegateAttached::setPressed(bool pressed) +{ + Q_D(QQuickSwipeDelegateAttached); + if (pressed == d->pressed) + return; + + d->pressed = pressed; + emit pressedChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquickswipe_p.cpp" +#include "moc_qquickswipedelegate_p.cpp" diff --git a/src/quicktemplates2/qquickswipedelegate_p.h b/src/quicktemplates2/qquickswipedelegate_p.h new file mode 100644 index 0000000000..cbf5f4b483 --- /dev/null +++ b/src/quicktemplates2/qquickswipedelegate_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSWIPEDELEGATE_P_H +#define QQUICKSWIPEDELEGATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickitemdelegate_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSwipe; +class QQuickSwipeDelegatePrivate; +class QQuickSwipeDelegateAttached; +class QQuickSwipeDelegateAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipeDelegate : public QQuickItemDelegate +{ + Q_OBJECT + Q_PROPERTY(QQuickSwipe *swipe READ swipe CONSTANT FINAL) + QML_NAMED_ELEMENT(SwipeDelegate) + QML_ATTACHED(QQuickSwipeDelegateAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickSwipeDelegate(QQuickItem *parent = nullptr); + + QQuickSwipe *swipe() const; + + enum Side { Left = 1, Right = -1 }; + Q_ENUM(Side) + + static QQuickSwipeDelegateAttached *qmlAttachedProperties(QObject *object); + +protected: + bool childMouseEventFilter(QQuickItem *child, QEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseUngrabEvent() override; + void touchEvent(QTouchEvent *event) override; + + void componentComplete() override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickSwipeDelegate) + Q_DECLARE_PRIVATE(QQuickSwipeDelegate) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipeDelegateAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) + +public: + explicit QQuickSwipeDelegateAttached(QObject *object = nullptr); + + bool isPressed() const; + void setPressed(bool pressed); + +Q_SIGNALS: + void pressedChanged(); + void clicked(); + +private: + Q_DISABLE_COPY(QQuickSwipeDelegateAttached) + Q_DECLARE_PRIVATE(QQuickSwipeDelegateAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSwipeDelegate) +QML_DECLARE_TYPEINFO(QQuickSwipeDelegate, QML_HAS_ATTACHED_PROPERTIES) +Q_DECLARE_METATYPE(QQuickSwipeDelegate::Side) + +#endif // QQUICKSWIPEDELEGATE_P_H diff --git a/src/quicktemplates2/qquickswipedelegate_p_p.h b/src/quicktemplates2/qquickswipedelegate_p_p.h new file mode 100644 index 0000000000..46e47eb677 --- /dev/null +++ b/src/quicktemplates2/qquickswipedelegate_p_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSWIPEDELEGATE_P_P_H +#define QQUICKSWIPEDELEGATE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickitemdelegate_p_p.h> +#include <QtQuickTemplates2/private/qquickswipe_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSwipeDelegate; + +class QQuickSwipeDelegatePrivate : public QQuickItemDelegatePrivate +{ + Q_DECLARE_PUBLIC(QQuickSwipeDelegate) + +public: + QQuickSwipeDelegatePrivate(QQuickSwipeDelegate *control); + + bool handleMousePressEvent(QQuickItem *item, QMouseEvent *event); + bool handleMouseMoveEvent(QQuickItem *item, QMouseEvent *event); + bool handleMouseReleaseEvent(QQuickItem *item, QMouseEvent *event); + void forwardMouseEvent(QMouseEvent *event, QQuickItem *destination, QPointF localPos); + bool attachedObjectsSetPressed(QQuickItem *item, QPointF scenePos, bool pressed, bool cancel = false); + + void resizeContent() override; + void resizeBackground() override; + + QPalette defaultPalette() const override; + + QQuickSwipe swipe; + QQuickItem *pressedItem = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKSWIPEDELEGATE_P_P_H diff --git a/src/quicktemplates2/qquickswipeview.cpp b/src/quicktemplates2/qquickswipeview.cpp new file mode 100644 index 0000000000..3277ba524f --- /dev/null +++ b/src/quicktemplates2/qquickswipeview.cpp @@ -0,0 +1,478 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickswipeview_p.h" + +#include <QtQml/qqmlinfo.h> +#include <QtQuickTemplates2/private/qquickcontainer_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype SwipeView + \inherits Container +//! \instantiates QQuickSwipeView + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-navigation + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Enables the user to navigate pages by swiping sideways. + + SwipeView provides a swipe-based navigation model. + + \image qtquickcontrols2-swipeview.gif + + SwipeView is populated with a set of pages. One page is visible at a time. + The user can navigate between the pages by swiping sideways. Notice that + SwipeView itself is entirely non-visual. It is recommended to combine it + with PageIndicator, to give the user a visual clue that there are multiple + pages. + + \snippet qtquickcontrols2-swipeview-indicator.qml 1 + + As shown above, SwipeView is typically populated with a static set of + pages that are defined inline as children of the view. It is also possible + to \l {Container::addItem()}{add}, \l {Container::insertItem()}{insert}, + \l {Container::moveItem()}{move}, and \l {Container::removeItem()}{remove} + pages dynamically at run time. + + \include container-currentindex.qdocinc {file} {SwipeView} {TabBar} + + It is generally not advisable to add excessive amounts of pages to a + SwipeView. However, when the amount of pages grows larger, or individual + pages are relatively complex, it may be desirable to free up resources by + unloading pages that are outside the immediate reach of the user. + The following example presents how to use \l Loader to keep a maximum of + three pages simultaneously instantiated. + + \code + SwipeView { + Repeater { + model: 6 + Loader { + active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem + sourceComponent: Text { + text: index + Component.onCompleted: console.log("created:", index) + Component.onDestruction: console.log("destroyed:", index) + } + } + } + } + \endcode + + \note SwipeView takes over the geometry management of items added to the + view. Using anchors on the items is not supported, and any \c width + or \c height assignment will be overridden by the view. Notice that + this only applies to the root of the item. Specifying width and height, + or using anchors for its children works as expected. + + \sa TabBar, PageIndicator, {Customizing SwipeView}, {Navigation Controls}, {Container Controls}, + {Focus Management in Qt Quick Controls} +*/ + +class QQuickSwipeViewPrivate : public QQuickContainerPrivate +{ + Q_DECLARE_PUBLIC(QQuickSwipeView) + +public: + void resizeItem(QQuickItem *item); + void resizeItems(); + + static QQuickSwipeViewPrivate *get(QQuickSwipeView *view); + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + qreal getContentWidth() const override; + qreal getContentHeight() const override; + + bool interactive = true; + Qt::Orientation orientation = Qt::Horizontal; +}; + +class QQuickSwipeViewAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickSwipeViewAttached) + +public: + static QQuickSwipeViewAttachedPrivate *get(QQuickSwipeViewAttached *attached) + { + return attached->d_func(); + } + + void update(QQuickSwipeView *newView, int newIndex); + void updateCurrentIndex(); + void setCurrentIndex(int i); + + QQuickSwipeView *swipeView = nullptr; + int index = -1; + int currentIndex = -1; +}; + +void QQuickSwipeViewPrivate::resizeItems() +{ + Q_Q(QQuickSwipeView); + const int count = q->count(); + for (int i = 0; i < count; ++i) { + QQuickItem *item = itemAt(i); + if (item) { + QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; + // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors + if (anchors && (anchors->fill() || anchors->centerIn()) && !item->property("_q_QQuickSwipeView_warned").toBool()) { + qmlWarning(item) << "SwipeView has detected conflicting anchors. Unable to layout the item."; + item->setProperty("_q_QQuickSwipeView_warned", true); + } + if (orientation == Qt::Horizontal) + item->setPosition({i * (contentItem->width() + spacing), 0}); + else + item->setPosition({0, i * (contentItem->height() + spacing)}); + item->setSize(QSizeF(contentItem->width(), contentItem->height())); + } + } +} + +QQuickSwipeViewPrivate *QQuickSwipeViewPrivate::get(QQuickSwipeView *view) +{ + return view->d_func(); +} + +void QQuickSwipeViewPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickSwipeView); + QQuickContainerPrivate::itemImplicitWidthChanged(item); + if (item == q->currentItem()) + updateImplicitContentWidth(); +} + +void QQuickSwipeViewPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickSwipeView); + QQuickContainerPrivate::itemImplicitHeightChanged(item); + if (item == q->currentItem()) + updateImplicitContentHeight(); +} + +qreal QQuickSwipeViewPrivate::getContentWidth() const +{ + Q_Q(const QQuickSwipeView); + QQuickItem *currentItem = q->currentItem(); + return currentItem ? currentItem->implicitWidth() : 0; +} + +qreal QQuickSwipeViewPrivate::getContentHeight() const +{ + Q_Q(const QQuickSwipeView); + QQuickItem *currentItem = q->currentItem(); + return currentItem ? currentItem->implicitHeight() : 0; +} + +QQuickSwipeView::QQuickSwipeView(QQuickItem *parent) + : QQuickContainer(*(new QQuickSwipeViewPrivate), parent) +{ + Q_D(QQuickSwipeView); + d->changeTypes |= QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; + setFlag(ItemIsFocusScope); + setActiveFocusOnTab(true); + QObjectPrivate::connect(this, &QQuickContainer::currentItemChanged, d, &QQuickControlPrivate::updateImplicitContentSize); +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlproperty bool QtQuick.Controls::SwipeView::interactive + + This property describes whether the user can interact with the SwipeView. + The user cannot swipe a view that is not interactive. + + The default value is \c true. +*/ +bool QQuickSwipeView::isInteractive() const +{ + Q_D(const QQuickSwipeView); + return d->interactive; +} + +void QQuickSwipeView::setInteractive(bool interactive) +{ + Q_D(QQuickSwipeView); + if (d->interactive == interactive) + return; + + d->interactive = interactive; + emit interactiveChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty enumeration QtQuick.Controls::SwipeView::orientation + + This property holds the orientation. + + Possible values: + \value Qt.Horizontal Horizontal (default) + \value Qt.Vertical Vertical + + \sa horizontal, vertical +*/ +Qt::Orientation QQuickSwipeView::orientation() const +{ + Q_D(const QQuickSwipeView); + return d->orientation; +} + +void QQuickSwipeView::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickSwipeView); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + if (isComponentComplete()) + d->resizeItems(); + emit orientationChanged(); +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::SwipeView::horizontal + \readonly + + This property holds whether the swipe view is horizontal. + + \sa orientation +*/ +bool QQuickSwipeView::isHorizontal() const +{ + Q_D(const QQuickSwipeView); + return d->orientation == Qt::Horizontal; +} + +/*! + \since QtQuick.Controls 2.3 (Qt 5.10) + \qmlproperty bool QtQuick.Controls::SwipeView::vertical + \readonly + + This property holds whether the swipe view is vertical. + + \sa orientation +*/ +bool QQuickSwipeView::isVertical() const +{ + Q_D(const QQuickSwipeView); + return d->orientation == Qt::Vertical; +} + +QQuickSwipeViewAttached *QQuickSwipeView::qmlAttachedProperties(QObject *object) +{ + return new QQuickSwipeViewAttached(object); +} + +void QQuickSwipeView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickSwipeView); + QQuickContainer::geometryChange(newGeometry, oldGeometry); + d->resizeItems(); +} + +void QQuickSwipeView::itemAdded(int index, QQuickItem *item) +{ + Q_D(QQuickSwipeView); + if (isComponentComplete()) + item->setSize(QSizeF(d->contentItem->width(), d->contentItem->height())); + QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item)); + if (attached) + QQuickSwipeViewAttachedPrivate::get(attached)->update(this, index); +} + +void QQuickSwipeView::itemMoved(int index, QQuickItem *item) +{ + QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item)); + if (attached) + QQuickSwipeViewAttachedPrivate::get(attached)->update(this, index); +} + +void QQuickSwipeView::itemRemoved(int, QQuickItem *item) +{ + QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item)); + if (attached) + QQuickSwipeViewAttachedPrivate::get(attached)->update(nullptr, -1); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickSwipeView::accessibleRole() const +{ + return QAccessible::PageTabList; +} +#endif + +/*! + \qmlattachedproperty int QtQuick.Controls::SwipeView::index + \readonly + + This attached property holds the index of each child item in the SwipeView. + + It is attached to each child item of the SwipeView. +*/ + +/*! + \qmlattachedproperty bool QtQuick.Controls::SwipeView::isCurrentItem + \readonly + + This attached property is \c true if this child is the current item. + + It is attached to each child item of the SwipeView. +*/ + +/*! + \qmlattachedproperty bool QtQuick.Controls::SwipeView::isNextItem + \since QtQuick.Controls 2.1 (Qt 5.8) + \readonly + + This attached property is \c true if this child is the next item. + + It is attached to each child item of the SwipeView. +*/ + +/*! + \qmlattachedproperty bool QtQuick.Controls::SwipeView::isPreviousItem + \since QtQuick.Controls 2.1 (Qt 5.8) + \readonly + + This attached property is \c true if this child is the previous item. + + It is attached to each child item of the SwipeView. +*/ + +/*! + \qmlattachedproperty SwipeView QtQuick.Controls::SwipeView::view + \readonly + + This attached property holds the view that manages this child item. + + It is attached to each child item of the SwipeView. +*/ + +void QQuickSwipeViewAttachedPrivate::updateCurrentIndex() +{ + setCurrentIndex(swipeView ? swipeView->currentIndex() : -1); +} + +void QQuickSwipeViewAttachedPrivate::setCurrentIndex(int i) +{ + if (i == currentIndex) + return; + + Q_Q(QQuickSwipeViewAttached); + const bool wasCurrent = q->isCurrentItem(); + const bool wasNext = q->isNextItem(); + const bool wasPrevious = q->isPreviousItem(); + + currentIndex = i; + if (wasCurrent != q->isCurrentItem()) + emit q->isCurrentItemChanged(); + if (wasNext != q->isNextItem()) + emit q->isNextItemChanged(); + if (wasPrevious != q->isPreviousItem()) + emit q->isPreviousItemChanged(); +} + +void QQuickSwipeViewAttachedPrivate::update(QQuickSwipeView *newView, int newIndex) +{ + Q_Q(QQuickSwipeViewAttached); + int oldIndex = index; + QQuickSwipeView *oldView = swipeView; + + index = newIndex; + swipeView = newView; + + if (oldView != newView) { + if (oldView) { + disconnect(oldView, &QQuickSwipeView::currentIndexChanged, + this, &QQuickSwipeViewAttachedPrivate::updateCurrentIndex); + } + if (newView) { + connect(newView, &QQuickSwipeView::currentIndexChanged, + this, &QQuickSwipeViewAttachedPrivate::updateCurrentIndex); + } + emit q->viewChanged(); + } + if (oldIndex != newIndex) + emit q->indexChanged(); + + updateCurrentIndex(); +} + +QQuickSwipeViewAttached::QQuickSwipeViewAttached(QObject *parent) + : QObject(*(new QQuickSwipeViewAttachedPrivate), parent) +{ + if (!qobject_cast<QQuickItem *>(parent)) + qmlWarning(parent) << "SwipeView: attached properties must be accessed from within a child item"; +} + +int QQuickSwipeViewAttached::index() const +{ + Q_D(const QQuickSwipeViewAttached); + return d->index; +} + +bool QQuickSwipeViewAttached::isCurrentItem() const +{ + Q_D(const QQuickSwipeViewAttached); + return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex; +} + +QQuickSwipeView *QQuickSwipeViewAttached::view() const +{ + Q_D(const QQuickSwipeViewAttached); + return d->swipeView; +} + +bool QQuickSwipeViewAttached::isNextItem() const +{ + Q_D(const QQuickSwipeViewAttached); + return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex + 1; +} + +bool QQuickSwipeViewAttached::isPreviousItem() const +{ + Q_D(const QQuickSwipeViewAttached); + return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex - 1; +} + +QT_END_NAMESPACE + +#include "moc_qquickswipeview_p.cpp" diff --git a/src/quicktemplates2/qquickswipeview_p.h b/src/quicktemplates2/qquickswipeview_p.h new file mode 100644 index 0000000000..8a09a37e22 --- /dev/null +++ b/src/quicktemplates2/qquickswipeview_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSWIPEVIEW_P_H +#define QQUICKSWIPEVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSwipeViewAttached; +class QQuickSwipeViewPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipeView : public QQuickContainer +{ + Q_OBJECT + // 2.1 (Qt 5.8) + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive NOTIFY interactiveChanged FINAL REVISION(2, 1)) + // 2.2 (Qt 5.9) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL REVISION(2, 2)) + // 2.3 (Qt 5.10) + Q_PROPERTY(bool horizontal READ isHorizontal NOTIFY orientationChanged FINAL REVISION(2, 3)) + Q_PROPERTY(bool vertical READ isVertical NOTIFY orientationChanged FINAL REVISION(2, 3)) + QML_NAMED_ELEMENT(SwipeView) + QML_ATTACHED(QQuickSwipeViewAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickSwipeView(QQuickItem *parent = nullptr); + + static QQuickSwipeViewAttached *qmlAttachedProperties(QObject *object); + + // 2.1 (Qt 5.8) + bool isInteractive() const; + void setInteractive(bool interactive); + + // 2.2 (Qt 5.9) + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + // 2.3 (Qt 5.10) + bool isHorizontal() const; + bool isVertical() const; + +Q_SIGNALS: + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void interactiveChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void orientationChanged(); + +protected: + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + void itemAdded(int index, QQuickItem *item) override; + void itemMoved(int index, QQuickItem *item) override; + void itemRemoved(int index, QQuickItem *item) override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickSwipeView) + Q_DECLARE_PRIVATE(QQuickSwipeView) +}; + +class QQuickSwipeViewAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipeViewAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL) + Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY isCurrentItemChanged FINAL) + Q_PROPERTY(QQuickSwipeView *view READ view NOTIFY viewChanged FINAL) + // 2.1 (Qt 5.8) + Q_PROPERTY(bool isNextItem READ isNextItem NOTIFY isNextItemChanged FINAL REVISION(2, 1)) + Q_PROPERTY(bool isPreviousItem READ isPreviousItem NOTIFY isPreviousItemChanged FINAL REVISION(2, 1)) + +public: + explicit QQuickSwipeViewAttached(QObject *parent = nullptr); + + int index() const; + bool isCurrentItem() const; + QQuickSwipeView *view() const; + + // 2.1 (Qt 5.8) + bool isNextItem() const; + bool isPreviousItem() const; + +Q_SIGNALS: + void indexChanged(); + void isCurrentItemChanged(); + void viewChanged(); + // 2.1 (Qt 5.8) + /*Q_REVISION(2, 1)*/ void isNextItemChanged(); + /*Q_REVISION(2, 1)*/ void isPreviousItemChanged(); + +private: + Q_DISABLE_COPY(QQuickSwipeViewAttached) + Q_DECLARE_PRIVATE(QQuickSwipeViewAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSwipeView) +QML_DECLARE_TYPEINFO(QQuickSwipeView, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKSWIPEVIEW_P_H diff --git a/src/quicktemplates2/qquickswitch.cpp b/src/quicktemplates2/qquickswitch.cpp new file mode 100644 index 0000000000..9d4ac4530b --- /dev/null +++ b/src/quicktemplates2/qquickswitch.cpp @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickswitch_p.h" +#include "qquickabstractbutton_p_p.h" + +#include <QtGui/qstylehints.h> +#include <QtGui/qguiapplication.h> +#include <QtQuick/private/qquickwindow_p.h> +#include <QtQuick/private/qquickevents_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Switch + \inherits AbstractButton +//! \instantiates QQuickSwitch + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-buttons + \brief Switch button that can be toggled on or off. + + \image qtquickcontrols2-switch.gif + + Switch is an option button that can be dragged or toggled on (checked) or + off (unchecked). Switches are typically used to select between two states. + For larger sets of options, such as those in a list, consider using + \l SwitchDelegate instead. + + Switch inherits its API from \l AbstractButton. For instance, the state + of the switch can be set with the \l {AbstractButton::}{checked} property. + + \code + ColumnLayout { + Switch { + text: qsTr("Wi-Fi") + } + Switch { + text: qsTr("Bluetooth") + } + } + \endcode + + \sa {Customizing Switch}, {Button Controls} +*/ + +class QQuickSwitchPrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickSwitch) + +public: + qreal positionAt(const QPointF &point) const; + + bool canDrag(const QPointF &movePoint) const; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::Switch); } + + qreal position = 0; +}; + +qreal QQuickSwitchPrivate::positionAt(const QPointF &point) const +{ + Q_Q(const QQuickSwitch); + qreal pos = 0.0; + if (indicator) + pos = indicator->mapFromItem(q, point).x() / indicator->width(); + if (q->isMirrored()) + return 1.0 - pos; + return pos; +} + +bool QQuickSwitchPrivate::canDrag(const QPointF &movePoint) const +{ + // don't start dragging the handle unless the initial press was at the indicator, + // or the drag has reached the indicator area. this prevents unnatural jumps when + // dragging far outside the indicator. + const qreal pressPos = positionAt(pressPoint); + const qreal movePos = positionAt(movePoint); + return (pressPos >= 0.0 && pressPos <= 1.0) || (movePos >= 0.0 && movePos <= 1.0); +} + +void QQuickSwitchPrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickSwitch); + QQuickAbstractButtonPrivate::handleMove(point); + if (q->keepMouseGrab() || q->keepTouchGrab()) + q->setPosition(positionAt(point)); +} + +void QQuickSwitchPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickSwitch); + QQuickAbstractButtonPrivate::handleRelease(point); + q->setKeepMouseGrab(false); + q->setKeepTouchGrab(false); +} + +QQuickSwitch::QQuickSwitch(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickSwitchPrivate), parent) +{ + Q_D(QQuickSwitch); + d->keepPressed = true; + setCheckable(true); +} + +/*! + \qmlproperty real QtQuick.Controls::Switch::position + \readonly + + \input includes/qquickswitch.qdocinc position +*/ +qreal QQuickSwitch::position() const +{ + Q_D(const QQuickSwitch); + return d->position; +} + +void QQuickSwitch::setPosition(qreal position) +{ + Q_D(QQuickSwitch); + position = qBound<qreal>(0.0, position, 1.0); + if (qFuzzyCompare(d->position, position)) + return; + + d->position = position; + emit positionChanged(); + emit visualPositionChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::Switch::visualPosition + \readonly + + \input includes/qquickswitch.qdocinc visualPosition +*/ +qreal QQuickSwitch::visualPosition() const +{ + Q_D(const QQuickSwitch); + if (isMirrored()) + return 1.0 - d->position; + return d->position; +} + +void QQuickSwitch::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickSwitch); + if (!keepMouseGrab()) { + const QPointF movePoint = event->position(); + if (d->canDrag(movePoint)) + setKeepMouseGrab(QQuickWindowPrivate::dragOverThreshold(movePoint.x() - d->pressPoint.x(), Qt::XAxis, event)); + } + QQuickAbstractButton::mouseMoveEvent(event); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickSwitch::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickSwitch); + if (!keepTouchGrab() && event->type() == QEvent::TouchUpdate) { + for (const QTouchEvent::TouchPoint &point : event->points()) { + if (point.id() != d->touchId || point.state() != QEventPoint::Updated) + continue; + if (d->canDrag(point.position())) + setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.position().x() - d->pressPoint.x(), Qt::XAxis, &point)); + } + } + QQuickAbstractButton::touchEvent(event); +} +#endif + +void QQuickSwitch::mirrorChange() +{ + QQuickAbstractButton::mirrorChange(); + emit visualPositionChanged(); +} + +void QQuickSwitch::nextCheckState() +{ + Q_D(QQuickSwitch); + if (keepMouseGrab() || keepTouchGrab()) { + d->toggle(d->position > 0.5); + // the checked state might not change => force a position update to + // avoid that the handle is left somewhere in the middle (QTBUG-57944) + setPosition(d->checked ? 1.0 : 0.0); + } else { + QQuickAbstractButton::nextCheckState(); + } +} + +void QQuickSwitch::buttonChange(ButtonChange change) +{ + Q_D(QQuickSwitch); + if (change == ButtonCheckedChange) + setPosition(d->checked ? 1.0 : 0.0); + else + QQuickAbstractButton::buttonChange(change); +} + +QFont QQuickSwitch::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::Switch); +} + +QT_END_NAMESPACE + +#include "moc_qquickswitch_p.cpp" diff --git a/src/quicktemplates2/qquickswitch_p.h b/src/quicktemplates2/qquickswitch_p.h new file mode 100644 index 0000000000..b3df029923 --- /dev/null +++ b/src/quicktemplates2/qquickswitch_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSWITCH_P_H +#define QQUICKSWITCH_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSwitchPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwitch : public QQuickAbstractButton +{ + Q_OBJECT + Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal visualPosition READ visualPosition NOTIFY visualPositionChanged FINAL) + QML_NAMED_ELEMENT(Switch) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickSwitch(QQuickItem *parent = nullptr); + + qreal position() const; + void setPosition(qreal position); + + qreal visualPosition() const; + +Q_SIGNALS: + void positionChanged(); + void visualPositionChanged(); + +protected: + void mouseMoveEvent(QMouseEvent *event) override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif + + void mirrorChange() override; + + void nextCheckState() override; + void buttonChange(ButtonChange change) override; + + QFont defaultFont() const override; + +private: + Q_DISABLE_COPY(QQuickSwitch) + Q_DECLARE_PRIVATE(QQuickSwitch) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSwitch) + +#endif // QQUICKSWITCH_P_H diff --git a/src/quicktemplates2/qquickswitchdelegate.cpp b/src/quicktemplates2/qquickswitchdelegate.cpp new file mode 100644 index 0000000000..4b9e3e79f4 --- /dev/null +++ b/src/quicktemplates2/qquickswitchdelegate.cpp @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickswitchdelegate_p.h" + +#include "qquickitemdelegate_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype SwitchDelegate + \inherits ItemDelegate +//! \instantiates QQuickSwitchDelegate + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-delegates + \brief Item delegate with a switch indicator that can be toggled on or off. + + \image qtquickcontrols2-switchdelegate.gif + + SwitchDelegate presents an item delegate that can be toggled on (checked) or + off (unchecked). Switch delegates are typically used to select one or more + options from a set of options. For smaller sets of options, or for options + that need to be uniquely identifiable, consider using \l Switch instead. + + SwitchDelegate inherits its API from \l ItemDelegate, which is inherited + from \l AbstractButton. For instance, you can set \l {AbstractButton::text}{text}, + and react to \l {AbstractButton::clicked}{clicks} using the \l AbstractButton + API. The state of the switch delegate can be set with the + \l {AbstractButton::}{checked} property. + + \code + ListView { + model: ["Option 1", "Option 2", "Option 3"] + delegate: SwitchDelegate { + text: modelData + } + } + \endcode + + \sa {Customizing SwitchDelegate}, {Delegate Controls} +*/ + +class QQuickSwitchDelegatePrivate : public QQuickItemDelegatePrivate +{ + Q_DECLARE_PUBLIC(QQuickSwitchDelegate) + +public: + qreal positionAt(const QPointF &point) const; + + bool canDrag(const QPointF &movePoint) const; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ListView); } + + qreal position = 0; +}; + +qreal QQuickSwitchDelegatePrivate::positionAt(const QPointF &point) const +{ + Q_Q(const QQuickSwitchDelegate); + qreal pos = 0.0; + if (indicator) + pos = indicator->mapFromItem(q, point).x() / indicator->width(); + if (q->isMirrored()) + return 1.0 - pos; + return pos; +} + +bool QQuickSwitchDelegatePrivate::canDrag(const QPointF &movePoint) const +{ + // don't start dragging the handle unless the initial press was at the indicator, + // or the drag has reached the indicator area. this prevents unnatural jumps when + // dragging far outside the indicator. + const qreal pressPos = positionAt(pressPoint); + const qreal movePos = positionAt(movePoint); + return (pressPos >= 0.0 && pressPos <= 1.0) || (movePos >= 0.0 && movePos <= 1.0); +} + +void QQuickSwitchDelegatePrivate::handleMove(const QPointF &point) +{ + Q_Q(QQuickSwitchDelegate); + QQuickItemDelegatePrivate::handleMove(point); + if (q->keepMouseGrab() || q->keepTouchGrab()) + q->setPosition(positionAt(point)); +} + +void QQuickSwitchDelegatePrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickSwitchDelegate); + QQuickItemDelegatePrivate::handleRelease(point); + q->setKeepMouseGrab(false); + q->setKeepTouchGrab(false); +} + +QQuickSwitchDelegate::QQuickSwitchDelegate(QQuickItem *parent) + : QQuickItemDelegate(*(new QQuickSwitchDelegatePrivate), parent) +{ + Q_D(QQuickSwitchDelegate); + d->keepPressed = true; + setCheckable(true); +} + +/*! + \qmlproperty real QtQuick.Controls::SwitchDelegate::position + \readonly + + \input includes/qquickswitch.qdocinc position +*/ +qreal QQuickSwitchDelegate::position() const +{ + Q_D(const QQuickSwitchDelegate); + return d->position; +} + +void QQuickSwitchDelegate::setPosition(qreal position) +{ + Q_D(QQuickSwitchDelegate); + position = qBound<qreal>(0.0, position, 1.0); + if (qFuzzyCompare(d->position, position)) + return; + + d->position = position; + emit positionChanged(); + emit visualPositionChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::SwitchDelegate::visualPosition + \readonly + + \input includes/qquickswitch.qdocinc visualPosition +*/ +qreal QQuickSwitchDelegate::visualPosition() const +{ + Q_D(const QQuickSwitchDelegate); + if (isMirrored()) + return 1.0 - d->position; + return d->position; +} + +void QQuickSwitchDelegate::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickSwitchDelegate); + if (!keepMouseGrab()) { + const QPointF movePoint = event->position(); + if (d->canDrag(movePoint)) + setKeepMouseGrab(QQuickWindowPrivate::dragOverThreshold(movePoint.x() - d->pressPoint.x(), Qt::XAxis, event)); + } + QQuickItemDelegate::mouseMoveEvent(event); +} + +#if QT_CONFIG(quicktemplates2_multitouch) +void QQuickSwitchDelegate::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickSwitchDelegate); + if (!keepTouchGrab() && event->type() == QEvent::TouchUpdate) { + for (const QTouchEvent::TouchPoint &point : event->points()) { + if (point.id() != d->touchId || point.state() != QEventPoint::Updated) + continue; + if (d->canDrag(point.position())) + setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.position().x() - d->pressPoint.x(), Qt::XAxis, &point)); + } + } + QQuickItemDelegate::touchEvent(event); +} +#endif + +QFont QQuickSwitchDelegate::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ListView); +} + +void QQuickSwitchDelegate::mirrorChange() +{ + QQuickItemDelegate::mirrorChange(); + emit visualPositionChanged(); +} + +void QQuickSwitchDelegate::nextCheckState() +{ + Q_D(QQuickSwitchDelegate); + if (keepMouseGrab() || keepTouchGrab()) { + d->toggle(d->position > 0.5); + // the checked state might not change => force a position update to + // avoid that the handle is left somewhere in the middle (QTBUG-57944) + setPosition(d->checked ? 1.0 : 0.0); + } else { + QQuickItemDelegate::nextCheckState(); + } +} + +void QQuickSwitchDelegate::buttonChange(ButtonChange change) +{ + Q_D(QQuickSwitchDelegate); + if (change == ButtonCheckedChange) + setPosition(d->checked ? 1.0 : 0.0); + else + QQuickAbstractButton::buttonChange(change); +} + +QT_END_NAMESPACE + +#include "moc_qquickswitchdelegate_p.cpp" diff --git a/src/quicktemplates2/qquickswitchdelegate_p.h b/src/quicktemplates2/qquickswitchdelegate_p.h new file mode 100644 index 0000000000..a2eb6f62ae --- /dev/null +++ b/src/quicktemplates2/qquickswitchdelegate_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSWITCHDELEGATE_P_H +#define QQUICKSWITCHDELEGATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickitemdelegate_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSwitchDelegatePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwitchDelegate : public QQuickItemDelegate +{ + Q_OBJECT + Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(qreal visualPosition READ visualPosition NOTIFY visualPositionChanged FINAL) + QML_NAMED_ELEMENT(SwitchDelegate) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickSwitchDelegate(QQuickItem *parent = nullptr); + + qreal position() const; + void setPosition(qreal position); + + qreal visualPosition() const; + +Q_SIGNALS: + void positionChanged(); + void visualPositionChanged(); + +protected: + void mouseMoveEvent(QMouseEvent *event) override; +#if QT_CONFIG(quicktemplates2_multitouch) + void touchEvent(QTouchEvent *event) override; +#endif + + QFont defaultFont() const override; + + void mirrorChange() override; + + void nextCheckState() override; + void buttonChange(ButtonChange change) override; + +private: + Q_DISABLE_COPY(QQuickSwitchDelegate) + Q_DECLARE_PRIVATE(QQuickSwitchDelegate) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSwitchDelegate) + +#endif // QQUICKSWITCHDELEGATE_P_H diff --git a/src/quicktemplates2/qquicktabbar.cpp b/src/quicktemplates2/qquicktabbar.cpp new file mode 100644 index 0000000000..0297324e01 --- /dev/null +++ b/src/quicktemplates2/qquicktabbar.cpp @@ -0,0 +1,508 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktabbar_p.h" +#include "qquicktabbutton_p.h" +#include "qquickcontainer_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype TabBar + \inherits Container +//! \instantiates QQuickTabBar + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-navigation + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Allows the user to switch between different views or subtasks. + + TabBar provides a tab-based navigation model. + + \image qtquickcontrols2-tabbar-wireframe.png + + TabBar is populated with TabButton controls, and can be used together with + any layout or container control that provides \c currentIndex -property, + such as \l StackLayout or \l SwipeView + + \snippet qtquickcontrols2-tabbar.qml 1 + + As shown above, TabBar is typically populated with a static set of tab buttons + that are defined inline as children of the tab bar. It is also possible to + \l {Container::addItem()}{add}, \l {Container::insertItem()}{insert}, + \l {Container::moveItem()}{move}, and \l {Container::removeItem()}{remove} + items dynamically at run time. The items can be accessed using + \l {Container::}{itemAt()} or \l {Container::}{contentChildren}. + + \include container-currentindex.qdocinc {file} {TabBar} {SwipeView} + + \section2 Resizing Tabs + + By default, TabBar resizes its buttons to fit the width of the control. + The available space is distributed equally to each button. The default + resizing behavior can be overridden by setting an explicit width for the + buttons. + + The following example illustrates how to keep each tab button at their + implicit size instead of being resized to fit the tabbar: + + \borderedimage qtquickcontrols2-tabbar-explicit.png + + \snippet qtquickcontrols2-tabbar-explicit.qml 1 + + \section2 Flickable Tabs + + If the total width of the buttons exceeds the available width of the tab bar, + it automatically becomes flickable. + + \image qtquickcontrols2-tabbar-flickable.png + + \snippet qtquickcontrols2-tabbar-flickable.qml 1 + + \sa TabButton, {Customizing TabBar}, {Navigation Controls}, {Container Controls}, + {Focus Management in Qt Quick Controls} +*/ + +class QQuickTabBarPrivate : public QQuickContainerPrivate +{ + Q_DECLARE_PUBLIC(QQuickTabBar) + +public: + void updateCurrentItem(); + void updateCurrentIndex(); + void updateLayout(); + + qreal getContentWidth() const override; + qreal getContentHeight() const override; + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::TabBar); } + + bool updatingLayout = false; + QQuickTabBar::Position position = QQuickTabBar::Header; +#if QT_CONFIG(wheelevent) + QPoint accumulatedAngleDelta; +#endif +}; + +class QQuickTabBarAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickTabBarAttached) + +public: + static QQuickTabBarAttachedPrivate *get(QQuickTabBarAttached *attached) + { + return attached->d_func(); + } + + void update(QQuickTabBar *tabBar, int index); + + int index = -1; + QQuickTabBar *tabBar = nullptr; +}; + +void QQuickTabBarPrivate::updateCurrentItem() +{ + QQuickTabButton *button = qobject_cast<QQuickTabButton *>(contentModel->get(currentIndex)); + if (button) + button->setChecked(true); +} + +void QQuickTabBarPrivate::updateCurrentIndex() +{ + Q_Q(QQuickTabBar); + QQuickTabButton *button = qobject_cast<QQuickTabButton *>(q->sender()); + if (button && button->isChecked()) + q->setCurrentIndex(contentModel->indexOf(button, nullptr)); +} + +void QQuickTabBarPrivate::updateLayout() +{ + Q_Q(QQuickTabBar); + const int count = contentModel->count(); + if (count <= 0 || !contentItem) + return; + + qreal reservedWidth = 0; + int resizableCount = 0; + + QList<QQuickItem *> allItems; + allItems.reserve(count); + + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) { + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!p->widthValid()) + ++resizableCount; + else + reservedWidth += item->width(); + allItems += item; + } + } + + const qreal totalSpacing = qMax(0, count - 1) * spacing; + const qreal itemWidth = (contentItem->width() - reservedWidth - totalSpacing) / qMax(1, resizableCount); + + updatingLayout = true; + for (QQuickItem *item : qAsConst(allItems)) { + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!p->widthValid()) { + item->setWidth(itemWidth); + p->widthValidFlag = false; + } + if (!p->heightValid()) { + item->setHeight(contentHeight); + p->heightValidFlag = false; + } else { + item->setY((contentHeight - item->height()) / 2); + } + } + updatingLayout = false; +} + +qreal QQuickTabBarPrivate::getContentWidth() const +{ + Q_Q(const QQuickTabBar); + const int count = contentModel->count(); + qreal totalWidth = qMax(0, count - 1) * spacing; + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) { + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + if (!p->widthValid()) + totalWidth += item->implicitWidth(); + else + totalWidth += item->width(); + } + } + return totalWidth; +} + +qreal QQuickTabBarPrivate::getContentHeight() const +{ + Q_Q(const QQuickTabBar); + const int count = contentModel->count(); + qreal maxHeight = 0; + for (int i = 0; i < count; ++i) { + QQuickItem *item = q->itemAt(i); + if (item) + maxHeight = qMax(maxHeight, item->implicitHeight()); + } + return maxHeight; +} + +void QQuickTabBarPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + QQuickContainerPrivate::itemGeometryChanged(item, change, diff); + if (!updatingLayout) { + if (change.sizeChange()) + updateImplicitContentSize(); + updateLayout(); + } +} + +void QQuickTabBarPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + QQuickContainerPrivate::itemImplicitWidthChanged(item); + if (item != contentItem) + updateImplicitContentWidth(); +} + +void QQuickTabBarPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + QQuickContainerPrivate::itemImplicitHeightChanged(item); + if (item != contentItem) + updateImplicitContentHeight(); +} + +QQuickTabBar::QQuickTabBar(QQuickItem *parent) + : QQuickContainer(*(new QQuickTabBarPrivate), parent) +{ + Q_D(QQuickTabBar); + d->changeTypes |= QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; + setFlag(ItemIsFocusScope); + QObjectPrivate::connect(this, &QQuickTabBar::currentIndexChanged, d, &QQuickTabBarPrivate::updateCurrentItem); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::TabBar::position + + This property holds the position of the tab bar. + + \note If the tab bar is assigned as a header or footer of \l ApplicationWindow + or \l Page, the appropriate position is set automatically. + + Possible values: + \value TabBar.Header The tab bar is at the top, as a window or page header. + \value TabBar.Footer The tab bar is at the bottom, as a window or page footer. + + The default value is style-specific. + + \sa ApplicationWindow::header, ApplicationWindow::footer, Page::header, Page::footer +*/ +QQuickTabBar::Position QQuickTabBar::position() const +{ + Q_D(const QQuickTabBar); + return d->position; +} + +void QQuickTabBar::setPosition(Position position) +{ + Q_D(QQuickTabBar); + if (d->position == position) + return; + + d->position = position; + emit positionChanged(); +} + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty real QtQuick.Controls::TabBar::contentWidth + + This property holds the content width. It is used for calculating the total + implicit width of the tab bar. + + \note This property is available in TabBar since QtQuick.Controls 2.2 (Qt 5.9), + but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12). + + \sa Container::contentWidth +*/ + +/*! + \since QtQuick.Controls 2.2 (Qt 5.9) + \qmlproperty real QtQuick.Controls::TabBar::contentHeight + + This property holds the content height. It is used for calculating the total + implicit height of the tab bar. + + \note This property is available in TabBar since QtQuick.Controls 2.2 (Qt 5.9), + but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12). + + \sa Container::contentHeight +*/ + +QQuickTabBarAttached *QQuickTabBar::qmlAttachedProperties(QObject *object) +{ + return new QQuickTabBarAttached(object); +} + +void QQuickTabBar::updatePolish() +{ + Q_D(QQuickTabBar); + QQuickContainer::updatePolish(); + d->updateLayout(); +} + +void QQuickTabBar::componentComplete() +{ + Q_D(QQuickTabBar); + QQuickContainer::componentComplete(); + d->updateCurrentItem(); + d->updateLayout(); +} + +void QQuickTabBar::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickTabBar); + QQuickContainer::geometryChange(newGeometry, oldGeometry); + d->updateLayout(); +} + +bool QQuickTabBar::isContent(QQuickItem *item) const +{ + return qobject_cast<QQuickTabButton *>(item); +} + +void QQuickTabBar::itemAdded(int index, QQuickItem *item) +{ + Q_D(QQuickTabBar); + Q_UNUSED(index); + QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-55129 + if (QQuickTabButton *button = qobject_cast<QQuickTabButton *>(item)) + QObjectPrivate::connect(button, &QQuickTabButton::checkedChanged, d, &QQuickTabBarPrivate::updateCurrentIndex); + QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(qmlAttachedPropertiesObject<QQuickTabBar>(item)); + if (attached) + QQuickTabBarAttachedPrivate::get(attached)->update(this, index); + d->updateImplicitContentSize(); + if (isComponentComplete()) + polish(); +} + +void QQuickTabBar::itemMoved(int index, QQuickItem *item) +{ + QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(qmlAttachedPropertiesObject<QQuickTabBar>(item)); + if (attached) + QQuickTabBarAttachedPrivate::get(attached)->update(this, index); +} + +void QQuickTabBar::itemRemoved(int index, QQuickItem *item) +{ + Q_D(QQuickTabBar); + Q_UNUSED(index); + if (QQuickTabButton *button = qobject_cast<QQuickTabButton *>(item)) + QObjectPrivate::disconnect(button, &QQuickTabButton::checkedChanged, d, &QQuickTabBarPrivate::updateCurrentIndex); + QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(qmlAttachedPropertiesObject<QQuickTabBar>(item)); + if (attached) + QQuickTabBarAttachedPrivate::get(attached)->update(nullptr, -1); + d->updateImplicitContentSize(); + if (isComponentComplete()) + polish(); +} + +#if QT_CONFIG(wheelevent) +void QQuickTabBar::wheelEvent(QWheelEvent *event) +{ + Q_D(QQuickTabBar); + QQuickContainer::wheelEvent(event); + if (d->wheelEnabled) { + d->accumulatedAngleDelta += event->angleDelta(); + int xSteps = d->accumulatedAngleDelta.x() / QWheelEvent::DefaultDeltasPerStep; + int ySteps = d->accumulatedAngleDelta.y() / QWheelEvent::DefaultDeltasPerStep; + if (xSteps > 0 || ySteps > 0) { + decrementCurrentIndex(); + d->accumulatedAngleDelta = QPoint(); + } else if (xSteps < 0 || ySteps < 0) { + incrementCurrentIndex(); + d->accumulatedAngleDelta = QPoint(); + } + } +} +#endif + +QFont QQuickTabBar::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::TabBar); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickTabBar::accessibleRole() const +{ + return QAccessible::PageTabList; +} +#endif + +/*! + \qmlattachedproperty int QtQuick.Controls::TabBar::index + \since QtQuick.Controls 2.3 (Qt 5.10) + \readonly + + This attached property holds the index of each tab button in the TabBar. + + It is attached to each tab button of the TabBar. +*/ + +/*! + \qmlattachedproperty TabBar QtQuick.Controls::TabBar::tabBar + \since QtQuick.Controls 2.3 (Qt 5.10) + \readonly + + This attached property holds the tab bar that manages this tab button. + + It is attached to each tab button of the TabBar. +*/ + +/*! + \qmlattachedproperty enumeration QtQuick.Controls::TabBar::position + \since QtQuick.Controls 2.3 (Qt 5.10) + \readonly + + This attached property holds the position of the tab bar. + + It is attached to each tab button of the TabBar. + + Possible values: + \value TabBar.Header The tab bar is at the top, as a window or page header. + \value TabBar.Footer The tab bar is at the bottom, as a window or page footer. +*/ + +void QQuickTabBarAttachedPrivate::update(QQuickTabBar *newTabBar, int newIndex) +{ + Q_Q(QQuickTabBarAttached); + const int oldIndex = index; + const QQuickTabBar *oldTabBar = tabBar; + const QQuickTabBar::Position oldPos = q->position(); + + index = newIndex; + tabBar = newTabBar; + + if (oldTabBar != newTabBar) { + if (oldTabBar) + QObject::disconnect(oldTabBar, &QQuickTabBar::positionChanged, q, &QQuickTabBarAttached::positionChanged); + if (newTabBar) + QObject::connect(newTabBar, &QQuickTabBar::positionChanged, q, &QQuickTabBarAttached::positionChanged); + emit q->tabBarChanged(); + } + + if (oldIndex != newIndex) + emit q->indexChanged(); + if (oldPos != q->position()) + emit q->positionChanged(); +} + +QQuickTabBarAttached::QQuickTabBarAttached(QObject *parent) + : QObject(*(new QQuickTabBarAttachedPrivate), parent) +{ +} + +int QQuickTabBarAttached::index() const +{ + Q_D(const QQuickTabBarAttached); + return d->index; +} + +QQuickTabBar *QQuickTabBarAttached::tabBar() const +{ + Q_D(const QQuickTabBarAttached); + return d->tabBar; +} + +QQuickTabBar::Position QQuickTabBarAttached::position() const +{ + Q_D(const QQuickTabBarAttached); + if (!d->tabBar) + return QQuickTabBar::Header; + return d->tabBar->position(); +} + +QT_END_NAMESPACE + +#include "moc_qquicktabbar_p.cpp" diff --git a/src/quicktemplates2/qquicktabbar_p.h b/src/quicktemplates2/qquicktabbar_p.h new file mode 100644 index 0000000000..2da6d4a77b --- /dev/null +++ b/src/quicktemplates2/qquicktabbar_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTABBAR_P_H +#define QQUICKTABBAR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickTabBarPrivate; +class QQuickTabBarAttached; +class QQuickTabBarAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTabBar : public QQuickContainer +{ + Q_OBJECT + Q_PROPERTY(Position position READ position WRITE setPosition NOTIFY positionChanged FINAL) + QML_NAMED_ELEMENT(TabBar) + QML_ATTACHED(QQuickTabBarAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickTabBar(QQuickItem *parent = nullptr); + + enum Position { + Header, + Footer + }; + Q_ENUM(Position) + + Position position() const; + void setPosition(Position position); + + static QQuickTabBarAttached *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void positionChanged(); + +protected: + void updatePolish() override; + void componentComplete() override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + bool isContent(QQuickItem *item) const override; + void itemAdded(int index, QQuickItem *item) override; + void itemMoved(int index, QQuickItem *item) override; + void itemRemoved(int index, QQuickItem *item) override; +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickTabBar) + Q_DECLARE_PRIVATE(QQuickTabBar) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTabBarAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL) + Q_PROPERTY(QQuickTabBar *tabBar READ tabBar NOTIFY tabBarChanged FINAL) + Q_PROPERTY(QQuickTabBar::Position position READ position NOTIFY positionChanged FINAL) + +public: + explicit QQuickTabBarAttached(QObject *parent = nullptr); + + int index() const; + QQuickTabBar *tabBar() const; + QQuickTabBar::Position position() const; + +Q_SIGNALS: + void indexChanged(); + void tabBarChanged(); + void positionChanged(); + +private: + Q_DISABLE_COPY(QQuickTabBarAttached) + Q_DECLARE_PRIVATE(QQuickTabBarAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickTabBar) +QML_DECLARE_TYPEINFO(QQuickTabBar, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKTABBAR_P_H diff --git a/src/quicktemplates2/qquicktabbutton.cpp b/src/quicktemplates2/qquicktabbutton.cpp new file mode 100644 index 0000000000..0faa2150ea --- /dev/null +++ b/src/quicktemplates2/qquicktabbutton.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktabbutton_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickabstractbutton_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype TabButton + \inherits AbstractButton +//! \instantiates QQuickTabButton + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-navigation + \brief Button with a look suitable for a TabBar. + + \image qtquickcontrols2-tabbutton.png + + TabButton is used in conjunction with a \l TabBar. + + \snippet qtquickcontrols2-tabbutton.qml 1 + + TabButton inherits its API from AbstractButton. For instance, you can set + \l {AbstractButton::text}{text}, and react to \l {AbstractButton::clicked}{clicks} + using the AbstractButton API. + + \sa TabBar, {Customizing TabButton}, {Button Controls}, {Navigation Controls} +*/ + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTabButtonPrivate : public QQuickAbstractButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickTabButton) + +public: + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::TabBar); } +}; + +QQuickTabButton::QQuickTabButton(QQuickItem *parent) + : QQuickAbstractButton(*(new QQuickTabButtonPrivate), parent) +{ + setCheckable(true); + setAutoExclusive(true); +} + +QFont QQuickTabButton::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::TabBar); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickTabButton::accessibleRole() const +{ + return QAccessible::PageTab; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquicktabbutton_p.cpp" diff --git a/src/quicktemplates2/qquicktabbutton_p.h b/src/quicktemplates2/qquicktabbutton_p.h new file mode 100644 index 0000000000..302180c47d --- /dev/null +++ b/src/quicktemplates2/qquicktabbutton_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTABBUTTON_P_H +#define QQUICKTABBUTTON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickabstractbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickTabButtonPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTabButton : public QQuickAbstractButton +{ + Q_OBJECT + QML_NAMED_ELEMENT(TabButton) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickTabButton(QQuickItem *parent = nullptr); + +protected: + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DECLARE_PRIVATE(QQuickTabButton) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickTabButton) + +#endif // QQUICKTABBUTTON_P_H diff --git a/src/quicktemplates2/qquicktextarea.cpp b/src/quicktemplates2/qquicktextarea.cpp new file mode 100644 index 0000000000..d28718f2fb --- /dev/null +++ b/src/quicktemplates2/qquicktextarea.cpp @@ -0,0 +1,1181 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktextarea_p.h" +#include "qquicktextarea_p_p.h" +#include "qquickcontrol_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickscrollview_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickclipnode_p.h> +#include <QtQuick/private/qquickflickable_p.h> + +#if QT_CONFIG(accessibility) +#include <QtQuick/private/qquickaccessibleattached_p.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \qmltype TextArea + \inherits TextEdit +//! \instantiates QQuickTextArea + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-input + \brief Multi-line text input area. + + TextArea is a multi-line text editor. TextArea extends TextEdit with + a \l {placeholderText}{placeholder text} functionality, and adds decoration. + + \image qtquickcontrols2-textarea.png + + \code + TextArea { + placeholderText: qsTr("Enter description") + } + \endcode + + TextArea is not scrollable by itself. Especially on screen-size constrained + platforms, it is often preferable to make entire application pages scrollable. + On such a scrollable page, a non-scrollable TextArea might behave better than + nested scrollable controls. Notice, however, that in such a scenario, the background + decoration of the TextArea scrolls together with the rest of the scrollable + content. + + \section2 Scrollable TextArea + + If you want to make a TextArea scrollable, for example, when it covers + an entire application page, it can be placed inside a \l ScrollView. + + \image qtquickcontrols2-textarea-scrollable.png + + \snippet qtquickcontrols2-textarea-scrollable.qml 1 + + A TextArea that is placed inside a \l ScrollView does the following: + + \list + \li Sets the content size automatically + \li Ensures that the background decoration stays in place + \li Clips the content + \endlist + + \section2 Tab Focus + + By default, pressing the tab key while TextArea has + \l {Item::activeFocus}{active focus} results in a tab character being input + into the control itself. To make tab pass active focus onto another item, + use the attached \l KeyNavigation properties: + + \code + TextField { + id: textField + } + + TextArea { + KeyNavigation.priority: KeyNavigation.BeforeItem + KeyNavigation.tab: textField + } + \endcode + + \sa TextField, {Customizing TextArea}, {Input Controls} +*/ + +/*! + \qmlsignal QtQuick.Controls::TextArea::pressAndHold(MouseEvent event) + + This signal is emitted when there is a long press (the delay depends on the platform plugin). + The \a event parameter provides information about the press, including the x and y + coordinates of the press, and which button is pressed. + + \sa pressed, released +*/ + +/*! + \qmlsignal QtQuick.Controls::TextArea::pressed(MouseEvent event) + \since QtQuick.Controls 2.1 (Qt 5.8) + + This signal is emitted when the text area is pressed by the user. + The \a event parameter provides information about the press, + including the x and y coordinates of the press, and which button is pressed. + + \sa released, pressAndHold +*/ + +/*! + \qmlsignal QtQuick.Controls::TextArea::released(MouseEvent event) + \since QtQuick.Controls 2.1 (Qt 5.8) + + This signal is emitted when the text area is released by the user. + The \a event parameter provides information about the release, + including the x and y coordinates of the press, and which button + is pressed. + + \sa pressed, pressAndHold +*/ + +QQuickTextAreaPrivate::QQuickTextAreaPrivate() +{ +#if QT_CONFIG(accessibility) + QAccessible::installActivationObserver(this); +#endif +} + +QQuickTextAreaPrivate::~QQuickTextAreaPrivate() +{ +#if QT_CONFIG(accessibility) + QAccessible::removeActivationObserver(this); +#endif +} + +void QQuickTextAreaPrivate::setTopInset(qreal value, bool reset) +{ + Q_Q(QQuickTextArea); + const QMarginsF oldInset = getInset(); + extra.value().topInset = value; + extra.value().hasTopInset = !reset; + if (!qFuzzyCompare(oldInset.top(), value)) { + emit q->topInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickTextAreaPrivate::setLeftInset(qreal value, bool reset) +{ + Q_Q(QQuickTextArea); + const QMarginsF oldInset = getInset(); + extra.value().leftInset = value; + extra.value().hasLeftInset = !reset; + if (!qFuzzyCompare(oldInset.left(), value)) { + emit q->leftInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickTextAreaPrivate::setRightInset(qreal value, bool reset) +{ + Q_Q(QQuickTextArea); + const QMarginsF oldInset = getInset(); + extra.value().rightInset = value; + extra.value().hasRightInset = !reset; + if (!qFuzzyCompare(oldInset.right(), value)) { + emit q->rightInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickTextAreaPrivate::setBottomInset(qreal value, bool reset) +{ + Q_Q(QQuickTextArea); + const QMarginsF oldInset = getInset(); + extra.value().bottomInset = value; + extra.value().hasBottomInset = !reset; + if (!qFuzzyCompare(oldInset.bottom(), value)) { + emit q->bottomInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickTextAreaPrivate::resizeBackground() +{ + if (!background) + return; + + resizingBackground = true; + + // When using the attached property TextArea.flickable, we reparent the background out + // of TextArea and into the Flickable since we don't want the background to move while + // flicking. This means that the size of the background should also follow the size of + // the Flickable rather than the size of the TextArea. + const auto flickable = qobject_cast<QQuickFlickable *>(background->parentItem()); + + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + if (((!p->widthValid() || !extra.isAllocated() || !extra->hasBackgroundWidth) && qFuzzyIsNull(background->x())) + || (extra.isAllocated() && (extra->hasLeftInset || extra->hasRightInset))) { + const qreal bgWidth = flickable ? flickable->width() : width; + background->setX(getLeftInset()); + background->setWidth(bgWidth - getLeftInset() - getRightInset()); + } + + if (((!p->heightValid() || !extra.isAllocated() || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y())) + || (extra.isAllocated() && (extra->hasTopInset || extra->hasBottomInset))) { + const qreal bgHeight = flickable ? flickable->height() : height; + background->setY(getTopInset()); + background->setHeight(bgHeight - getTopInset() - getBottomInset()); + } + + resizingBackground = false; +} + +/*! + \internal + + Determine which font is implicitly imposed on this control by its ancestors + and QGuiApplication::font, resolve this against its own font (attributes from + the implicit font are copied over). Then propagate this font to this + control's children. +*/ +void QQuickTextAreaPrivate::resolveFont() +{ + Q_Q(QQuickTextArea); + inheritFont(QQuickControlPrivate::parentFont(q)); +} + +void QQuickTextAreaPrivate::inheritFont(const QFont &font) +{ + QFont parentFont = extra.isAllocated() ? extra->requestedFont.resolve(font) : font; + parentFont.setResolveMask(extra.isAllocated() ? extra->requestedFont.resolveMask() | font.resolveMask() : font.resolveMask()); + + const QFont defaultFont = QQuickTheme::font(QQuickTheme::TextArea); + QFont resolvedFont = parentFont.resolve(defaultFont); + + setFont_helper(resolvedFont); +} + +/*! + \internal + + Assign \a font to this control, and propagate it to all children. +*/ +void QQuickTextAreaPrivate::updateFont(const QFont &font) +{ + Q_Q(QQuickTextArea); + QFont oldFont = sourceFont; + q->QQuickTextEdit::setFont(font); + + QQuickControlPrivate::updateFontRecur(q, font); + + if (oldFont != font) + emit q->fontChanged(); +} + +#if QT_CONFIG(quicktemplates2_hover) +void QQuickTextAreaPrivate::updateHoverEnabled(bool enabled, bool xplicit) +{ + Q_Q(QQuickTextArea); + if (!xplicit && explicitHoverEnabled) + return; + + bool wasEnabled = q->isHoverEnabled(); + explicitHoverEnabled = xplicit; + if (wasEnabled != enabled) { + q->setAcceptHoverEvents(enabled); + QQuickControlPrivate::updateHoverEnabledRecur(q, enabled); + emit q->hoverEnabledChanged(); + } +} +#endif + +void QQuickTextAreaPrivate::attachFlickable(QQuickFlickable *item) +{ + Q_Q(QQuickTextArea); + flickable = item; + q->setParentItem(flickable->contentItem()); + + if (background) + background->setParentItem(flickable); + + QObjectPrivate::connect(q, &QQuickTextArea::contentSizeChanged, this, &QQuickTextAreaPrivate::resizeFlickableContent); + QObjectPrivate::connect(q, &QQuickTextEdit::cursorRectangleChanged, this, &QQuickTextAreaPrivate::ensureCursorVisible); + + QObject::connect(flickable, &QQuickFlickable::contentXChanged, q, &QQuickItem::update); + QObject::connect(flickable, &QQuickFlickable::contentYChanged, q, &QQuickItem::update); + + QQuickItemPrivate::get(flickable)->updateOrAddGeometryChangeListener(this, QQuickGeometryChange::Size); + QQuickItemPrivate::get(flickable)->addItemChangeListener(this, QQuickItemPrivate::Destroyed); + QObjectPrivate::connect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl); + QObjectPrivate::connect(flickable, &QQuickFlickable::contentHeightChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl); + + resizeFlickableControl(); +} + +void QQuickTextAreaPrivate::detachFlickable() +{ + Q_Q(QQuickTextArea); + q->setParentItem(nullptr); + if (background && background->parentItem() == flickable) + background->setParentItem(q); + + QObjectPrivate::disconnect(q, &QQuickTextArea::contentSizeChanged, this, &QQuickTextAreaPrivate::resizeFlickableContent); + QObjectPrivate::disconnect(q, &QQuickTextEdit::cursorRectangleChanged, this, &QQuickTextAreaPrivate::ensureCursorVisible); + + QObject::disconnect(flickable, &QQuickFlickable::contentXChanged, q, &QQuickItem::update); + QObject::disconnect(flickable, &QQuickFlickable::contentYChanged, q, &QQuickItem::update); + + QQuickItemPrivate::get(flickable)->updateOrRemoveGeometryChangeListener(this, QQuickGeometryChange::Nothing); + QQuickItemPrivate::get(flickable)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed); + QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl); + QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentHeightChanged, this, &QQuickTextAreaPrivate::resizeFlickableControl); + + flickable = nullptr; + + resizeBackground(); +} + +void QQuickTextAreaPrivate::ensureCursorVisible() +{ + Q_Q(QQuickTextArea); + if (!flickable) + return; + + const qreal cx = flickable->contentX(); + const qreal cy = flickable->contentY(); + const qreal w = flickable->width(); + const qreal h = flickable->height(); + + const qreal tp = q->topPadding(); + const qreal lp = q->leftPadding(); + const QRectF cr = q->cursorRectangle(); + + if (cr.left() <= cx + lp) { + flickable->setContentX(cr.left() - lp); + } else { + // calculate the rectangle of the next character and ensure that + // it's visible if it's on the same line with the cursor + const qreal rp = q->rightPadding(); + const QRectF nr = q->cursorPosition() < q->length() ? q->positionToRectangle(q->cursorPosition() + 1) : QRectF(); + if (qFuzzyCompare(nr.y(), cr.y()) && nr.right() >= cx + lp + w - rp) + flickable->setContentX(nr.right() - w + rp); + else if (cr.right() >= cx + lp + w - rp) + flickable->setContentX(cr.right() - w + rp); + } + + if (cr.top() <= cy + tp) { + flickable->setContentY(cr.top() - tp); + } else { + const qreal bp = q->bottomPadding(); + if (cr.bottom() >= cy + tp + h - bp) + flickable->setContentY(cr.bottom() - h + bp); + } +} + +void QQuickTextAreaPrivate::resizeFlickableControl() +{ + Q_Q(QQuickTextArea); + if (!flickable) + return; + + const qreal w = wrapMode == QQuickTextArea::NoWrap ? qMax(flickable->width(), flickable->contentWidth()) : flickable->width(); + const qreal h = qMax(flickable->height(), flickable->contentHeight()); + q->setSize(QSizeF(w, h)); + + resizeBackground(); +} + +void QQuickTextAreaPrivate::resizeFlickableContent() +{ + Q_Q(QQuickTextArea); + if (!flickable) + return; + + flickable->setContentWidth(q->contentWidth() + q->leftPadding() + q->rightPadding()); + flickable->setContentHeight(q->contentHeight() + q->topPadding() + q->bottomPadding()); +} + +void QQuickTextAreaPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + Q_UNUSED(diff); + if (!resizingBackground && item == background) { + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + // Only set hasBackgroundWidth/Height if it was a width/height change, + // otherwise we're prevented from setting a width/height in the future. + if (change.widthChange()) + extra.value().hasBackgroundWidth = p->widthValid(); + if (change.heightChange()) + extra.value().hasBackgroundHeight = p->heightValid(); + } + + if (flickable) + resizeFlickableControl(); + else + resizeBackground(); +} + +qreal QQuickTextAreaPrivate::getImplicitWidth() const +{ + return QQuickItemPrivate::getImplicitWidth(); +} + +qreal QQuickTextAreaPrivate::getImplicitHeight() const +{ + return QQuickItemPrivate::getImplicitHeight(); +} + +void QQuickTextAreaPrivate::implicitWidthChanged() +{ + Q_Q(QQuickTextArea); + QQuickItemPrivate::implicitWidthChanged(); + emit q->implicitWidthChanged3(); +} + +void QQuickTextAreaPrivate::implicitHeightChanged() +{ + Q_Q(QQuickTextArea); + QQuickItemPrivate::implicitHeightChanged(); + emit q->implicitHeightChanged3(); +} + +void QQuickTextAreaPrivate::readOnlyChanged(bool isReadOnly) +{ + Q_UNUSED(isReadOnly); +#if QT_CONFIG(accessibility) + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(q_func())) + accessibleAttached->set_readOnly(isReadOnly); +#endif +#if QT_CONFIG(cursor) + q_func()->setCursor(isReadOnly && !selectByMouse ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif +} + +#if QT_CONFIG(accessibility) +void QQuickTextAreaPrivate::accessibilityActiveChanged(bool active) +{ + if (!active) + return; + + Q_Q(QQuickTextArea); + QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true)); + Q_ASSERT(accessibleAttached); + accessibleAttached->setRole(accessibleRole()); + accessibleAttached->set_readOnly(q->isReadOnly()); + accessibleAttached->setDescription(placeholder); +} + +QAccessible::Role QQuickTextAreaPrivate::accessibleRole() const +{ + return QAccessible::EditableText; +} +#endif + +static inline QString backgroundName() { return QStringLiteral("background"); } + +void QQuickTextAreaPrivate::cancelBackground() +{ + Q_Q(QQuickTextArea); + quickCancelDeferred(q, backgroundName()); +} + +void QQuickTextAreaPrivate::executeBackground(bool complete) +{ + Q_Q(QQuickTextArea); + if (background.wasExecuted()) + return; + + if (!background || complete) + quickBeginDeferred(q, backgroundName(), background); + if (complete) + quickCompleteDeferred(q, backgroundName(), background); +} + +void QQuickTextAreaPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickTextArea); + if (item == background) + emit q->implicitBackgroundWidthChanged(); +} + +void QQuickTextAreaPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickTextArea); + if (item == background) + emit q->implicitBackgroundHeightChanged(); +} + +void QQuickTextAreaPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickTextArea); + if (item == background) { + background = nullptr; + emit q->implicitBackgroundWidthChanged(); + emit q->implicitBackgroundHeightChanged(); + } else if (item == flickable) { + detachFlickable(); + } +} + +QPalette QQuickTextAreaPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::TextArea); +} + +QQuickTextArea::QQuickTextArea(QQuickItem *parent) + : QQuickTextEdit(*(new QQuickTextAreaPrivate), parent) +{ + Q_D(QQuickTextArea); + setActiveFocusOnTab(true); + setAcceptedMouseButtons(Qt::AllButtons); + d->setImplicitResizeEnabled(false); + d->pressHandler.control = this; +#if QT_CONFIG(cursor) + setCursor(Qt::IBeamCursor); +#endif + QObjectPrivate::connect(this, &QQuickTextEdit::readOnlyChanged, + d, &QQuickTextAreaPrivate::readOnlyChanged); +} + +QQuickTextArea::~QQuickTextArea() +{ + Q_D(QQuickTextArea); + if (d->flickable) + d->detachFlickable(); + QQuickControlPrivate::removeImplicitSizeListener(d->background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); +} + +QQuickTextAreaAttached *QQuickTextArea::qmlAttachedProperties(QObject *object) +{ + return new QQuickTextAreaAttached(object); +} + +QFont QQuickTextArea::font() const +{ + return QQuickTextEdit::font(); +} + +void QQuickTextArea::setFont(const QFont &font) +{ + Q_D(QQuickTextArea); + if (d->extra.value().requestedFont.resolveMask() == font.resolveMask() && d->extra.value().requestedFont == font) + return; + + d->extra.value().requestedFont = font; + d->resolveFont(); +} + +/*! + \qmlproperty Item QtQuick.Controls::TextArea::background + + This property holds the background item. + + \input qquickcontrol-background.qdocinc notes + + \sa {Customizing TextArea} +*/ +QQuickItem *QQuickTextArea::background() const +{ + QQuickTextAreaPrivate *d = const_cast<QQuickTextAreaPrivate *>(d_func()); + if (!d->background) + d->executeBackground(); + return d->background; +} + +void QQuickTextArea::setBackground(QQuickItem *background) +{ + Q_D(QQuickTextArea); + if (d->background == background) + return; + + if (!d->background.isExecuting()) + d->cancelBackground(); + + const qreal oldImplicitBackgroundWidth = implicitBackgroundWidth(); + const qreal oldImplicitBackgroundHeight = implicitBackgroundHeight(); + + if (d->extra.isAllocated()) { + d->extra.value().hasBackgroundWidth = false; + d->extra.value().hasBackgroundHeight = false; + } + + QQuickControlPrivate::removeImplicitSizeListener(d->background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + QQuickControlPrivate::hideOldItem(d->background); + d->background = background; + + if (background) { + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + if (p->widthValid() || p->heightValid()) { + d->extra.value().hasBackgroundWidth = p->widthValid(); + d->extra.value().hasBackgroundHeight = p->heightValid(); + } + if (d->flickable) + background->setParentItem(d->flickable); + else + background->setParentItem(this); + if (qFuzzyIsNull(background->z())) + background->setZ(-1); + if (isComponentComplete()) + d->resizeBackground(); + QQuickControlPrivate::addImplicitSizeListener(background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + } + + if (!qFuzzyCompare(oldImplicitBackgroundWidth, implicitBackgroundWidth())) + emit implicitBackgroundWidthChanged(); + if (!qFuzzyCompare(oldImplicitBackgroundHeight, implicitBackgroundHeight())) + emit implicitBackgroundHeightChanged(); + if (!d->background.isExecuting()) + emit backgroundChanged(); +} + +/*! + \qmlproperty string QtQuick.Controls::TextArea::placeholderText + + This property holds the short hint that is displayed in the text area before + the user enters a value. +*/ +QString QQuickTextArea::placeholderText() const +{ + Q_D(const QQuickTextArea); + return d->placeholder; +} + +void QQuickTextArea::setPlaceholderText(const QString &text) +{ + Q_D(QQuickTextArea); + if (d->placeholder == text) + return; + + d->placeholder = text; +#if QT_CONFIG(accessibility) + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this)) + accessibleAttached->setDescription(text); +#endif + emit placeholderTextChanged(); +} + +/*! + \qmlproperty color QtQuick.Controls::TextArea::placeholderTextColor + \since QtQuick.Controls 2.5 (Qt 5.12) + + This property holds the color of placeholderText. + + \sa placeholderText +*/ +QColor QQuickTextArea::placeholderTextColor() const +{ + Q_D(const QQuickTextArea); + return d->placeholderColor; +} + +void QQuickTextArea::setPlaceholderTextColor(const QColor &color) +{ + Q_D(QQuickTextArea); + if (d->placeholderColor == color) + return; + + d->placeholderColor = color; + emit placeholderTextColorChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::TextArea::focusReason + + \include qquickcontrol-focusreason.qdocinc +*/ +Qt::FocusReason QQuickTextArea::focusReason() const +{ + Q_D(const QQuickTextArea); + return d->focusReason; +} + +void QQuickTextArea::setFocusReason(Qt::FocusReason reason) +{ + Q_D(QQuickTextArea); + if (d->focusReason == reason) + return; + + d->focusReason = reason; + emit focusReasonChanged(); +} + +bool QQuickTextArea::contains(const QPointF &point) const +{ + Q_D(const QQuickTextArea); + if (d->flickable && !d->flickable->contains(d->flickable->mapFromItem(this, point))) + return false; + return QQuickTextEdit::contains(point); +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlproperty bool QtQuick.Controls::TextArea::hovered + \readonly + + This property holds whether the text area is hovered. + + \sa hoverEnabled +*/ +bool QQuickTextArea::isHovered() const +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(const QQuickTextArea); + return d->hovered; +#else + return false; +#endif +} + +void QQuickTextArea::setHovered(bool hovered) +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickTextArea); + if (hovered == d->hovered) + return; + + d->hovered = hovered; + emit hoveredChanged(); +#else + Q_UNUSED(hovered); +#endif +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlproperty bool QtQuick.Controls::TextArea::hoverEnabled + + This property determines whether the text area accepts hover events. The default value is \c true. + + \sa hovered +*/ +bool QQuickTextArea::isHoverEnabled() const +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(const QQuickTextArea); + return d->hoverEnabled; +#else + return false; +#endif +} + +void QQuickTextArea::setHoverEnabled(bool enabled) +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickTextArea); + if (d->explicitHoverEnabled && enabled == d->hoverEnabled) + return; + + d->updateHoverEnabled(enabled, true); // explicit=true +#else + Q_UNUSED(enabled); +#endif +} + +void QQuickTextArea::resetHoverEnabled() +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickTextArea); + if (!d->explicitHoverEnabled) + return; + + d->explicitHoverEnabled = false; + d->updateHoverEnabled(QQuickControlPrivate::calcHoverEnabled(d->parentItem), false); // explicit=false +#endif +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextArea::implicitBackgroundWidth + \readonly + + This property holds the implicit background width. + + The value is equal to \c {background ? background.implicitWidth : 0}. + + \sa implicitBackgroundHeight +*/ +qreal QQuickTextArea::implicitBackgroundWidth() const +{ + Q_D(const QQuickTextArea); + if (!d->background) + return 0; + return d->background->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextArea::implicitBackgroundHeight + \readonly + + This property holds the implicit background height. + + The value is equal to \c {background ? background.implicitHeight : 0}. + + \sa implicitBackgroundWidth +*/ +qreal QQuickTextArea::implicitBackgroundHeight() const +{ + Q_D(const QQuickTextArea); + if (!d->background) + return 0; + return d->background->implicitHeight(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextArea::topInset + + This property holds the top inset for the background. + + \sa {Control Layout}, bottomInset +*/ +qreal QQuickTextArea::topInset() const +{ + Q_D(const QQuickTextArea); + return d->getTopInset(); +} + +void QQuickTextArea::setTopInset(qreal inset) +{ + Q_D(QQuickTextArea); + d->setTopInset(inset); +} + +void QQuickTextArea::resetTopInset() +{ + Q_D(QQuickTextArea); + d->setTopInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextArea::leftInset + + This property holds the left inset for the background. + + \sa {Control Layout}, rightInset +*/ +qreal QQuickTextArea::leftInset() const +{ + Q_D(const QQuickTextArea); + return d->getLeftInset(); +} + +void QQuickTextArea::setLeftInset(qreal inset) +{ + Q_D(QQuickTextArea); + d->setLeftInset(inset); +} + +void QQuickTextArea::resetLeftInset() +{ + Q_D(QQuickTextArea); + d->setLeftInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextArea::rightInset + + This property holds the right inset for the background. + + \sa {Control Layout}, leftInset +*/ +qreal QQuickTextArea::rightInset() const +{ + Q_D(const QQuickTextArea); + return d->getRightInset(); +} + +void QQuickTextArea::setRightInset(qreal inset) +{ + Q_D(QQuickTextArea); + d->setRightInset(inset); +} + +void QQuickTextArea::resetRightInset() +{ + Q_D(QQuickTextArea); + d->setRightInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextArea::bottomInset + + This property holds the bottom inset for the background. + + \sa {Control Layout}, topInset +*/ +qreal QQuickTextArea::bottomInset() const +{ + Q_D(const QQuickTextArea); + return d->getBottomInset(); +} + +void QQuickTextArea::setBottomInset(qreal inset) +{ + Q_D(QQuickTextArea); + d->setBottomInset(inset); +} + +void QQuickTextArea::resetBottomInset() +{ + Q_D(QQuickTextArea); + d->setBottomInset(0, true); +} + +void QQuickTextArea::classBegin() +{ + Q_D(QQuickTextArea); + QQuickTextEdit::classBegin(); + d->resolveFont(); +} + +void QQuickTextArea::componentComplete() +{ + Q_D(QQuickTextArea); + d->executeBackground(true); + QQuickTextEdit::componentComplete(); + d->resizeBackground(); +#if QT_CONFIG(quicktemplates2_hover) + if (!d->explicitHoverEnabled) + setAcceptHoverEvents(QQuickControlPrivate::calcHoverEnabled(d->parentItem)); +#endif +#if QT_CONFIG(accessibility) + if (QAccessible::isActive()) + d->accessibilityActiveChanged(true); +#endif +} + +void QQuickTextArea::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +{ + Q_D(QQuickTextArea); + QQuickTextEdit::itemChange(change, value); + switch (change) { + case ItemEnabledHasChanged: + break; + case ItemSceneChange: + case ItemParentHasChanged: + if ((change == ItemParentHasChanged && value.item) || (change == ItemSceneChange && value.window)) { + d->resolveFont(); +#if QT_CONFIG(quicktemplates2_hover) + if (!d->explicitHoverEnabled) + d->updateHoverEnabled(QQuickControlPrivate::calcHoverEnabled(d->parentItem), false); // explicit=false +#endif + if (change == ItemParentHasChanged) { + QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(value.item->parentItem()); + if (flickable) { + QQuickScrollView *scrollView = qobject_cast<QQuickScrollView *>(flickable->parentItem()); + if (scrollView) + d->attachFlickable(flickable); + } + } + } + break; + default: + break; + } +} + +void QQuickTextArea::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickTextArea); + QQuickTextEdit::geometryChange(newGeometry, oldGeometry); + d->resizeBackground(); +} + +void QQuickTextArea::insetChange(const QMarginsF &newInset, const QMarginsF &oldInset) +{ + Q_D(QQuickTextArea); + Q_UNUSED(newInset); + Q_UNUSED(oldInset); + d->resizeBackground(); +} + +QSGNode *QQuickTextArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_D(QQuickTextArea); + QQuickDefaultClipNode *clipNode = static_cast<QQuickDefaultClipNode *>(oldNode); + if (!clipNode) + clipNode = new QQuickDefaultClipNode(QRectF()); + + QQuickItem *clipper = this; + if (d->flickable) + clipper = d->flickable; + + const QRectF cr = clipper->clipRect().adjusted( + leftPadding(), topPadding(), + (!d->cursorItem && effectiveHAlign() == HAlignment::AlignRight ? 1 : 0) - rightPadding(), + -bottomPadding()); + + clipNode->setRect(!d->flickable ? cr : cr.translated(d->flickable->contentX(), d->flickable->contentY())); + clipNode->update(); + + QSGNode *textNode = QQuickTextEdit::updatePaintNode(clipNode->firstChild(), data); + if (!textNode->parent()) + clipNode->appendChildNode(textNode); + + if (d->cursorItem) { + QQuickDefaultClipNode *cursorNode = QQuickItemPrivate::get(d->cursorItem)->clipNode(); + if (cursorNode) + cursorNode->setClipRect(d->cursorItem->mapRectFromItem(clipper, cr)); + } + + return clipNode; +} + +void QQuickTextArea::focusInEvent(QFocusEvent *event) +{ + QQuickTextEdit::focusInEvent(event); + setFocusReason(event->reason()); +} + +void QQuickTextArea::focusOutEvent(QFocusEvent *event) +{ + QQuickTextEdit::focusOutEvent(event); + setFocusReason(event->reason()); +} + +#if QT_CONFIG(quicktemplates2_hover) +void QQuickTextArea::hoverEnterEvent(QHoverEvent *event) +{ + Q_D(QQuickTextArea); + QQuickTextEdit::hoverEnterEvent(event); + setHovered(d->hoverEnabled); + event->ignore(); +} + +void QQuickTextArea::hoverLeaveEvent(QHoverEvent *event) +{ + QQuickTextEdit::hoverLeaveEvent(event); + setHovered(false); + event->ignore(); +} +#endif + +void QQuickTextArea::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickTextArea); + d->pressHandler.mousePressEvent(event); + if (d->pressHandler.isActive()) { + if (d->pressHandler.delayedMousePressEvent) { + QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); + d->pressHandler.clearDelayedMouseEvent(); + } + // Calling the base class implementation will result in QQuickTextControl's + // press handler being called, which ignores events that aren't Qt::LeftButton. + const bool wasAccepted = event->isAccepted(); + QQuickTextEdit::mousePressEvent(event); + if (wasAccepted) + event->accept(); + } +} + +void QQuickTextArea::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickTextArea); + d->pressHandler.mouseMoveEvent(event); + if (d->pressHandler.isActive()) { + if (d->pressHandler.delayedMousePressEvent) { + QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); + d->pressHandler.clearDelayedMouseEvent(); + } + QQuickTextEdit::mouseMoveEvent(event); + } +} + +void QQuickTextArea::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickTextArea); + d->pressHandler.mouseReleaseEvent(event); + if (d->pressHandler.isActive()) { + if (d->pressHandler.delayedMousePressEvent) { + QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); + d->pressHandler.clearDelayedMouseEvent(); + } + QQuickTextEdit::mouseReleaseEvent(event); + } +} + +void QQuickTextArea::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QQuickTextArea); + if (d->pressHandler.delayedMousePressEvent) { + QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); + d->pressHandler.clearDelayedMouseEvent(); + } + QQuickTextEdit::mouseDoubleClickEvent(event); +} + +void QQuickTextArea::timerEvent(QTimerEvent *event) +{ + Q_D(QQuickTextArea); + if (event->timerId() == d->pressHandler.timer.timerId()) + d->pressHandler.timerEvent(event); + else + QQuickTextEdit::timerEvent(event); +} + +class QQuickTextAreaAttachedPrivate : public QObjectPrivate +{ +public: + QQuickTextArea *control = nullptr; +}; + +QQuickTextAreaAttached::QQuickTextAreaAttached(QObject *parent) + : QObject(*(new QQuickTextAreaAttachedPrivate), parent) +{ +} + +/*! + \qmlattachedproperty TextArea QtQuick.Controls::TextArea::flickable + + This property attaches a text area to a \l Flickable. + + \sa ScrollBar, ScrollIndicator, {Scrollable TextArea} +*/ +QQuickTextArea *QQuickTextAreaAttached::flickable() const +{ + Q_D(const QQuickTextAreaAttached); + return d->control; +} + +void QQuickTextAreaAttached::setFlickable(QQuickTextArea *control) +{ + Q_D(QQuickTextAreaAttached); + QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(parent()); + if (!flickable) { + qmlWarning(parent()) << "TextArea must be attached to a Flickable"; + return; + } + + if (d->control == control) + return; + + if (d->control) + QQuickTextAreaPrivate::get(d->control)->detachFlickable(); + + d->control = control; + + if (control) + QQuickTextAreaPrivate::get(control)->attachFlickable(flickable); + + emit flickableChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquicktextarea_p.cpp" diff --git a/src/quicktemplates2/qquicktextarea_p.h b/src/quicktemplates2/qquicktextarea_p.h new file mode 100644 index 0000000000..8ab62d4d78 --- /dev/null +++ b/src/quicktemplates2/qquicktextarea_p.h @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTEXTAREA_P_H +#define QQUICKTEXTAREA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qpalette.h> +#include <QtQuick/private/qquicktextedit_p.h> +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickText; +class QQuickTextAreaPrivate; +class QQuickTextAreaAttached; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTextArea : public QQuickTextEdit +{ + Q_OBJECT + Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) // override + Q_PROPERTY(qreal implicitWidth READ implicitWidth WRITE setImplicitWidth NOTIFY implicitWidthChanged3 FINAL) + Q_PROPERTY(qreal implicitHeight READ implicitHeight WRITE setImplicitHeight NOTIFY implicitHeightChanged3 FINAL) + Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) + Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText NOTIFY placeholderTextChanged FINAL) + Q_PROPERTY(Qt::FocusReason focusReason READ focusReason WRITE setFocusReason NOTIFY focusReasonChanged FINAL) + // 2.1 (Qt 5.8) + Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged FINAL REVISION(2, 1)) + Q_PROPERTY(bool hoverEnabled READ isHoverEnabled WRITE setHoverEnabled RESET resetHoverEnabled NOTIFY hoverEnabledChanged FINAL REVISION(2, 1)) + // 2.5 (Qt 5.12) + Q_PROPERTY(QColor placeholderTextColor READ placeholderTextColor WRITE setPlaceholderTextColor NOTIFY placeholderTextColorChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundWidth READ implicitBackgroundWidth NOTIFY implicitBackgroundWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundHeight READ implicitBackgroundHeight NOTIFY implicitBackgroundHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal topInset READ topInset WRITE setTopInset RESET resetTopInset NOTIFY topInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal leftInset READ leftInset WRITE setLeftInset RESET resetLeftInset NOTIFY leftInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal rightInset READ rightInset WRITE setRightInset RESET resetRightInset NOTIFY rightInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal bottomInset READ bottomInset WRITE setBottomInset RESET resetBottomInset NOTIFY bottomInsetChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background") + QML_NAMED_ELEMENT(TextArea) + QML_ATTACHED(QQuickTextAreaAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickTextArea(QQuickItem *parent = nullptr); + ~QQuickTextArea(); + + static QQuickTextAreaAttached *qmlAttachedProperties(QObject *object); + + QFont font() const; + void setFont(const QFont &font); + + QQuickItem *background() const; + void setBackground(QQuickItem *background); + + QString placeholderText() const; + void setPlaceholderText(const QString &text); + + Qt::FocusReason focusReason() const; + void setFocusReason(Qt::FocusReason reason); + + bool contains(const QPointF &point) const override; + + // 2.1 (Qt 5.8) + bool isHovered() const; + void setHovered(bool hovered); + + bool isHoverEnabled() const; + void setHoverEnabled(bool enabled); + void resetHoverEnabled(); + + // 2.5 (Qt 5.12) + QColor placeholderTextColor() const; + void setPlaceholderTextColor(const QColor &color); + + qreal implicitBackgroundWidth() const; + qreal implicitBackgroundHeight() const; + + qreal topInset() const; + void setTopInset(qreal inset); + void resetTopInset(); + + qreal leftInset() const; + void setLeftInset(qreal inset); + void resetLeftInset(); + + qreal rightInset() const; + void setRightInset(qreal inset); + void resetRightInset(); + + qreal bottomInset() const; + void setBottomInset(qreal inset); + void resetBottomInset(); + +Q_SIGNALS: + void fontChanged(); + void implicitWidthChanged3(); + void implicitHeightChanged3(); + void backgroundChanged(); + void placeholderTextChanged(); + void focusReasonChanged(); + void pressAndHold(QQuickMouseEvent *event); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void pressed(QQuickMouseEvent *event); + Q_REVISION(2, 1) void released(QQuickMouseEvent *event); + Q_REVISION(2, 1) void hoveredChanged(); + Q_REVISION(2, 1) void hoverEnabledChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void placeholderTextColorChanged(); + Q_REVISION(2, 5) void implicitBackgroundWidthChanged(); + Q_REVISION(2, 5) void implicitBackgroundHeightChanged(); + Q_REVISION(2, 5) void topInsetChanged(); + Q_REVISION(2, 5) void leftInsetChanged(); + Q_REVISION(2, 5) void rightInsetChanged(); + Q_REVISION(2, 5) void bottomInsetChanged(); + +protected: + friend struct QQuickPressHandler; + + void classBegin() override; + void componentComplete() override; + + void itemChange(ItemChange change, const ItemChangeData &value) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + virtual void insetChange(const QMarginsF &newInset, const QMarginsF &oldInset); + + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) override; + + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; +#if QT_CONFIG(quicktemplates2_hover) + void hoverEnterEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; +#endif + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void timerEvent(QTimerEvent *event) override; + +private: + Q_DISABLE_COPY(QQuickTextArea) + Q_DECLARE_PRIVATE(QQuickTextArea) +}; + +class QQuickTextAreaAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTextAreaAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickTextArea *flickable READ flickable WRITE setFlickable NOTIFY flickableChanged FINAL) + +public: + explicit QQuickTextAreaAttached(QObject *parent); + + QQuickTextArea *flickable() const; + void setFlickable(QQuickTextArea *control); + +Q_SIGNALS: + void flickableChanged(); + +private: + Q_DISABLE_COPY(QQuickTextAreaAttached) + Q_DECLARE_PRIVATE(QQuickTextAreaAttached) +}; + +struct QQuickTextEditForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickTextEdit) + QML_ADDED_IN_VERSION(2, 3) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickTextArea) +QML_DECLARE_TYPEINFO(QQuickTextArea, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKTEXTAREA_P_H diff --git a/src/quicktemplates2/qquicktextarea_p_p.h b/src/quicktemplates2/qquicktextarea_p_p.h new file mode 100644 index 0000000000..2ed2b44f31 --- /dev/null +++ b/src/quicktemplates2/qquicktextarea_p_p.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTEXTAREA_P_P_H +#define QQUICKTEXTAREA_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/private/qlazilyallocated_p.h> +#include <QtQuick/private/qquicktextedit_p_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuickTemplates2/private/qquickpresshandler_p_p.h> +#include <QtQuickTemplates2/private/qquickdeferredpointer_p_p.h> +#include <QtQuickTemplates2/private/qquicktheme_p.h> + +#include <QtQuickTemplates2/private/qquicktextarea_p.h> + +#if QT_CONFIG(accessibility) +#include <QtGui/qaccessible.h> +#endif + +QT_BEGIN_NAMESPACE + +class QQuickFlickable; + +class QQuickTextAreaPrivate : public QQuickTextEditPrivate, public QQuickItemChangeListener +#if QT_CONFIG(accessibility) + , public QAccessible::ActivationObserver +#endif +{ + Q_DECLARE_PUBLIC(QQuickTextArea) + +public: + QQuickTextAreaPrivate(); + ~QQuickTextAreaPrivate(); + + static QQuickTextAreaPrivate *get(QQuickTextArea *item) + { + return static_cast<QQuickTextAreaPrivate *>(QObjectPrivate::get(item)); + } + + inline QMarginsF getInset() const { return QMarginsF(getLeftInset(), getTopInset(), getRightInset(), getBottomInset()); } + inline qreal getTopInset() const { return extra.isAllocated() ? extra->topInset : 0; } + inline qreal getLeftInset() const { return extra.isAllocated() ? extra->leftInset : 0; } + inline qreal getRightInset() const { return extra.isAllocated() ? extra->rightInset : 0; } + inline qreal getBottomInset() const { return extra.isAllocated() ? extra->bottomInset : 0; } + + void setTopInset(qreal value, bool reset = false); + void setLeftInset(qreal value, bool reset = false); + void setRightInset(qreal value, bool reset = false); + void setBottomInset(qreal value, bool reset = false); + + void resizeBackground(); + + void resolveFont(); + void inheritFont(const QFont &font); + void updateFont(const QFont &font); + inline void setFont_helper(const QFont &font) { + if (sourceFont.resolveMask() == font.resolveMask() && sourceFont == font) + return; + updateFont(font); + } + +#if QT_CONFIG(quicktemplates2_hover) + void updateHoverEnabled(bool h, bool e); +#endif + + void attachFlickable(QQuickFlickable *flickable); + void detachFlickable(); + void ensureCursorVisible(); + void resizeFlickableControl(); + void resizeFlickableContent(); + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + + qreal getImplicitWidth() const override; + qreal getImplicitHeight() const override; + + void implicitWidthChanged() override; + void implicitHeightChanged() override; + + void readOnlyChanged(bool isReadOnly); + +#if QT_CONFIG(accessibility) + void accessibilityActiveChanged(bool active) override; + QAccessible::Role accessibleRole() const override; +#endif + + void cancelBackground(); + void executeBackground(bool complete = false); + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; + + QPalette defaultPalette() const override; + +#if QT_CONFIG(quicktemplates2_hover) + bool hovered = false; + bool explicitHoverEnabled = false; +#endif + + struct ExtraData { + bool hasTopInset = false; + bool hasLeftInset = false; + bool hasRightInset = false; + bool hasBottomInset = false; + bool hasBackgroundWidth = false; + bool hasBackgroundHeight = false; + qreal topInset = 0; + qreal leftInset = 0; + qreal rightInset = 0; + qreal bottomInset = 0; + QFont requestedFont; + QPalette requestedPalette; + }; + QLazilyAllocated<ExtraData> extra; + + bool resizingBackground = false; + QPalette resolvedPalette; + QQuickDeferredPointer<QQuickItem> background; + QString placeholder; + QColor placeholderColor; + Qt::FocusReason focusReason = Qt::OtherFocusReason; + QQuickPressHandler pressHandler; + QQuickFlickable *flickable = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKTEXTAREA_P_P_H diff --git a/src/quicktemplates2/qquicktextfield.cpp b/src/quicktemplates2/qquicktextfield.cpp new file mode 100644 index 0000000000..7cac120805 --- /dev/null +++ b/src/quicktemplates2/qquicktextfield.cpp @@ -0,0 +1,953 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktextfield_p.h" +#include "qquicktextfield_p_p.h" +#include "qquickcontrol_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickdeferredexecute_p_p.h" + +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquicktextinput_p.h> +#include <QtQuick/private/qquickclipnode_p.h> + +#if QT_CONFIG(accessibility) +#include <QtQuick/private/qquickaccessibleattached_p.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \qmltype TextField + \inherits TextInput +//! \instantiates QQuickTextField + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-input + \brief Single-line text input field. + + TextField is a single line text editor. TextField extends TextInput with + a \l {placeholderText}{placeholder text} functionality, and adds decoration. + + \table + \row \li \image qtquickcontrols2-textfield-normal.png + \li A text field in its normal state. + \row \li \image qtquickcontrols2-textfield-focused.png + \li A text field that has active focus. + \row \li \image qtquickcontrols2-textfield-disabled.png + \li A text field that is disabled. + \endtable + + \code + TextField { + placeholderText: qsTr("Enter name") + } + \endcode + + \sa TextArea, {Customizing TextField}, {Input Controls} +*/ + +/*! + \qmlsignal QtQuick.Controls::TextField::pressAndHold(MouseEvent event) + + This signal is emitted when there is a long press (the delay depends on the platform plugin). + The \a event parameter provides information about the press, including the x and y + coordinates of the press, and which button is pressed. + + \sa pressed, released +*/ + +/*! + \qmlsignal QtQuick.Controls::TextField::pressed(MouseEvent event) + \since QtQuick.Controls 2.1 (Qt 5.8) + + This signal is emitted when the text field is pressed by the user. + The \a event parameter provides information about the press, + including the x and y coordinates of the press, and which button + is pressed. + + \sa released, pressAndHold +*/ + +/*! + \qmlsignal QtQuick.Controls::TextField::released(MouseEvent event) + \since QtQuick.Controls 2.1 (Qt 5.8) + + This signal is emitted when the text field is released by the user. + The \a event parameter provides information about the release, + including the x and y coordinates of the press, and which button + is pressed. + + \sa pressed, pressAndHold +*/ + +QQuickTextFieldPrivate::QQuickTextFieldPrivate() +{ +#if QT_CONFIG(accessibility) + QAccessible::installActivationObserver(this); +#endif +} + +QQuickTextFieldPrivate::~QQuickTextFieldPrivate() +{ +#if QT_CONFIG(accessibility) + QAccessible::removeActivationObserver(this); +#endif +} + +void QQuickTextFieldPrivate::setTopInset(qreal value, bool reset) +{ + Q_Q(QQuickTextField); + const QMarginsF oldInset = getInset(); + extra.value().topInset = value; + extra.value().hasTopInset = !reset; + if (!qFuzzyCompare(oldInset.top(), value)) { + emit q->topInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickTextFieldPrivate::setLeftInset(qreal value, bool reset) +{ + Q_Q(QQuickTextField); + const QMarginsF oldInset = getInset(); + extra.value().leftInset = value; + extra.value().hasLeftInset = !reset; + if (!qFuzzyCompare(oldInset.left(), value)) { + emit q->leftInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickTextFieldPrivate::setRightInset(qreal value, bool reset) +{ + Q_Q(QQuickTextField); + const QMarginsF oldInset = getInset(); + extra.value().rightInset = value; + extra.value().hasRightInset = !reset; + if (!qFuzzyCompare(oldInset.right(), value)) { + emit q->rightInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickTextFieldPrivate::setBottomInset(qreal value, bool reset) +{ + Q_Q(QQuickTextField); + const QMarginsF oldInset = getInset(); + extra.value().bottomInset = value; + extra.value().hasBottomInset = !reset; + if (!qFuzzyCompare(oldInset.bottom(), value)) { + emit q->bottomInsetChanged(); + q->insetChange(getInset(), oldInset); + } +} + +void QQuickTextFieldPrivate::resizeBackground() +{ + if (!background) + return; + + resizingBackground = true; + + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + if (((!p->widthValid() || !extra.isAllocated() || !extra->hasBackgroundWidth) && qFuzzyIsNull(background->x())) + || (extra.isAllocated() && (extra->hasLeftInset || extra->hasRightInset))) { + const bool wasWidthValid = p->widthValid(); + background->setX(getLeftInset()); + background->setWidth(width - getLeftInset() - getRightInset()); + // If the user hadn't previously set the width, that shouldn't change when we set it for them. + if (!wasWidthValid) + p->widthValidFlag = false; + } + if (((!p->heightValid() || !extra.isAllocated() || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y())) + || (extra.isAllocated() && (extra->hasTopInset || extra->hasBottomInset))) { + const bool wasHeightValid = p->heightValid(); + background->setY(getTopInset()); + background->setHeight(height - getTopInset() - getBottomInset()); + if (!wasHeightValid) + p->heightValidFlag = false; + } + + resizingBackground = false; +} + +/*! + \internal + + Determine which font is implicitly imposed on this control by its ancestors + and QGuiApplication::font, resolve this against its own font (attributes from + the implicit font are copied over). Then propagate this font to this + control's children. +*/ +void QQuickTextFieldPrivate::resolveFont() +{ + Q_Q(QQuickTextField); + inheritFont(QQuickControlPrivate::parentFont(q)); +} + +void QQuickTextFieldPrivate::inheritFont(const QFont &font) +{ + QFont parentFont = extra.isAllocated() ? extra->requestedFont.resolve(font) : font; + parentFont.setResolveMask(extra.isAllocated() ? extra->requestedFont.resolveMask() | font.resolveMask() : font.resolveMask()); + + const QFont defaultFont = QQuickTheme::font(QQuickTheme::TextField); + QFont resolvedFont = parentFont.resolve(defaultFont); + + setFont_helper(resolvedFont); +} + +/*! + \internal + + Assign \a font to this control, and propagate it to all children. +*/ +void QQuickTextFieldPrivate::updateFont(const QFont &font) +{ + Q_Q(QQuickTextField); + QFont oldFont = sourceFont; + q->QQuickTextInput::setFont(font); + + QQuickControlPrivate::updateFontRecur(q, font); + + if (oldFont != font) + emit q->fontChanged(); +} + +#if QT_CONFIG(quicktemplates2_hover) +void QQuickTextFieldPrivate::updateHoverEnabled(bool enabled, bool xplicit) +{ + Q_Q(QQuickTextField); + if (!xplicit && explicitHoverEnabled) + return; + + bool wasEnabled = q->isHoverEnabled(); + explicitHoverEnabled = xplicit; + if (wasEnabled != enabled) { + q->setAcceptHoverEvents(enabled); + QQuickControlPrivate::updateHoverEnabledRecur(q, enabled); + emit q->hoverEnabledChanged(); + } +} +#endif + +qreal QQuickTextFieldPrivate::getImplicitWidth() const +{ + return QQuickItemPrivate::getImplicitWidth(); +} + +qreal QQuickTextFieldPrivate::getImplicitHeight() const +{ + return QQuickItemPrivate::getImplicitHeight(); +} + +void QQuickTextFieldPrivate::implicitWidthChanged() +{ + Q_Q(QQuickTextField); + QQuickItemPrivate::implicitWidthChanged(); + emit q->implicitWidthChanged3(); +} + +void QQuickTextFieldPrivate::implicitHeightChanged() +{ + Q_Q(QQuickTextField); + QQuickItemPrivate::implicitHeightChanged(); + emit q->implicitHeightChanged3(); +} + +void QQuickTextFieldPrivate::readOnlyChanged(bool isReadOnly) +{ + Q_UNUSED(isReadOnly); +#if QT_CONFIG(accessibility) + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(q_func())) + accessibleAttached->set_readOnly(isReadOnly); +#endif +#if QT_CONFIG(cursor) + q_func()->setCursor(isReadOnly && !selectByMouse ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif +} + +void QQuickTextFieldPrivate::echoModeChanged(QQuickTextField::EchoMode echoMode) +{ +#if QT_CONFIG(accessibility) + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(q_func())) + accessibleAttached->set_passwordEdit((echoMode == QQuickTextField::Password || echoMode == QQuickTextField::PasswordEchoOnEdit) ? true : false); +#else + Q_UNUSED(echoMode); +#endif +} + +#if QT_CONFIG(accessibility) +void QQuickTextFieldPrivate::accessibilityActiveChanged(bool active) +{ + if (!active) + return; + + Q_Q(QQuickTextField); + QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true)); + Q_ASSERT(accessibleAttached); + accessibleAttached->setRole(accessibleRole()); + accessibleAttached->set_readOnly(m_readOnly); + accessibleAttached->set_passwordEdit((m_echoMode == QQuickTextField::Password || m_echoMode == QQuickTextField::PasswordEchoOnEdit) ? true : false); + accessibleAttached->setDescription(placeholder); +} + +QAccessible::Role QQuickTextFieldPrivate::accessibleRole() const +{ + return QAccessible::EditableText; +} +#endif + +static inline QString backgroundName() { return QStringLiteral("background"); } + +void QQuickTextFieldPrivate::cancelBackground() +{ + Q_Q(QQuickTextField); + quickCancelDeferred(q, backgroundName()); +} + +void QQuickTextFieldPrivate::executeBackground(bool complete) +{ + Q_Q(QQuickTextField); + if (background.wasExecuted()) + return; + + if (!background || complete) + quickBeginDeferred(q, backgroundName(), background); + if (complete) + quickCompleteDeferred(q, backgroundName(), background); +} + +void QQuickTextFieldPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + Q_UNUSED(diff); + if (resizingBackground || item != background || !change.sizeChange()) + return; + + QQuickItemPrivate *p = QQuickItemPrivate::get(item); + // QTBUG-71875: only allocate the extra data if we have to. + // resizeBackground() relies on the value of extra.isAllocated() + // as part of its checks to see whether it should resize the background or not. + if (p->widthValid() || extra.isAllocated()) + extra.value().hasBackgroundWidth = p->widthValid(); + if (p->heightValid() || extra.isAllocated()) + extra.value().hasBackgroundHeight = p->heightValid(); + resizeBackground(); +} + +void QQuickTextFieldPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_Q(QQuickTextField); + if (item == background) + emit q->implicitBackgroundWidthChanged(); +} + +void QQuickTextFieldPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_Q(QQuickTextField); + if (item == background) + emit q->implicitBackgroundHeightChanged(); +} + +void QQuickTextFieldPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickTextField); + if (item == background) { + background = nullptr; + emit q->implicitBackgroundWidthChanged(); + emit q->implicitBackgroundHeightChanged(); + } +} + +QPalette QQuickTextFieldPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::TextField); +} + +QQuickTextField::QQuickTextField(QQuickItem *parent) + : QQuickTextInput(*(new QQuickTextFieldPrivate), parent) +{ + Q_D(QQuickTextField); + d->pressHandler.control = this; + d->setImplicitResizeEnabled(false); + setAcceptedMouseButtons(Qt::AllButtons); + setActiveFocusOnTab(true); +#if QT_CONFIG(cursor) + setCursor(Qt::IBeamCursor); +#endif + QObjectPrivate::connect(this, &QQuickTextInput::readOnlyChanged, d, &QQuickTextFieldPrivate::readOnlyChanged); + QObjectPrivate::connect(this, &QQuickTextInput::echoModeChanged, d, &QQuickTextFieldPrivate::echoModeChanged); +} + +QQuickTextField::~QQuickTextField() +{ + Q_D(QQuickTextField); + QQuickControlPrivate::removeImplicitSizeListener(d->background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); +} + +QFont QQuickTextField::font() const +{ + return QQuickTextInput::font(); +} + +void QQuickTextField::setFont(const QFont &font) +{ + Q_D(QQuickTextField); + if (d->extra.value().requestedFont.resolveMask() == font.resolveMask() && d->extra.value().requestedFont == font) + return; + + d->extra.value().requestedFont = font; + d->resolveFont(); +} + +/*! + \qmlproperty Item QtQuick.Controls::TextField::background + + This property holds the background item. + + \input qquickcontrol-background.qdocinc notes + + \sa {Customizing TextField} +*/ +QQuickItem *QQuickTextField::background() const +{ + QQuickTextFieldPrivate *d = const_cast<QQuickTextFieldPrivate *>(d_func()); + if (!d->background) + d->executeBackground(); + return d->background; +} + +void QQuickTextField::setBackground(QQuickItem *background) +{ + Q_D(QQuickTextField); + if (d->background == background) + return; + + if (!d->background.isExecuting()) + d->cancelBackground(); + + const qreal oldImplicitBackgroundWidth = implicitBackgroundWidth(); + const qreal oldImplicitBackgroundHeight = implicitBackgroundHeight(); + + if (d->extra.isAllocated()) { + d->extra.value().hasBackgroundWidth = false; + d->extra.value().hasBackgroundHeight = false; + } + + QQuickControlPrivate::removeImplicitSizeListener(d->background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + QQuickControlPrivate::hideOldItem(d->background); + d->background = background; + + if (background) { + background->setParentItem(this); + if (qFuzzyIsNull(background->z())) + background->setZ(-1); + QQuickItemPrivate *p = QQuickItemPrivate::get(background); + if (p->widthValid() || p->heightValid()) { + d->extra.value().hasBackgroundWidth = p->widthValid(); + d->extra.value().hasBackgroundHeight = p->heightValid(); + } + if (isComponentComplete()) + d->resizeBackground(); + QQuickControlPrivate::addImplicitSizeListener(background, d, QQuickControlPrivate::ImplicitSizeChanges | QQuickItemPrivate::Geometry); + } + + if (!qFuzzyCompare(oldImplicitBackgroundWidth, implicitBackgroundWidth())) + emit implicitBackgroundWidthChanged(); + if (!qFuzzyCompare(oldImplicitBackgroundHeight, implicitBackgroundHeight())) + emit implicitBackgroundHeightChanged(); + if (!d->background.isExecuting()) + emit backgroundChanged(); +} + +/*! + \qmlproperty string QtQuick.Controls::TextField::placeholderText + + This property holds the hint that is displayed in the TextField before the user + enters text. +*/ +QString QQuickTextField::placeholderText() const +{ + Q_D(const QQuickTextField); + return d->placeholder; +} + +void QQuickTextField::setPlaceholderText(const QString &text) +{ + Q_D(QQuickTextField); + if (d->placeholder == text) + return; + + d->placeholder = text; +#if QT_CONFIG(accessibility) + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this)) + accessibleAttached->setDescription(text); +#endif + emit placeholderTextChanged(); +} + +/*! + \qmlproperty color QtQuick.Controls::TextField::placeholderTextColor + \since QtQuick.Controls 2.5 (Qt 5.12) + + This property holds the color of placeholderText. + + \sa placeholderText +*/ +QColor QQuickTextField::placeholderTextColor() const +{ + Q_D(const QQuickTextField); + return d->placeholderColor; +} + +void QQuickTextField::setPlaceholderTextColor(const QColor &color) +{ + Q_D(QQuickTextField); + if (d->placeholderColor == color) + return; + + d->placeholderColor = color; + emit placeholderTextColorChanged(); +} + +/*! + \qmlproperty enumeration QtQuick.Controls::TextField::focusReason + + \include qquickcontrol-focusreason.qdocinc +*/ +Qt::FocusReason QQuickTextField::focusReason() const +{ + Q_D(const QQuickTextField); + return d->focusReason; +} + +void QQuickTextField::setFocusReason(Qt::FocusReason reason) +{ + Q_D(QQuickTextField); + if (d->focusReason == reason) + return; + + d->focusReason = reason; + emit focusReasonChanged(); +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlproperty bool QtQuick.Controls::TextField::hovered + \readonly + + This property holds whether the text field is hovered. + + \sa hoverEnabled +*/ +bool QQuickTextField::isHovered() const +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(const QQuickTextField); + return d->hovered; +#else + return false; +#endif +} + +void QQuickTextField::setHovered(bool hovered) +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickTextField); + if (hovered == d->hovered) + return; + + d->hovered = hovered; + emit hoveredChanged(); +#else + Q_UNUSED(hovered); +#endif +} + +/*! + \since QtQuick.Controls 2.1 (Qt 5.8) + \qmlproperty bool QtQuick.Controls::TextField::hoverEnabled + + This property determines whether the text field accepts hover events. The default value is \c false. + + \sa hovered +*/ +bool QQuickTextField::isHoverEnabled() const +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(const QQuickTextField); + return d->hoverEnabled; +#else + return false; +#endif +} + +void QQuickTextField::setHoverEnabled(bool enabled) +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickTextField); + if (d->explicitHoverEnabled && enabled == d->hoverEnabled) + return; + + d->updateHoverEnabled(enabled, true); // explicit=true +#else + Q_UNUSED(enabled); +#endif +} + +void QQuickTextField::resetHoverEnabled() +{ +#if QT_CONFIG(quicktemplates2_hover) + Q_D(QQuickTextField); + if (!d->explicitHoverEnabled) + return; + + d->explicitHoverEnabled = false; + d->updateHoverEnabled(QQuickControlPrivate::calcHoverEnabled(d->parentItem), false); // explicit=false +#endif +} + +void QQuickTextField::classBegin() +{ + Q_D(QQuickTextField); + QQuickTextInput::classBegin(); + d->resolveFont(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextField::implicitBackgroundWidth + \readonly + + This property holds the implicit background width. + + The value is equal to \c {background ? background.implicitWidth : 0}. + + \sa implicitBackgroundHeight +*/ +qreal QQuickTextField::implicitBackgroundWidth() const +{ + Q_D(const QQuickTextField); + if (!d->background) + return 0; + return d->background->implicitWidth(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextField::implicitBackgroundHeight + \readonly + + This property holds the implicit background height. + + The value is equal to \c {background ? background.implicitHeight : 0}. + + \sa implicitBackgroundWidth +*/ +qreal QQuickTextField::implicitBackgroundHeight() const +{ + Q_D(const QQuickTextField); + if (!d->background) + return 0; + return d->background->implicitHeight(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextField::topInset + + This property holds the top inset for the background. + + \sa {Control Layout}, bottomInset +*/ +qreal QQuickTextField::topInset() const +{ + Q_D(const QQuickTextField); + return d->getTopInset(); +} + +void QQuickTextField::setTopInset(qreal inset) +{ + Q_D(QQuickTextField); + d->setTopInset(inset); +} + +void QQuickTextField::resetTopInset() +{ + Q_D(QQuickTextField); + d->setTopInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextField::leftInset + + This property holds the left inset for the background. + + \sa {Control Layout}, rightInset +*/ +qreal QQuickTextField::leftInset() const +{ + Q_D(const QQuickTextField); + return d->getLeftInset(); +} + +void QQuickTextField::setLeftInset(qreal inset) +{ + Q_D(QQuickTextField); + d->setLeftInset(inset); +} + +void QQuickTextField::resetLeftInset() +{ + Q_D(QQuickTextField); + d->setLeftInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextField::rightInset + + This property holds the right inset for the background. + + \sa {Control Layout}, leftInset +*/ +qreal QQuickTextField::rightInset() const +{ + Q_D(const QQuickTextField); + return d->getRightInset(); +} + +void QQuickTextField::setRightInset(qreal inset) +{ + Q_D(QQuickTextField); + d->setRightInset(inset); +} + +void QQuickTextField::resetRightInset() +{ + Q_D(QQuickTextField); + d->setRightInset(0, true); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlproperty real QtQuick.Controls::TextField::bottomInset + + This property holds the bottom inset for the background. + + \sa {Control Layout}, topInset +*/ +qreal QQuickTextField::bottomInset() const +{ + Q_D(const QQuickTextField); + return d->getBottomInset(); +} + +void QQuickTextField::setBottomInset(qreal inset) +{ + Q_D(QQuickTextField); + d->setBottomInset(inset); +} + +void QQuickTextField::resetBottomInset() +{ + Q_D(QQuickTextField); + d->setBottomInset(0, true); +} + +void QQuickTextField::componentComplete() +{ + Q_D(QQuickTextField); + d->executeBackground(true); + QQuickTextInput::componentComplete(); + d->resizeBackground(); +#if QT_CONFIG(quicktemplates2_hover) + if (!d->explicitHoverEnabled) + setAcceptHoverEvents(QQuickControlPrivate::calcHoverEnabled(d->parentItem)); +#endif +#if QT_CONFIG(accessibility) + if (QAccessible::isActive()) + d->accessibilityActiveChanged(true); +#endif +} + +void QQuickTextField::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +{ + Q_D(QQuickTextField); + QQuickTextInput::itemChange(change, value); + switch (change) { + case ItemEnabledHasChanged: + break; + case ItemSceneChange: + case ItemParentHasChanged: + if ((change == ItemParentHasChanged && value.item) || (change == ItemSceneChange && value.window)) { + d->resolveFont(); +#if QT_CONFIG(quicktemplates2_hover) + if (!d->explicitHoverEnabled) + d->updateHoverEnabled(QQuickControlPrivate::calcHoverEnabled(d->parentItem), false); // explicit=false +#endif + } + break; + default: + break; + } +} + +void QQuickTextField::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickTextField); + QQuickTextInput::geometryChange(newGeometry, oldGeometry); + d->resizeBackground(); +} + +void QQuickTextField::insetChange(const QMarginsF &newInset, const QMarginsF &oldInset) +{ + Q_D(QQuickTextField); + Q_UNUSED(newInset); + Q_UNUSED(oldInset); + d->resizeBackground(); +} +QSGNode *QQuickTextField::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + QQuickDefaultClipNode *clipNode = static_cast<QQuickDefaultClipNode *>(oldNode); + if (!clipNode) + clipNode = new QQuickDefaultClipNode(QRectF()); + + clipNode->setRect(clipRect().adjusted(leftPadding(), topPadding(), -rightPadding(), -bottomPadding())); + clipNode->update(); + + QSGNode *textNode = QQuickTextInput::updatePaintNode(clipNode->firstChild(), data); + if (!textNode->parent()) + clipNode->appendChildNode(textNode); + + return clipNode; +} + +void QQuickTextField::focusInEvent(QFocusEvent *event) +{ + QQuickTextInput::focusInEvent(event); + setFocusReason(event->reason()); +} + +void QQuickTextField::focusOutEvent(QFocusEvent *event) +{ + QQuickTextInput::focusOutEvent(event); + setFocusReason(event->reason()); +} + +#if QT_CONFIG(quicktemplates2_hover) +void QQuickTextField::hoverEnterEvent(QHoverEvent *event) +{ + Q_D(QQuickTextField); + QQuickTextInput::hoverEnterEvent(event); + setHovered(d->hoverEnabled); + event->ignore(); +} + +void QQuickTextField::hoverLeaveEvent(QHoverEvent *event) +{ + QQuickTextInput::hoverLeaveEvent(event); + setHovered(false); + event->ignore(); +} +#endif + +void QQuickTextField::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickTextField); + d->pressHandler.mousePressEvent(event); + if (d->pressHandler.isActive()) { + if (d->pressHandler.delayedMousePressEvent) { + QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent); + d->pressHandler.clearDelayedMouseEvent(); + } + if (event->buttons() != Qt::RightButton) + QQuickTextInput::mousePressEvent(event); + } +} + +void QQuickTextField::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickTextField); + d->pressHandler.mouseMoveEvent(event); + if (d->pressHandler.isActive()) { + if (d->pressHandler.delayedMousePressEvent) { + QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent); + d->pressHandler.clearDelayedMouseEvent(); + } + if (event->buttons() != Qt::RightButton) + QQuickTextInput::mouseMoveEvent(event); + } +} + +void QQuickTextField::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickTextField); + d->pressHandler.mouseReleaseEvent(event); + if (d->pressHandler.isActive()) { + if (d->pressHandler.delayedMousePressEvent) { + QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent); + d->pressHandler.clearDelayedMouseEvent(); + } + if (event->buttons() != Qt::RightButton) + QQuickTextInput::mouseReleaseEvent(event); + } +} + +void QQuickTextField::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QQuickTextField); + if (d->pressHandler.delayedMousePressEvent) { + QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent); + d->pressHandler.clearDelayedMouseEvent(); + } + if (event->buttons() != Qt::RightButton) + QQuickTextInput::mouseDoubleClickEvent(event); +} + +void QQuickTextField::timerEvent(QTimerEvent *event) +{ + Q_D(QQuickTextField); + if (event->timerId() == d->pressHandler.timer.timerId()) + d->pressHandler.timerEvent(event); + else + QQuickTextInput::timerEvent(event); +} + +QT_END_NAMESPACE + +#include "moc_qquicktextfield_p.cpp" diff --git a/src/quicktemplates2/qquicktextfield_p.h b/src/quicktemplates2/qquicktextfield_p.h new file mode 100644 index 0000000000..a1d433c0b2 --- /dev/null +++ b/src/quicktemplates2/qquicktextfield_p.h @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTEXTFIELD_P_H +#define QQUICKTEXTFIELD_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qpalette.h> +#include <QtQuick/private/qquicktextinput_p.h> +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickTextFieldPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTextField : public QQuickTextInput +{ + Q_OBJECT + Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) // override + Q_PROPERTY(qreal implicitWidth READ implicitWidth WRITE setImplicitWidth NOTIFY implicitWidthChanged3 FINAL) + Q_PROPERTY(qreal implicitHeight READ implicitHeight WRITE setImplicitHeight NOTIFY implicitHeightChanged3 FINAL) + Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) + Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText NOTIFY placeholderTextChanged FINAL) + Q_PROPERTY(Qt::FocusReason focusReason READ focusReason WRITE setFocusReason NOTIFY focusReasonChanged FINAL) + // 2.1 (Qt 5.8) + Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged FINAL REVISION(2, 1)) + Q_PROPERTY(bool hoverEnabled READ isHoverEnabled WRITE setHoverEnabled RESET resetHoverEnabled NOTIFY hoverEnabledChanged FINAL REVISION(2, 1)) + // 2.5 (Qt 5.12) + Q_PROPERTY(QColor placeholderTextColor READ placeholderTextColor WRITE setPlaceholderTextColor NOTIFY placeholderTextColorChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundWidth READ implicitBackgroundWidth NOTIFY implicitBackgroundWidthChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal implicitBackgroundHeight READ implicitBackgroundHeight NOTIFY implicitBackgroundHeightChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal topInset READ topInset WRITE setTopInset RESET resetTopInset NOTIFY topInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal leftInset READ leftInset WRITE setLeftInset RESET resetLeftInset NOTIFY leftInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal rightInset READ rightInset WRITE setRightInset RESET resetRightInset NOTIFY rightInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(qreal bottomInset READ bottomInset WRITE setBottomInset RESET resetBottomInset NOTIFY bottomInsetChanged FINAL REVISION(2, 5)) + Q_CLASSINFO("DeferredPropertyNames", "background") + QML_NAMED_ELEMENT(TextField) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickTextField(QQuickItem *parent = nullptr); + ~QQuickTextField(); + + QFont font() const; + void setFont(const QFont &font); + + QQuickItem *background() const; + void setBackground(QQuickItem *background); + + QString placeholderText() const; + void setPlaceholderText(const QString &text); + + Qt::FocusReason focusReason() const; + void setFocusReason(Qt::FocusReason reason); + + // 2.1 (Qt 5.8) + bool isHovered() const; + void setHovered(bool hovered); + + bool isHoverEnabled() const; + void setHoverEnabled(bool enabled); + void resetHoverEnabled(); + + // 2.5 (Qt 5.12) + QColor placeholderTextColor() const; + void setPlaceholderTextColor(const QColor &color); + + qreal implicitBackgroundWidth() const; + qreal implicitBackgroundHeight() const; + + qreal topInset() const; + void setTopInset(qreal inset); + void resetTopInset(); + + qreal leftInset() const; + void setLeftInset(qreal inset); + void resetLeftInset(); + + qreal rightInset() const; + void setRightInset(qreal inset); + void resetRightInset(); + + qreal bottomInset() const; + void setBottomInset(qreal inset); + void resetBottomInset(); + +Q_SIGNALS: + void fontChanged(); + void implicitWidthChanged3(); + void implicitHeightChanged3(); + void backgroundChanged(); + void placeholderTextChanged(); + void focusReasonChanged(); + void pressAndHold(QQuickMouseEvent *event); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void pressed(QQuickMouseEvent *event); + Q_REVISION(2, 1) void released(QQuickMouseEvent *event); + Q_REVISION(2, 1) void hoveredChanged(); + Q_REVISION(2, 1) void hoverEnabledChanged(); + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) void placeholderTextColorChanged(); + Q_REVISION(2, 5) void implicitBackgroundWidthChanged(); + Q_REVISION(2, 5) void implicitBackgroundHeightChanged(); + Q_REVISION(2, 5) void topInsetChanged(); + Q_REVISION(2, 5) void leftInsetChanged(); + Q_REVISION(2, 5) void rightInsetChanged(); + Q_REVISION(2, 5) void bottomInsetChanged(); + +protected: + friend struct QQuickPressHandler; + + void classBegin() override; + void componentComplete() override; + + void itemChange(ItemChange change, const ItemChangeData &value) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + virtual void insetChange(const QMarginsF &newInset, const QMarginsF &oldInset); + + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) override; + + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; +#if QT_CONFIG(quicktemplates2_hover) + void hoverEnterEvent(QHoverEvent *event) override; + void hoverLeaveEvent(QHoverEvent *event) override; +#endif + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void timerEvent(QTimerEvent *event) override; + +private: + Q_DISABLE_COPY(QQuickTextField) + Q_DECLARE_PRIVATE(QQuickTextField) +}; + +struct QQuickTextFieldForeign +{ + Q_GADGET + QML_ANONYMOUS + QML_FOREIGN(QQuickTextInput) + QML_ADDED_IN_VERSION(2, 2) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickTextField) + +#endif // QQUICKTEXTFIELD_P_H diff --git a/src/quicktemplates2/qquicktextfield_p_p.h b/src/quicktemplates2/qquicktextfield_p_p.h new file mode 100644 index 0000000000..d81289726b --- /dev/null +++ b/src/quicktemplates2/qquicktextfield_p_p.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTEXTFIELD_P_P_H +#define QQUICKTEXTFIELD_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQml/private/qlazilyallocated_p.h> +#include <QtQuick/private/qquicktextinput_p_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuickTemplates2/private/qquickpresshandler_p_p.h> +#include <QtQuickTemplates2/private/qquickdeferredpointer_p_p.h> +#include <QtQuickTemplates2/private/qquicktheme_p.h> + +#include <QtQuickTemplates2/private/qquicktextfield_p.h> + +#if QT_CONFIG(accessibility) +#include <QtGui/qaccessible.h> +#endif + +QT_BEGIN_NAMESPACE + +class QQuickTextFieldPrivate : public QQuickTextInputPrivate, public QQuickItemChangeListener +#if QT_CONFIG(accessibility) + , public QAccessible::ActivationObserver +#endif +{ + Q_DECLARE_PUBLIC(QQuickTextField) + +public: + QQuickTextFieldPrivate(); + ~QQuickTextFieldPrivate(); + + static QQuickTextFieldPrivate *get(QQuickTextField *item) { + return static_cast<QQuickTextFieldPrivate *>(QObjectPrivate::get(item)); } + + inline QMarginsF getInset() const { return QMarginsF(getLeftInset(), getTopInset(), getRightInset(), getBottomInset()); } + inline qreal getTopInset() const { return extra.isAllocated() ? extra->topInset : 0; } + inline qreal getLeftInset() const { return extra.isAllocated() ? extra->leftInset : 0; } + inline qreal getRightInset() const { return extra.isAllocated() ? extra->rightInset : 0; } + inline qreal getBottomInset() const { return extra.isAllocated() ? extra->bottomInset : 0; } + + void setTopInset(qreal value, bool reset = false); + void setLeftInset(qreal value, bool reset = false); + void setRightInset(qreal value, bool reset = false); + void setBottomInset(qreal value, bool reset = false); + + void resizeBackground(); + + void resolveFont(); + void inheritFont(const QFont &font); + void updateFont(const QFont &font); + inline void setFont_helper(const QFont &font) { + if (sourceFont.resolveMask() == font.resolveMask() && sourceFont == font) + return; + updateFont(font); + } + +#if QT_CONFIG(quicktemplates2_hover) + void updateHoverEnabled(bool h, bool e); +#endif + + qreal getImplicitWidth() const override; + qreal getImplicitHeight() const override; + + void implicitWidthChanged() override; + void implicitHeightChanged() override; + + void readOnlyChanged(bool isReadOnly); + void echoModeChanged(QQuickTextField::EchoMode echoMode); + +#if QT_CONFIG(accessibility) + void accessibilityActiveChanged(bool active) override; + QAccessible::Role accessibleRole() const override; +#endif + + void cancelBackground(); + void executeBackground(bool complete = false); + + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; + + QPalette defaultPalette() const override; + +#if QT_CONFIG(quicktemplates2_hover) + bool hovered = false; + bool explicitHoverEnabled = false; +#endif + + struct ExtraData { + bool hasTopInset = false; + bool hasLeftInset = false; + bool hasRightInset = false; + bool hasBottomInset = false; + bool hasBackgroundWidth = false; + bool hasBackgroundHeight = false; + qreal topInset = 0; + qreal leftInset = 0; + qreal rightInset = 0; + qreal bottomInset = 0; + QFont requestedFont; + }; + QLazilyAllocated<ExtraData> extra; + + bool resizingBackground = false; + QQuickDeferredPointer<QQuickItem> background; + QString placeholder; + QColor placeholderColor; + Qt::FocusReason focusReason = Qt::OtherFocusReason; + QQuickPressHandler pressHandler; +}; + +QT_END_NAMESPACE + +#endif // QQUICKTEXTFIELD_P_P_H diff --git a/src/quicktemplates2/qquicktheme.cpp b/src/quicktemplates2/qquicktheme.cpp new file mode 100644 index 0000000000..b94b419845 --- /dev/null +++ b/src/quicktemplates2/qquicktheme.cpp @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktheme_p.h" +#include "qquicktheme_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> +#include <QtGui/private/qguiapplication_p.h> + +QT_BEGIN_NAMESPACE + +std::unique_ptr<QQuickTheme> QQuickThemePrivate::instance; + +static void cleanup_instance() +{ + QQuickThemePrivate::instance.reset(); +} + +static void install_instance_cleanuper() +{ + qAddPostRoutine(cleanup_instance); +} + +Q_COREAPP_STARTUP_FUNCTION(install_instance_cleanuper) + +static QPlatformTheme::Font platformFont(QQuickTheme::Scope scope) +{ + switch (scope) { + case QQuickTheme::Button: return QPlatformTheme::PushButtonFont; + case QQuickTheme::CheckBox: return QPlatformTheme::CheckBoxFont; + case QQuickTheme::ComboBox: return QPlatformTheme::ComboMenuItemFont; + case QQuickTheme::GroupBox: return QPlatformTheme::GroupBoxTitleFont; + case QQuickTheme::ItemView: return QPlatformTheme::ItemViewFont; + case QQuickTheme::Label: return QPlatformTheme::LabelFont; + case QQuickTheme::ListView: return QPlatformTheme::ListViewFont; + case QQuickTheme::Menu: return QPlatformTheme::MenuFont; + case QQuickTheme::MenuBar: return QPlatformTheme::MenuBarFont; + case QQuickTheme::RadioButton: return QPlatformTheme::RadioButtonFont; + case QQuickTheme::SpinBox: return QPlatformTheme::EditorFont; + case QQuickTheme::Switch: return QPlatformTheme::CheckBoxFont; + case QQuickTheme::TabBar: return QPlatformTheme::TabButtonFont; + case QQuickTheme::TextArea: return QPlatformTheme::EditorFont; + case QQuickTheme::TextField: return QPlatformTheme::EditorFont; + case QQuickTheme::ToolBar: return QPlatformTheme::ToolButtonFont; + case QQuickTheme::ToolTip: return QPlatformTheme::TipLabelFont; + case QQuickTheme::Tumbler: return QPlatformTheme::ItemViewFont; + default: return QPlatformTheme::SystemFont; + } +} + +static QPlatformTheme::Palette platformPalette(QQuickTheme::Scope scope) +{ + switch (scope) { + case QQuickTheme::Button: return QPlatformTheme::ButtonPalette; + case QQuickTheme::CheckBox: return QPlatformTheme::CheckBoxPalette; + case QQuickTheme::ComboBox: return QPlatformTheme::ComboBoxPalette; + case QQuickTheme::GroupBox: return QPlatformTheme::GroupBoxPalette; + case QQuickTheme::ItemView: return QPlatformTheme::ItemViewPalette; + case QQuickTheme::Label: return QPlatformTheme::LabelPalette; + case QQuickTheme::ListView: return QPlatformTheme::ItemViewPalette; + case QQuickTheme::Menu: return QPlatformTheme::MenuPalette; + case QQuickTheme::MenuBar: return QPlatformTheme::MenuBarPalette; + case QQuickTheme::RadioButton: return QPlatformTheme::RadioButtonPalette; + case QQuickTheme::SpinBox: return QPlatformTheme::TextLineEditPalette; + case QQuickTheme::Switch: return QPlatformTheme::CheckBoxPalette; + case QQuickTheme::TabBar: return QPlatformTheme::TabBarPalette; + case QQuickTheme::TextArea: return QPlatformTheme::TextEditPalette; + case QQuickTheme::TextField: return QPlatformTheme::TextLineEditPalette; + case QQuickTheme::ToolBar: return QPlatformTheme::ToolButtonPalette; + case QQuickTheme::ToolTip: return QPlatformTheme::ToolTipPalette; + case QQuickTheme::Tumbler: return QPlatformTheme::ItemViewPalette; + default: return QPlatformTheme::SystemPalette; + } +} + +QQuickTheme::QQuickTheme() + : d_ptr(new QQuickThemePrivate) +{ +} + +QQuickTheme::~QQuickTheme() +{ +} + +QQuickTheme *QQuickTheme::instance() +{ + return QQuickThemePrivate::instance.get(); +} + +QFont QQuickTheme::font(Scope scope) +{ + const QFont *font = nullptr; + if (QQuickTheme *theme = instance()) + font = QQuickThemePrivate::get(theme)->fonts[scope].data(); + else if (QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) + font = theme->font(platformFont(scope)); + + if (font) { + QFont f = *font; + if (scope == System) + f.setResolveMask(0); + return f; + } + + if (scope != System) + return QQuickTheme::font(System); + + return QFont(); +} + +QPalette QQuickTheme::palette(Scope scope) +{ + const QPalette *palette = nullptr; + if (QQuickTheme *theme = instance()) + palette = QQuickThemePrivate::get(theme)->palettes[scope].data(); + else if (QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) + palette = theme->palette(platformPalette(scope)); + + if (palette) { + QPalette f = *palette; + if (scope == System) + f.setResolveMask(0); + return f; + } + + if (scope != System) + return QQuickTheme::palette(System); + + return QPalette(); +} + +void QQuickTheme::setFont(Scope scope, const QFont &font) +{ + Q_D(QQuickTheme); + d->fonts[scope] = QSharedPointer<QFont>::create(d->defaultFont ? d->defaultFont->resolve(font) : font); +} + +void QQuickTheme::setPalette(Scope scope, const QPalette &palette) +{ + Q_D(QQuickTheme); + d->palettes[scope] = QSharedPointer<QPalette>::create(d->defaultPalette ? d->defaultPalette->resolve(palette) : palette); +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquicktheme_p.h b/src/quicktemplates2/qquicktheme_p.h new file mode 100644 index 0000000000..d51cc5b658 --- /dev/null +++ b/src/quicktemplates2/qquicktheme_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTHEME_P_H +#define QQUICKTHEME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtCore/qscopedpointer.h> +#include <QtGui/qfont.h> +#include <QtGui/qpalette.h> + +QT_BEGIN_NAMESPACE + +class QQuickThemePrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTheme +{ +public: + QQuickTheme(); + ~QQuickTheme(); + + static QQuickTheme *instance(); + + enum Scope { + System, + Button, + CheckBox, + ComboBox, + GroupBox, + ItemView, + Label, + ListView, + Menu, + MenuBar, + RadioButton, + SpinBox, + Switch, + TabBar, + TextArea, + TextField, + ToolBar, + ToolTip, + Tumbler + }; + + static QFont font(Scope scope); + static QPalette palette(Scope scope); + + void setFont(Scope scope, const QFont &font); + void setPalette(Scope scope, const QPalette &palette); + +private: + Q_DISABLE_COPY(QQuickTheme) + Q_DECLARE_PRIVATE(QQuickTheme) + QScopedPointer<QQuickThemePrivate> d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QQUICKTHEME_P_H diff --git a/src/quicktemplates2/qquicktheme_p_p.h b/src/quicktemplates2/qquicktheme_p_p.h new file mode 100644 index 0000000000..b2c40eeb41 --- /dev/null +++ b/src/quicktemplates2/qquicktheme_p_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTHEME_P_P_H +#define QQUICKTHEME_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquicktheme_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickThemePrivate +{ +public: + static QQuickThemePrivate *get(QQuickTheme *theme) + { + return theme->d_func(); + } + + static std::unique_ptr<QQuickTheme> instance; + + static const int NScopes = QQuickTheme::Tumbler + 1; + + QScopedPointer<const QFont> defaultFont; + QScopedPointer<const QPalette> defaultPalette; + QSharedPointer<QFont> fonts[NScopes]; + QSharedPointer<QPalette> palettes[NScopes]; +}; + +QT_END_NAMESPACE + +#endif // QQUICKTHEME_P_P_H diff --git a/src/quicktemplates2/qquicktoolbar.cpp b/src/quicktemplates2/qquicktoolbar.cpp new file mode 100644 index 0000000000..ea08d84930 --- /dev/null +++ b/src/quicktemplates2/qquicktoolbar.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktoolbar_p.h" +#include "qquickpane_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ToolBar + \inherits Pane +//! \instantiates QQuickToolBar + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-containers + \brief Container for context-sensitive controls. + + ToolBar is a container of application-wide and context sensitive + actions and controls, such as navigation buttons and search fields. + ToolBar is commonly used as a \l {ApplicationWindow::header}{header} + or a \l {ApplicationWindow::footer}{footer} of an \l ApplicationWindow. + + ToolBar does not provide a layout of its own, but requires you to + position its contents, for instance by creating a \l RowLayout. If only + a single item is used within the ToolBar, it will resize to fit the + implicit size of its contained item. This makes it particularly suitable + for use together with layouts. + + \image qtquickcontrols2-toolbar.png + + \code + ApplicationWindow { + visible:true + + header: ToolBar { + RowLayout { + anchors.fill: parent + ToolButton { + text: qsTr("‹") + onClicked: stack.pop() + } + Label { + text: "Title" + elide: Label.ElideRight + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + Layout.fillWidth: true + } + ToolButton { + text: qsTr("⋮") + onClicked: menu.open() + } + } + } + + StackView { + id: stack + anchors.fill: parent + } + } + \endcode + + \sa ApplicationWindow, ToolButton, {Customizing ToolBar}, {Container Controls} +*/ + +class QQuickToolBarPrivate : public QQuickPanePrivate +{ +public: + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ToolBar); } + + QQuickToolBar::Position position = QQuickToolBar::Header; +}; + +QQuickToolBar::QQuickToolBar(QQuickItem *parent) + : QQuickPane(*(new QQuickToolBarPrivate), parent) +{ +} + +/*! + \qmlproperty enumeration QtQuick.Controls::ToolBar::position + + This property holds the position of the toolbar. + + \note If the toolbar is assigned as a header or footer of \l ApplicationWindow + or \l Page, the appropriate position is set automatically. + + Possible values: + \value ToolBar.Header The toolbar is at the top, as a window or page header. + \value ToolBar.Footer The toolbar is at the bottom, as a window or page footer. + + The default value is style-specific. + + \sa ApplicationWindow::header, ApplicationWindow::footer, Page::header, Page::footer +*/ +QQuickToolBar::Position QQuickToolBar::position() const +{ + Q_D(const QQuickToolBar); + return d->position; +} + +void QQuickToolBar::setPosition(Position position) +{ + Q_D(QQuickToolBar); + if (d->position == position) + return; + + d->position = position; + emit positionChanged(); +} + +QFont QQuickToolBar::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ToolBar); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickToolBar::accessibleRole() const +{ + return QAccessible::ToolBar; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquicktoolbar_p.cpp" diff --git a/src/quicktemplates2/qquicktoolbar_p.h b/src/quicktemplates2/qquicktoolbar_p.h new file mode 100644 index 0000000000..cefdeaa14a --- /dev/null +++ b/src/quicktemplates2/qquicktoolbar_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTOOLBAR_P_H +#define QQUICKTOOLBAR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpane_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickToolBarPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolBar : public QQuickPane +{ + Q_OBJECT + Q_PROPERTY(Position position READ position WRITE setPosition NOTIFY positionChanged FINAL) + QML_NAMED_ELEMENT(ToolBar) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickToolBar(QQuickItem *parent = nullptr); + + enum Position { + Header, + Footer + }; + Q_ENUM(Position) + + Position position() const; + void setPosition(Position position); + +Q_SIGNALS: + void positionChanged(); + +protected: + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickToolBar) + Q_DECLARE_PRIVATE(QQuickToolBar) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickToolBar) + +#endif // QQUICKTOOLBAR_P_H diff --git a/src/quicktemplates2/qquicktoolbutton.cpp b/src/quicktemplates2/qquicktoolbutton.cpp new file mode 100644 index 0000000000..100cf06a09 --- /dev/null +++ b/src/quicktemplates2/qquicktoolbutton.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktoolbutton_p.h" +#include "qquickcontrol_p_p.h" +#include "qquickbutton_p_p.h" + +#include <QtGui/qpa/qplatformtheme.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ToolButton + \inherits Button +//! \instantiates QQuickToolButton + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-buttons + \brief Button with a look suitable for a ToolBar. + + ToolButton is functionally similar to \l Button, but provides a look that + is more suitable within a \l ToolBar. + + \image qtquickcontrols2-toolbar.png + + \snippet qtquickcontrols2-toolbar.qml 1 + + ToolButton inherits its API from AbstractButton. For instance, you can set + \l {AbstractButton::text}{text}, display an \l {Icons in Qt Quick Controls}{icon}, + and react to \l {AbstractButton::clicked}{clicks} using the AbstractButton API. + + \sa ToolBar, {Customizing ToolButton}, {Button Controls} +*/ + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolPrivate : public QQuickButtonPrivate +{ + Q_DECLARE_PUBLIC(QQuickToolButton) + +public: + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ToolBar); } +}; + +QQuickToolButton::QQuickToolButton(QQuickItem *parent) + : QQuickButton(*(new QQuickToolPrivate), parent) +{ +} + +QFont QQuickToolButton::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ToolBar); +} + +QT_END_NAMESPACE + +#include "moc_qquicktoolbutton_p.cpp" diff --git a/src/quicktemplates2/qquicktoolbutton_p.h b/src/quicktemplates2/qquicktoolbutton_p.h new file mode 100644 index 0000000000..2db052f640 --- /dev/null +++ b/src/quicktemplates2/qquicktoolbutton_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTOOLBUTTON_P_H +#define QQUICKTOOLBUTTON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickbutton_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickToolButtonPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolButton : public QQuickButton +{ + Q_OBJECT + QML_NAMED_ELEMENT(ToolButton) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickToolButton(QQuickItem *parent = nullptr); + +protected: + QFont defaultFont() const override; + +private: + Q_DECLARE_PRIVATE(QQuickToolButton) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickToolButton) + +#endif // QQUICKTOOLBUTTON_P_H diff --git a/src/quicktemplates2/qquicktoolseparator.cpp b/src/quicktemplates2/qquicktoolseparator.cpp new file mode 100644 index 0000000000..21d496434a --- /dev/null +++ b/src/quicktemplates2/qquicktoolseparator.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktoolseparator_p.h" + +#include "qquickcontrol_p_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ToolSeparator + \inherits Control +//! \instantiates QQuickToolSeparator + \inqmlmodule QtQuick.Controls + \since 5.8 + \ingroup qtquickcontrols2-separators + \brief Separates a group of items in a toolbar from adjacent items. + + ToolSeparator is used to visually distinguish between groups of items in a + toolbar by separating them with a line. It can be used in horizontal or + vertical toolbars by setting the \l orientation property to \c Qt.Vertical + or \c Qt.Horizontal, respectively. + + \image qtquickcontrols2-toolseparator.png + + \snippet qtquickcontrols2-toolseparator.qml 1 + + \sa {Customizing ToolSeparator}, {Separator Controls} +*/ + +class QQuickToolSeparatorPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickToolSeparator) + +public: + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ToolBar); } + + Qt::Orientation orientation = Qt::Vertical; +}; + +QQuickToolSeparator::QQuickToolSeparator(QQuickItem *parent) + : QQuickControl(*(new QQuickToolSeparatorPrivate), parent) +{ +} + +/*! + \qmlproperty enumeration QtQuick.Controls::ToolSeparator::orientation + + This property holds the orientation of the tool separator. + + Possible values: + \value Qt.Horizontal A horizontal separator is used in a vertical toolbar. + \value Qt.Vertical A vertical separator is used in a horizontal toolbar. (default) +*/ +Qt::Orientation QQuickToolSeparator::orientation() const +{ + Q_D(const QQuickToolSeparator); + return d->orientation; +} + +void QQuickToolSeparator::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickToolSeparator); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + emit orientationChanged(); +} + +/*! + \readonly + \qmlproperty bool QtQuick.Controls::ToolSeparator::horizontal + + This property holds whether \l orientation is equal to \c Qt.Horizontal. + + It is useful for \l {Customizing ToolSeparator}{customizing ToolSeparator}. + + \sa orientation, vertical +*/ +bool QQuickToolSeparator::isHorizontal() const +{ + Q_D(const QQuickToolSeparator); + return d->orientation == Qt::Horizontal; +} + +/*! + \readonly + \qmlproperty bool QtQuick.Controls::ToolSeparator::vertical + + This property holds whether \l orientation is equal to \c Qt.Vertical. + + It is useful for \l {Customizing ToolSeparator}{customizing ToolSeparator}. + + \sa orientation, horizontal +*/ +bool QQuickToolSeparator::isVertical() const +{ + Q_D(const QQuickToolSeparator); + return d->orientation == Qt::Vertical; +} + +QFont QQuickToolSeparator::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ToolBar); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickToolSeparator::accessibleRole() const +{ + return QAccessible::Separator; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qquicktoolseparator_p.cpp" diff --git a/src/quicktemplates2/qquicktoolseparator_p.h b/src/quicktemplates2/qquicktoolseparator_p.h new file mode 100644 index 0000000000..e4692154d2 --- /dev/null +++ b/src/quicktemplates2/qquicktoolseparator_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTOOLSEPARATOR_P_H +#define QQUICKTOOLSEPARATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickToolSeparatorPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolSeparator : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + Q_PROPERTY(bool horizontal READ isHorizontal NOTIFY orientationChanged FINAL) + Q_PROPERTY(bool vertical READ isVertical NOTIFY orientationChanged FINAL) + QML_NAMED_ELEMENT(ToolSeparator) + QML_ADDED_IN_VERSION(2, 1) + +public: + explicit QQuickToolSeparator(QQuickItem *parent = nullptr); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + bool isHorizontal() const; + bool isVertical() const; + +Q_SIGNALS: + void orientationChanged(); + +protected: + QFont defaultFont() const override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickToolSeparator) + Q_DECLARE_PRIVATE(QQuickToolSeparator) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickToolSeparator) + +#endif // QQUICKTOOLSEPARATOR_P_H diff --git a/src/quicktemplates2/qquicktooltip.cpp b/src/quicktemplates2/qquicktooltip.cpp new file mode 100644 index 0000000000..9d64360fd9 --- /dev/null +++ b/src/quicktemplates2/qquicktooltip.cpp @@ -0,0 +1,576 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktooltip_p.h" +#include "qquickpopup_p_p.h" +#include "qquickpopupitem_p_p.h" +#include "qquickcontrol_p_p.h" + +#include <QtCore/qbasictimer.h> +#include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQuick/qquickwindow.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype ToolTip + \inherits Popup +//! \instantiates QQuickToolTip + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-popups + \brief Provides tool tips for any control. + + A tool tip is a short piece of text that informs the user of a control's + function. It is typically placed above or below the parent control. The + tip text can be any \l{Rich Text Processing}{rich text} formatted string. + + \image qtquickcontrols2-tooltip.png + + \section2 Attached Tool Tips + + The most straight-forward way to setup tool tips for controls is to + specify \l text and \l {visible}{visibility} via attached properties. + The following example illustrates this approach: + + \snippet qtquickcontrols2-tooltip.qml 1 + + Under normal circumstances, there is only one tool tip visible at a time. + In order to save resources, all items that use the ToolTip attached property + share the same visual tool tip label instance. Even though the visuals are + shared, \c text, \c timeout and \c delay are stored individually for each item + that uses the respective attached property. However, multiple items cannot + make the shared tool tip visible at the same time. The shared tool tip is only + shown for the last item that made it visible. The position of the shared tool + tip is determined by the framework. + + \include qquicktooltip.qdocinc customize-note + + \section2 Delay and Timeout + + Tool tips are typically transient in a sense that they are shown as a + result of a certain external event or user interaction, and they usually + hide after a certain timeout. It is possible to control the delay when + a tool tip is shown, and the timeout when it is hidden. This makes it + possible to implement varying strategies for showing and hiding tool tips. + + For example, on touch screens, it is a common pattern to show a tool tip + as a result of pressing and holding down a button. The following example + demonstrates how to delay showing a tool tip until the press-and-hold + interval is reached. In this example, the tool tip hides as soon as the + button is released. + + \snippet qtquickcontrols2-tooltip-pressandhold.qml 1 + + With pointer devices, however, it might be desired to show a tool tip as + a result of hovering a button for a while. The following example presents + how to show a tool tip after hovering a button for a second, and hide it + after a timeout of five seconds. + + \snippet qtquickcontrols2-tooltip-hover.qml 1 + + \section2 Custom Tool Tips + + Should one need more fine-grained control over the tool tip position, or + multiple simultaneous tool tip instances are needed, it is also possible + to create local tool tip instances. This way, it is possible to + \l {Customizing ToolTip}{customize} the tool tip, and the whole \l Popup + API is available. The following example presents a tool tip that presents + the value of a slider when the handle is dragged. + + \image qtquickcontrols2-tooltip-slider.png + + \snippet qtquickcontrols2-tooltip-slider.qml 1 + + \sa {Customizing ToolTip}, {Popup Controls}, + {QtQuick.Controls::Popup::closePolicy}{closePolicy} +*/ + +class QQuickToolTipPrivate : public QQuickPopupPrivate +{ + Q_DECLARE_PUBLIC(QQuickToolTip) + +public: + void startDelay(); + void stopDelay(); + + void startTimeout(); + void stopTimeout(); + + void opened() override; + + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ToolTip); } + + int delay = 0; + int timeout = -1; + QString text; + QBasicTimer delayTimer; + QBasicTimer timeoutTimer; +}; + +void QQuickToolTipPrivate::startDelay() +{ + Q_Q(QQuickToolTip); + if (delay > 0) + delayTimer.start(delay, q); +} + +void QQuickToolTipPrivate::stopDelay() +{ + delayTimer.stop(); +} + +void QQuickToolTipPrivate::startTimeout() +{ + Q_Q(QQuickToolTip); + if (timeout > 0) + timeoutTimer.start(timeout, q); +} + +void QQuickToolTipPrivate::stopTimeout() +{ + timeoutTimer.stop(); +} + +void QQuickToolTipPrivate::opened() +{ + QQuickPopupPrivate::opened(); + startTimeout(); +} + +QQuickToolTip::QQuickToolTip(QQuickItem *parent) + : QQuickPopup(*(new QQuickToolTipPrivate), parent) +{ + Q_D(QQuickToolTip); + d->allowVerticalFlip = true; + d->allowHorizontalFlip = true; + d->popupItem->setHoverEnabled(false); // QTBUG-63644 +} + +/*! + \qmlproperty string QtQuick.Controls::ToolTip::text + + This property holds the text shown on the tool tip. +*/ +QString QQuickToolTip::text() const +{ + Q_D(const QQuickToolTip); + return d->text; +} + +void QQuickToolTip::setText(const QString &text) +{ + Q_D(QQuickToolTip); + if (d->text == text) + return; + + d->text = text; + maybeSetAccessibleName(text); + emit textChanged(); +} + +/*! + \qmlproperty int QtQuick.Controls::ToolTip::delay + + This property holds the delay (milliseconds) after which the tool tip is + shown. A tooltip with a negative delay is shown immediately. The default + value is \c 0. + + \sa {Delay and Timeout} +*/ +int QQuickToolTip::delay() const +{ + Q_D(const QQuickToolTip); + return d->delay; +} + +void QQuickToolTip::setDelay(int delay) +{ + Q_D(QQuickToolTip); + if (d->delay == delay) + return; + + d->delay = delay; + emit delayChanged(); +} + +/*! + \qmlproperty int QtQuick.Controls::ToolTip::timeout + + This property holds the timeout (milliseconds) after which the tool tip is + hidden. A tooltip with a negative timeout does not hide automatically. The + default value is \c -1. + + \sa {Delay and Timeout} +*/ +int QQuickToolTip::timeout() const +{ + Q_D(const QQuickToolTip); + return d->timeout; +} + +void QQuickToolTip::setTimeout(int timeout) +{ + Q_D(QQuickToolTip); + if (d->timeout == timeout) + return; + + d->timeout = timeout; + + if (timeout <= 0) + d->stopTimeout(); + else if (isOpened()) + d->startTimeout(); + + emit timeoutChanged(); +} + +void QQuickToolTip::setVisible(bool visible) +{ + Q_D(QQuickToolTip); + if (visible) { + if (!d->visible) { + // We are being made visible, and we weren't before. + if (d->delay > 0) { + d->startDelay(); + return; + } + } + } else { + d->stopDelay(); + } + QQuickPopup::setVisible(visible); +} + +QQuickToolTipAttached *QQuickToolTip::qmlAttachedProperties(QObject *object) +{ + QQuickItem *item = qobject_cast<QQuickItem *>(object); + if (!item) + qmlWarning(object) << "ToolTip must be attached to an Item"; + + return new QQuickToolTipAttached(object); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlmethod void QtQuick.Controls::ToolTip::show(string text, int timeout) + + This method shows the \a text as a tooltip, which times out in + \a timeout (milliseconds). +*/ +void QQuickToolTip::show(const QString &text, int ms) +{ + if (ms >= 0) + setTimeout(ms); + setText(text); + open(); +} + +/*! + \since QtQuick.Controls 2.5 (Qt 5.12) + \qmlmethod void QtQuick.Controls::ToolTip::hide() + + This method hides the tooltip. +*/ +void QQuickToolTip::hide() +{ + close(); +} + +QFont QQuickToolTip::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ToolTip); +} + +void QQuickToolTip::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) +{ + Q_D(QQuickToolTip); + QQuickPopup::itemChange(change, data); + if (change == QQuickItem::ItemVisibleHasChanged) { + if (!data.boolValue) + d->stopTimeout(); + + QQuickToolTipAttached *attached = qobject_cast<QQuickToolTipAttached *>(qmlAttachedPropertiesObject<QQuickToolTip>(d->parentItem, false)); + if (attached) + emit attached->visibleChanged(); + } +} + +void QQuickToolTip::timerEvent(QTimerEvent *event) +{ + Q_D(QQuickToolTip); + if (event->timerId() == d->timeoutTimer.timerId()) { + d->stopTimeout(); + QQuickPopup::setVisible(false); + return; + } + if (event->timerId() == d->delayTimer.timerId()) { + d->stopDelay(); + QQuickPopup::setVisible(true); + return; + } + QQuickPopup::timerEvent(event); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickToolTip::accessibleRole() const +{ + return QAccessible::ToolTip; +} + +void QQuickToolTip::accessibilityActiveChanged(bool active) +{ + Q_D(QQuickToolTip); + QQuickPopup::accessibilityActiveChanged(active); + + if (active) + maybeSetAccessibleName(d->text); +} +#endif + +class QQuickToolTipAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickToolTipAttached) + +public: + QQuickToolTip *instance(bool create) const; + + int delay = 0; + int timeout = -1; + QString text; +}; + +QQuickToolTip *QQuickToolTipAttachedPrivate::instance(bool create) const +{ + QQmlEngine *engine = qmlEngine(parent); + if (!engine) + return nullptr; + + static const char *name = "_q_QQuickToolTip"; + + QQuickToolTip *tip = engine->property(name).value<QQuickToolTip *>(); + if (!tip && create) { + // TODO: a cleaner way to create the instance? QQml(Meta)Type? + QQmlComponent component(engine); + component.setData("import QtQuick.Controls; ToolTip { }", QUrl()); + + QObject *object = component.create(); + if (object) + object->setParent(engine); + + tip = qobject_cast<QQuickToolTip *>(object); + if (!tip) + delete object; + else + engine->setProperty(name, QVariant::fromValue(object)); + } + return tip; +} + +QQuickToolTipAttached::QQuickToolTipAttached(QObject *parent) + : QObject(*(new QQuickToolTipAttachedPrivate), parent) +{ +} + +/*! + \qmlattachedproperty string QtQuick.Controls::ToolTip::text + + This attached property holds the text of the shared tool tip. + The property can be attached to any item. + + \sa {Attached Tool Tips} +*/ +QString QQuickToolTipAttached::text() const +{ + Q_D(const QQuickToolTipAttached); + return d->text; +} + +void QQuickToolTipAttached::setText(const QString &text) +{ + Q_D(QQuickToolTipAttached); + if (d->text == text) + return; + + d->text = text; + emit textChanged(); + + if (isVisible()) + d->instance(true)->setText(text); +} + +/*! + \qmlattachedproperty int QtQuick.Controls::ToolTip::delay + + This attached property holds the delay (milliseconds) of the shared tool tip. + The property can be attached to any item. + + \sa {Attached Tool Tips}, {Delay and Timeout} +*/ +int QQuickToolTipAttached::delay() const +{ + Q_D(const QQuickToolTipAttached); + return d->delay; +} + +void QQuickToolTipAttached::setDelay(int delay) +{ + Q_D(QQuickToolTipAttached); + if (d->delay == delay) + return; + + d->delay = delay; + emit delayChanged(); + + if (isVisible()) + d->instance(true)->setDelay(delay); +} + +/*! + \qmlattachedproperty int QtQuick.Controls::ToolTip::timeout + + This attached property holds the timeout (milliseconds) of the shared tool tip. + The property can be attached to any item. + + \sa {Attached Tool Tips}, {Delay and Timeout} +*/ +int QQuickToolTipAttached::timeout() const +{ + Q_D(const QQuickToolTipAttached); + return d->timeout; +} + +void QQuickToolTipAttached::setTimeout(int timeout) +{ + Q_D(QQuickToolTipAttached); + if (d->timeout == timeout) + return; + + d->timeout = timeout; + emit timeoutChanged(); + + if (isVisible()) + d->instance(true)->setTimeout(timeout); +} + +/*! + \qmlattachedproperty bool QtQuick.Controls::ToolTip::visible + + This attached property holds whether the shared tool tip is visible. + The property can be attached to any item. + + \sa {Attached Tool Tips} +*/ +bool QQuickToolTipAttached::isVisible() const +{ + Q_D(const QQuickToolTipAttached); + QQuickToolTip *tip = d->instance(false); + if (!tip) + return false; + + return tip->isVisible() && tip->parentItem() == parent(); +} + +void QQuickToolTipAttached::setVisible(bool visible) +{ + Q_D(QQuickToolTipAttached); + if (visible) + show(d->text); + else + hide(); +} + +/*! + \qmlattachedproperty ToolTip QtQuick.Controls::ToolTip::toolTip + + This attached property holds the shared tool tip instance. The property + can be attached to any item. + + \sa {Attached Tool Tips} +*/ +QQuickToolTip *QQuickToolTipAttached::toolTip() const +{ + Q_D(const QQuickToolTipAttached); + return d->instance(true); +} + +/*! + \qmlattachedmethod void QtQuick.Controls::ToolTip::show(string text, int timeout = -1) + + This attached method shows the shared tooltip with \a text and \a timeout (milliseconds). + The method can be attached to any item. + + \sa {Attached Tool Tips} +*/ +void QQuickToolTipAttached::show(const QString &text, int ms) +{ + Q_D(QQuickToolTipAttached); + QQuickToolTip *tip = d->instance(true); + if (!tip) + return; + + tip->resetWidth(); + tip->resetHeight(); + tip->setParentItem(qobject_cast<QQuickItem *>(parent())); + tip->setDelay(d->delay); + tip->setTimeout(ms >= 0 ? ms : d->timeout); + tip->show(text); +} + +/*! + \qmlattachedmethod void QtQuick.Controls::ToolTip::hide() + + This attached method hides the shared tooltip. The method can be attached to any item. + + \sa {Attached Tool Tips} +*/ +void QQuickToolTipAttached::hide() +{ + Q_D(QQuickToolTipAttached); + QQuickToolTip *tip = d->instance(false); + if (!tip) + return; + // check the parent item to prevent unexpectedly closing tooltip by new created invisible tooltip + if (parent() == tip->parentItem()) + tip->close(); +} + +QT_END_NAMESPACE + +#include "moc_qquicktooltip_p.cpp" diff --git a/src/quicktemplates2/qquicktooltip_p.h b/src/quicktemplates2/qquicktooltip_p.h new file mode 100644 index 0000000000..5be22fd8c2 --- /dev/null +++ b/src/quicktemplates2/qquicktooltip_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTOOLTIP_P_H +#define QQUICKTOOLTIP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickpopup_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickToolTipPrivate; +class QQuickToolTipAttached; +class QQuickToolTipAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolTip : public QQuickPopup +{ + Q_OBJECT + Q_PROPERTY(int delay READ delay WRITE setDelay NOTIFY delayChanged FINAL) + Q_PROPERTY(int timeout READ timeout WRITE setTimeout NOTIFY timeoutChanged FINAL) + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL) + QML_NAMED_ELEMENT(ToolTip) + QML_ATTACHED(QQuickToolTipAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickToolTip(QQuickItem *parent = nullptr); + + QString text() const; + void setText(const QString &text); + + int delay() const; + void setDelay(int delay); + + int timeout() const; + void setTimeout(int timeout); + + void setVisible(bool visible) override; + + static QQuickToolTipAttached *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void textChanged(); + void delayChanged(); + void timeoutChanged(); + +public Q_SLOTS: + Q_REVISION(2, 5) void show(const QString &text, int ms = -1); + Q_REVISION(2, 5) void hide(); + +protected: + QFont defaultFont() const override; + + void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) override; + void timerEvent(QTimerEvent *event) override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; + void accessibilityActiveChanged(bool active) override; +#endif + +private: + Q_DISABLE_COPY(QQuickToolTip) + Q_DECLARE_PRIVATE(QQuickToolTip) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolTipAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL) + Q_PROPERTY(int delay READ delay WRITE setDelay NOTIFY delayChanged FINAL) + Q_PROPERTY(int timeout READ timeout WRITE setTimeout NOTIFY timeoutChanged FINAL) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged FINAL) + Q_PROPERTY(QQuickToolTip *toolTip READ toolTip CONSTANT FINAL) + +public: + explicit QQuickToolTipAttached(QObject *parent = nullptr); + + QString text() const; + void setText(const QString &text); + + int delay() const; + void setDelay(int delay); + + int timeout() const; + void setTimeout(int timeout); + + bool isVisible() const; + void setVisible(bool visible); + + QQuickToolTip *toolTip() const; + +Q_SIGNALS: + void textChanged(); + void delayChanged(); + void timeoutChanged(); + void visibleChanged(); + +public Q_SLOTS: + void show(const QString &text, int ms = -1); + void hide(); + +private: + Q_DISABLE_COPY(QQuickToolTipAttached) + Q_DECLARE_PRIVATE(QQuickToolTipAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickToolTip) +QML_DECLARE_TYPEINFO(QQuickToolTip, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKTOOLTIP_P_H diff --git a/src/quicktemplates2/qquicktumbler.cpp b/src/quicktemplates2/qquicktumbler.cpp new file mode 100644 index 0000000000..f66428b83e --- /dev/null +++ b/src/quicktemplates2/qquicktumbler.cpp @@ -0,0 +1,1047 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktumbler_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickflickable_p.h> +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#include <QtQuickTemplates2/private/qquicktumbler_p_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTumbler, "qt.quick.controls.tumbler") + +/*! + \qmltype Tumbler + \inherits Control +//! \instantiates QQuickTumbler + \inqmlmodule QtQuick.Controls + \since 5.7 + \ingroup qtquickcontrols2-input + \brief Spinnable wheel of items that can be selected. + + \image qtquickcontrols2-tumbler-wrap.gif + + \code + Tumbler { + model: 5 + // ... + } + \endcode + + Tumbler allows the user to select an option from a spinnable \e "wheel" of + items. It is useful for when there are too many options to use, for + example, a RadioButton, and too few options to require the use of an + editable SpinBox. It is convenient in that it requires no keyboard usage + and wraps around at each end when there are a large number of items. + + The API is similar to that of views like \l ListView and \l PathView; a + \l model and \l delegate can be set, and the \l count and \l currentItem + properties provide read-only access to information about the view. To + position the view at a certain index, use \l positionViewAtIndex(). + + Unlike views like \l PathView and \l ListView, however, there is always a + current item (when the model isn't empty). This means that when \l count is + equal to \c 0, \l currentIndex will be \c -1. In all other cases, it will + be greater than or equal to \c 0. + + By default, Tumbler \l {wrap}{wraps} when it reaches the top and bottom, as + long as there are more items in the model than there are visible items; + that is, when \l count is greater than \l visibleItemCount: + + \snippet qtquickcontrols2-tumbler-timePicker.qml tumbler + + \sa {Customizing Tumbler}, {Input Controls} +*/ + +namespace { + static inline qreal delegateHeight(const QQuickTumbler *tumbler) + { + return tumbler->availableHeight() / tumbler->visibleItemCount(); + } +} + +/* + Finds the contentItem of the view that is a child of the control's \a contentItem. + The type is stored in \a type. +*/ +QQuickItem *QQuickTumblerPrivate::determineViewType(QQuickItem *contentItem) +{ + if (!contentItem) { + resetViewData(); + return nullptr; + } + + if (contentItem->inherits("QQuickPathView")) { + view = contentItem; + viewContentItem = contentItem; + viewContentItemType = PathViewContentItem; + viewOffset = 0; + + return contentItem; + } else if (contentItem->inherits("QQuickListView")) { + view = contentItem; + viewContentItem = qobject_cast<QQuickFlickable*>(contentItem)->contentItem(); + viewContentItemType = ListViewContentItem; + viewContentY = 0; + + return contentItem; + } else { + const auto childItems = contentItem->childItems(); + for (QQuickItem *childItem : childItems) { + QQuickItem *item = determineViewType(childItem); + if (item) + return item; + } + } + + resetViewData(); + viewContentItemType = UnsupportedContentItemType; + return nullptr; +} + +void QQuickTumblerPrivate::resetViewData() +{ + view = nullptr; + viewContentItem = nullptr; + if (viewContentItemType == PathViewContentItem) + viewOffset = 0; + else if (viewContentItemType == ListViewContentItem) + viewContentY = 0; + viewContentItemType = NoContentItem; +} + +QList<QQuickItem *> QQuickTumblerPrivate::viewContentItemChildItems() const +{ + if (!viewContentItem) + return QList<QQuickItem *>(); + + return viewContentItem->childItems(); +} + +QQuickTumblerPrivate *QQuickTumblerPrivate::get(QQuickTumbler *tumbler) +{ + return tumbler->d_func(); +} + +void QQuickTumblerPrivate::_q_updateItemHeights() +{ + if (ignoreSignals) + return; + + // Can't use our own private padding members here, as the padding property might be set, + // which doesn't affect them, only their getters. + Q_Q(const QQuickTumbler); + const qreal itemHeight = delegateHeight(q); + const auto items = viewContentItemChildItems(); + for (QQuickItem *childItem : items) + childItem->setHeight(itemHeight); +} + +void QQuickTumblerPrivate::_q_updateItemWidths() +{ + if (ignoreSignals) + return; + + Q_Q(const QQuickTumbler); + const qreal availableWidth = q->availableWidth(); + const auto items = viewContentItemChildItems(); + for (QQuickItem *childItem : items) + childItem->setWidth(availableWidth); +} + +void QQuickTumblerPrivate::_q_onViewCurrentIndexChanged() +{ + Q_Q(QQuickTumbler); + if (!view || ignoreCurrentIndexChanges || currentIndexSetDuringModelChange) { + // If the user set currentIndex in the onModelChanged handler, + // we have to respect that currentIndex by ignoring changes in the view + // until the model has finished being set. + qCDebug(lcTumbler).nospace() << "view currentIndex changed to " + << (view ? view->property("currentIndex").toString() : QStringLiteral("unknown index (no view)")) + << ", but we're ignoring it because one or more of the following conditions are true:" + << "\n- !view: " << !view + << "\n- ignoreCurrentIndexChanges: " << ignoreCurrentIndexChanges + << "\n- currentIndexSetDuringModelChange: " << currentIndexSetDuringModelChange; + return; + } + + const int oldCurrentIndex = currentIndex; + currentIndex = view->property("currentIndex").toInt(); + + qCDebug(lcTumbler).nospace() << "view currentIndex changed to " + << (view ? view->property("currentIndex").toString() : QStringLiteral("unknown index (no view)")) + << ", our old currentIndex was " << oldCurrentIndex; + + if (oldCurrentIndex != currentIndex) + emit q->currentIndexChanged(); +} + +void QQuickTumblerPrivate::_q_onViewCountChanged() +{ + Q_Q(QQuickTumbler); + qCDebug(lcTumbler) << "view count changed - ignoring signals?" << ignoreSignals; + if (ignoreSignals) + return; + + setCount(view->property("count").toInt()); + + if (count > 0) { + if (pendingCurrentIndex != -1) { + // If there was an attempt to set currentIndex at creation, try to finish that attempt now. + // componentComplete() is too early, because the count might only be known sometime after completion. + setCurrentIndex(pendingCurrentIndex); + // If we could successfully set the currentIndex, consider it done. + // Otherwise, we'll try again later in updatePolish(). + if (currentIndex == pendingCurrentIndex) + setPendingCurrentIndex(-1); + else + q->polish(); + } else if (currentIndex == -1) { + // If new items were added and our currentIndex was -1, we must + // enforce our rule of a non-negative currentIndex when count > 0. + setCurrentIndex(0); + } + } else { + setCurrentIndex(-1); + } +} + +void QQuickTumblerPrivate::_q_onViewOffsetChanged() +{ + viewOffset = view->property("offset").toReal(); + calculateDisplacements(); +} + +void QQuickTumblerPrivate::_q_onViewContentYChanged() +{ + viewContentY = view->property("contentY").toReal(); + calculateDisplacements(); +} + +void QQuickTumblerPrivate::calculateDisplacements() +{ + const auto items = viewContentItemChildItems(); + for (QQuickItem *childItem : items) { + QQuickTumblerAttached *attached = qobject_cast<QQuickTumblerAttached *>(qmlAttachedPropertiesObject<QQuickTumbler>(childItem, false)); + if (attached) + QQuickTumblerAttachedPrivate::get(attached)->calculateDisplacement(); + } +} + +void QQuickTumblerPrivate::itemChildAdded(QQuickItem *, QQuickItem *) +{ + _q_updateItemWidths(); + _q_updateItemHeights(); +} + +void QQuickTumblerPrivate::itemChildRemoved(QQuickItem *, QQuickItem *) +{ + _q_updateItemWidths(); + _q_updateItemHeights(); +} + +void QQuickTumblerPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + QQuickControlPrivate::itemGeometryChanged(item, change, diff); + if (change.sizeChange()) + calculateDisplacements(); +} + +QPalette QQuickTumblerPrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::Tumbler); +} + +QQuickTumbler::QQuickTumbler(QQuickItem *parent) + : QQuickControl(*(new QQuickTumblerPrivate), parent) +{ + setActiveFocusOnTab(true); + + connect(this, SIGNAL(leftPaddingChanged()), this, SLOT(_q_updateItemWidths())); + connect(this, SIGNAL(rightPaddingChanged()), this, SLOT(_q_updateItemWidths())); + connect(this, SIGNAL(topPaddingChanged()), this, SLOT(_q_updateItemHeights())); + connect(this, SIGNAL(bottomPaddingChanged()), this, SLOT(_q_updateItemHeights())); +} + +QQuickTumbler::~QQuickTumbler() +{ + Q_D(QQuickTumbler); + // Ensure that the item change listener is removed. + d->disconnectFromView(); +} + +/*! + \qmlproperty variant QtQuick.Controls::Tumbler::model + + This property holds the model that provides data for this tumbler. +*/ +QVariant QQuickTumbler::model() const +{ + Q_D(const QQuickTumbler); + return d->model; +} + +void QQuickTumbler::setModel(const QVariant &model) +{ + Q_D(QQuickTumbler); + if (model == d->model) + return; + + d->beginSetModel(); + + d->model = model; + emit modelChanged(); + + d->endSetModel(); + + d->currentIndexSetDuringModelChange = false; + + // Don't try to correct the currentIndex if count() isn't known yet. + // We can check in setupViewData() instead. + if (isComponentComplete() && d->view && count() == 0) + d->setCurrentIndex(-1); +} + +/*! + \qmlproperty int QtQuick.Controls::Tumbler::count + \readonly + + This property holds the number of items in the model. +*/ +int QQuickTumbler::count() const +{ + Q_D(const QQuickTumbler); + return d->count; +} + +/*! + \qmlproperty int QtQuick.Controls::Tumbler::currentIndex + + This property holds the index of the current item. + + The value of this property is \c -1 when \l count is equal to \c 0. In all + other cases, it will be greater than or equal to \c 0. + + \sa currentItem, positionViewAtIndex() +*/ +int QQuickTumbler::currentIndex() const +{ + Q_D(const QQuickTumbler); + return d->currentIndex; +} + +void QQuickTumbler::setCurrentIndex(int currentIndex) +{ + Q_D(QQuickTumbler); + if (d->modelBeingSet) + d->currentIndexSetDuringModelChange = true; + d->setCurrentIndex(currentIndex, QQuickTumblerPrivate::UserChange); +} + +/*! + \qmlproperty Item QtQuick.Controls::Tumbler::currentItem + \readonly + + This property holds the item at the current index. + + \sa currentIndex, positionViewAtIndex() +*/ +QQuickItem *QQuickTumbler::currentItem() const +{ + Q_D(const QQuickTumbler); + return d->view ? d->view->property("currentItem").value<QQuickItem*>() : nullptr; +} + +/*! + \qmlproperty Component QtQuick.Controls::Tumbler::delegate + + This property holds the delegate used to display each item. +*/ +QQmlComponent *QQuickTumbler::delegate() const +{ + Q_D(const QQuickTumbler); + return d->delegate; +} + +void QQuickTumbler::setDelegate(QQmlComponent *delegate) +{ + Q_D(QQuickTumbler); + if (delegate == d->delegate) + return; + + d->delegate = delegate; + emit delegateChanged(); +} + +/*! + \qmlproperty int QtQuick.Controls::Tumbler::visibleItemCount + + This property holds the number of items visible in the tumbler. It must be + an odd number, as the current item is always vertically centered. +*/ +int QQuickTumbler::visibleItemCount() const +{ + Q_D(const QQuickTumbler); + return d->visibleItemCount; +} + +void QQuickTumbler::setVisibleItemCount(int visibleItemCount) +{ + Q_D(QQuickTumbler); + if (visibleItemCount == d->visibleItemCount) + return; + + d->visibleItemCount = visibleItemCount; + d->_q_updateItemHeights(); + emit visibleItemCountChanged(); +} + +QQuickTumblerAttached *QQuickTumbler::qmlAttachedProperties(QObject *object) +{ + return new QQuickTumblerAttached(object); +} + +/*! + \qmlproperty bool QtQuick.Controls::Tumbler::wrap + \since QtQuick.Controls 2.1 (Qt 5.8) + + This property determines whether or not the tumbler wraps around when it + reaches the top or bottom. + + The default value is \c false when \l count is less than + \l visibleItemCount, as it is simpler to interact with a non-wrapping Tumbler + when there are only a few items. To override this behavior, explicitly set + the value of this property. To return to the default behavior, set this + property to \c undefined. +*/ +bool QQuickTumbler::wrap() const +{ + Q_D(const QQuickTumbler); + return d->wrap; +} + +void QQuickTumbler::setWrap(bool wrap) +{ + Q_D(QQuickTumbler); + d->setWrap(wrap, true); +} + +void QQuickTumbler::resetWrap() +{ + Q_D(QQuickTumbler); + d->explicitWrap = false; + d->setWrapBasedOnCount(); +} + +/*! + \qmlproperty bool QtQuick.Controls::Tumbler::moving + \since QtQuick.Controls 2.2 (Qt 5.9) + + This property describes whether the tumbler is currently moving, due to + the user either dragging or flicking it. +*/ +bool QQuickTumbler::isMoving() const +{ + Q_D(const QQuickTumbler); + return d->view && d->view->property("moving").toBool(); +} + +/*! + \qmlmethod void QtQuick.Controls::Tumbler::positionViewAtIndex(int index, PositionMode mode) + \since QtQuick.Controls 2.5 (Qt 5.12) + + Positions the view so that the \a index is at the position specified by \a mode. + + For example: + + \code + positionViewAtIndex(10, Tumbler.Center) + \endcode + + If \l wrap is true (the default), the modes available to \l {PathView}'s + \l {PathView::}{positionViewAtIndex()} function + are available, otherwise the modes available to \l {ListView}'s + \l {ListView::}{positionViewAtIndex()} function + are available. + + \note There is a known limitation that using \c Tumbler.Beginning when \l + wrap is \c true will result in the wrong item being positioned at the top + of view. As a workaround, pass \c {index - 1}. + + \sa currentIndex +*/ +void QQuickTumbler::positionViewAtIndex(int index, QQuickTumbler::PositionMode mode) +{ + Q_D(QQuickTumbler); + if (!d->view) { + d->warnAboutIncorrectContentItem(); + return; + } + + QMetaObject::invokeMethod(d->view, "positionViewAtIndex", Q_ARG(int, index), Q_ARG(int, mode)); +} + +void QQuickTumbler::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickTumbler); + + QQuickControl::geometryChange(newGeometry, oldGeometry); + + d->_q_updateItemHeights(); + + if (newGeometry.width() != oldGeometry.width()) + d->_q_updateItemWidths(); +} + +void QQuickTumbler::componentComplete() +{ + Q_D(QQuickTumbler); + qCDebug(lcTumbler) << "componentComplete()"; + QQuickControl::componentComplete(); + + if (!d->view) { + // Force the view to be created. + qCDebug(lcTumbler) << "emitting wrapChanged() to force view to be created"; + emit wrapChanged(); + // Determine the type of view for attached properties, etc. + d->setupViewData(d->contentItem); + } + + // If there was no contentItem or it was of an unsupported type, + // we don't have anything else to do. + if (!d->view) + return; + + // Update item heights after we've populated the model, + // otherwise ignoreSignals will cause these functions to return early. + d->_q_updateItemHeights(); + d->_q_updateItemWidths(); + d->_q_onViewCountChanged(); + + qCDebug(lcTumbler) << "componentComplete() is done"; +} + +void QQuickTumbler::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) +{ + Q_D(QQuickTumbler); + + QQuickControl::contentItemChange(newItem, oldItem); + + if (oldItem) + d->disconnectFromView(); + + if (newItem) { + // We wait until wrap is set to that we know which type of view to create. + // If we try to set up the view too early, we'll issue warnings about it not existing. + if (isComponentComplete()) { + // Make sure we use the new content item and not the current one, as that won't + // be changed until after contentItemChange() has finished. + d->setupViewData(newItem); + + d->_q_updateItemHeights(); + d->_q_updateItemWidths(); + } + } +} + +void QQuickTumblerPrivate::disconnectFromView() +{ + Q_Q(QQuickTumbler); + if (!view) { + // If a custom content item is declared, it can happen that + // the original contentItem exists without the view etc. having been + // determined yet, and then this is called when the custom content item + // is eventually set. + return; + } + + QObject::disconnect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged())); + QObject::disconnect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged())); + QObject::disconnect(view, SIGNAL(countChanged()), q, SLOT(_q_onViewCountChanged())); + QObject::disconnect(view, SIGNAL(movingChanged()), q, SIGNAL(movingChanged())); + + if (viewContentItemType == PathViewContentItem) + QObject::disconnect(view, SIGNAL(offsetChanged()), q, SLOT(_q_onViewOffsetChanged())); + else + QObject::disconnect(view, SIGNAL(contentYChanged()), q, SLOT(_q_onViewContentYChanged())); + + QQuickItemPrivate *oldViewContentItemPrivate = QQuickItemPrivate::get(viewContentItem); + oldViewContentItemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Children | QQuickItemPrivate::Geometry); + + resetViewData(); +} + +void QQuickTumblerPrivate::setupViewData(QQuickItem *newControlContentItem) +{ + // Don't do anything if we've already set up. + if (view) + return; + + determineViewType(newControlContentItem); + + if (viewContentItemType == QQuickTumblerPrivate::NoContentItem) + return; + + if (viewContentItemType == QQuickTumblerPrivate::UnsupportedContentItemType) { + warnAboutIncorrectContentItem(); + return; + } + + Q_Q(QQuickTumbler); + QObject::connect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged())); + QObject::connect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged())); + QObject::connect(view, SIGNAL(countChanged()), q, SLOT(_q_onViewCountChanged())); + QObject::connect(view, SIGNAL(movingChanged()), q, SIGNAL(movingChanged())); + + if (viewContentItemType == PathViewContentItem) { + QObject::connect(view, SIGNAL(offsetChanged()), q, SLOT(_q_onViewOffsetChanged())); + _q_onViewOffsetChanged(); + } else { + QObject::connect(view, SIGNAL(contentYChanged()), q, SLOT(_q_onViewContentYChanged())); + _q_onViewContentYChanged(); + } + + QQuickItemPrivate *viewContentItemPrivate = QQuickItemPrivate::get(viewContentItem); + viewContentItemPrivate->addItemChangeListener(this, QQuickItemPrivate::Children | QQuickItemPrivate::Geometry); + + // Sync the view's currentIndex with ours. + syncCurrentIndex(); + + calculateDisplacements(); +} + +void QQuickTumblerPrivate::warnAboutIncorrectContentItem() +{ + Q_Q(QQuickTumbler); + qmlWarning(q) << "Tumbler: contentItem must contain either a PathView or a ListView"; +} + +void QQuickTumblerPrivate::syncCurrentIndex() +{ + const int actualViewIndex = view->property("currentIndex").toInt(); + Q_Q(QQuickTumbler); + + const bool isPendingCurrentIndex = pendingCurrentIndex != -1; + const int indexToSet = isPendingCurrentIndex ? pendingCurrentIndex : currentIndex; + + // Nothing to do. + if (actualViewIndex == indexToSet) { + setPendingCurrentIndex(-1); + return; + } + + // actualViewIndex might be 0 or -1 for PathView and ListView respectively, + // but we always use -1 for that. + if (q->count() == 0 && actualViewIndex <= 0) + return; + + ignoreCurrentIndexChanges = true; + view->setProperty("currentIndex", QVariant(indexToSet)); + ignoreCurrentIndexChanges = false; + + if (view->property("currentIndex").toInt() == indexToSet) + setPendingCurrentIndex(-1); + else if (isPendingCurrentIndex) + q->polish(); +} + +void QQuickTumblerPrivate::setPendingCurrentIndex(int index) +{ + qCDebug(lcTumbler) << "setting pendingCurrentIndex to" << index; + pendingCurrentIndex = index; +} + +QString QQuickTumblerPrivate::propertyChangeReasonToString( + QQuickTumblerPrivate::PropertyChangeReason changeReason) +{ + return changeReason == UserChange ? QStringLiteral("UserChange") : QStringLiteral("InternalChange"); +} + +void QQuickTumblerPrivate::setCurrentIndex(int newCurrentIndex, + QQuickTumblerPrivate::PropertyChangeReason changeReason) +{ + Q_Q(QQuickTumbler); + qCDebug(lcTumbler).nospace() << "setting currentIndex to " << newCurrentIndex + << ", old currentIndex was " << currentIndex + << ", changeReason is " << propertyChangeReasonToString(changeReason); + if (newCurrentIndex == currentIndex || newCurrentIndex < -1) + return; + + if (!q->isComponentComplete()) { + // Views can't set currentIndex until they're ready. + qCDebug(lcTumbler) << "we're not complete; setting pendingCurrentIndex instead"; + setPendingCurrentIndex(newCurrentIndex); + return; + } + + if (modelBeingSet && changeReason == UserChange) { + // If modelBeingSet is true and the user set the currentIndex, + // the model is in the process of being set and the user has set + // the currentIndex in onModelChanged. We have to queue the currentIndex + // change until we're ready. + qCDebug(lcTumbler) << "a model is being set; setting pendingCurrentIndex instead"; + setPendingCurrentIndex(newCurrentIndex); + return; + } + + // -1 doesn't make sense for a non-empty Tumbler, because unlike + // e.g. ListView, there's always one item selected. + // Wait until the component has finished before enforcing this rule, though, + // because the count might not be known yet. + if ((count > 0 && newCurrentIndex == -1) || (newCurrentIndex >= count)) { + return; + } + + // The view might not have been created yet, as is the case + // if you create a Tumbler component and pass e.g. { currentIndex: 2 } + // to createObject(). + if (view) { + // Only actually set our currentIndex if the view was able to set theirs. + bool couldSet = false; + if (count == 0 && newCurrentIndex == -1) { + // PathView insists on using 0 as the currentIndex when there are no items. + couldSet = true; + } else { + ignoreCurrentIndexChanges = true; + ignoreSignals = true; + view->setProperty("currentIndex", newCurrentIndex); + ignoreSignals = false; + ignoreCurrentIndexChanges = false; + + couldSet = view->property("currentIndex").toInt() == newCurrentIndex; + } + + if (couldSet) { + // The view's currentIndex might not have actually changed, but ours has, + // and that's what user code sees. + currentIndex = newCurrentIndex; + emit q->currentIndexChanged(); + } + + qCDebug(lcTumbler) << "view's currentIndex is now" << view->property("currentIndex").toInt() + << "and ours is" << currentIndex; + } +} + +void QQuickTumblerPrivate::setCount(int newCount) +{ + qCDebug(lcTumbler).nospace() << "setting count to " << newCount + << ", old count was " << count; + if (newCount == count) + return; + + count = newCount; + + Q_Q(QQuickTumbler); + setWrapBasedOnCount(); + + emit q->countChanged(); +} + +void QQuickTumblerPrivate::setWrapBasedOnCount() +{ + if (count == 0 || explicitWrap || modelBeingSet) + return; + + setWrap(count >= visibleItemCount, false); +} + +void QQuickTumblerPrivate::setWrap(bool shouldWrap, bool isExplicit) +{ + qCDebug(lcTumbler) << "setting wrap to" << shouldWrap << "- exlicit?" << isExplicit; + if (isExplicit) + explicitWrap = true; + + Q_Q(QQuickTumbler); + if (q->isComponentComplete() && shouldWrap == wrap) + return; + + // Since we use the currentIndex of the contentItem directly, we must + // ensure that we keep track of the currentIndex so it doesn't get lost + // between view changes. + const int oldCurrentIndex = currentIndex; + + disconnectFromView(); + + wrap = shouldWrap; + + // New views will set their currentIndex upon creation, which we'd otherwise + // take as the correct one, so we must ignore them. + ignoreCurrentIndexChanges = true; + + // This will cause the view to be created if our contentItem is a TumblerView. + emit q->wrapChanged(); + + ignoreCurrentIndexChanges = false; + + // If isComponentComplete() is true, we require a contentItem. If it's not + // true, it might not have been created yet, so we wait until + // componentComplete() is called. + // + // When the contentItem (usually QQuickTumblerView) has been created, we + // can start determining its type, etc. If the delegates use attached + // properties, this will have already been called, in which case it will + // return early. If the delegate doesn't use attached properties, we need + // to call it here. + if (q->isComponentComplete() || contentItem) + setupViewData(contentItem); + + setCurrentIndex(oldCurrentIndex); +} + +void QQuickTumblerPrivate::beginSetModel() +{ + modelBeingSet = true; +} + +void QQuickTumblerPrivate::endSetModel() +{ + modelBeingSet = false; + setWrapBasedOnCount(); +} + +void QQuickTumbler::keyPressEvent(QKeyEvent *event) +{ + QQuickControl::keyPressEvent(event); + + Q_D(QQuickTumbler); + if (event->isAutoRepeat() || !d->view) + return; + + if (event->key() == Qt::Key_Up) { + QMetaObject::invokeMethod(d->view, "decrementCurrentIndex"); + } else if (event->key() == Qt::Key_Down) { + QMetaObject::invokeMethod(d->view, "incrementCurrentIndex"); + } +} + +void QQuickTumbler::updatePolish() +{ + Q_D(QQuickTumbler); + if (d->pendingCurrentIndex != -1) { + // Update our count, as ignoreSignals might have been true + // when _q_onViewCountChanged() was last called. + d->setCount(d->view->property("count").toInt()); + + // If the count is still 0, it's not going to happen. + if (d->count == 0) { + d->setPendingCurrentIndex(-1); + return; + } + + // If there is a pending currentIndex at this stage, it means that + // the view wouldn't set our currentIndex in _q_onViewCountChanged + // because it wasn't ready. Try one last time here. + d->setCurrentIndex(d->pendingCurrentIndex); + + if (d->currentIndex != d->pendingCurrentIndex && d->currentIndex == -1) { + // If we *still* couldn't set it, it's probably invalid. + // See if we can at least enforce our rule of "non-negative currentIndex when count > 0" instead. + d->setCurrentIndex(0); + } + + d->setPendingCurrentIndex(-1); + } +} + +QFont QQuickTumbler::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::Tumbler); +} + +void QQuickTumblerAttachedPrivate::init(QQuickItem *delegateItem) +{ + Q_Q(QQuickTumblerAttached); + if (!delegateItem->parentItem()) { + qmlWarning(q) << "Tumbler: attached properties must be accessed through a delegate item that has a parent"; + return; + } + + QVariant indexContextProperty = qmlContext(delegateItem)->contextProperty(QStringLiteral("index")); + if (!indexContextProperty.isValid()) { + qmlWarning(q) << "Tumbler: attempting to access attached property on item without an \"index\" property"; + return; + } + + index = indexContextProperty.toInt(); + + QQuickItem *parentItem = delegateItem; + while ((parentItem = parentItem->parentItem())) { + if ((tumbler = qobject_cast<QQuickTumbler*>(parentItem))) + break; + } +} + +void QQuickTumblerAttachedPrivate::calculateDisplacement() +{ + const qreal previousDisplacement = displacement; + displacement = 0; + + if (!tumbler) { + // Can happen if the attached properties are accessed on the wrong type of item or the tumbler was destroyed. + // We don't want to emit the change signal though, as this could cause warnings about Tumbler.tumbler being null. + return; + } + + // Can happen if there is no ListView or PathView within the contentItem. + QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(tumbler); + if (!tumblerPrivate->viewContentItem) { + emitIfDisplacementChanged(previousDisplacement, displacement); + return; + } + + // The attached property gets created before our count is updated, so just cheat here + // to avoid having to listen to count changes. + const int count = tumblerPrivate->view->property("count").toInt(); + // This can happen in tests, so it may happen in normal usage too. + if (count == 0) { + emitIfDisplacementChanged(previousDisplacement, displacement); + return; + } + + if (tumblerPrivate->viewContentItemType == QQuickTumblerPrivate::PathViewContentItem) { + const qreal offset = tumblerPrivate->viewOffset; + + displacement = count > 1 ? count - index - offset : 0; + // Don't add 1 if count <= visibleItemCount + const int visibleItems = tumbler->visibleItemCount(); + const int halfVisibleItems = visibleItems / 2 + (visibleItems < count ? 1 : 0); + if (displacement > halfVisibleItems) + displacement -= count; + else if (displacement < -halfVisibleItems) + displacement += count; + } else { + const qreal contentY = tumblerPrivate->viewContentY; + const qreal delegateH = delegateHeight(tumbler); + const qreal preferredHighlightBegin = tumblerPrivate->view->property("preferredHighlightBegin").toReal(); + const qreal itemY = qobject_cast<QQuickItem*>(parent)->y(); + qreal currentItemY = 0; + auto currentItem = tumblerPrivate->view->property("currentItem").value<QQuickItem*>(); + if (currentItem) + currentItemY = currentItem->y(); + // Start from the y position of the current item. + const qreal topOfCurrentItemInViewport = currentItemY - contentY; + // Then, calculate the distance between it and the preferredHighlightBegin. + const qreal relativePositionToPreferredHighlightBegin = topOfCurrentItemInViewport - preferredHighlightBegin; + // Next, calculate the distance between us and the current item. + const qreal distanceFromCurrentItem = currentItemY - itemY; + const qreal displacementInPixels = distanceFromCurrentItem - relativePositionToPreferredHighlightBegin; + // Convert it from pixels to a floating point index. + displacement = displacementInPixels / delegateH; + } + + emitIfDisplacementChanged(previousDisplacement, displacement); +} + +void QQuickTumblerAttachedPrivate::emitIfDisplacementChanged(qreal oldDisplacement, qreal newDisplacement) +{ + Q_Q(QQuickTumblerAttached); + if (newDisplacement != oldDisplacement) + emit q->displacementChanged(); +} + +QQuickTumblerAttached::QQuickTumblerAttached(QObject *parent) + : QObject(*(new QQuickTumblerAttachedPrivate), parent) +{ + Q_D(QQuickTumblerAttached); + QQuickItem *delegateItem = qobject_cast<QQuickItem *>(parent); + if (delegateItem) + d->init(delegateItem); + else if (parent) + qmlWarning(parent) << "Tumbler: attached properties of Tumbler must be accessed through a delegate item"; + + if (d->tumbler) { + // When the Tumbler is completed, wrapChanged() is emitted to let QQuickTumblerView + // know that it can create the view. The view itself might instantiate delegates + // that use attached properties. At this point, setupViewData() hasn't been called yet + // (it's called on the next line in componentComplete()), so we call it here so that + // we have access to the view. + QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(d->tumbler); + tumblerPrivate->setupViewData(tumblerPrivate->contentItem); + + if (delegateItem && delegateItem->parentItem() == tumblerPrivate->viewContentItem) { + // This item belongs to the "new" view, meaning that the tumbler's contentItem + // was probably assigned declaratively. If they're not equal, calling + // calculateDisplacement() would use the old contentItem data, which is bad. + d->calculateDisplacement(); + } + } +} + +/*! + \qmlattachedproperty Tumbler QtQuick.Controls::Tumbler::tumbler + \readonly + + This attached property holds the tumbler. The property can be attached to + a tumbler delegate. The value is \c null if the item is not a tumbler delegate. +*/ +QQuickTumbler *QQuickTumblerAttached::tumbler() const +{ + Q_D(const QQuickTumblerAttached); + return d->tumbler; +} + +/*! + \qmlattachedproperty real QtQuick.Controls::Tumbler::displacement + \readonly + + This attached property holds a value from \c {-visibleItemCount / 2} to + \c {visibleItemCount / 2}, which represents how far away this item is from + being the current item, with \c 0 being completely current. + + For example, the item below will be 40% opaque when it is not the current item, + and transition to 100% opacity when it becomes the current item: + + \code + delegate: Text { + text: modelData + opacity: 0.4 + Math.max(0, 1 - Math.abs(Tumbler.displacement)) * 0.6 + } + \endcode +*/ +qreal QQuickTumblerAttached::displacement() const +{ + Q_D(const QQuickTumblerAttached); + return d->displacement; +} + +QT_END_NAMESPACE + +#include "moc_qquicktumbler_p.cpp" diff --git a/src/quicktemplates2/qquicktumbler_p.h b/src/quicktemplates2/qquicktumbler_p.h new file mode 100644 index 0000000000..f9d5a13bef --- /dev/null +++ b/src/quicktemplates2/qquicktumbler_p.h @@ -0,0 +1,179 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTUMBLER_P_H +#define QQUICKTUMBLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qvariant.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQuickTemplates2/private/qquickcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickTumblerAttached; +class QQuickTumblerPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTumbler : public QQuickControl +{ + Q_OBJECT + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged FINAL) + Q_PROPERTY(int count READ count NOTIFY countChanged FINAL) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL) + Q_PROPERTY(QQuickItem *currentItem READ currentItem NOTIFY currentItemChanged FINAL) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) + Q_PROPERTY(int visibleItemCount READ visibleItemCount WRITE setVisibleItemCount NOTIFY visibleItemCountChanged FINAL) + // 2.1 (Qt 5.8) + Q_PROPERTY(bool wrap READ wrap WRITE setWrap RESET resetWrap NOTIFY wrapChanged FINAL REVISION(2, 1)) + // 2.2 (Qt 5.9) + Q_PROPERTY(bool moving READ isMoving NOTIFY movingChanged FINAL REVISION(2, 2)) + QML_NAMED_ELEMENT(Tumbler) + QML_ATTACHED(QQuickTumblerAttached) + QML_ADDED_IN_VERSION(2, 0) + +public: + explicit QQuickTumbler(QQuickItem *parent = nullptr); + ~QQuickTumbler(); + + QVariant model() const; + void setModel(const QVariant &model); + + int count() const; + + int currentIndex() const; + void setCurrentIndex(int currentIndex); + QQuickItem *currentItem() const; + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + int visibleItemCount() const; + void setVisibleItemCount(int visibleItemCount); + + static QQuickTumblerAttached *qmlAttachedProperties(QObject *object); + + // 2.1 (Qt 5.8) + bool wrap() const; + void setWrap(bool wrap); + void resetWrap(); + + // 2.2 (Qt 5.9) + bool isMoving() const; + + enum PositionMode { + Beginning, + Center, + End, + Visible, // ListView-only + Contain, + SnapPosition + }; + Q_ENUM(PositionMode) + + // 2.5 (Qt 5.12) + Q_REVISION(2, 5) Q_INVOKABLE void positionViewAtIndex(int index, PositionMode mode); + +Q_SIGNALS: + void modelChanged(); + void countChanged(); + void currentIndexChanged(); + void currentItemChanged(); + void delegateChanged(); + void visibleItemCountChanged(); + // 2.1 (Qt 5.8) + Q_REVISION(2, 1) void wrapChanged(); + // 2.2 (Qt 5.9) + Q_REVISION(2, 2) void movingChanged(); + +protected: + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; + void componentComplete() override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + void keyPressEvent(QKeyEvent *event) override; + void updatePolish() override; + + QFont defaultFont() const override; + +private: + Q_DISABLE_COPY(QQuickTumbler) + Q_DECLARE_PRIVATE(QQuickTumbler) + + Q_PRIVATE_SLOT(d_func(), void _q_updateItemWidths()) + Q_PRIVATE_SLOT(d_func(), void _q_updateItemHeights()) + Q_PRIVATE_SLOT(d_func(), void _q_onViewCurrentIndexChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_onViewCountChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_onViewOffsetChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_onViewContentYChanged()) +}; + +class QQuickTumblerAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTumblerAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickTumbler *tumbler READ tumbler CONSTANT FINAL) + Q_PROPERTY(qreal displacement READ displacement NOTIFY displacementChanged FINAL) + +public: + explicit QQuickTumblerAttached(QObject *parent = nullptr); + + QQuickTumbler *tumbler() const; + qreal displacement() const; + +Q_SIGNALS: + void displacementChanged(); + +private: + Q_DISABLE_COPY(QQuickTumblerAttached) + Q_DECLARE_PRIVATE(QQuickTumblerAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickTumbler) +QML_DECLARE_TYPEINFO(QQuickTumbler, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKTUMBLER_P_H diff --git a/src/quicktemplates2/qquicktumbler_p_p.h b/src/quicktemplates2/qquicktumbler_p_p.h new file mode 100644 index 0000000000..aa5d58c076 --- /dev/null +++ b/src/quicktemplates2/qquicktumbler_p_p.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKTUMBLER_P_P_H +#define QQUICKTUMBLER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#include <QtQuickTemplates2/private/qquicktumbler_p.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTumblerPrivate : public QQuickControlPrivate +{ + Q_DECLARE_PUBLIC(QQuickTumbler) + +public: + enum ContentItemType { + NoContentItem, + UnsupportedContentItemType, + PathViewContentItem, + ListViewContentItem + }; + + QQuickItem *determineViewType(QQuickItem *contentItem); + void resetViewData(); + QList<QQuickItem *> viewContentItemChildItems() const; + + static QQuickTumblerPrivate *get(QQuickTumbler *tumbler); + + QPalette defaultPalette() const override; + + QVariant model; + QQmlComponent *delegate = nullptr; + int visibleItemCount = 5; + bool wrap = true; + bool explicitWrap = false; + bool modelBeingSet = false; + bool currentIndexSetDuringModelChange = false; + QQuickItem *view = nullptr; + QQuickItem *viewContentItem = nullptr; + ContentItemType viewContentItemType = UnsupportedContentItemType; + union { + qreal viewOffset; // PathView + qreal viewContentY; // ListView + }; + int currentIndex = -1; + int pendingCurrentIndex = -1; + bool ignoreCurrentIndexChanges = false; + int count = 0; + bool ignoreSignals = false; + + void _q_updateItemHeights(); + void _q_updateItemWidths(); + void _q_onViewCurrentIndexChanged(); + void _q_onViewCountChanged(); + void _q_onViewOffsetChanged(); + void _q_onViewContentYChanged(); + + void calculateDisplacements(); + + void disconnectFromView(); + void setupViewData(QQuickItem *newControlContentItem); + void warnAboutIncorrectContentItem(); + void syncCurrentIndex(); + void setPendingCurrentIndex(int index); + + enum PropertyChangeReason { + UserChange, + InternalChange + }; + + static QString propertyChangeReasonToString(PropertyChangeReason changeReason); + + void setCurrentIndex(int newCurrentIndex, PropertyChangeReason changeReason = InternalChange); + void setCount(int newCount); + void setWrapBasedOnCount(); + void setWrap(bool shouldWrap, bool isExplicit); + void beginSetModel(); + void endSetModel(); + + void itemChildAdded(QQuickItem *, QQuickItem *) override; + void itemChildRemoved(QQuickItem *, QQuickItem *) override; + void itemGeometryChanged(QQuickItem *, QQuickGeometryChange , const QRectF &) override; +}; + +class QQuickTumblerAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickTumblerAttached) + +public: + static QQuickTumblerAttachedPrivate *get(QQuickTumblerAttached *attached) + { + return attached->d_func(); + } + + void init(QQuickItem *delegateItem); + + void calculateDisplacement(); + void emitIfDisplacementChanged(qreal oldDisplacement, qreal newDisplacement); + + // The Tumbler that contains the delegate. Required to calculated the displacement. + QPointer<QQuickTumbler> tumbler; + // The index of the delegate. Used to calculate the displacement. + int index = -1; + // The displacement for our delegate. + qreal displacement = 0; +}; + +QT_END_NAMESPACE + +#endif // QQUICKTUMBLER_P_P_H diff --git a/src/quicktemplates2/qquickvelocitycalculator.cpp b/src/quicktemplates2/qquickvelocitycalculator.cpp new file mode 100644 index 0000000000..a0c5ec0d26 --- /dev/null +++ b/src/quicktemplates2/qquickvelocitycalculator.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickvelocitycalculator_p_p.h" + +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +/* + Usage: + + QQuickVelocityCalculator velocityCalculator; + + // ... + + velocityCalcular.startMeasuring(event->pos(), event->timestamp()); + velocityCalcular.stopMeasuring(event->pos(), event->timestamp()); + + // ... + + if (velocityCalculator.velocity().x() > someAmount) + doSomething(); + else if (velocityCalculator.velocity().x() < -someAmount) + doSomethingElse(); +*/ + +void QQuickVelocityCalculator::startMeasuring(const QPointF &point1, qint64 timestamp) +{ + m_point1 = point1; + + if (timestamp != 0) + m_point1Timestamp = timestamp; + else + m_timer.start(); +} + +void QQuickVelocityCalculator::stopMeasuring(const QPointF &point2, qint64 timestamp) +{ + if (timestamp == 0 && !m_timer.isValid()) { + qWarning() << "QQuickVelocityCalculator: a call to stopMeasuring() must be preceded by a call to startMeasuring()"; + return; + } + + m_point2 = point2; + m_point2Timestamp = timestamp != 0 ? timestamp : m_timer.elapsed(); + m_timer.invalidate(); +} + +void QQuickVelocityCalculator::reset() +{ + m_point1 = QPointF(); + m_point2 = QPointF(); + m_point1Timestamp = 0; + m_point2Timestamp = 0; + m_timer.invalidate(); +} + +QPointF QQuickVelocityCalculator::velocity() const +{ + if ((m_point2Timestamp == 0 || m_point1Timestamp == m_point2Timestamp) && !m_timer.isValid()) + return QPointF(); + + const qreal secondsElapsed = (m_point2Timestamp != 0 ? m_point2Timestamp - m_point1Timestamp : m_timer.elapsed()) / 1000.0; + const QPointF distance = m_point2 - m_point1; + return distance / secondsElapsed; +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qquickvelocitycalculator_p_p.h b/src/quicktemplates2/qquickvelocitycalculator_p_p.h new file mode 100644 index 0000000000..2b13ff07e4 --- /dev/null +++ b/src/quicktemplates2/qquickvelocitycalculator_p_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKVELOCITYCALCULATOR_P_P_H +#define QQUICKVELOCITYCALCULATOR_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qpoint.h> +#include <QtCore/qelapsedtimer.h> + +QT_BEGIN_NAMESPACE + +class QQuickVelocityCalculator +{ +public: + void startMeasuring(const QPointF &point1, qint64 timestamp = 0); + void stopMeasuring(const QPointF &m_point2, qint64 timestamp = 0); + void reset(); + QPointF velocity() const; + +private: + QPointF m_point1; + QPointF m_point2; + qint64 m_point1Timestamp = 0; + qint64 m_point2Timestamp = 0; + // When a timestamp isn't available, we must use a timer. + // When stopMeasuring() has been called, we store the elapsed time in point2timestamp. + QElapsedTimer m_timer; +}; + +QT_END_NAMESPACE + +#endif // QQUICKVELOCITYCALCULATOR_P_P_H diff --git a/src/quicktemplates2/qt_cmdline.cmake b/src/quicktemplates2/qt_cmdline.cmake new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/src/quicktemplates2/qt_cmdline.cmake diff --git a/src/quicktemplates2/qtquicktemplates2global.cpp b/src/quicktemplates2/qtquicktemplates2global.cpp new file mode 100644 index 0000000000..5100e610bc --- /dev/null +++ b/src/quicktemplates2/qtquicktemplates2global.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtquicktemplates2global_p.h" + +#include <QtGui/qtguiglobal.h> + +#if QT_CONFIG(accessibility) +#include "qquickpage_p.h" +#include "accessible/qaccessiblequickpage_p.h" +#endif + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(accessibility) +static QAccessibleInterface *qQuickAccessibleFactory(const QString &classname, QObject *object) +{ + if (classname == u"QQuickPage") { + return new QAccessibleQuickPage(qobject_cast<QQuickPage *>(object)); + } + return nullptr; +} +#endif + +void QQuickTemplates_initializeModule() +{ +#if QT_CONFIG(accessibility) + QAccessible::installFactory(&qQuickAccessibleFactory); +#endif +} + +Q_CONSTRUCTOR_FUNCTION(QQuickTemplates_initializeModule) + +QT_END_NAMESPACE diff --git a/src/quicktemplates2/qtquicktemplates2global_p.h b/src/quicktemplates2/qtquicktemplates2global_p.h new file mode 100644 index 0000000000..db848229d5 --- /dev/null +++ b/src/quicktemplates2/qtquicktemplates2global_p.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTQUICKTEMPLATES2GLOBAL_P_H +#define QTQUICKTEMPLATES2GLOBAL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtQml/private/qqmlglobal_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2-config_p.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QT_BUILD_QUICKTEMPLATES2_LIB) +# define Q_QUICKTEMPLATES2_PRIVATE_EXPORT Q_DECL_EXPORT +# else +# define Q_QUICKTEMPLATES2_PRIVATE_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_QUICKTEMPLATES2_PRIVATE_EXPORT +#endif + +Q_QUICKTEMPLATES2_PRIVATE_EXPORT void QQuickTemplates_initializeModule(); + +QT_END_NAMESPACE + +Q_QUICKTEMPLATES2_PRIVATE_EXPORT void qml_register_types_QtQuick_Templates(); + +#endif // QTQUICKTEMPLATES2GLOBAL_P_H diff --git a/src/quicktemplates2/qtquicktemplates2plugin.cpp b/src/quicktemplates2/qtquicktemplates2plugin.cpp new file mode 100644 index 0000000000..0c0683e834 --- /dev/null +++ b/src/quicktemplates2/qtquicktemplates2plugin.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/qqmlextensionplugin.h> +#include <QtQml/private/qqmlglobal_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +#if QT_CONFIG(shortcut) +#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h> + +// qtdeclarative/src/quick/util/qquickshortcut.cpp +typedef bool (*ShortcutContextMatcher)(QObject *, Qt::ShortcutContext); +extern ShortcutContextMatcher qt_quick_shortcut_context_matcher(); +extern void qt_quick_set_shortcut_context_matcher(ShortcutContextMatcher matcher); +#endif + +Q_GHS_KEEP_REFERENCE(qml_register_types_QtQuick_Templates); +Q_GHS_KEEP_REFERENCE(QQuickTemplates_initializeModule); + + +QT_BEGIN_NAMESPACE + +class QtQuickTemplates2Plugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) + +public: + QtQuickTemplates2Plugin(QObject *parent = nullptr); + ~QtQuickTemplates2Plugin(); + + void registerTypes(const char *uri) override; + void unregisterTypes() override; + +private: + bool registered; +#if QT_CONFIG(shortcut) + ShortcutContextMatcher originalContextMatcher; +#endif +}; + +QtQuickTemplates2Plugin::QtQuickTemplates2Plugin(QObject *parent) + : QQmlExtensionPlugin(parent), registered(false) +{ + volatile auto registration = &qml_register_types_QtQuick_Templates; + volatile auto initialization = &QQuickTemplates_initializeModule; + + Q_UNUSED(registration) + Q_UNUSED(initialization) +} + +QtQuickTemplates2Plugin::~QtQuickTemplates2Plugin() +{ + // Intentionally empty: we use register/unregisterTypes() to do + // initialization and cleanup, as plugins are not unloaded on macOS. +} + +void QtQuickTemplates2Plugin::registerTypes(const char * /*uri*/) +{ +#if QT_CONFIG(shortcut) + originalContextMatcher = qt_quick_shortcut_context_matcher(); + qt_quick_set_shortcut_context_matcher(QQuickShortcutContext::matcher); +#endif + + registered = true; +} + +void QtQuickTemplates2Plugin::unregisterTypes() +{ +#if QT_CONFIG(shortcut) + qt_quick_set_shortcut_context_matcher(originalContextMatcher); +#endif +} + +QT_END_NAMESPACE + +#include "qtquicktemplates2plugin.moc" diff --git a/src/quicktemplates2/quicktemplates2.pri b/src/quicktemplates2/quicktemplates2.pri new file mode 100644 index 0000000000..0f19de5e9d --- /dev/null +++ b/src/quicktemplates2/quicktemplates2.pri @@ -0,0 +1,182 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qquickabstractbutton_p.h \ + $$PWD/qquickabstractbutton_p_p.h \ + $$PWD/qquickaction_p.h \ + $$PWD/qquickactiongroup_p.h \ + $$PWD/qquickapplicationwindow_p.h \ + $$PWD/qquickbusyindicator_p.h \ + $$PWD/qquickbutton_p.h \ + $$PWD/qquickbutton_p_p.h \ + $$PWD/qquickbuttongroup_p.h \ + $$PWD/qquickcheckbox_p.h \ + $$PWD/qquickcheckdelegate_p.h \ + $$PWD/qquickcombobox_p.h \ + $$PWD/qquickcontainer_p.h \ + $$PWD/qquickcontainer_p_p.h \ + $$PWD/qquickcontentitem_p.h \ + $$PWD/qquickcontrol_p.h \ + $$PWD/qquickcontrol_p_p.h \ + $$PWD/qquickdeferredexecute_p_p.h \ + $$PWD/qquickdeferredpointer_p_p.h \ + $$PWD/qquickdelaybutton_p.h \ + $$PWD/qquickdial_p.h \ + $$PWD/qquickdialog_p.h \ + $$PWD/qquickdialog_p_p.h \ + $$PWD/qquickdialogbuttonbox_p.h \ + $$PWD/qquickdialogbuttonbox_p_p.h \ + $$PWD/qquickdrawer_p.h \ + $$PWD/qquickdrawer_p_p.h \ + $$PWD/qquickframe_p.h \ + $$PWD/qquickframe_p_p.h \ + $$PWD/qquickgroupbox_p.h \ + $$PWD/qquickheaderview_p.h \ + $$PWD/qquickheaderview_p_p.h \ + $$PWD/qquickicon_p.h \ + $$PWD/qquickindicatorbutton_p.h \ + $$PWD/qquickitemdelegate_p.h \ + $$PWD/qquickitemdelegate_p_p.h \ + $$PWD/qquicklabel_p.h \ + $$PWD/qquicklabel_p_p.h \ + $$PWD/qquickmenu_p.h \ + $$PWD/qquickmenu_p_p.h \ + $$PWD/qquickmenubar_p.h \ + $$PWD/qquickmenubar_p_p.h \ + $$PWD/qquickmenubaritem_p.h \ + $$PWD/qquickmenubaritem_p_p.h \ + $$PWD/qquickmenuitem_p.h \ + $$PWD/qquickmenuitem_p_p.h \ + $$PWD/qquickmenuseparator_p.h \ + $$PWD/qquickoverlay_p.h \ + $$PWD/qquickoverlay_p_p.h \ + $$PWD/qquickpage_p.h \ + $$PWD/qquickpage_p_p.h \ + $$PWD/qquickpageindicator_p.h \ + $$PWD/qquickpane_p.h \ + $$PWD/qquickpane_p_p.h \ + $$PWD/qquickpopup_p.h \ + $$PWD/qquickpopup_p_p.h \ + $$PWD/qquickpopupanchors_p.h \ + $$PWD/qquickpopupanchors_p_p.h \ + $$PWD/qquickpopupitem_p_p.h \ + $$PWD/qquickpopuppositioner_p_p.h \ + $$PWD/qquickpresshandler_p_p.h \ + $$PWD/qquickprogressbar_p.h \ + $$PWD/qquickradiobutton_p.h \ + $$PWD/qquickradiodelegate_p.h \ + $$PWD/qquickrangeslider_p.h \ + $$PWD/qquickroundbutton_p.h \ + $$PWD/qquickscrollbar_p.h \ + $$PWD/qquickscrollbar_p_p.h \ + $$PWD/qquickscrollindicator_p.h \ + $$PWD/qquickscrollview_p.h \ + $$PWD/qquickselectionrectangle_p.h \ + $$PWD/qquickselectionrectangle_p_p.h \ + $$PWD/qquickshortcutcontext_p_p.h \ + $$PWD/qquickslider_p.h \ + $$PWD/qquickspinbox_p.h \ + $$PWD/qquicksplitview_p.h \ + $$PWD/qquickstackelement_p_p.h \ + $$PWD/qquickstacktransition_p_p.h \ + $$PWD/qquickstackview_p.h \ + $$PWD/qquickstackview_p_p.h \ + $$PWD/qquickswipe_p.h \ + $$PWD/qquickswipedelegate_p.h \ + $$PWD/qquickswipedelegate_p_p.h \ + $$PWD/qquickswipeview_p.h \ + $$PWD/qquickswitch_p.h \ + $$PWD/qquickswitchdelegate_p.h \ + $$PWD/qquicktabbar_p.h \ + $$PWD/qquicktabbutton_p.h \ + $$PWD/qquicktextarea_p.h \ + $$PWD/qquicktextarea_p_p.h \ + $$PWD/qquicktextfield_p.h \ + $$PWD/qquicktextfield_p_p.h \ + $$PWD/qquicktheme_p.h \ + $$PWD/qquicktheme_p_p.h \ + $$PWD/qquicktoolbar_p.h \ + $$PWD/qquicktoolbutton_p.h \ + $$PWD/qquicktoolseparator_p.h \ + $$PWD/qquicktooltip_p.h \ + $$PWD/qquickvelocitycalculator_p_p.h + +SOURCES += \ + $$PWD/qquickabstractbutton.cpp \ + $$PWD/qquickaction.cpp \ + $$PWD/qquickactiongroup.cpp \ + $$PWD/qquickapplicationwindow.cpp \ + $$PWD/qquickbusyindicator.cpp \ + $$PWD/qquickbutton.cpp \ + $$PWD/qquickbuttongroup.cpp \ + $$PWD/qquickcheckbox.cpp \ + $$PWD/qquickcheckdelegate.cpp \ + $$PWD/qquickcombobox.cpp \ + $$PWD/qquickcontainer.cpp \ + $$PWD/qquickcontentitem.cpp \ + $$PWD/qquickcontrol.cpp \ + $$PWD/qquickdeferredexecute.cpp \ + $$PWD/qquickdelaybutton.cpp \ + $$PWD/qquickdial.cpp \ + $$PWD/qquickdialog.cpp \ + $$PWD/qquickdialogbuttonbox.cpp \ + $$PWD/qquickdrawer.cpp \ + $$PWD/qquickframe.cpp \ + $$PWD/qquickgroupbox.cpp \ + $$PWD/qquickheaderview.cpp \ + $$PWD/qquickicon.cpp \ + $$PWD/qquickindicatorbutton_p.cpp \ + $$PWD/qquickitemdelegate.cpp \ + $$PWD/qquicklabel.cpp \ + $$PWD/qquickmenu.cpp \ + $$PWD/qquickmenubar.cpp \ + $$PWD/qquickmenubaritem.cpp \ + $$PWD/qquickmenuitem.cpp \ + $$PWD/qquickmenuseparator.cpp \ + $$PWD/qquickoverlay.cpp \ + $$PWD/qquickpage.cpp \ + $$PWD/qquickpageindicator.cpp \ + $$PWD/qquickpane.cpp \ + $$PWD/qquickpopup.cpp \ + $$PWD/qquickpopupanchors.cpp \ + $$PWD/qquickpopupitem.cpp \ + $$PWD/qquickpopuppositioner.cpp \ + $$PWD/qquickpresshandler.cpp \ + $$PWD/qquickprogressbar.cpp \ + $$PWD/qquickradiobutton.cpp \ + $$PWD/qquickradiodelegate.cpp \ + $$PWD/qquickrangeslider.cpp \ + $$PWD/qquickroundbutton.cpp \ + $$PWD/qquickscrollbar.cpp \ + $$PWD/qquickscrollindicator.cpp \ + $$PWD/qquickscrollview.cpp \ + $$PWD/qquickshortcutcontext.cpp \ + $$PWD/qquickslider.cpp \ + $$PWD/qquickspinbox.cpp \ + $$PWD/qquicksplitview.cpp \ + $$PWD/qquickstackelement.cpp \ + $$PWD/qquickstacktransition.cpp \ + $$PWD/qquickstackview.cpp \ + $$PWD/qquickstackview_p.cpp \ + $$PWD/qquickswipedelegate.cpp \ + $$PWD/qquickswipeview.cpp \ + $$PWD/qquickswitch.cpp \ + $$PWD/qquickswitchdelegate.cpp \ + $$PWD/qquicktabbar.cpp \ + $$PWD/qquicktabbutton.cpp \ + $$PWD/qquicktextarea.cpp \ + $$PWD/qquicktextfield.cpp \ + $$PWD/qquicktheme.cpp \ + $$PWD/qquicktoolbar.cpp \ + $$PWD/qquicktoolbutton.cpp \ + $$PWD/qquicktoolseparator.cpp \ + $$PWD/qquicktooltip.cpp \ + $$PWD/qquickvelocitycalculator.cpp + +qtConfig(quick-listview):qtConfig(quick-pathview) { + HEADERS += \ + $$PWD/qquicktumbler_p.h \ + $$PWD/qquicktumbler_p_p.h + SOURCES += \ + $$PWD/qquicktumbler.cpp +} |