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:
Topi Reinio 2014-09-24 14:37:46 +02:00 committed by Topi Reiniö
parent fccf0e2912
commit f83d12e0c2
6 changed files with 181 additions and 52 deletions

View File

@ -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 {

View File

@ -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()
}
}

View File

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

View File

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

View File

@ -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 = ""
}
}

View File

@ -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