1/*
2    SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
3    SPDX-FileCopyrightText: 2011 Nokia Corporation and /or its subsidiary(-ies) <qt-info@nokia.com>
4
5    This file is part of the Qt Components project.
6
7    SPDX-License-Identifier: BSD-3-Clause
8*/
9
10import QtQuick 2.2
11import "private/TabGroup.js" as Engine
12
13import org.kde.plasma.core 2.0 as PlasmaCore
14import "." 2.0 as PlasmaComponents
15
16/**
17 * Provides a set of pages for a tab-based interface.
18 *
19 * A tabbed interface is made up of tab buttons plus content for each button. A
20 * TabGroup component has, as its children, each page of content in the
21 * interface. These pages can be any QML items but are typically Page
22 * components for a single page of content or PageStack components when a
23 * hierarchical navigation system is required for the tab content.
24 *
25 * If you use Page components for your tab content, the status property of each
26 * page is updated appropriately when the current tab is changed: the current
27 * page has status PageStatus.Active and other pages have the status
28 * PageStatus.Inactive. During page transitions, PageStatus.Activating (for the
29 * page that is becoming the current page) and PageStatus.Deactivating (for the
30 * page that was the current page) statuses are also set.
31 *
32 * @inherit QtQuick.FocusScope
33 */
34FocusScope {
35    id: root
36
37    /**
38     * The tab that is currently active and visible to the user.
39     *
40     * The currentTab property is initialized to null and is automatically set
41     * to point to the first tab when content is added. You can set the
42     * currentTab at any time to activate a particular tab.
43     */
44    property Item currentTab
45
46    property list<Item> privateContents
47    // Qt defect: cannot have list as default property
48    default property alias privateContentsDefault: root.privateContents
49
50    onChildrenChanged: {
51        //  [0] is containerHost
52        if (children.length > 1)
53            Engine.addTab(children[1])
54    }
55
56    onPrivateContentsChanged: {
57        Engine.ensureContainers()
58    }
59
60    Component.onCompleted: {
61        // Set first tabs as current if currentTab is not set by application
62        if (currentTab == null && containerHost.children[0] && containerHost.children[0].children[0])
63            currentTab = containerHost.children[0].children[0]
64        priv.complete = true;
65    }
66
67    Item {
68        id: containerHost
69        objectName: "containerHost"
70        anchors.fill: parent
71    }
72
73    Component {
74        id: tabContainerComponent
75        Item {
76            id: tabContainerItem
77
78            onChildrenChanged: {
79                if (children.length == 0)
80                    Engine.removeContainer(tabContainerItem)
81
82                else if (children.length == 1) {
83                    children[0].width = width
84                    children[0].height = height
85                    // tab content created. set the first tab as current (if not set before, and if
86                    // child is added after TabGroup has completed)
87                    if (priv.complete && root.currentTab == null)
88                        root.currentTab = children[0]
89                }
90            }
91
92            onWidthChanged: {
93                if (children.length > 0)
94                    children[0].width = width
95            }
96
97            onHeightChanged: {
98                if (children.length > 0)
99                    children[0].height = height
100            }
101
102            Component.onDestruction: {
103                if (typeof(root) != "undefined" && !root.currentTab) {
104                    // selected one deleted. try to activate the neighbour
105                    var removedIndex = -1
106                    for (var i = 0; i < containerHost.children.length; i++) {
107                        if (containerHost.children[i] == tabContainerItem) {
108                            removedIndex = i
109                            break
110                        }
111                    }
112                    var newIndex = -1
113                    if (removedIndex != -1) {
114                        if (removedIndex != containerHost.children.length - 1)
115                            newIndex = removedIndex + 1
116                        else if (removedIndex != 0)
117                            newIndex = removedIndex - 1
118                    }
119
120                    if (newIndex != -1)
121                        root.currentTab = containerHost.children[newIndex].children[0]
122                    else
123                        root.currentTab = null
124                }
125            }
126
127            function incomingDone() {
128                state = ""
129                if (priv.incomingPage) {
130                    if (priv.incomingPage instanceof PlasmaComponents.Page) {
131                        priv.incomingPage.status = PlasmaComponents.PageStatus.Active
132                    }
133                    priv.incomingPage = null
134                }
135            }
136
137            function outgoingDone() {
138                if (priv.outgoingPage) {
139                    if (priv.outgoingPage instanceof PlasmaComponents.Page) {
140                        priv.outgoingPage.status = PlasmaComponents.PageStatus.Active
141                    }
142                    priv.outgoingPage.visible = false
143                    priv.outgoingPage = null
144                }
145                state = "HiddenLeft"
146            }
147
148            width: parent ? parent.width : 0
149            height: parent ? parent.height : 0
150            state: "HiddenLeft"
151
152            states: [
153                State { name: ""; PropertyChanges { target: tabContainerItem; opacity: 1.0; x: 0 } },
154                State { name: "Incoming"; PropertyChanges { target: tabContainerItem; opacity: 1.0; x: 0 } },
155
156                State {
157                    name: "OutgoingLeft"
158                    PropertyChanges {
159                        target: tabContainerItem
160                        opacity: 0.0
161                        x: LayoutMirroring.enabled ? root.width : -root.width
162                    }
163                },
164                State {
165                    name: "OutgoingRight"
166                    PropertyChanges {
167                        target: tabContainerItem
168                        opacity: 0.0
169                        x: LayoutMirroring.enabled ? -root.width : root.width
170                    }
171                },
172
173                State {
174                    name: "HiddenLeft"
175                    PropertyChanges {
176                        target: tabContainerItem
177                        opacity: 0.0
178                        x: LayoutMirroring.enabled ? -root.width : root.width
179                    }
180                },
181
182                State {
183                    name: "HiddenRight"
184                    PropertyChanges {
185                        target: tabContainerItem
186                        opacity: 0.0
187                        x: LayoutMirroring.enabled ? root.width : -root.width
188                    }
189                }
190            ]
191
192            transitions:  [
193                Transition {
194                    to: "Incoming"
195                    enabled: root.visible
196                    SequentialAnimation {
197                        ScriptAction { script: root.clip = true }
198                        ParallelAnimation {
199                            OpacityAnimator {
200                                easing.type: Easing.InQuad
201                                duration: PlasmaCore.Units.longDuration
202                            }
203                            XAnimator {
204                                easing.type: Easing.InQuad
205                                duration: PlasmaCore.Units.longDuration
206                            }
207                        }
208                        ScriptAction { script: {incomingDone(); root.clip = false} }
209                    }
210                },
211                Transition {
212                    to: "OutgoingLeft,OutgoingRight"
213                    enabled: root.visible
214                    SequentialAnimation {
215                        ParallelAnimation {
216                            OpacityAnimator {
217                                easing.type: Easing.InQuad
218                                duration: PlasmaCore.Units.longDuration
219                            }
220                            XAnimator {
221                                easing.type: Easing.InQuad
222                                duration: PlasmaCore.Units.longDuration
223                            }
224                        }
225                        ScriptAction { script: outgoingDone() }
226                    }
227                }
228            ]
229        }
230    }
231
232    QtObject {
233        id: priv
234        property bool reparenting: false
235        property bool complete: false
236        property Item currentTabContainer: root.currentTab ? root.currentTab.parent : null
237        property int currentIndex: 0
238        property Item incomingPage
239        property Item outgoingPage
240        property bool animate: true
241
242        onCurrentTabContainerChanged: {
243            var newCurrentIndex = 0
244            for (var i = 0; i < containerHost.children.length; i++) {
245                if (containerHost.children[i] == currentTabContainer) {
246                    newCurrentIndex = i
247                    break
248                }
249            }
250
251            currentTabContainer.visible = true
252            incomingPage = currentTabContainer.children[0]
253            incomingPage.visible = true
254            if (incomingPage instanceof PlasmaComponents.Page) {
255                incomingPage.status = PlasmaComponents.PageStatus.Activating
256            }
257            if (currentIndex < newCurrentIndex) {
258                currentTabContainer.state = "HiddenLeft"
259            } else {
260                currentTabContainer.state = "HiddenRight"
261            }
262            if (animate) {
263                currentTabContainer.state = "Incoming"
264            } else {
265                currentTabContainer.incomingDone()
266            }
267
268            if (newCurrentIndex == currentIndex) {
269                return
270            }
271
272            var oldPage = containerHost.children[currentIndex]
273            outgoingPage = oldPage.children[0]
274            if (animate) {
275                if (currentIndex < newCurrentIndex) {
276                    oldPage.state = "OutgoingLeft"
277                } else {
278                    oldPage.state = "OutgoingRight"
279                }
280            } else {
281                oldPage.outgoingDone()
282            }
283            currentIndex = newCurrentIndex
284        }
285    }
286}
287