1 /*
2  *  SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
3  *
4  *  SPDX-License-Identifier: LGPL-2.0-or-later
5  */
6 
7 #pragma once
8 
9 #include <QPointer>
10 #include <QQuickItem>
11 #include <QVariant>
12 
13 class ContentItem;
14 class ColumnView;
15 
16 class ScrollIntentionEvent : public QObject
17 {
18     Q_OBJECT
Q_PROPERTY(QPointF delta MEMBER delta CONSTANT)19     Q_PROPERTY(QPointF delta MEMBER delta CONSTANT)
20     Q_PROPERTY(bool accepted MEMBER accepted)
21 public:
22     ScrollIntentionEvent()
23     {
24     }
~ScrollIntentionEvent()25     ~ScrollIntentionEvent() override
26     {
27     }
28 
29     QPointF delta;
30     bool accepted = false;
31 };
32 
33 /**
34  * This is an attached property to every item that is inserted in the ColumnView,
35  * used to access the view and page information such as the position and information for layouting, such as fillWidth
36  * @since 2.7
37  */
38 class ColumnViewAttached : public QObject
39 {
40     Q_OBJECT
41 
42     /**
43      * The index position of the column in the view, starting from 0
44      */
45     Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)
46 
47     /**
48      * If true, the column will expand to take the whole viewport space minus reservedSpace
49      */
50     Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged)
51 
52     /**
53      * When a column is fillWidth, it will keep reservedSpace amount of pixels from going to fill the full viewport width
54      */
55     Q_PROPERTY(qreal reservedSpace READ reservedSpace WRITE setReservedSpace NOTIFY reservedSpaceChanged)
56 
57     /**
58      * Like the same property of MouseArea, when this is true, the column view won't
59      * try to manage events by itself when filtering from a child, not
60      * disturbing user interaction
61      */
62     Q_PROPERTY(bool preventStealing READ preventStealing WRITE setPreventStealing NOTIFY preventStealingChanged)
63 
64     /**
65      * If true the page will never go out of view, but will stay either
66      * at the right or left side of the Columnview
67      */
68     Q_PROPERTY(bool pinned READ isPinned WRITE setPinned NOTIFY pinnedChanged)
69 
70     /**
71      * The view this column belongs to
72      */
73     Q_PROPERTY(ColumnView *view READ view NOTIFY viewChanged)
74 
75     /**
76      * True if this column is at least partly visible in the ColumnView's viewport.
77      * @since 5.77
78      */
79     Q_PROPERTY(bool inViewport READ inViewport NOTIFY inViewportChanged)
80 
81 public:
82     ColumnViewAttached(QObject *parent = nullptr);
83     ~ColumnViewAttached() override;
84 
85     void setIndex(int index);
86     int index() const;
87 
88     void setFillWidth(bool fill);
89     bool fillWidth() const;
90 
91     qreal reservedSpace() const;
92     void setReservedSpace(qreal space);
93 
94     ColumnView *view();
95     void setView(ColumnView *view);
96 
97     // Private API, not for QML use
98     QQuickItem *originalParent() const;
99     void setOriginalParent(QQuickItem *parent);
100 
101     bool shouldDeleteOnRemove() const;
102     void setShouldDeleteOnRemove(bool del);
103 
104     bool preventStealing() const;
105     void setPreventStealing(bool prevent);
106 
107     bool isPinned() const;
108     void setPinned(bool pinned);
109 
110     bool inViewport() const;
111     void setInViewport(bool inViewport);
112 
113 Q_SIGNALS:
114     void indexChanged();
115     void fillWidthChanged();
116     void reservedSpaceChanged();
117     void viewChanged();
118     void preventStealingChanged();
119     void pinnedChanged();
120     void scrollIntention(ScrollIntentionEvent *event);
121     void inViewportChanged();
122 
123 private:
124     int m_index = -1;
125     bool m_fillWidth = false;
126     qreal m_reservedSpace = 0;
127     QPointer<ColumnView> m_view;
128     QPointer<QQuickItem> m_originalParent;
129     bool m_customFillWidth = false;
130     bool m_customReservedSpace = false;
131     bool m_shouldDeleteOnRemove = true;
132     bool m_preventStealing = false;
133     bool m_pinned = false;
134     bool m_inViewport = false;
135 };
136 
137 /**
138  * ColumnView is a container that lays out items horizontally in a row,
139  * when not all items fit in the ColumnView, it will behave like a Flickable and will be a scrollable view which shows only a determined number of columns.
140  * The columns can either all have the same fixed size (recommended),
141  * size themselves with implicitWidth, or automatically expand to take all the available width: by default the last column will always be the expanding one.
142  * Items inside the Columnview can access info of the view and set layouting hints via the Columnview attached property.
143  *
144  * This is the base for the implementation of PageRow
145  * @since 2.7
146  */
147 class ColumnView : public QQuickItem
148 {
149     Q_OBJECT
150 
151     /**
152      * The strategy to follow while automatically resizing the columns,
153      * the enum can have the following values:
154      * * FixedColumns: every column is fixed at the same width of the columnWidth property
155      * * DynamicColumns: columns take their width from their implicitWidth
156      * * SingleColumn: only one column at a time is shown, as wide as the viewport, eventual reservedSpace on the column's attached property is ignored
157      */
158     Q_PROPERTY(ColumnResizeMode columnResizeMode READ columnResizeMode WRITE setColumnResizeMode NOTIFY columnResizeModeChanged)
159 
160     /**
161      * The width of all columns when columnResizeMode is FixedColumns
162      */
163     Q_PROPERTY(qreal columnWidth READ columnWidth WRITE setColumnWidth NOTIFY columnWidthChanged)
164 
165     /**
166      * How many columns this view containsItem*/
167     Q_PROPERTY(int count READ count NOTIFY countChanged)
168 
169     /**
170      * The position of the currently active column. The current column will also have keyboard focus
171      */
172     Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
173 
174     /**
175      * The currently active column. The current column will also have keyboard focus
176      */
177     Q_PROPERTY(QQuickItem *currentItem READ currentItem NOTIFY currentItemChanged)
178 
179     /**
180      * The main content item of this view: it's the parent of the column items
181      */
182     Q_PROPERTY(QQuickItem *contentItem READ contentItem CONSTANT)
183 
184     /**
185      * The value of the horizontal scroll of the view, in pixels
186      */
187     Q_PROPERTY(qreal contentX READ contentX WRITE setContentX NOTIFY contentXChanged)
188 
189     /**
190      * The compound width of all columns in the view
191      */
192     Q_PROPERTY(qreal contentWidth READ contentWidth NOTIFY contentWidthChanged)
193 
194     /**
195      * The padding this will have at the top
196      */
197     Q_PROPERTY(qreal topPadding READ topPadding WRITE setTopPadding NOTIFY topPaddingChanged)
198 
199     /**
200      * The padding this will have at the bottom
201      */
202     Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding NOTIFY bottomPaddingChanged)
203 
204     /**
205      * The duration for scrolling animations
206      */
207     Q_PROPERTY(int scrollDuration READ scrollDuration WRITE setScrollDuration NOTIFY scrollDurationChanged)
208 
209     /**
210      * True if columns should be visually separated by a separator line
211      */
212     Q_PROPERTY(bool separatorVisible READ separatorVisible WRITE setSeparatorVisible NOTIFY separatorVisibleChanged)
213 
214     /**
215      * The list of all visible column items that are at least partially in the viewport at any given moment
216      */
217     Q_PROPERTY(QList<QObject *> visibleItems READ visibleItems NOTIFY visibleItemsChanged)
218 
219     /**
220      * The first of visibleItems provided from convenience
221      */
222     Q_PROPERTY(QQuickItem *firstVisibleItem READ firstVisibleItem NOTIFY firstVisibleItemChanged)
223 
224     /**
225      * The last of visibleItems provided from convenience
226      */
227     Q_PROPERTY(QQuickItem *lastVisibleItem READ lastVisibleItem NOTIFY lastVisibleItemChanged)
228 
229     // Properties to make it similar to Flickable
230     /**
231      * True when the user is dragging around with touch gestures the view contents
232      */
233     Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
234 
235     /**
236      * True both when the user is dragging around with touch gestures the view contents or the view is animating
237      */
238     Q_PROPERTY(bool moving READ moving NOTIFY movingChanged)
239 
240     /**
241      * True if it supports moving the contents by dragging
242      */
243     Q_PROPERTY(bool interactive READ interactive WRITE setInteractive NOTIFY interactiveChanged)
244 
245     /**
246      * True if the contents can be dragged also with mouse besides touch
247      */
248     Q_PROPERTY(bool acceptsMouse READ acceptsMouse WRITE setAcceptsMouse NOTIFY acceptsMouseChanged)
249 
250     // Default properties
251     /**
252      * Every column item the view contains
253      */
254     Q_PROPERTY(QQmlListProperty<QQuickItem> contentChildren READ contentChildren NOTIFY contentChildrenChanged FINAL)
255     /**
256      * every item declared inside the view, both visual and non-visual items
257      */
258     Q_PROPERTY(QQmlListProperty<QObject> contentData READ contentData FINAL)
259     Q_CLASSINFO("DefaultProperty", "contentData")
260 
261 public:
262     enum ColumnResizeMode {
263         FixedColumns = 0,
264         DynamicColumns,
265         SingleColumn,
266     };
267     Q_ENUM(ColumnResizeMode)
268 
269     ColumnView(QQuickItem *parent = nullptr);
270     ~ColumnView() override;
271 
272     // QML property accessors
273     ColumnResizeMode columnResizeMode() const;
274     void setColumnResizeMode(ColumnResizeMode mode);
275 
276     qreal columnWidth() const;
277     void setColumnWidth(qreal width);
278 
279     int currentIndex() const;
280     void setCurrentIndex(int index);
281 
282     int scrollDuration() const;
283     void setScrollDuration(int duration);
284 
285     bool separatorVisible() const;
286     void setSeparatorVisible(bool visible);
287 
288     int count() const;
289 
290     qreal topPadding() const;
291     void setTopPadding(qreal padding);
292 
293     qreal bottomPadding() const;
294     void setBottomPadding(qreal padding);
295 
296     QQuickItem *currentItem();
297 
298     // NOTE: It's a QList<QObject *> as QML can't correctly build an Array out of QList<QQuickItem*>
299     QList<QObject *> visibleItems() const;
300     QQuickItem *firstVisibleItem() const;
301     QQuickItem *lastVisibleItem() const;
302 
303     QQuickItem *contentItem() const;
304 
305     QQmlListProperty<QQuickItem> contentChildren();
306     QQmlListProperty<QObject> contentData();
307 
308     bool dragging() const;
309     bool moving() const;
310     qreal contentWidth() const;
311 
312     qreal contentX() const;
313     void setContentX(qreal x) const;
314 
315     bool interactive() const;
316     void setInteractive(bool interactive);
317 
318     bool acceptsMouse() const;
319     void setAcceptsMouse(bool accepts);
320 
321     // Api not intended for QML use
322     // can't do overloads in QML
323     QQuickItem *removeItem(QQuickItem *item);
324     QQuickItem *removeItem(int item);
325 
326     // QML attached property
327     static ColumnViewAttached *qmlAttachedProperties(QObject *object);
328 
329 public Q_SLOTS:
330     /**
331      * Pushes a new item at the end of the view
332      * @param item the new item which will be reparented and managed
333      */
334     void addItem(QQuickItem *item);
335 
336     /**
337      * Inserts a new item in the view at a given position.
338      * The current Item will not be changed, currentIndex will be adjusted
339      * accordingly if needed to keep the same current item.
340      * @param pos the position we want the new item to be inserted in
341      * @param item the new item which will be reparented and managed
342      */
343     void insertItem(int pos, QQuickItem *item);
344 
345     /**
346      * Replaces an item in the view at a given position with a new item.
347      * The current Item and currentIndex will not be changed.
348      * @param pos the position we want the new item to be placed in
349      * @param item the new item which will be reparented and managed
350      */
351     void replaceItem(int pos, QQuickItem *item);
352 
353     /**
354      * Move an item inside the view.
355      * The currentIndex property may be changed in order to keep currentItem the same.
356      * @param from the old position
357      * @param to the new position
358      */
359     void moveItem(int from, int to);
360 
361     /**
362      * Removes an item from the view.
363      * Items will be reparented to their old parent.
364      * If they have JavaScript ownership and they didn't have an old parent, they will be destroyed.
365      * CurrentIndex may be changed in order to keep the same currentItem
366      * @param item it can either be a pointer of an item or an integer specifying the position to remove
367      * @returns the item that has just been removed
368      */
369     QQuickItem *removeItem(const QVariant &item);
370 
371     /**
372      * Removes all the items after item. Starting from the last column, every column will be removed until item is found, which will be left in place.
373      * Items will be reparented to their old parent.
374      * If they have JavaScript ownership and they didn't have an old parent, they will be destroyed
375      * @param item the item which will be the new last one of the row.
376      * @returns the last item that has been removed
377      */
378     QQuickItem *pop(QQuickItem *item);
379 
380     /**
381      * Removes every item in the view.
382      * Items will be reparented to their old parent.
383      * If they have JavaScript ownership and they didn't have an old parent, they will be destroyed
384      */
385     void clear();
386 
387     /**
388      * @returns true if the view contains the given item
389      */
390     bool containsItem(QQuickItem *item);
391 
392     /**
393      * Returns the visible item containing the point x, y in content coordinates.
394      * If there is no item at the point specified, or the item is not visible null is returned.
395      */
396     QQuickItem *itemAt(qreal x, qreal y);
397 
398 protected:
399     void classBegin() override;
400     void componentComplete() override;
401     void updatePolish() override;
402     void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
403     void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
404     bool childMouseEventFilter(QQuickItem *item, QEvent *event) override;
405     void mousePressEvent(QMouseEvent *event) override;
406     void mouseMoveEvent(QMouseEvent *event) override;
407     void mouseReleaseEvent(QMouseEvent *event) override;
408     void mouseUngrabEvent() override;
409 
410 Q_SIGNALS:
411     /**
412      * A new item has been inserted
413      * @param position where the page has been inserted
414      * @param item a pointer to the new item
415      */
416     void itemInserted(int position, QQuickItem *item);
417 
418     /**
419      * An item has just been removed from the view
420      * @param item a pointer to the item that has just been removed
421      */
422     void itemRemoved(QQuickItem *item);
423 
424     // Property notifiers
425     void contentChildrenChanged();
426     void columnResizeModeChanged();
427     void columnWidthChanged();
428     void currentIndexChanged();
429     void currentItemChanged();
430     void visibleItemsChanged();
431     void countChanged();
432     void draggingChanged();
433     void movingChanged();
434     void contentXChanged();
435     void contentWidthChanged();
436     void interactiveChanged();
437     void acceptsMouseChanged();
438     void scrollDurationChanged();
439     void separatorVisibleChanged();
440     void firstVisibleItemChanged();
441     void lastVisibleItemChanged();
442     void topPaddingChanged();
443     void bottomPaddingChanged();
444 
445 private:
446     static void contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *object);
447     static int contentChildren_count(QQmlListProperty<QQuickItem> *prop);
448     static QQuickItem *contentChildren_at(QQmlListProperty<QQuickItem> *prop, int index);
449     static void contentChildren_clear(QQmlListProperty<QQuickItem> *prop);
450 
451     static void contentData_append(QQmlListProperty<QObject> *prop, QObject *object);
452     static int contentData_count(QQmlListProperty<QObject> *prop);
453     static QObject *contentData_at(QQmlListProperty<QObject> *prop, int index);
454     static void contentData_clear(QQmlListProperty<QObject> *prop);
455 
456     QList<QObject *> m_contentData;
457 
458     ContentItem *m_contentItem;
459     QPointer<QQuickItem> m_currentItem;
460 
461     static QHash<QObject *, ColumnViewAttached *> m_attachedObjects;
462     qreal m_oldMouseX = -1.0;
463     qreal m_startMouseX = -1.0;
464     qreal m_oldMouseY = -1.0;
465     qreal m_startMouseY = -1.0;
466     int m_currentIndex = -1;
467     qreal m_topPadding = 0;
468     qreal m_bottomPadding = 0;
469 
470     bool m_mouseDown = false;
471     bool m_interactive = true;
472     bool m_dragging = false;
473     bool m_moving = false;
474     bool m_separatorVisible = true;
475     bool m_complete = false;
476     bool m_acceptsMouse = false;
477 };
478 
479 QML_DECLARE_TYPEINFO(ColumnView, QML_HAS_ATTACHED_PROPERTIES)
480