457 lines
20 KiB
QML
457 lines
20 KiB
QML
|
import QtQuick 2.6
|
||
|
import Sailfish.Silica 1.0
|
||
|
import Sailfish.Pickers 1.0
|
||
|
import FileIO 1.0
|
||
|
import Nemo.Notifications 1.0
|
||
|
|
||
|
|
||
|
|
||
|
Dialog {
|
||
|
id: pageSettings
|
||
|
|
||
|
property int showMaintenanceButtonsCounter : 0
|
||
|
property string notificationString : ""
|
||
|
//property int oldProjectIndex
|
||
|
|
||
|
allowedOrientations: Orientation.Portrait
|
||
|
backNavigation: (bannerAddProject.opacity < 1) // && (updateEvenWhenCanceled === false)
|
||
|
forwardNavigation: (bannerAddProject.opacity < 1)
|
||
|
onOpened: {
|
||
|
//console.log("old project index = " + activeProjectID_unixtime)
|
||
|
showMaintenanceButtonsCounter = 0
|
||
|
updateEvenWhenCanceled = false
|
||
|
idComboboxProject.currentIndex = 0
|
||
|
for (var j = 0; j < listModel_allProjects.count ; j++) {
|
||
|
if (Number(listModel_allProjects.get(j).project_id_timestamp) === Number(storageItem.getSettings("activeProjectID_unixtime", 0))) {
|
||
|
idComboboxProject.currentIndex = j
|
||
|
}
|
||
|
}
|
||
|
idComboboxSortingExpenses.currentIndex = Number(storageItem.getSettings("sortOrderExpensesIndex", 0)) // 0=descending, 1=ascending
|
||
|
idComboboxExchangeRateMode.currentIndex = Number(storageItem.getSettings("exchangeRateModeIndex", 0)) // 0=collective, 1=individual
|
||
|
idComboboxShowInteractiveScrollbar.currentIndex = Number(storageItem.getSettings("interativeScrollbarMode", 0)) //0=standard, 1=interactive
|
||
|
notificationString = ""
|
||
|
}
|
||
|
onDone: {
|
||
|
if (result == DialogResult.Accepted) {
|
||
|
writeDB_Settings()
|
||
|
}
|
||
|
}
|
||
|
onRejected: {
|
||
|
// in certain cases reload list even on cancel: if project was cleared, delted or created
|
||
|
// then use previous activeProjectID_unixtime instead of the new one from dropdown menu
|
||
|
if (updateEvenWhenCanceled === true) {
|
||
|
loadActiveProjectInfos_FromDB(activeProjectID_unixtime)
|
||
|
updateEvenWhenCanceled = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BannerAddProject {
|
||
|
id: bannerAddProject
|
||
|
}
|
||
|
Banner2ButtonsChoice {
|
||
|
id: banner2ButtonsChoice
|
||
|
}
|
||
|
Notification {
|
||
|
id: notificationBackup
|
||
|
expireTimeout: 4000
|
||
|
//appName: qsTr("Expenditure")
|
||
|
//icon: "image://theme/icon-lock-warning"
|
||
|
|
||
|
function showSmall(message) {
|
||
|
replacesId = 0
|
||
|
previewSummary = ""
|
||
|
previewBody = message
|
||
|
publish()
|
||
|
}
|
||
|
function showBig(title, message) {
|
||
|
replacesId = 0
|
||
|
previewSummary = title
|
||
|
previewBody = message
|
||
|
publish()
|
||
|
}
|
||
|
}
|
||
|
RemorsePopup {
|
||
|
z: 10
|
||
|
id: remorse_clearExchangeRatesDB
|
||
|
}
|
||
|
RemorsePopup {
|
||
|
z: 10
|
||
|
id: remorse_restoreBackupFile
|
||
|
}
|
||
|
ListModel {
|
||
|
id: listModel_tempProjectExpenses
|
||
|
}
|
||
|
Component {
|
||
|
id: idFolderPickerPage
|
||
|
|
||
|
FolderPickerPage {
|
||
|
dialogTitle: qsTr("Backup to")
|
||
|
onSelectedPathChanged: {
|
||
|
backupProjectExpenses( selectedPath )
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Component {
|
||
|
id: idFilePickerPage
|
||
|
|
||
|
FilePickerPage {
|
||
|
title: qsTr("Restore backup file")
|
||
|
nameFilters: [ '*.csv' ]
|
||
|
onSelectedContentPropertiesChanged: {
|
||
|
var selectedPath = selectedContentProperties.filePath
|
||
|
idTextFileBackup.source = selectedPath
|
||
|
var tempProjectIndex_File = ((idTextFileBackup.text).split("\n"))[0]
|
||
|
var tempProjectIndex = listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp
|
||
|
var headlineText = qsTr("Restore backup - choose action")
|
||
|
var otherText = qsTr("Replace deletes all former project-expenses and uses those from backup-file instead.") + " "
|
||
|
+ qsTr("Merge keeps former project-expenses and adds those from backup-file which are not yet on the list.")
|
||
|
if (parseInt(tempProjectIndex) !== parseInt(tempProjectIndex_File)) {
|
||
|
var detailText = qsTr("File info: This backup was created by a different project.")
|
||
|
} else {
|
||
|
detailText = qsTr("File info: This backup was created by the original project.")
|
||
|
}
|
||
|
var choiceText_1 = qsTr("Replace")
|
||
|
var choiceText_2 = qsTr("Merge")
|
||
|
banner2ButtonsChoice.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, headlineText, detailText, otherText, choiceText_1, choiceText_2, selectedPath )
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
TextFileIO {
|
||
|
id: idTextFileBackup
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
SilicaFlickable{
|
||
|
anchors.fill: parent
|
||
|
contentHeight: column.height // tell overall height
|
||
|
|
||
|
Column {
|
||
|
id: column
|
||
|
width: pageSettings.width
|
||
|
|
||
|
DialogHeader {
|
||
|
//title: qsTr("Settings")
|
||
|
}
|
||
|
Row {
|
||
|
width: parent.width
|
||
|
bottomPadding: Theme.paddingLarge
|
||
|
|
||
|
Label {
|
||
|
id: idLabelSettingsHeader
|
||
|
width: parent.width / 6 * 5
|
||
|
leftPadding: Theme.paddingLarge
|
||
|
font.pixelSize: Theme.fontSizeExtraLarge
|
||
|
color: Theme.highlightColor
|
||
|
text: qsTr("Settings")
|
||
|
|
||
|
MouseArea {
|
||
|
anchors.fill: parent
|
||
|
onClicked: showMaintenanceButtonsCounter = showMaintenanceButtonsCounter + 1
|
||
|
}
|
||
|
}
|
||
|
IconButton {
|
||
|
width: parent.width / 6
|
||
|
anchors.verticalCenter: idLabelSettingsHeader.verticalCenter
|
||
|
icon.color: Theme.highlightColor
|
||
|
icon.scale: 1.1
|
||
|
icon.source: "image://theme/icon-m-about?"
|
||
|
onClicked: {
|
||
|
pageStack.animatorPush(Qt.resolvedUrl("AboutPage.qml"), {})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Row {
|
||
|
width: parent.width
|
||
|
|
||
|
ComboBox {
|
||
|
id: idComboboxProject
|
||
|
width: (listModel_allProjects.count === 0) ? (parent.width / 6*5) : (parent.width / 6*4)
|
||
|
label: qsTr("Project")
|
||
|
menu: ContextMenu {
|
||
|
|
||
|
Repeater {
|
||
|
enabled: listModel_allProjects.count > 0
|
||
|
model: listModel_allProjects
|
||
|
|
||
|
MenuItem {
|
||
|
text: project_name
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
MouseArea {
|
||
|
enabled: listModel_allProjects.count === 0
|
||
|
anchors.fill: parent
|
||
|
preventStealing: true
|
||
|
onClicked: bannerAddProject.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, "new", idComboboxProject.currentIndex )
|
||
|
}
|
||
|
}
|
||
|
IconButton {
|
||
|
visible: listModel_allProjects.count > 0
|
||
|
width: parent.width / 6
|
||
|
icon.source: "image://theme/icon-m-edit?"
|
||
|
onClicked: {
|
||
|
bannerAddProject.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, "edit", idComboboxProject.currentIndex )
|
||
|
}
|
||
|
}
|
||
|
IconButton {
|
||
|
width: parent.width / 6
|
||
|
icon.source: "image://theme/icon-m-add?"
|
||
|
onClicked: {
|
||
|
bannerAddProject.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, "new", idComboboxProject.currentIndex )
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ComboBox {
|
||
|
id: idComboboxSortingExpenses
|
||
|
width: parent.width
|
||
|
label: qsTr("Sorting")
|
||
|
|
||
|
menu: ContextMenu {
|
||
|
MenuItem {
|
||
|
text: qsTr("descending")
|
||
|
}
|
||
|
MenuItem {
|
||
|
text: qsTr("ascending")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ComboBox {
|
||
|
id: idComboboxExchangeRateMode
|
||
|
width: parent.width
|
||
|
label: qsTr("Exchange rate")
|
||
|
menu: ContextMenu {
|
||
|
MenuItem {
|
||
|
text: qsTr("per currency (constant)")
|
||
|
}
|
||
|
MenuItem {
|
||
|
text: qsTr("per transaction (dates)")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ComboBox {
|
||
|
id: idComboboxShowInteractiveScrollbar
|
||
|
width: parent.width
|
||
|
label: qsTr("Scrollbar")
|
||
|
menu: ContextMenu {
|
||
|
MenuItem {
|
||
|
text: qsTr("normal")
|
||
|
}
|
||
|
MenuItem {
|
||
|
text: qsTr("interactive (beta)")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Column {
|
||
|
visible: showMaintenanceButtonsCounter > 9
|
||
|
width: parent.width
|
||
|
topPadding: Theme.paddingLarge * 3
|
||
|
spacing: Theme.paddingLarge
|
||
|
|
||
|
Label {
|
||
|
width: parent.width
|
||
|
wrapMode: Text.WordWrap
|
||
|
horizontalAlignment: Text.AlignHCenter
|
||
|
color: Theme.errorColor
|
||
|
text: qsTr("Database cleanup - requires restart:")
|
||
|
leftPadding: Theme.paddingLarge + Theme.paddingSmall
|
||
|
rightPadding: leftPadding
|
||
|
}
|
||
|
Button {
|
||
|
text: qsTr("settings")
|
||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||
|
onClicked: {
|
||
|
remorse_clearExchangeRatesDB.execute(qsTr("Delete stored settings?"), function() {
|
||
|
storageItem.removeFullTable( "settings_table" )
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
Button {
|
||
|
text: qsTr("exchange rates")
|
||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||
|
onClicked: {
|
||
|
remorse_clearExchangeRatesDB.execute(qsTr("Delete stored exchange rates?"), function() {
|
||
|
storageItem.removeFullTable( "exchange_rates_table" )
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
Button {
|
||
|
text: qsTr("projects")
|
||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||
|
onClicked: {
|
||
|
remorse_clearExchangeRatesDB.execute(qsTr("Delete stored projects?"), function() {
|
||
|
// remove all individual project expense tables
|
||
|
for (var j = 0; j < listModel_allProjects.count ; j++) {
|
||
|
var tempTablename = "table_" + listModel_allProjects.get(j).project_id_timestamp
|
||
|
storageItem.removeFullTable(tempTablename)
|
||
|
}
|
||
|
// remove all projects overview table
|
||
|
storageItem.removeFullTable( "projects_table" )
|
||
|
// set latest setting_id to zero
|
||
|
activeProjectID_unixtime = 0
|
||
|
storageItem.setSettings("activeProjectID_unixtime", activeProjectID_unixtime)
|
||
|
// update front page
|
||
|
updateEvenWhenCanceled = true
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Item {
|
||
|
width: parent.width
|
||
|
height: Theme.paddingLarge
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// ******************************************** important functions ******************************************** //
|
||
|
|
||
|
function writeDB_Settings() {
|
||
|
storageItem.setSettings("sortOrderExpensesIndex", idComboboxSortingExpenses.currentIndex)
|
||
|
sortOrderExpenses = idComboboxSortingExpenses.currentIndex
|
||
|
listModel_activeProjectExpenses.quick_sort()
|
||
|
|
||
|
storageItem.setSettings("exchangeRateModeIndex", idComboboxExchangeRateMode.currentIndex)
|
||
|
exchangeRateMode = idComboboxExchangeRateMode.currentIndex
|
||
|
|
||
|
storageItem.setSettings("interativeScrollbarMode", idComboboxShowInteractiveScrollbar.currentIndex)
|
||
|
interativeScrollbarMode = idComboboxShowInteractiveScrollbar.currentIndex
|
||
|
|
||
|
if (listModel_allProjects.count > 0) { // only works if a project is actually created and loaded, otherwise this gets triggered directly in BannerAddProject.qml
|
||
|
storageItem.setSettings("activeProjectID_unixtime", Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp))
|
||
|
activeProjectID_unixtime = Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp)
|
||
|
loadActiveProjectInfos_FromDB(Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp))
|
||
|
}
|
||
|
|
||
|
// ToDo: update base currency, if it was changed
|
||
|
}
|
||
|
|
||
|
function backupProjectExpenses(selectedPath) {
|
||
|
listModel_tempProjectExpenses.clear()
|
||
|
var tempProjectIndex = listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp
|
||
|
var backupFileName = encodeURIComponent(listModel_allProjects.get(idComboboxProject.currentIndex).project_name) + "_backup.csv" //replaces misleading special characters with %-symbols
|
||
|
var backupFilePath = selectedPath + "/" + backupFileName //StandardPaths.documents
|
||
|
|
||
|
// check if project exists
|
||
|
var currentProjectEntries = storageItem.getAllExpenses(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp, "none")
|
||
|
if (currentProjectEntries !== "none") {
|
||
|
// generate temp project expenses list from chosen list
|
||
|
for (var i = 0; i < currentProjectEntries.length ; i++) {
|
||
|
listModel_tempProjectExpenses.append({
|
||
|
id_unixtime_created : Number(currentProjectEntries[i][0]).toFixed(0),
|
||
|
date_time : Number(currentProjectEntries[i][1]).toFixed(0),
|
||
|
expense_name : currentProjectEntries[i][2],
|
||
|
expense_sum : Number(currentProjectEntries[i][3]).toFixed(2),
|
||
|
expense_currency : currentProjectEntries[i][4],
|
||
|
expense_info : currentProjectEntries[i][5],
|
||
|
expense_payer : currentProjectEntries[i][6],
|
||
|
expense_members : currentProjectEntries[i][7],
|
||
|
})
|
||
|
}
|
||
|
// create string from these temp info
|
||
|
var toBackupString = tempProjectIndex + "\n"
|
||
|
+ listModel_allProjects.get(idComboboxProject.currentIndex).project_name + "\n"
|
||
|
+ "id_unixtime_created;*;date_time;*;expense_payer;*;expense_name;*;expense_sum;*;expense_currency;*;expense_members;*;expense_info" + "\n"
|
||
|
for (var j = 0; j < listModel_tempProjectExpenses.count; j++) {
|
||
|
toBackupString += listModel_tempProjectExpenses.get(j).id_unixtime_created
|
||
|
+ ";*;" + listModel_tempProjectExpenses.get(j).date_time
|
||
|
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_payer
|
||
|
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_name
|
||
|
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_sum
|
||
|
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_currency
|
||
|
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_members
|
||
|
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_info
|
||
|
+ "\n"
|
||
|
}
|
||
|
//console.log(toBackupString)
|
||
|
|
||
|
// store in file and give notification
|
||
|
idTextFileBackup.source = backupFilePath
|
||
|
idTextFileBackup.text = toBackupString
|
||
|
notificationString = backupFilePath
|
||
|
|
||
|
//show notification somehow on top
|
||
|
var headlineText = qsTr("Backup successful")
|
||
|
var detailText = qsTr("File saved to:") + backupFilePath
|
||
|
var triggerHiding = false
|
||
|
notificationBackup.showBig(headlineText, detailText)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function restoreProjectExpenses(selectedPath, selectedAction) {
|
||
|
idTextFileBackup.source = selectedPath
|
||
|
var loadTextString = (idTextFileBackup.text)
|
||
|
var pos = loadTextString.lastIndexOf("\n") // ToDo: remove last occurance of "\n"
|
||
|
var loadTextLinesArray = (loadTextString.substring(0,pos) + loadTextString.substring(pos+1)).split("\n")
|
||
|
var tempStringExistingId_unixtime = ""
|
||
|
//var tempProjectIndex = listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp
|
||
|
|
||
|
// plausibility check on lines 0 to 2
|
||
|
if (loadTextLinesArray[2] === "id_unixtime_created;*;date_time;*;expense_payer;*;expense_name;*;expense_sum;*;expense_currency;*;expense_members;*;expense_info") {
|
||
|
var tempProjectIndex = listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp
|
||
|
|
||
|
// if REPLACE: delete old entries in project-specific expense table in database, otherwise just MERGE
|
||
|
if (selectedAction === "replace") {
|
||
|
storageItem.deleteAllExpenses(tempProjectIndex)
|
||
|
} else { //"merge"
|
||
|
// generate expenses list for this project
|
||
|
var currentProjectEntries = storageItem.getAllExpenses( activeProjectID_unixtime, "none")
|
||
|
if (currentProjectEntries !== "none") {
|
||
|
for (var i = 0; i < currentProjectEntries.length ; i++) {
|
||
|
tempStringExistingId_unixtime += (currentProjectEntries[i][0]).toString() + ";"
|
||
|
}
|
||
|
}
|
||
|
//console.log(tempStringExistingId_unixtime)
|
||
|
}
|
||
|
|
||
|
// fill with backup entries
|
||
|
for (i = 3; i < loadTextLinesArray.length; i++) {
|
||
|
var tempExpenseLineArray = (loadTextLinesArray[i]).split(";*;")
|
||
|
var project_name_table = tempProjectIndex
|
||
|
var id_unixtime_created = tempExpenseLineArray[0]
|
||
|
var date_time = tempExpenseLineArray[1]
|
||
|
var expense_payer = tempExpenseLineArray[2]
|
||
|
var expense_name = tempExpenseLineArray[3]
|
||
|
var expense_sum = tempExpenseLineArray[4]
|
||
|
var expense_currency = tempExpenseLineArray[5]
|
||
|
var expense_members = tempExpenseLineArray[6]
|
||
|
var expense_info = tempExpenseLineArray[7]
|
||
|
|
||
|
if (selectedAction === "replace") {
|
||
|
storageItem.setExpense(project_name_table, id_unixtime_created.toString(), date_time.toString(), expense_name, expense_sum, expense_currency, expense_info, expense_payer, expense_members)
|
||
|
//console.log("entering DB -> " + tempExpenseLineArray)
|
||
|
} else {
|
||
|
// check for double entries, only add if not existent
|
||
|
if (tempStringExistingId_unixtime.indexOf(id_unixtime_created.toString()) === -1) {
|
||
|
storageItem.setExpense(project_name_table, id_unixtime_created.toString(), date_time.toString(), expense_name, expense_sum, expense_currency, expense_info, expense_payer, expense_members)
|
||
|
//console.log("entering DB -> " + tempExpenseLineArray)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//show notification somehow on top
|
||
|
var headlineText = qsTr("Backup successfully restored.")
|
||
|
if (selectedAction === "replace") {
|
||
|
var detailText = qsTr("Project expenses have been overwritten by backup-file expenses.")
|
||
|
} else {
|
||
|
detailText = qsTr("Project expenses have been merged with backup-file expenses.")
|
||
|
}
|
||
|
var triggerHiding = false
|
||
|
notificationBackup.showBig(headlineText, detailText)
|
||
|
|
||
|
// set this flag when the current project gets merged or replaced with a backup
|
||
|
if ( Number(tempProjectIndex) === Number(activeProjectID_unixtime) ) {
|
||
|
updateEvenWhenCanceled = true
|
||
|
}
|
||
|
} else {
|
||
|
//show notification somehow on top
|
||
|
headlineText = qsTr("Validity check failed.")
|
||
|
detailText = qsTr("This backup file does not seem to be created by Expenditure:") + " " + backupFilePath
|
||
|
triggerHiding = false
|
||
|
notificationBackup.showBig(headlineText, detailText)
|
||
|
}
|
||
|
}
|
||
|
}
|