plasma-framework/declarativeimports/plasmacomponents/qml/ToolBarLayout.qml
2011-11-08 21:10:30 +01:00

312 lines
15 KiB
QML

/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** 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 Nokia Corporation 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$
**
****************************************************************************/
// ToolBarLayout is a container for items on a toolbar that automatically
// implements an appropriate layout for its children.
import QtQuick 1.1
import "." 0.1
Item {
id: root
implicitWidth: parent.width
implicitHeight: parent.height
visible: false
Connections {
target: privateStyle
onLayoutParametersChanged: internal.layoutChildren()
}
Connections {
target: screen
onCurrentOrientationChanged: internal.layoutChildren()
}
QtObject {
id: internal
objectName: "internal"
property bool portrait: screen.width < screen.height
// These are the dynamic layout parameters used by the toolbar layout.
property real defaultHeightToolBar: portrait ?
privateStyle.toolBarHeightPortrait : privateStyle.toolBarHeightLandscape
property real defaultHeightToolButton: privateStyle.toolBarHeightLandscape
property real outerMarginHorizontal: portrait ?
0 : (2 * platformStyle.paddingLarge)
property real outerMarginButtonRowLong: portrait ?
platformStyle.paddingLarge : (3 * platformStyle.paddingLarge)
property real innerSpacingTextButtonSingle: portrait ?
platformStyle.paddingMedium + (3 * platformStyle.paddingLarge) : (3 * platformStyle.paddingLarge)
property real innerSpacingTextButtonDouble: portrait ?
platformStyle.paddingSmall : (3 * platformStyle.paddingLarge)
property real innerSpacingButtonRowTwoChildren: portrait ?
platformStyle.paddingMedium : (3 * platformStyle.paddingLarge)
property real innerSpacingButtonRowLong: portrait ?
platformStyle.paddingMedium : platformStyle.paddingLarge
property real centerSpacingTextButtonDouble: platformStyle.paddingLarge
function isIconButton(item) {
return item.hasOwnProperty("iconSource")
&& item.hasOwnProperty("text")
&& item.text == ""
}
function isTextToolButton(item) {
// ToolButton has both iconSource and flat property,
// Button only has iconSource
return (item.hasOwnProperty("iconSource")
&& item.iconSource == ""
&& item.hasOwnProperty("flat"))
}
function isButtonRow(item) {
return item.hasOwnProperty("checkedButton")
}
function buttonWidth(child) {
if ((isTextToolButton(child)) || !(child.hasOwnProperty("implicitWidth"))) {
// ImplicitWidth for the ToolButton returns wrong value right after
// orientation change, and also we want to override its own
// layout width calculation, so use the actual width
return child.width
}
return child.implicitWidth
}
function centerOffset(outerLength, innerLength) {
// calculate the offset of the leading edge of a centered child item
return Math.floor((outerLength - innerLength) / 2.0)
}
function widthTextButtonSingle(leftMargin, innerSpacing) {
// calculate the remaining width for a centered item
var outerContents = leftMargin + innerSpacing
return root.width - (outerContents * 2)
}
function widthTextButtonDouble(leftMargin, innerSpacing, centerSpacing) {
// calculate the space available when there are two items with a center
// margin, and share it between the two
var outerContents = leftMargin + innerSpacing
return Math.round((root.width - (outerContents * 2) - centerSpacing) / 2.0)
}
function widthButtonRowLong(leftButton, rightButton, itemMargin, innerSpacing, outerMargin) {
// calculate the width of a long button row, which is used in the case that there are more
// than three icons. If either left or right button is present, allocate the itemMargin to
// ensure that there is sufficient space; otherwise we can use the special
// outerMargin value
var leftContents = leftButton ? itemMargin + innerSpacing : outerMargin
var rightContents = rightButton ? itemMargin + innerSpacing : outerMargin
return root.width - leftContents - rightContents
}
function layoutChildren() {
var numChildren = children.length
if (parent == null || root.width == 0 || numChildren == 0)
return
for (var i = 0; i < numChildren; ++i) {
// make sure all the children have correct parent, height, and y
children[i].parent = root
if (isButtonRow(children[i])) {
var buttonRow = children[i]
// ButtonRow frame height is always tool bar's height in
// landscape, regardless of the current orientation, so we need
// to override the heights of the buttons within (because it's a
// Row, so its height is based on its children)
for (var j = 0; j < buttonRow.children.length; ++j) {
buttonRow.children[j].implicitHeight = defaultHeightToolButton
}
}
// child's vertical center always goes to middle of the toolbar
var childHeight = children[i].hasOwnProperty("implicitHeight") ?
children[i].implicitHeight : children[i].height
children[i].y = root.y + centerOffset(root.implicitHeight, childHeight)
}
// detect whether we have left and or right items. we need to lay out
// the remaining children (that are not left or right items) whether they
// are tool buttons, text buttons or a button row
var leftItem = isIconButton(children[0]) ?
children[0] : undefined
var rightItem = isIconButton(children[numChildren-1]) ?
children[numChildren-1] : undefined
var childrenRemaining = numChildren - (leftItem != undefined ? 1 : 0) - (rightItem != undefined ? 1 : 0)
var firstRemainingIndex = leftItem != undefined ? 1 : 0
var lastRemainingIndex = rightItem != undefined ? (numChildren - 2) : (numChildren - 1)
// precalculate the margins for the left and right items, we will work
// out child sizes assuming they are present
var leftMargin = outerMarginHorizontal + defaultHeightToolBar
var rightMargin = root.width - leftMargin
// In the case of a lone remaining chlld, or in the case of 2 text
// buttons, we need to override the width
var overrideChildWidth = 0
for (var p = firstRemainingIndex; p <= lastRemainingIndex; p++) {
var child = children[p]
overrideChildWidth = buttonWidth(child)
// If necessary, we calculate and override the width first before we
// can calculate the x positions
if ((isTextToolButton(child) && childrenRemaining == 1)
|| (isButtonRow(child) && child.children.length == 1)) {
// we treat a button row with a single item like a single tool button,
// but in landscape, calculate size as if there were two buttons
overrideChildWidth = portrait ?
widthTextButtonSingle(leftMargin, innerSpacingTextButtonSingle) :
widthTextButtonDouble(leftMargin, innerSpacingTextButtonDouble, innerSpacingTextButtonDouble)
} else if (isTextToolButton(child) && childrenRemaining == 2) {
// special case of margins for two text buttons
overrideChildWidth = widthTextButtonDouble(
leftMargin, innerSpacingTextButtonDouble, centerSpacingTextButtonDouble)
} else if (isButtonRow(child) && ((child.children.length == 2)
|| (child.children.length > 2 && leftItem != undefined && rightItem != undefined))) {
// there are special margins if the button row has two children,
// or if it has more than two children and there is a left and
// a right item
overrideChildWidth = widthTextButtonSingle(
leftMargin, innerSpacingButtonRowTwoChildren)
} else if (isButtonRow(child) && child.children.length > 2) {
// the long button row has special margins, which are used on
// either side if the side icon button is missing on that side. If the item is present,
// the leftMargin can be used on either side to leave space for either icon button
overrideChildWidth = widthButtonRowLong(
leftItem != undefined,
rightItem != undefined,
leftMargin,
innerSpacingButtonRowLong,
outerMarginButtonRowLong)
}
child.width = overrideChildWidth
}
if (numChildren == 1) {
var loneChild = children[0]
var loneChildWidth = buttonWidth(loneChild)
if (isButtonRow(loneChild)) {
loneChildWidth = overrideChildWidth
}
if (isIconButton(loneChild))
loneChild.x = outerMarginHorizontal
else
loneChild.x = centerOffset(root.width, loneChildWidth)
return
}
// we can easily calculate the positions of the left and right items,
// but if they are missing then correct the margins
if (leftItem != undefined){
leftItem.x = outerMarginHorizontal
} else {
leftMargin = 0
}
if (rightItem != undefined){
rightItem.x = root.width - defaultHeightToolBar - outerMarginHorizontal
} else {
rightMargin = root.width
}
if (!childrenRemaining)
return;
if (childrenRemaining == 1) {
var loneChild = children[firstRemainingIndex]
var loneChildWidth = buttonWidth(loneChild)
if (isButtonRow(loneChild)) {
// ButtonRow should have the override width (but it won't have
// been updated yet)
loneChildWidth = overrideChildWidth
}
// lone child is always centered, unless it's a long button row on
// one side only
if (isButtonRow(loneChild) && loneChild.children.length >= 3
&& ((leftItem == undefined) != (rightItem == undefined))) {
loneChild.x = (leftItem != undefined) ?
(leftMargin + innerSpacingButtonRowLong) : outerMarginButtonRowLong
} else {
loneChild.x = centerOffset(root.width, loneChildWidth)
}
} else if (childrenRemaining == 2 && isTextToolButton(children[firstRemainingIndex])) {
// text buttons are distributed around the center with a center spacing
var midPoint = Math.floor(root.width / 2.0)
var halfSpacing = Math.round(platformStyle.paddingLarge / 2.0)
children[firstRemainingIndex].x = midPoint - halfSpacing - buttonWidth(children[firstRemainingIndex])
children[firstRemainingIndex + 1].x = midPoint + halfSpacing
} else {
// icon buttons are deployed evenly in the remaining space,
// but we need to ensure that the spacings are integer values,
// and share the rounding error to ensure that they are centered
var remainingSpace = rightMargin - leftMargin
var spacingNotRounded = remainingSpace
for (var p = 0; p < childrenRemaining; p++) {
var nextChild = children[leftItem != undefined ? p + 1 : p]
spacingNotRounded -= buttonWidth(nextChild)
}
spacingNotRounded /= (childrenRemaining + 1)
var spacing = Math.floor(spacingNotRounded)
var totalRoundingError = (spacingNotRounded - spacing) * (childrenRemaining + 1)
var curPos = leftMargin + Math.floor(totalRoundingError / 2.0)
for (var p = 0; p < childrenRemaining; p++) {
var nextChild = children[leftItem != undefined ? p + 1 : p]
curPos += spacing
nextChild.x = curPos
curPos += buttonWidth(nextChild)
}
}
}
}
Component.onCompleted: internal.layoutChildren()
onParentChanged: internal.layoutChildren()
onChildrenChanged: internal.layoutChildren()
onImplicitWidthChanged: internal.layoutChildren()
onImplicitHeightChanged: internal.layoutChildren()
}