summaryrefslogtreecommitdiff
path: root/src/qtdesktop/private/Splitter.qml
diff options
context:
space:
mode:
Diffstat (limited to 'src/qtdesktop/private/Splitter.qml')
-rw-r--r--src/qtdesktop/private/Splitter.qml461
1 files changed, 461 insertions, 0 deletions
diff --git a/src/qtdesktop/private/Splitter.qml b/src/qtdesktop/private/Splitter.qml
new file mode 100644
index 00000000..a6c18511
--- /dev/null
+++ b/src/qtdesktop/private/Splitter.qml
@@ -0,0 +1,461 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtDesktop 1.0
+
+Splitter {
+ id: root
+ default property alias items: splitterItems.children
+ property alias handles: splitterHandles.children
+ property Component handleDelegate: Rectangle { width:3; color: "black" }
+ property int handleWidth: -1
+ property real preferredSize: 0
+ property int orientation: Qt.Horizontal
+
+ clip: true
+ Component.onCompleted: d.init();
+ onWidthChanged: d.updateLayout();
+ onHeightChanged: d.updateLayout();
+
+ QtObject {
+ id: d
+
+ property bool horizontal: orientation == Qt.Horizontal
+ property string size: horizontal ? "width" : "height"
+ property string minimum: horizontal ? "minimumWidth" : "minimumHeight"
+ property string maximum: horizontal ? "maximumWidth" : "maximumHeight"
+
+ property string offset: horizontal ? "x" : "y"
+ property int expandingIndex: -1
+ property bool updateLayoutGuard: true
+ property bool itemWidthGuard: false
+ property bool itemExpandingGuard: true
+
+ function init()
+ {
+ for (var i=0; i<items.length; ++i) {
+ var item = items[i];
+
+ item.Splitter.itemIndex = i
+ // Assign one, and only one, item to be expanding:
+ if (item.Splitter.expanding === true) {
+ if (d.expandingIndex === -1 && item.visible === true)
+ d.expandingIndex = i
+ else
+ item.Splitter.expanding = false
+ }
+
+ // Anchor each item to fill out all space vertically:
+ if (d.horizontal) {
+ item.anchors.top = splitterItems.top
+ item.anchors.bottom = splitterItems.bottom
+ } else {
+ item.anchors.left = splitterItems.left
+ item.anchors.right = splitterItems.right
+
+ }
+
+ // Listen for changes to width and expanding:
+ propertyChangeListener.createObject(item, {"itemIndex":i});
+ if (i < items.length-1) {
+ // Create a handle for the item, unless its the last:
+ var handle = handleloader.createObject(splitterHandles, {"handleIndex":i});
+
+ if (d.horizontal) {
+ handle.anchors.top = splitterHandles.top
+ handle.anchors.bottom = splitterHandles.bottom
+ } else {
+ handle.anchors.left = splitterHandles.left
+ handle.anchors.right = splitterHandles.right
+ }
+ }
+ }
+
+ if (d.expandingIndex === -1) {
+ // INVARIANT: No item was set as expanding.
+ // We then choose the last visible item instead:
+ d.expandingIndex = items.length - 1
+ for (i=items.length-1; i>=0; --i) {
+ var item = items[i]
+ if (item.visible === true) {
+ d.expandingIndex = i
+ item = items[i]
+ break
+ }
+ }
+ if (items.length && item.visible) {
+ item.Splitter.expanding = true
+ }
+ }
+
+ d.itemExpandingGuard = false
+ d.updateLayoutGuard = false
+ d.updateLayout()
+ }
+
+ function accumulatedSize(firstIndex, lastIndex, includeExpandingMinimum)
+ {
+ // Go through items and handles, and
+ // calculate their acummulated width.
+ var w = 0
+ for (var i=firstIndex; i<lastIndex; ++i) {
+ var item = items[i]
+ if (item.visible) {
+ if (i !== d.expandingIndex)
+ w += item[d.size];
+ else if (includeExpandingMinimum && item.Splitter[minimum] != -1)
+ w += item[minimum]
+ }
+
+ var handle = handles[i]
+ if (handle && items[i + ((d.expandingIndex > i) ? 0 : 1)].visible)
+ w += handle[d.size]
+ }
+ return w
+ }
+
+ function updateLayout()
+ {
+ // This function will reposition both handles and
+ // items according to the _width of the each item_
+ if (items.length === 0)
+ return;
+ if (d.updateLayoutGuard === true)
+ return
+ d.updateLayoutGuard = true
+
+ // Use a temporary variable to store values to avoid breaking
+ // property bindings when the value does not actually change:
+ var newValue
+
+ // Ensure all items within min/max:
+ for (var i=0; i<items.length; ++i) {
+ if (i !== d.expandingIndex) {
+ item = items[i];
+ // If the item is using percentage width, convert
+ // that number to real width now:
+ if (item.Splitter.percentageSize !== -1) {
+ newValue = item.Splitter.percentageSize * (root[d.size] / 100)
+ if (newValue !== item[d.size])
+ item[d.size] = newValue
+ }
+ // Ensure item width is not more than maximumSize:
+ if (item.Splitter[maximum] !== -1) {
+ newValue = Math.min(item[d.size], item.Splitter[maximum])
+ if (newValue !== item[d.size])
+ item[d.size] = newValue
+ }
+ // Ensure item width is not more less minimumWidth:
+ if (item.Splitter[minimum] !== -1) {
+ newValue = Math.max(item[d.size], item.Splitter[minimum])
+ if (newValue !== item[d.size])
+ item[d.size] = newValue
+ }
+ }
+ }
+
+ // Special case: set width of expanding item to available space:
+ newValue = root[d.size] - d.accumulatedSize(0, items.length, false);
+ var expandingItem = items[d.expandingIndex]
+ var expandingMinimum = 0
+ if (expandingItem.Splitter[minimum] !== -1)
+ expandingMinimum = expandingItem.Splitter[minimum]
+ newValue = Math.max(newValue, expandingMinimum)
+ if (expandingItem[d.size] !== 0 && expandingItem.Splitter.percentageSize !== -1)
+ expandingItem.Splitter.percentageSize = newValue * (100 / root[d.size])
+ if (expandingItem[d.size] !== newValue)
+ expandingItem[d.size] = newValue
+
+ // Then, position items and handles according to their width:
+ var item, lastVisibleItem
+ var handle, lastVisibleHandle
+ var newpreferredSize = expandingMinimum - expandingItem[d.size]
+
+ for (i=0; i<items.length; ++i) {
+ // Position item to the right of the previous visible handle:
+ item = items[i];
+ if (item.visible) {
+ if (lastVisibleHandle) {
+ newValue = lastVisibleHandle[d.offset] + lastVisibleHandle[d.size]
+ if (newValue !== item[d.offset])
+ item[d.offset] = newValue
+ } else {
+ newValue = 0
+ if (newValue !== item[d.offset])
+ item[d.offset] = newValue
+ }
+ newpreferredSize += item[d.size]
+ lastVisibleItem = item
+ }
+
+ // Position handle to the right of the previous visible item. We use an alterative way of
+ // checking handle visibility because that property might not have updated correctly yet:
+ handle = handles[i]
+ if (handle && items[i + ((d.expandingIndex > i) ? 0 : 1)].visible) {
+ newValue = lastVisibleItem[d.offset] + Math.max(0, lastVisibleItem[d.size])
+ if (newValue !== handle[d.offset])
+ handle[d.offset] = newValue
+ newpreferredSize += handle[d.size]
+ lastVisibleHandle = handle
+ }
+ }
+
+ root.preferredSize = newpreferredSize
+ d.updateLayoutGuard = false
+ }
+ }
+
+ Component {
+ id: handleloader
+ Loader {
+ id: myHandle
+ property int handleIndex: 0
+ property Item handle: myHandle
+ property Item splitterItem: items[handleIndex + ((d.expandingIndex > handleIndex) ? 0 : 1)]
+
+ // 'splitterRow' should be an alias, but that fails to resolve runtime:
+ property Item splitterRow: root
+ property Item background: item
+
+ visible: splitterItem.visible
+ sourceComponent: handleDelegate
+ onWidthChanged: d.updateLayout()
+
+ onXChanged: {
+ // For some unknown reason, dragging by X axis only not working in MouseArea, so
+ // to enable it Drag.XandYAxis should be used, therefore not only Y coordinate
+ // changes, but also X and we need to filter out this events, if we have splitter,
+ // that should move vertically
+ if (d.horizontal) {
+ moveHandle()
+ }
+ }
+
+ onYChanged: {
+ moveHandle()
+ }
+
+ function moveHandle() {
+ // Moving the handle means resizing an item. Which one,
+ // left or right, depends on where the expanding item is.
+ // 'updateLayout' will override in case new width violates max/min.
+ // And 'updateLayout will be triggered when an item changes width.
+ if (d.updateLayoutGuard)
+ return
+
+ var leftHandle, leftItem, rightItem, rightHandle
+ var leftEdge, rightEdge, newWidth, leftStopX, rightStopX
+ var i
+
+ if (d.expandingIndex > handleIndex) {
+ // Resize item to the left.
+ // Ensure that the handle is not crossing other handles. So
+ // find the first visible handle to the left to determine the left edge:
+ leftEdge = 0
+ for (i=handleIndex-1; i>=0; --i) {
+ leftHandle = handles[i]
+ if (leftHandle.visible) {
+ leftEdge = leftHandle[d.offset] + leftHandle[d.size]
+ break;
+ }
+ }
+
+ // Ensure: leftStopX >= myHandle[d.offset] >= rightStopX
+ var min = d.accumulatedSize(handleIndex+1, items.length, true)
+ rightStopX = root[d.size] - min - myHandle[d.size]
+ leftStopX = Math.max(leftEdge, myHandle[d.offset])
+ myHandle[d.offset] = Math.min(rightStopX, Math.max(leftStopX, myHandle[d.offset]))
+
+ newWidth = myHandle[d.offset] - leftEdge
+ leftItem = items[handleIndex]
+ if (root[d.size] != 0 && leftItem.Splitter.percentageSize !== -1)
+ leftItem.Splitter.percentageSize = newWidth * (100 / root[d.size])
+ // The next line will trigger 'updateLayout' inside 'propertyChangeListener':
+ leftItem[d.size] = newWidth
+ } else {
+ // Resize item to the right.
+ // Ensure that the handle is not crossing other handles. So
+ // find the first visible handle to the right to determine the right edge:
+ rightEdge = root[d.size]
+ for (i=handleIndex+1; i<handles.length; ++i) {
+ rightHandle = handles[i]
+ if (rightHandle.visible) {
+ rightEdge = rightHandle[d.offset]
+ break;
+ }
+ }
+
+ // Ensure: leftStopX <= myHandle[d.offset] <= rightStopX
+ var min = d.accumulatedSize(0, handleIndex+1, true)
+ leftStopX = min - myHandle[d.size]
+ rightStopX = Math.min((rightEdge - myHandle[d.size]), myHandle[d.offset])
+ myHandle[d.offset] = Math.max(leftStopX, Math.min(myHandle[d.offset], rightStopX))
+
+ newWidth = rightEdge - (myHandle[d.offset] + myHandle[d.size])
+ rightItem = items[handleIndex+1]
+ if (root[d.size] !== 0 && rightItem[d.percentageSize] !== -1)
+ rightItem.Splitter.percentageSize = newWidth * (100 / root[d.size])
+ // The next line will trigger 'updateLayout' inside 'propertyChangeListener':
+ rightItem[d.size] = newWidth
+ }
+ }
+ }
+ }
+
+ Item {
+ id: splitterItems
+ anchors.fill: parent
+ }
+ Item {
+ id: splitterHandles
+ anchors.fill: parent
+ }
+
+ Component {
+ // This dummy item becomes a child of all
+ // items it the splitter, just to provide a way
+ // to listen for changes to their width, expanding etc.
+ id: propertyChangeListener
+ Item {
+ id: target
+ width: parent[d.size]
+ property bool expanding: parent.Splitter.expanding
+ property real percentageSize: parent.Splitter.percentageSize
+ property real minimumWidth: parent.Splitter[d.minimum]
+ property real maximumSize: parent.Splitter[d.maximum]
+ property int itemIndex: parent.Splitter.itemIndex
+
+ onPercentageSizeChanged: d.updateLayout();
+ onMinimumWidthChanged: d.updateLayout();
+ onMaximumSizeChanged: d.updateLayout();
+ onExpandingChanged: updateExpandingIndex()
+
+ function updateExpandingIndex()
+ {
+ // The following code is needed to avoid a binding
+ // loop, since we might change 'expanding' again to a different item:
+ if (d.itemExpandingGuard === true)
+ return
+ d.itemExpandingGuard = true
+ // break binding:
+ expanding = false
+
+ // 'expanding' follows radio button behavior:
+ // First, find the new expanding item:
+ var newIndex = items.length-1
+ for (var i=0; i<items.length; ++i) {
+ var item = items[i]
+ if (i !== d.expandingIndex && item.Splitter.expanding === true && item.visible === true) {
+ newIndex = i
+ break
+ }
+ }
+ item = items[newIndex]
+ if (item.visible === false) {
+ // So now we ended up with the last item in the splitter to be
+ // expanding, but it turns out to not be visible. So we need to
+ // traverse backwards again to find one that is visible...
+ for (i=items.length-2; i>=0; --i) {
+ var item = items[i]
+ if (item.visible === true) {
+ newIndex = i
+ item = items[newIndex]
+ break
+ }
+ }
+ }
+
+ // Tell the found item that it is expanding:
+ if (item.Splitter.expanding !== true)
+ item.Splitter.expanding = true
+ // ...and the old one that it is not:
+ if (newIndex !== d.expandingIndex) {
+ item = items[d.expandingIndex]
+ if (item.Splitter.expanding !== false)
+ item.Splitter.expanding = false
+ }
+ // update index:
+ d.expandingIndex = newIndex
+ d.updateLayout();
+ // recreate binding:
+ expanding = Qt.binding(function() { return parent.Splitter.expanding })
+ d.itemExpandingGuard = false
+ }
+
+ function handleSizeChanged() {
+ // We need to update the layout.
+ // The following code is needed to avoid a binding
+ // loop, since we might change 'width' again to a different value:
+ if (d.itemWidthGuard === true)
+ return
+ d.itemWidthGuard = true
+ // Break binding:
+ this[d.size] = 0
+
+ d.updateLayout()
+
+ // Restablish binding:
+ width = Qt.binding(function() { return parent[d.size]; })
+ d.itemWidthGuard = false
+ }
+
+ onWidthChanged: handleSizeChanged()
+ onHeightChanged: handleSizeChanged()
+ onVisibleChanged: {
+ // Hiding the expanding item forces us to
+ // select a new one (and therefore not recommended):
+ if (d.expandingIndex === itemIndex) {
+ updateExpandingIndex()
+ } else {
+ if (visible) {
+ // Try to keep all items within the SplitterRow. When an item
+ // has been hidden, the expanding item might no longer be large enough
+ // to give away space to the new items width. So we need to resize:
+ var overflow = d.accumulatedSize(0, items.length, true) - root[d.size];
+ if (overflow > 0)
+ parent[d.size] -= overflow
+ }
+ d.updateLayout()
+ }
+ }
+ }
+ }
+}