/**************************************************************************** ** ** 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() }