I want to have a spin box with little buttons under each number. Depending on which button is checked, the spinbox's step size will adjust according. For example, if the first button left of the decimal is selected the step size is 1 and if the first button right of the decimal is selected the step size is 0.1.
I've figure out how to get all the behavior I want and I've combined the spin box with appropriate buttons.
The next step is to size and position the buttons so they line up directly under the respective digit location. At a very low level this would involve getting the font details, determining the size and location of each digit, and then sizing and positioning the buttons accordingly. Is this something I can achieve in qml?
Here is the qml for the above button:
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Item {
id: root
property int decimalsLeft: 2
property int decimalsRight: 3
property alias maximumValue: spinBox.maximumValue
property alias minimumValue: spinBox.minimumValue
property alias value: spinBox.value
width: layout.width
height: layout.height
ColumnLayout {
id: layout
SpinBox {
id: spinBox
Layout.fillWidth: true
width: parent.width
decimals: root.decimalsRight
horizontalAlignment: Qt.AlignRight
maximumValue: 1e3
minimumValue: -1e3
}
// Make row of buttons under number to select
// step size
ExclusiveGroup {id: decimalSelector}
RowLayout{
Repeater {
id: rdButtons
RadioButton {
exclusiveGroup: decimalSelector
onCheckedChanged: {
if(!checked) {
return
}
var relIndex = decimalsLeft-index-1
if(relIndex == -1) {// decimal location so don't do anything
return
}
if(relIndex < 0) {
relIndex += 1
}
spinBox.stepSize = Math.pow(10, relIndex)
}
}
onModelChanged: {
for(var i=0; i<rdButtons.count; i++) {
rdButtons.itemAt(i).enabled = true
}
// disable selector associated with the decimal point
if(rdButtons.count > decimalsLeft) {
rdButtons.itemAt(decimalsLeft).enabled = false
}
// now find which selector matches our current step size
var log = Math.round(Math.log(spinBox.stepSize) / Math.LN10)
var idx = -1
if(log >= 0) {
idx = decimalsLeft-log-1
}
else {
idx = decimalsLeft-log
}
// an finally apply the selection
if(rdButtons.count == 0) {
return
}
else if(idx < 0) {
rdButtons.itemAt(0).checked = true
}
else if(idx >= rdButtons.count) {
if(idx == decimalsLeft+1) {
rdButtons.itemAt(rdButtons.count-2).checked = true
}
else {
rdButtons.itemAt(rdButtons.count-1).checked = true
}
}
else {
rdButtons.itemAt(idx).checked = true
}
}
model: decimalsLeft + decimalsRight + 1
Component.onCompleted: {
if(decimalsLeft < rdButtons.count) {
rdButtons.itemAt(decimalsLeft).enabled = false
}
if(decimalsLeft > 0) {
rdButtons.itemAt(decimalsLeft-1).checked = true
}
}
}
}
}
}
You can get font information in QML with FontMetrics. If you know that the characters in the font you're using are all around the same size, you can use averageCharacterWidth, otherwise you can use the boundingRect property of TextMetrics with each individual character of the string. There are a few alternatives in terms of properties/functions in these types; check the documentation.
You will probably need to increase the
pixelSize
andletterSpacing
of the font used in theSpinBox
, because they're quite close together by default, which doesn't leave the radio buttons with much room.Access to the size of the style delegate instances (incrementControl/decrementControl) will be necessary to find the position at which the text is right aligned. This means you'll need to create your own SpinBoxStyle so that you can give these instances IDs in order to access them elsewhere.
However, the controls can't cater for every problem, and this one is quite specific. At this point, it may just be easier to create your own
DecimalSpinBox
component. You then have total control over where the characters and radio buttons are positioned. As soon as you create your own style (the previous approach mentioned above), you lose the native styling anyway.