1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the Qt Components project.
9 ** $QT_BEGIN_LICENSE:BSD$
10 ** You may use this file under the terms of the BSD license as follows:
12 ** "Redistribution and use in source and binary forms, with or without
13 ** modification, are permitted provided that the following conditions are
15 ** * Redistributions of source code must retain the above copyright
16 ** notice, this list of conditions and the following disclaimer.
17 ** * Redistributions in binary form must reproduce the above copyright
18 ** notice, this list of conditions and the following disclaimer in
19 ** the documentation and/or other materials provided with the
21 ** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
22 ** the names of its contributors may be used to endorse or promote
23 ** products derived from this software without specific prior written
26 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
39 ****************************************************************************/
41 // ToolBarLayout is a container for items on a toolbar that automatically
42 // implements an appropriate layout for its children.
50 property bool backButton: true // deprecated
51 onBackButtonChanged: { console.log("warning: ToolBarLayout.backButton is deprecated.") }
53 implicitWidth: screen.width
54 implicitHeight: internal.defaultHeightToolBar
60 onLayoutParametersChanged: internal.layoutChildren()
64 onCurrentOrientationChanged: internal.layoutChildren()
69 objectName: "internal"
71 property bool portrait: screen.width < screen.height
73 // These are the dynamic layout parameters used by the toolbar layout.
74 property real defaultHeightToolBar: portrait ?
75 privateStyle.toolBarHeightPortrait : privateStyle.toolBarHeightLandscape
76 property real defaultHeightToolButton: privateStyle.toolBarHeightLandscape
77 property real outerMarginHorizontal: portrait ?
78 0 : (2 * platformStyle.paddingLarge)
79 property real outerMarginButtonRowLong: portrait ?
80 platformStyle.paddingLarge : (3 * platformStyle.paddingLarge)
81 property real innerSpacingTextButtonSingle: portrait ?
82 platformStyle.paddingMedium + (3 * platformStyle.paddingLarge) : (3 * platformStyle.paddingLarge)
83 property real innerSpacingTextButtonDouble: portrait ?
84 platformStyle.paddingSmall : (3 * platformStyle.paddingLarge)
85 property real innerSpacingButtonRowTwoChildren: portrait ?
86 platformStyle.paddingMedium : (3 * platformStyle.paddingLarge)
87 property real innerSpacingButtonRowLong: portrait ?
88 platformStyle.paddingMedium : platformStyle.paddingLarge
89 property real centerSpacingTextButtonDouble: platformStyle.paddingLarge
91 function isIconButton(item) {
92 return item.hasOwnProperty("iconSource")
93 && item.hasOwnProperty("text")
97 function isTextToolButton(item) {
98 // ToolButton has both iconSource and flat property,
99 // Button only has iconSource
100 return (item.hasOwnProperty("iconSource")
101 && item.iconSource == ""
102 && item.hasOwnProperty("flat"))
105 function isButtonRow(item) {
106 return item.hasOwnProperty("checkedButton")
109 function buttonWidth(child) {
110 if ((isTextToolButton(child)) || !(child.hasOwnProperty("implicitWidth"))) {
111 // ImplicitWidth for the ToolButton returns wrong value right after
112 // orientation change, and also we want to override its own
113 // layout width calculation, so use the actual width
116 return child.implicitWidth
119 function centerOffset(outerLength, innerLength) {
120 // calculate the offset of the leading edge of a centered child item
121 return Math.floor((outerLength - innerLength) / 2.0)
124 function widthTextButtonSingle(leftMargin, innerSpacing) {
125 // calculate the remaining width for a centered item
126 var outerContents = leftMargin + innerSpacing
127 return root.width - (outerContents * 2)
130 function widthTextButtonDouble(leftMargin, innerSpacing, centerSpacing) {
131 // calculate the space available when there are two items with a center
132 // margin, and share it between the two
133 var outerContents = leftMargin + innerSpacing
134 return Math.round((root.width - (outerContents * 2) - centerSpacing) / 2.0)
137 function widthButtonRowLong(leftButton, rightButton, itemMargin, innerSpacing, outerMargin) {
138 // calculate the width of a long button row, which is used in the case that there are more
139 // than three icons. If either left or right button is present, allocate the itemMargin to
140 // ensure that there is sufficient space; otherwise we can use the special
142 var leftContents = leftButton ? itemMargin + innerSpacing : outerMargin
143 var rightContents = rightButton ? itemMargin + innerSpacing : outerMargin
144 return root.width - leftContents - rightContents
147 function layoutChildren() {
148 var numChildren = children.length
149 if (parent == null || root.width == 0 || numChildren == 0)
152 for (var i = 0; i < numChildren; ++i) {
153 // make sure all the children have correct parent, height, and y
154 children[i].parent = root
155 if (isButtonRow(children[i])) {
156 var buttonRow = children[i]
157 // ButtonRow frame height is always tool bar's height in
158 // landscape, regardless of the current orientation, so we need
159 // to override the heights of the buttons within (because it's a
160 // Row, so its height is based on its children)
161 for (var j = 0; j < buttonRow.children.length; ++j) {
162 buttonRow.children[j].implicitHeight = defaultHeightToolButton
165 // child's vertical center always goes to middle of the toolbar
166 var childHeight = children[i].hasOwnProperty("implicitHeight") ?
167 children[i].implicitHeight : children[i].height
168 children[i].y = root.y + centerOffset(root.implicitHeight, childHeight)
171 // detect whether we have left and or right items. we need to lay out
172 // the remaining children (that are not left or right items) whether they
173 // are tool buttons, text buttons or a button row
174 var leftItem = isIconButton(children[0]) ?
175 children[0] : undefined
176 var rightItem = isIconButton(children[numChildren-1]) ?
177 children[numChildren-1] : undefined
178 var childrenRemaining = numChildren - (leftItem != undefined ? 1 : 0) - (rightItem != undefined ? 1 : 0)
179 var firstRemainingIndex = leftItem != undefined ? 1 : 0
180 var lastRemainingIndex = rightItem != undefined ? (numChildren - 2) : (numChildren - 1)
182 // precalculate the margins for the left and right items, we will work
183 // out child sizes assuming they are present
184 var leftMargin = outerMarginHorizontal + defaultHeightToolBar
185 var rightMargin = root.width - leftMargin
187 // In the case of a lone remaining chlld, or in the case of 2 text
188 // buttons, we need to override the width
189 var overrideChildWidth = 0
190 for (var p = firstRemainingIndex; p <= lastRemainingIndex; p++) {
191 var child = children[p]
192 overrideChildWidth = buttonWidth(child)
194 // If necessary, we calculate and override the width first before we
195 // can calculate the x positions
196 if ((isTextToolButton(child) && childrenRemaining == 1)
197 || (isButtonRow(child) && child.children.length == 1)) {
198 // we treat a button row with a single item like a single tool button,
199 // but in landscape, calculate size as if there were two buttons
200 overrideChildWidth = portrait ?
201 widthTextButtonSingle(leftMargin, innerSpacingTextButtonSingle) :
202 widthTextButtonDouble(leftMargin, innerSpacingTextButtonDouble, innerSpacingTextButtonDouble)
204 } else if (isTextToolButton(child) && childrenRemaining == 2) {
205 // special case of margins for two text buttons
206 overrideChildWidth = widthTextButtonDouble(
207 leftMargin, innerSpacingTextButtonDouble, centerSpacingTextButtonDouble)
209 } else if (isButtonRow(child) && ((child.children.length == 2)
210 || (child.children.length > 2 && leftItem != undefined && rightItem != undefined))) {
211 // there are special margins if the button row has two children,
212 // or if it has more than two children and there is a left and
214 overrideChildWidth = widthTextButtonSingle(
215 leftMargin, innerSpacingButtonRowTwoChildren)
217 } else if (isButtonRow(child) && child.children.length > 2) {
218 // the long button row has special margins, which are used on
219 // either side if the side icon button is missing on that side. If the item is present,
220 // the leftMargin can be used on either side to leave space for either icon button
221 overrideChildWidth = widthButtonRowLong(
222 leftItem != undefined,
223 rightItem != undefined,
225 innerSpacingButtonRowLong,
226 outerMarginButtonRowLong)
229 child.width = overrideChildWidth
232 if (numChildren == 1) {
233 var loneChild = children[0]
234 var loneChildWidth = buttonWidth(loneChild)
235 if (isButtonRow(loneChild)) {
236 loneChildWidth = overrideChildWidth
238 if (isIconButton(loneChild))
239 loneChild.x = outerMarginHorizontal
241 loneChild.x = centerOffset(root.width, loneChildWidth)
245 // we can easily calculate the positions of the left and right items,
246 // but if they are missing then correct the margins
247 if (leftItem != undefined){
248 leftItem.x = outerMarginHorizontal
252 if (rightItem != undefined){
253 rightItem.x = root.width - defaultHeightToolBar - outerMarginHorizontal
255 rightMargin = root.width
258 if (!childrenRemaining)
261 if (childrenRemaining == 1) {
262 var loneChild = children[firstRemainingIndex]
263 var loneChildWidth = buttonWidth(loneChild)
264 if (isButtonRow(loneChild)) {
265 // ButtonRow should have the override width (but it won't have
267 loneChildWidth = overrideChildWidth
269 // lone child is always centered, unless it's a long button row on
271 if (isButtonRow(loneChild) && loneChild.children.length >= 3
272 && ((leftItem == undefined) != (rightItem == undefined))) {
273 loneChild.x = (leftItem != undefined) ?
274 (leftMargin + innerSpacingButtonRowLong) : outerMarginButtonRowLong
276 loneChild.x = centerOffset(root.width, loneChildWidth)
278 } else if (childrenRemaining == 2 && isTextToolButton(children[firstRemainingIndex])) {
279 // text buttons are distributed around the center with a center spacing
280 var midPoint = Math.floor(root.width / 2.0)
281 var halfSpacing = Math.round(platformStyle.paddingLarge / 2.0)
282 children[firstRemainingIndex].x = midPoint - halfSpacing - buttonWidth(children[firstRemainingIndex])
283 children[firstRemainingIndex + 1].x = midPoint + halfSpacing
285 // icon buttons are deployed evenly in the remaining space,
286 // but we need to ensure that the spacings are integer values,
287 // and share the rounding error to ensure that they are centered
288 var remainingSpace = rightMargin - leftMargin
289 var spacingNotRounded = remainingSpace
290 for (var p = 0; p < childrenRemaining; p++) {
291 var nextChild = children[leftItem != undefined ? p + 1 : p]
292 spacingNotRounded -= buttonWidth(nextChild)
294 spacingNotRounded /= (childrenRemaining + 1)
295 var spacing = Math.floor(spacingNotRounded)
296 var totalRoundingError = (spacingNotRounded - spacing) * (childrenRemaining + 1)
297 var curPos = leftMargin + Math.floor(totalRoundingError / 2.0)
299 for (var p = 0; p < childrenRemaining; p++) {
300 var nextChild = children[leftItem != undefined ? p + 1 : p]
303 curPos += buttonWidth(nextChild)
309 Component.onCompleted: internal.layoutChildren()
310 onParentChanged: internal.layoutChildren()
311 onChildrenChanged: internal.layoutChildren()
312 onImplicitWidthChanged: internal.layoutChildren()
313 onImplicitHeightChanged: internal.layoutChildren()