The Code for QShark Attack
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QMortgage
import Qt.labs.qmlmodels
import loanHelperModule
import "components"
import "layouts"
import "tables"
import "js/common.js" as Common
GridLayout {
property int fontSize: 28
property string currencySymbol: "$"
id: appLayout
columns: 2
flow: GridLayout.LeftToRight
rowSpacing: 16
LoanHelper{
id: loanHelper
}
Inputs {
id: leftBox
Layout.columnSpan: 2
Layout.maximumWidth: 480
Layout.fillHeight: true
Layout.fillWidth: true
}
Outputs {
id: rightBox
Layout.maximumWidth: appLayout.width
Layout.columnSpan: 2
spacing: 16
}
CalculateButtonLayout {
id: buttonLayout
Layout.columnSpan: 2
Layout.maximumWidth: 480
Layout.fillHeight: true
Layout.fillWidth: true
visible: true
}
AmortTable{
id: tableLayout
Layout.columnSpan: 2
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
visible: false
loanHelper: loanHelper
}
Connections{
target: leftBox
function onCalculateInterest(loanAmount, loanRate, loanTerm){
loanHelper.calculateInterest();
tableLayout.visible = true
}
}
Connections{
target: buttonLayout
function onCalculateInterest(){
loanHelper.calculateInterest();
tableLayout.visible = true
}
}
Connections{
target: appLayout
function onWidthChanged(){
tableLayout.updateLayout(appLayout.width)
}
}
states: [
State {
when: appLayout.width <= 480
PropertyChanges {
target: leftBox
Layout.columnSpan: 2
}
PropertyChanges {
target: rightBox
Layout.columnSpan: 2
}
},
State {
when: appLayout.width > 480
PropertyChanges {
target: leftBox
Layout.columnSpan: 1
}
PropertyChanges {
target: leftBox.calculateButton
visible: true
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
PropertyChanges {
target: rightBox
Layout.columnSpan: 1
}
PropertyChanges {
target: buttonLayout
Layout.columnSpan: 1
visible: false
}
}
]
Binding{loanHelper.amount: leftBox.loanAmount}
Binding{loanHelper.rate: leftBox.loanRate}
Binding{loanHelper.term: leftBox.loanTerm}
Binding{rightBox.paymentAmount: Number(loanHelper.payment).toLocaleCurrencyString(Qt.locale(), currencySymbol)}
Binding{rightBox.principal: Number(loanHelper.amount).toLocaleCurrencyString(Qt.locale(), currencySymbol)}
Binding{rightBox.cost: Number(loanHelper.totalCost).toLocaleCurrencyString(Qt.locale(), currencySymbol)}
Binding{rightBox.interest: Number(loanHelper.totalInterest).toLocaleCurrencyString(Qt.locale(), currencySymbol)}
}
Screen01.ui.qml
This is where all the qml components come together to be rendered to the screen. The layout of the application is based on a grid that can resize depending on the width of the screen.
Custom Components
LoanHelper
This is a component created in C++. It is the components handling all the calculations and business logic.
Inputs
This is a custom qml component that is in charge of taking input from users. It renders a few textFields that check for valid input. If the user tries to enter something other than numbers, the input fields will not accept the text.
Outputs
This component is made out of labels that will display the results of the calculation after pressing the calculate button.
CalculateButtonLayout
This is a layout that contains the calculate button. The reason I put a single button inside a layout was for easier control of its positioning in the parent grid.
AmortTable
This component is the TableView that displays the amortization information and how long will it take to pay the debt.
Connections Section
There are 3 Connections elements that target leftBox, buttonLayout and appLayout respectively.
leftBox contains an input button that emits a calculateInterest signal when pressed.
This signal will in turn call on to the loanHelper's C++ calculateInterest function.
buttonLayout works in a similar fashion to leftBox signals. The reason to have it defined in two places is that depending on the size of the screen the button will become visible or invisible.
The appLayout connection is trigerred when the screen is being resized. If the screen becomes small, then the app will be rendered in a single column, but after there is some more room, then two columns will be used to render the UI.
States Section
The states allow to control how the UI will be drawn to the screen. If the screen width is less than 480 pixels, then the UI will be in rendered as a single column.
This effect is achieved by forcing the layout to take 2 columns of space instead of one. In short, it creates the illusion of it being a single column.
When the screen is bigger than 480 pixels, then layout elements will each use a single column, changing the positioningof the UI elements.
Bindings Section
The bindings I am using allow me to assign values to variables in a simple fashion. One advantage of using Bindings is that I don't have to manually update elements upon change. For example, when entering one value into one of the text fieds, the binding will update automatically.
InputsForm {
signal calculateInterest()
calculateButton.onClicked: {
if(!loanAmount || !loanRate || !loanTerm){
return
}
calculateInterest();
}
}
Inputs.qml
This file is the implementation file of the InputsForm. I use implementation files as a way to try to UI and logic separated. As I am instantiating an InputsForm component, I still get access to the UI elements, but I don't need to modify the InputsForm directly.
calculateButton.onClicked
The calculateButton is a button that has been defined in InputForms.ui.qml, here we are setting a clicked event to call on a custom qml signal calculateInterest(); When this signal is send, there will be a listener in the main screen waiting for the event, so that the calculations can be performed.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../components"
ColumnLayout {
property alias loanAmount: loanAmountField.amountTextField
property alias loanRate: rateAmountField.amountTextField
property alias loanTerm: termAmountField.amountTextField
property alias calculateButton: calculateButton
id: root
spacing: 16
Label {
id: appTitleLabel
text: qsTr("Loan Calculator")
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true
font {
pixelSize: 32
family: "Roboto"
}
}
NumberInput {
id: loanAmountField
amountLabelText: qsTr("Loan Amount")
}
NumberInput {
id: termAmountField
amountLabelText: qsTr("Loan Term")
placeholderText: qsTr("Months")
}
NumberInput {
id: rateAmountField
amountLabelText: qsTr("Loan Rate")
}
Button {
id: calculateButton
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
text: qsTr("Calculate")
Layout.fillWidth: true
Layout.maximumWidth: root.width / 3
visible: false
font {
pixelSize: fontSize
family: "Roboto"
}
}
}
InputsForm.ui.qml
This file is the implementation file of the InputsForm. I use implementation files as a way to try to UI and logic separated. As I am instantiating an InputsForm component, I still get access to the UI elements, but I don't need to modify the InputsForm directly.
property alias loanAmount