QML-EnhancedCalcExample

Contents

About the Enhanced Calc Example

See Youtube video for demo of the app.


Source code available from qml examples garage page

Calculator.qml

The file below defines the layouts of the calculator. It uses the button below to draw nice looking button shape, that has glow effect.

import Qt 4.6

Rectangle {
   width: parent.width; height: 480; color: palette.window
   anchors.fill: parent;
   SystemPalette { id: palette }
   Script { source: "calculator.js" }

We use the system palette for colors and the calculator.js file for all of the calculation logic.

   Column {
       x: 2; spacing: 2;
       Row {
         id: numericOperations
         spacing: 2

We use column layout for the numeric operations (the field that shows the calcuations) layout, with small intendation (2 px) and small padding.

         Rectangle {
             id: container
             width: 400; height: 55
             border.color: palette.dark; color: palette.base
 
             Text {
                 id: curNum
                 font.bold: true; font.pointSize: 22
                 color: palette.text
                 anchors.right: container.right
                 anchors.rightMargin: 5
                 anchors.verticalCenter: container.verticalCenter
             }
 
             Text {
                 id: currentOperation
                 color: palette.text
                 font.bold: true; font.pointSize: 26
                 anchors.left: container.left
                 anchors.leftMargin: 5
                 anchors.verticalCenter: container.verticalCenter
             }
             
         }
         CalcButton { operation: "Bksp"; id: bksp; opacity: 0 }
       }

The Text area for the calculations, surrounded by dark border, with two text areas: the operation and the value. The operation (e.g. "*" to signal multiplication, is anchored left, and the calculation is anchored to the right.

       Item {
           height:460; width: 420;

           Item {
               id: basicButtons
               x: 55; width: 460; height: 400

The above defines the compound item of the basicButtons. The actual buttons are defined below. We intend by default this with 55 so that the buttons in basic layout don't feel like they are in the left side of the screen. This padding is changed in the transition to advanced layout.

               Row {
                   id: commonOperations
                   spacing: 0
                   height:150
                       
                   CalcButton { 
                       operation: "Advanced"
                       id: advancedCheckBox
                       width: 160
                       toggable: true
                   }
                   CalcButton { operation: "C"; id: c; }
                   CalcButton { operation: "AC"; id: ac;}
                   
               }

Common operations to clean the calcuations are on one row, along with speacial wider toggle button for advanced mode.


               Grid {
                   id: numKeypad; y:60; spacing: 0; columns: 3
                   CalcButton { operation: "7" }
                   CalcButton { operation: "8" }
                   CalcButton { operation: "9" }
                   CalcButton { operation: "4" }
                   CalcButton { operation: "5" }
                   CalcButton { operation: "6" }
                   CalcButton { operation: "1" }
                   CalcButton { operation: "2" }
                   CalcButton { operation: "3" }
                   CalcButton { operation: "0" }
                   CalcButton { operation: "." }
                   CalcButton { operation: "=" }                    
               }

Normal numbers and . and = keys are in grid layout

               Column {
                   id: simpleOperations
                   x: 240;  y: 60; spacing: 0
                   CalcButton { operation: "x" }
                   CalcButton { operation: "/" }
                   CalcButton { operation: "-" }
                   CalcButton { operation: "+" }
               }
           }

Basic calculation operations are in one column (for easy layouting).

           Grid {
               id: advancedButtons
               x: 250; spacing: 0; columns: 2; opacity: 0
               CalcButton { operation: "Abs" }
               CalcButton { operation: "Int" }
               CalcButton { operation: "MC" }
               CalcButton { operation: "Sqrt" }
               CalcButton { operation: "MR" }
               CalcButton { operation: "^2" }
               CalcButton { operation: "MS" }
               CalcButton { operation: "1/x" }
               CalcButton { operation: "M+" }
               CalcButton { operation: "+/-" }
           }
           Row {
             id: trigonometryOperations
             spacing: 0;opacity: 0;y: 280;x:40
             CalcButton { operation: "Sin" }
             CalcButton { operation: "Cos" }
             CalcButton { operation: "Tan" }
             CalcButton { operation: "Log" }
             CalcButton { operation: "e^x" }
             CalcButton { operation: "x^y" }
           }

Advanced operations are in 2x5 grid and trigonometry operations are on one row. Easy positioning of those compound elements.

       }
   }
   states: State {
       name: "Advanced"; when: advancedCheckBox.toggled == true
       PropertyChanges { target: basicButtons; x: 0 }
       PropertyChanges { target: bksp; opacity: 1 }
       PropertyChanges { target: commonOperations; x: 0;  }
       PropertyChanges { target: advancedButtons; x: 320; opacity: 1 }
       PropertyChanges { target: trigonometryOperations; x:0; y: 300; opacity: 1 }
       
   }

States definition for advanced mode. Basic mode doesn't need to be defined, as we have defined that already as the default mode of operation. For advanced state, we modify the bacspace to be visible, we move basic buttons a bit, we enable advanced and trigonometry buttons and move them a bit for visual candy.


   transitions: Transition {
       NumberAnimation { matchProperties: "x,y,width"; easing: "easeOutBounce"; duration: 500 }
       NumberAnimation { matchProperties: "opacity"; easing: "easeInOutQuad"; duration: 500 }
   }
}

Above defines the animations for the transition to and from advanced mode.


CalcButton

The code below makes a button that has a glow effect when tapped on.

import Qt 4.6


Rectangle {
   property alias operation: label.text
   property bool toggable: false
   property bool toggled: false
   signal clicked
   id: button; width: 80; height: 60
   color: "black"      
   BorderImage {
     id: img
     width: parent.width
     height: parent.height
     border.left: 14
     border.right: 14      
     source:"Button_h.png"
     transformOrigin: Item.Center  
     
   }
   
   BorderImage {
     id: bgimg
     width: parent.width
     height: parent.height
     border.left: 14
     border.right: 14      
     source:"Button.png"  
   }
   Text { id: label; anchors.centerIn: parent; color: palette.buttonText }

On the above, we define the bg of the button and the highlight of the button. THe highlight is img and the bg image is, well, bgimg.


   MouseRegion {
       id: clickRegion
       anchors.fill: parent
       onClicked: {
           doOp(operation);
           button.clicked();
           if (!button.toggable) return;
           button.toggled ? button.toggled = false : button.toggled = true
       }
   }

Above handles the mouse clicks.

   states: [
       State {
           name: "Pressed"; when: clickRegion.pressed == true
           PropertyChanges { target: img; scale: 2.0 }
           PropertyChanges { target: button; z: 1 }
           PropertyChanges { target: button.parent; z: 1 }
           PropertyChanges { target: button.parent.parent; z: 1 }
           PropertyChanges { target: img; z: 1.1 }
           PropertyChanges { target: img; opacity: 0 }
           
       }
   ]

Above defines state "pressed", which triggers the highlight to come up all the way, moves all of the parent objects also up in the stack and sets target opacity for the highlight to be 0.

       transitions: Transition {
           NumberAnimation { matchProperties: "z,scale"; easing: "easeOutExpo"; duration: 200 }
           NumberAnimation { matchProperties: "opacity"; easing: "easeInQuad"; duration: 300 }
           
       }

Above defines the transitions for the button.

}


Calculator.js

I won't comment the javascript code. It's self explanatory.


var curVal = 0; var memory = 0; var lastOp = ""; var timer = 0;

function disabled(op) {

   if (op == "." && curNum.text.toString().search(/\./) != -1) {
       return true;
   } else if (op == "Sqrt" &&  curNum.text.toString().search(/-/) != -1) {
       return true;
   } else {
       return false;
   }

}

function doOp(op) {

   if (disabled(op)) {
       return;
   }
   if (op.toString().length==1 && ((op >= "0" && op <= "9") || op==".") ) {
       if (curNum.text.toString().length >= 14)
           return; // No arbitrary length numbers
       if (lastOp.toString().length == 1 && ((lastOp >= "0" && lastOp <= "9") || lastOp==".") ) {
           curNum.text = curNum.text + op.toString();
       } else {
           curNum.text = op;
       }
       lastOp = op;
       return;
   }
   lastOp = op;
   // Pending operations
   if (currentOperation.text == "+") {
       curNum.text = Number(curNum.text.valueOf()) + Number(curVal.valueOf());
   } else if (currentOperation.text == "-") {
       curNum.text = Number(curVal) - Number(curNum.text.valueOf());
   } else if (currentOperation.text == "x") {
       curNum.text = Number(curVal) * Number(curNum.text.valueOf());
   } else if (currentOperation.text == "x^y") {
       curNum.text = (Math.pow(Number(curVal),curNum.text.valueOf())).toString();
   } else if (currentOperation.text == "/") {
       curNum.text = Number(Number(curVal) / Number(curNum.text.valueOf())).toString();
   } else if (currentOperation.text == "=") {
   }
   if (op == "+" || op == "-" || op == "x" || op=="x^y" || op == "/") {
       currentOperation.text = op;
       curVal = curNum.text.valueOf();
       return;
   }
   curVal = 0;
   currentOperation.text = "";
   // Immediate operations
   if (op == "1/x") { // reciprocal
       curNum.text = (1 / curNum.text.valueOf()).toString();
   } else if (op == "^2") { // squared
       curNum.text = (curNum.text.valueOf() * curNum.text.valueOf()).toString();
   } else if (op == "Abs") {
       curNum.text = (Math.abs(curNum.text.valueOf())).toString();
   } else if (op == "Sin") {
       curNum.text = (Math.sin(curNum.text.valueOf())).toString();
   } else if (op == "Cos") {
       curNum.text = (Math.cos(curNum.text.valueOf())).toString();
   } else if (op == "Tan") {
       curNum.text = (Math.tan(curNum.text.valueOf())).toString();
   } else if (op == "Log") {
       curNum.text = (Math.log(curNum.text.valueOf())).toString();
   } else if (op == "e^x") {
       curNum.text = (Math.exp(curNum.text.valueOf())).toString();
   } else if (op == "Int") {
       curNum.text = (Math.floor(curNum.text.valueOf())).toString();
   } else if (op == "+/-") { // plus/minus
       curNum.text = (curNum.text.valueOf() * -1).toString();
   } else if (op == "Sqrt") { // square root
       curNum.text = (Math.sqrt(curNum.text.valueOf())).toString();
   } else if (op == "MC") { // memory clear
       memory = 0;
   } else if (op == "M+") { // memory increment
       memory += curNum.text.valueOf();
   } else if (op == "MR") { // memory recall
       curNum.text = memory.toString();
   } else if (op == "MS") { // memory set
       memory = curNum.text.valueOf();
   } else if (op == "Bksp") {
       curNum.text = curNum.text.toString().slice(0, -1);
   } else if (op == "C") {
       curNum.text = "0";
   } else if (op == "AC") {
       curVal = 0;
       memory = 0;
       lastOp = "";
       curNum.text ="0";
   }

}