1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include "qquickswitchdelegate_p.h"
38 
39 #include "qquickitemdelegate_p_p.h"
40 
41 QT_BEGIN_NAMESPACE
42 
43 /*!
44     \qmltype SwitchDelegate
45     \inherits ItemDelegate
46 //!     \instantiates QQuickSwitchDelegate
47     \inqmlmodule QtQuick.Controls
48     \since 5.7
49     \ingroup qtquickcontrols2-delegates
50     \brief Item delegate with a switch indicator that can be toggled on or off.
51 
52     \image qtquickcontrols2-switchdelegate.gif
53 
54     SwitchDelegate presents an item delegate that can be toggled on (checked) or
55     off (unchecked). Switch delegates are typically used to select one or more
56     options from a set of options. For smaller sets of options, or for options
57     that need to be uniquely identifiable, consider using \l Switch instead.
58 
59     SwitchDelegate inherits its API from \l ItemDelegate, which is inherited
60     from \l AbstractButton. For instance, you can set \l {AbstractButton::text}{text},
61     and react to \l {AbstractButton::clicked}{clicks} using the \l AbstractButton
62     API. The state of the switch delegate can be set with the
63     \l {AbstractButton::}{checked} property.
64 
65     \code
66     ListView {
67         model: ["Option 1", "Option 2", "Option 3"]
68         delegate: SwitchDelegate {
69             text: modelData
70         }
71     }
72     \endcode
73 
74     \sa {Customizing SwitchDelegate}, {Delegate Controls}
75 */
76 
77 class QQuickSwitchDelegatePrivate : public QQuickItemDelegatePrivate
78 {
79     Q_DECLARE_PUBLIC(QQuickSwitchDelegate)
80 
81 public:
82     qreal positionAt(const QPointF &point) const;
83 
84     bool canDrag(const QPointF &movePoint) const;
85     void handleMove(const QPointF &point) override;
86     void handleRelease(const QPointF &point) override;
87 
88     qreal position = 0;
89 };
90 
positionAt(const QPointF & point) const91 qreal QQuickSwitchDelegatePrivate::positionAt(const QPointF &point) const
92 {
93     Q_Q(const QQuickSwitchDelegate);
94     qreal pos = 0.0;
95     if (indicator)
96         pos = indicator->mapFromItem(q, point).x() / indicator->width();
97     if (q->isMirrored())
98         return 1.0 - pos;
99     return pos;
100 }
101 
canDrag(const QPointF & movePoint) const102 bool QQuickSwitchDelegatePrivate::canDrag(const QPointF &movePoint) const
103 {
104     // don't start dragging the handle unless the initial press was at the indicator,
105     // or the drag has reached the indicator area. this prevents unnatural jumps when
106     // dragging far outside the indicator.
107     const qreal pressPos = positionAt(pressPoint);
108     const qreal movePos = positionAt(movePoint);
109     return (pressPos >= 0.0 && pressPos <= 1.0) || (movePos >= 0.0 && movePos <= 1.0);
110 }
111 
handleMove(const QPointF & point)112 void QQuickSwitchDelegatePrivate::handleMove(const QPointF &point)
113 {
114     Q_Q(QQuickSwitchDelegate);
115     QQuickItemDelegatePrivate::handleMove(point);
116     if (q->keepMouseGrab() || q->keepTouchGrab())
117         q->setPosition(positionAt(point));
118 }
119 
handleRelease(const QPointF & point)120 void QQuickSwitchDelegatePrivate::handleRelease(const QPointF &point)
121 {
122     Q_Q(QQuickSwitchDelegate);
123     QQuickItemDelegatePrivate::handleRelease(point);
124     q->setKeepMouseGrab(false);
125     q->setKeepTouchGrab(false);
126 }
127 
QQuickSwitchDelegate(QQuickItem * parent)128 QQuickSwitchDelegate::QQuickSwitchDelegate(QQuickItem *parent)
129     : QQuickItemDelegate(*(new QQuickSwitchDelegatePrivate), parent)
130 {
131     Q_D(QQuickSwitchDelegate);
132     d->keepPressed = true;
133     setCheckable(true);
134 }
135 
136 /*!
137     \qmlproperty real QtQuick.Controls::SwitchDelegate::position
138     \readonly
139 
140     \input includes/qquickswitch.qdocinc position
141 */
position() const142 qreal QQuickSwitchDelegate::position() const
143 {
144     Q_D(const QQuickSwitchDelegate);
145     return d->position;
146 }
147 
setPosition(qreal position)148 void QQuickSwitchDelegate::setPosition(qreal position)
149 {
150     Q_D(QQuickSwitchDelegate);
151     position = qBound<qreal>(0.0, position, 1.0);
152     if (qFuzzyCompare(d->position, position))
153         return;
154 
155     d->position = position;
156     emit positionChanged();
157     emit visualPositionChanged();
158 }
159 
160 /*!
161     \qmlproperty real QtQuick.Controls::SwitchDelegate::visualPosition
162     \readonly
163 
164     \input includes/qquickswitch.qdocinc visualPosition
165 */
visualPosition() const166 qreal QQuickSwitchDelegate::visualPosition() const
167 {
168     Q_D(const QQuickSwitchDelegate);
169     if (isMirrored())
170         return 1.0 - d->position;
171     return d->position;
172 }
173 
mouseMoveEvent(QMouseEvent * event)174 void QQuickSwitchDelegate::mouseMoveEvent(QMouseEvent *event)
175 {
176     Q_D(QQuickSwitchDelegate);
177     if (!keepMouseGrab()) {
178         const QPointF movePoint = event->localPos();
179         if (d->canDrag(movePoint))
180             setKeepMouseGrab(QQuickWindowPrivate::dragOverThreshold(movePoint.x() - d->pressPoint.x(), Qt::XAxis, event));
181     }
182     QQuickItemDelegate::mouseMoveEvent(event);
183 }
184 
185 #if QT_CONFIG(quicktemplates2_multitouch)
touchEvent(QTouchEvent * event)186 void QQuickSwitchDelegate::touchEvent(QTouchEvent *event)
187 {
188     Q_D(QQuickSwitchDelegate);
189     if (!keepTouchGrab() && event->type() == QEvent::TouchUpdate) {
190         for (const QTouchEvent::TouchPoint &point : event->touchPoints()) {
191             if (point.id() != d->touchId || point.state() != Qt::TouchPointMoved)
192                 continue;
193             if (d->canDrag(point.pos()))
194                 setKeepTouchGrab(QQuickWindowPrivate::dragOverThreshold(point.pos().x() - d->pressPoint.x(), Qt::XAxis, &point));
195         }
196     }
197     QQuickItemDelegate::touchEvent(event);
198 }
199 #endif
200 
defaultFont() const201 QFont QQuickSwitchDelegate::defaultFont() const
202 {
203     return QQuickTheme::font(QQuickTheme::ListView);
204 }
205 
defaultPalette() const206 QPalette QQuickSwitchDelegate::defaultPalette() const
207 {
208     return QQuickTheme::palette(QQuickTheme::ListView);
209 }
210 
mirrorChange()211 void QQuickSwitchDelegate::mirrorChange()
212 {
213     QQuickItemDelegate::mirrorChange();
214     emit visualPositionChanged();
215 }
216 
nextCheckState()217 void QQuickSwitchDelegate::nextCheckState()
218 {
219     Q_D(QQuickSwitchDelegate);
220     if (keepMouseGrab() || keepTouchGrab()) {
221         d->toggle(d->position > 0.5);
222         // the checked state might not change => force a position update to
223         // avoid that the handle is left somewhere in the middle (QTBUG-57944)
224         setPosition(d->checked ? 1.0 : 0.0);
225     } else {
226         QQuickItemDelegate::nextCheckState();
227     }
228 }
229 
buttonChange(ButtonChange change)230 void QQuickSwitchDelegate::buttonChange(ButtonChange change)
231 {
232     Q_D(QQuickSwitchDelegate);
233     if (change == ButtonCheckedChange)
234         setPosition(d->checked ? 1.0 : 0.0);
235     else
236         QQuickAbstractButton::buttonChange(change);
237 }
238 
239 QT_END_NAMESPACE
240