QtQuick buttons under spinbox numbers

1.5k views Asked by At

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.

enter image description here

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
                    }
                }
            }
        }
    }
}
1

There are 1 answers

2
Mitch On BEST ANSWER

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 and letterSpacing of the font used in the SpinBox, 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.