Update the Calqltr demo visuals and engine logic
- Add logic for displaying the calculation result in the best available precision, determined by the display width - Display 'ERROR' when the result cannot be displayed - Animate the number pad button colors to react to presses and visually disable them when pressing the button has no effect - Fix issues in calculator.js logic - Update documentation accordingly Task-number: QTBUG-41253 Change-Id: Ibed7b8218ea4cd074b8f9b90d9bb4e3ea6b25ba2 Reviewed-by: Johanna Äijälä <johanna.aijala@digia.com> Reviewed-by: Leena Miettinen <riitta-leena.miettinen@digia.com> Reviewed-by: Venugopal Shivashankar <venugopal.shivashankar@digia.com>
This commit is contained in:
parent
fccf0e2912
commit
f83d12e0c2
|
@ -53,13 +53,22 @@ Rectangle {
|
|||
onWidthChanged: controller.reload()
|
||||
onHeightChanged: controller.reload()
|
||||
|
||||
function operatorPressed(operator) { CalcEngine.operatorPressed(operator) }
|
||||
function digitPressed(digit) { CalcEngine.digitPressed(digit) }
|
||||
function operatorPressed(operator) {
|
||||
CalcEngine.operatorPressed(operator)
|
||||
numPad.buttonPressed()
|
||||
}
|
||||
function digitPressed(digit) {
|
||||
CalcEngine.digitPressed(digit)
|
||||
numPad.buttonPressed()
|
||||
}
|
||||
function isButtonDisabled(op) {
|
||||
return CalcEngine.disabled(op)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: pad
|
||||
width: 180
|
||||
NumberPad { y: 10; anchors.horizontalCenter: parent.horizontalCenter }
|
||||
NumberPad { id: numPad; y: 10; anchors.horizontalCenter: parent.horizontalCenter }
|
||||
}
|
||||
|
||||
AnimationController {
|
||||
|
|
|
@ -41,12 +41,13 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Item {
|
||||
id: button
|
||||
property alias text: textItem.text
|
||||
property alias color: textItem.color
|
||||
property color color: "#eceeea"
|
||||
|
||||
property bool operator: false
|
||||
|
||||
signal clicked
|
||||
property bool dimmable: false
|
||||
property bool dimmed: false
|
||||
|
||||
width: 30
|
||||
height: 50
|
||||
|
@ -56,7 +57,18 @@ Item {
|
|||
font.pixelSize: 48
|
||||
wrapMode: Text.WordWrap
|
||||
lineHeight: 0.75
|
||||
color: "white"
|
||||
color: (dimmable && dimmed) ? Qt.darker(button.color) : button.color
|
||||
Behavior on color { ColorAnimation { duration: 120; easing.type: Easing.OutElastic} }
|
||||
states: [
|
||||
State {
|
||||
name: "pressed"
|
||||
when: mouse.pressed && !dimmed
|
||||
PropertyChanges {
|
||||
target: textItem
|
||||
color: Qt.lighter(button.color)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
@ -70,4 +82,13 @@ Item {
|
|||
window.digitPressed(parent.text)
|
||||
}
|
||||
}
|
||||
|
||||
function updateDimmed() {
|
||||
dimmed = window.isButtonDisabled(button.text)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
numPad.buttonPressed.connect(updateDimmed)
|
||||
updateDimmed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,16 @@
|
|||
****************************************************************************/
|
||||
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Window 2.0
|
||||
|
||||
Item {
|
||||
id: display
|
||||
property real fontSize: Math.floor(Screen.pixelDensity * 5.0)
|
||||
property bool enteringDigits: false
|
||||
property int maxDigits: (width / fontSize) + 1
|
||||
property string displayedOperand
|
||||
property string errorString: qsTr("ERROR")
|
||||
property bool isError: displayedOperand === errorString
|
||||
|
||||
function displayOperator(operator)
|
||||
{
|
||||
|
@ -53,7 +59,8 @@ Item {
|
|||
|
||||
function newLine(operator, operand)
|
||||
{
|
||||
listView.model.append({ "operator": operator, "operand": operand })
|
||||
displayedOperand = displayNumber(operand)
|
||||
listView.model.append({ "operator": operator, "operand": displayedOperand })
|
||||
enteringDigits = false
|
||||
listView.positionViewAtEnd()
|
||||
}
|
||||
|
@ -68,8 +75,16 @@ Item {
|
|||
listView.positionViewAtEnd()
|
||||
}
|
||||
|
||||
function setDigit(digit)
|
||||
{
|
||||
var i = listView.model.count - 1;
|
||||
listView.model.get(i).operand = digit;
|
||||
listView.positionViewAtEnd()
|
||||
}
|
||||
|
||||
function clear()
|
||||
{
|
||||
displayedOperand = ""
|
||||
if (enteringDigits) {
|
||||
var i = listView.model.count - 1
|
||||
if (i >= 0)
|
||||
|
@ -78,6 +93,42 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Returns a string representation of a number that fits in
|
||||
// display.maxDigits characters, trying to keep as much precision
|
||||
// as possible. If the number cannot be displayed, returns an
|
||||
// error string.
|
||||
function displayNumber(num) {
|
||||
if (typeof(num) != "number")
|
||||
return errorString;
|
||||
|
||||
var intNum = parseInt(num);
|
||||
var intLen = intNum.toString().length;
|
||||
|
||||
// Do not count the minus sign as a digit
|
||||
var maxLen = num < 0 ? maxDigits + 1 : maxDigits;
|
||||
|
||||
if (num.toString().length <= maxLen) {
|
||||
if (isFinite(num))
|
||||
return num.toString();
|
||||
return errorString;
|
||||
}
|
||||
|
||||
// Integer part of the number is too long - try
|
||||
// an exponential notation
|
||||
if (intNum == num || intLen > maxLen - 3) {
|
||||
var expVal = num.toExponential(maxDigits - 6).toString();
|
||||
if (expVal.length <= maxLen)
|
||||
return expVal;
|
||||
}
|
||||
|
||||
// Try a float presentation with fixed number of digits
|
||||
var floatStr = parseFloat(num).toFixed(maxDigits - intLen - 1).toString();
|
||||
if (floatStr.length <= maxLen)
|
||||
return floatStr;
|
||||
|
||||
return errorString;
|
||||
}
|
||||
|
||||
Item {
|
||||
id: theItem
|
||||
width: parent.width + 32
|
||||
|
@ -121,16 +172,16 @@ Item {
|
|||
width: parent.width
|
||||
Text {
|
||||
id: operator
|
||||
x: 8
|
||||
font.pixelSize: 18
|
||||
x: 6
|
||||
font.pixelSize: display.fontSize
|
||||
color: "#6da43d"
|
||||
text: model.operator
|
||||
}
|
||||
Text {
|
||||
id: operand
|
||||
font.pixelSize: 18
|
||||
font.pixelSize: display.fontSize
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 26
|
||||
anchors.rightMargin: 22
|
||||
text: model.operand
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ Grid {
|
|||
columnSpacing: 32
|
||||
rowSpacing: 16
|
||||
|
||||
signal buttonPressed
|
||||
|
||||
Button { text: "7" }
|
||||
Button { text: "8" }
|
||||
Button { text: "9" }
|
||||
|
@ -55,15 +57,15 @@ Grid {
|
|||
Button { text: "2" }
|
||||
Button { text: "3" }
|
||||
Button { text: "0" }
|
||||
Button { text: "." }
|
||||
Button { text: "."; dimmable: true }
|
||||
Button { text: " " }
|
||||
Button { text: "±"; color: "#6da43d"; operator: true }
|
||||
Button { text: "−"; color: "#6da43d"; operator: true }
|
||||
Button { text: "+"; color: "#6da43d"; operator: true }
|
||||
Button { text: "√"; color: "#6da43d"; operator: true }
|
||||
Button { text: "÷"; color: "#6da43d"; operator: true }
|
||||
Button { text: "×"; color: "#6da43d"; operator: true }
|
||||
Button { text: "±"; color: "#6da43d"; operator: true; dimmable: true }
|
||||
Button { text: "−"; color: "#6da43d"; operator: true; dimmable: true }
|
||||
Button { text: "+"; color: "#6da43d"; operator: true; dimmable: true }
|
||||
Button { text: "√"; color: "#6da43d"; operator: true; dimmable: true }
|
||||
Button { text: "÷"; color: "#6da43d"; operator: true; dimmable: true }
|
||||
Button { text: "×"; color: "#6da43d"; operator: true; dimmable: true }
|
||||
Button { text: "C"; color: "#6da43d"; operator: true }
|
||||
Button { text: " "; color: "#6da43d"; operator: true }
|
||||
Button { text: "="; color: "#6da43d"; operator: true }
|
||||
Button { text: "="; color: "#6da43d"; operator: true; dimmable: true }
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
var curVal = 0
|
||||
var memory = 0
|
||||
var lastOp = ""
|
||||
|
@ -46,9 +45,13 @@ var previousOperator = ""
|
|||
var digits = ""
|
||||
|
||||
function disabled(op) {
|
||||
if (op == "." && digits.toString().search(/\./) != -1) {
|
||||
if (digits == "" && !((op >= "0" && op <= "9") || op == "."))
|
||||
return true
|
||||
} else if (op == window.squareRoot && digits.toString().search(/-/) != -1) {
|
||||
else if (op == '=' && previousOperator.length != 1)
|
||||
return true
|
||||
else if (op == "." && digits.toString().search(/\./) != -1) {
|
||||
return true
|
||||
} else if (op == "√" && digits.toString().search(/-/) != -1) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
@ -59,7 +62,7 @@ function digitPressed(op)
|
|||
{
|
||||
if (disabled(op))
|
||||
return
|
||||
if (digits.toString().length >= 8)
|
||||
if (digits.toString().length >= display.maxDigits)
|
||||
return
|
||||
if (lastOp.toString().length == 1 && ((lastOp >= "0" && lastOp <= "9") || lastOp == ".") ) {
|
||||
digits = digits + op.toString()
|
||||
|
@ -77,6 +80,12 @@ function operatorPressed(op)
|
|||
return
|
||||
lastOp = op
|
||||
|
||||
if (op == "±") {
|
||||
digits = Number(digits.valueOf() * -1)
|
||||
display.setDigit(display.displayNumber(digits))
|
||||
return
|
||||
}
|
||||
|
||||
if (previousOperator == "+") {
|
||||
digits = Number(digits.valueOf()) + Number(curVal.valueOf())
|
||||
} else if (previousOperator == "−") {
|
||||
|
@ -85,7 +94,6 @@ function operatorPressed(op)
|
|||
digits = Number(curVal) * Number(digits.valueOf())
|
||||
} else if (previousOperator == "÷") {
|
||||
digits = Number(curVal) / Number(digits.valueOf())
|
||||
} else if (previousOperator == "=") {
|
||||
}
|
||||
|
||||
if (op == "+" || op == "−" || op == "×" || op == "÷") {
|
||||
|
@ -97,10 +105,7 @@ function operatorPressed(op)
|
|||
}
|
||||
|
||||
if (op == "=") {
|
||||
if (digits.toString().length >= 9)
|
||||
digits = digits.toExponential(2)
|
||||
|
||||
display.newLine("=", digits.toString())
|
||||
display.newLine("=", digits.valueOf())
|
||||
}
|
||||
|
||||
curVal = 0
|
||||
|
@ -114,12 +119,9 @@ function operatorPressed(op)
|
|||
digits = (Math.abs(digits.valueOf())).toString()
|
||||
} else if (op == "Int") {
|
||||
digits = (Math.floor(digits.valueOf())).toString()
|
||||
} else if (op == "±") {
|
||||
digits = (digits.valueOf() * -1).toString()
|
||||
display.clear()
|
||||
display.appendDigit(digits)
|
||||
} else if (op == "√") {
|
||||
digits = (Math.sqrt(digits.valueOf())).toString()
|
||||
digits = Number(Math.sqrt(digits.valueOf()))
|
||||
display.newLine("√", digits.valueOf())
|
||||
} else if (op == "mc") {
|
||||
memory = 0;
|
||||
} else if (op == "m+") {
|
||||
|
@ -134,17 +136,16 @@ function operatorPressed(op)
|
|||
display.appendDigit(digits)
|
||||
} else if (op == "Off") {
|
||||
Qt.quit();
|
||||
} else if (op == "C") {
|
||||
}
|
||||
|
||||
// Reset the state on 'C' operator or after
|
||||
// an error occurred
|
||||
if (op == "C" || display.isError) {
|
||||
display.clear()
|
||||
curVal = 0
|
||||
memory = 0
|
||||
lastOp = ""
|
||||
digits ="0"
|
||||
} else if (op == "AC") {
|
||||
curVal = 0
|
||||
memory = 0
|
||||
lastOp = ""
|
||||
digits ="0"
|
||||
digits = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,6 @@
|
|||
\li Button.qml
|
||||
\li Display.qml
|
||||
\li NumberPad.qml
|
||||
\li StyleLabel.qml
|
||||
\endlist
|
||||
|
||||
To use the custom types, we add an import statement to the main QML file,
|
||||
|
@ -70,7 +69,7 @@
|
|||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
Further, we use the Button type in the NumberPad type to create the
|
||||
Further, we use the Button type in the \c NumberPad type to create the
|
||||
calculator buttons. Button.qml specifies the basic properties for a
|
||||
button that we can modify for each button instance in NumberPad.qml. For the
|
||||
digit and separator buttons, we additionally specify the text property using
|
||||
|
@ -86,6 +85,11 @@
|
|||
\skipto Grid
|
||||
\printuntil /^\}/
|
||||
|
||||
Some of the buttons also have a \c dimmable property set, meaning that they
|
||||
can be visually disabled (dimmed) whenever the calculator engine does not
|
||||
accept input from that button. As an example, the button for square root
|
||||
operator is dimmed for negative values.
|
||||
|
||||
\section1 Animating Components
|
||||
|
||||
We use the Display type to display calculations. In Display.qml, we use
|
||||
|
@ -100,7 +104,11 @@
|
|||
is the id of our AnimationController:
|
||||
|
||||
\quotefromfile demos/calqlatr/calqlatr.qml
|
||||
\skipto onPressed
|
||||
\skipto MouseArea
|
||||
\printuntil {
|
||||
\dots 12
|
||||
\skipto onReleased
|
||||
\printuntil }
|
||||
\printuntil }
|
||||
|
||||
Unlike other QML animation types, AnimationController is not driven by
|
||||
|
@ -123,26 +131,63 @@
|
|||
We use the easing curve of the type \c Easing.InOutQuad to accelerate the
|
||||
motion until halfway and then decelerate it.
|
||||
|
||||
In Button.qml, the text colors of the number pad buttons are also animated.
|
||||
|
||||
\quotefromfile demos/calqlatr/content/Button.qml
|
||||
\skipto Text
|
||||
\printuntil id:
|
||||
\dots 8
|
||||
\skipto color:
|
||||
\printuntil ]
|
||||
\printuntil }
|
||||
|
||||
We use \l {QtQml::Qt::darker()}{Qt.darker()} to darken the color when the
|
||||
button is dimmed, and \l {QtQml::Qt::lighter()}{Qt.lighter()} to \e {light up}
|
||||
the button when pressed. The latter is done in a separate \l [QML] {State}
|
||||
{state} called \e "pressed", which activates when the \c pressed
|
||||
property of the button's MouseArea is set.
|
||||
|
||||
The color changes are animated by defining a \l Behavior on the \c color
|
||||
property.
|
||||
|
||||
In order to dynamically change the \c dimmed property of all the buttons
|
||||
of the \c NumberPad, we connect its \c buttonPressed signal to the
|
||||
\c Button's \c updateDimmed() function in Button.qml:
|
||||
|
||||
\quotefromfile demos/calqlatr/content/Button.qml
|
||||
\skipto function updateDimmed() {
|
||||
\printuntil buttonPressed.connect
|
||||
\printuntil }
|
||||
|
||||
This way, when a button is pressed, all buttons on the \c NumPad
|
||||
receive a \c buttonPressed signal and are activated or deactivated
|
||||
according to the state of the calculator engine.
|
||||
|
||||
\section1 Performing Calculations
|
||||
|
||||
The calculator.js file contains definitions for the functions to execute
|
||||
when users press the digit and operator buttons. To use the functions, we
|
||||
The calculator.js file defines our calculator engine. It contains variables
|
||||
to store the calculator state, and functions that are called when the
|
||||
user presses the digit and operator buttons. To use the engine, we
|
||||
import calculator.js in the calqlatr.qml file as \c CalcEngine:
|
||||
|
||||
\code
|
||||
import "content/calculator.js" as CalcEngine
|
||||
\endcode
|
||||
|
||||
We can then declare the functions to execute depending on whether the
|
||||
operator property for a button is set to \c true in NumberPad.qml:
|
||||
Importing the engine creates a new instance of it. Therefore, we only do it
|
||||
in the main QML file, \c calqlatr.qml. The root item defined in this file
|
||||
contains helper functions that allow other types to access the calculator
|
||||
engine:
|
||||
|
||||
\quotefromfile demos/calqlatr/calqlatr.qml
|
||||
\skipto operatorPressed
|
||||
\printuntil digitPressed
|
||||
\printuntil CalcEngine.disabled
|
||||
\printuntil }
|
||||
|
||||
When users press a digit or operator, the text from the digit appears on the
|
||||
display. When they press the equals operator (=), the appropriate
|
||||
calculation is performed, and the results appear on the display.
|
||||
When users press a digit, the text from the digit appears on the
|
||||
display. When they press an operator, the appropriate calculation is
|
||||
performed, and the result can be displayed using the equals (=) operator.
|
||||
The clear (C) operator resets the calculator engine.
|
||||
|
||||
\section1 List of Files
|
||||
|
||||
|
|
Loading…
Reference in New Issue