From 5305661f325ea84bbbc9c8fc7b6f2a4813a9147d Mon Sep 17 00:00:00 2001 From: Calvin Morrison Date: Sat, 27 Dec 2025 15:25:36 -0500 Subject: v.01 --- CashFlo.pro | 9 ++- cashflow.cpp | 215 ++++++++++++++++++++++++++++++++++------------------- cashflow.h | 11 ++- cashflow.ui | 154 ++++++++++++-------------------------- settingsdialog.cpp | 66 ++++++++++++++++ settingsdialog.h | 36 +++++++++ settingsdialog.ui | 178 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 480 insertions(+), 189 deletions(-) create mode 100644 settingsdialog.cpp create mode 100644 settingsdialog.h create mode 100644 settingsdialog.ui diff --git a/CashFlo.pro b/CashFlo.pro index 70333c2..920badf 100644 --- a/CashFlo.pro +++ b/CashFlo.pro @@ -11,15 +11,18 @@ CONFIG += c++17 SOURCES += \ main.cpp \ cashflow.cpp \ - database.cpp + database.cpp \ + settingsdialog.cpp HEADERS += \ cashflow.h \ database.h \ - transaction.h + transaction.h \ + settingsdialog.h FORMS += \ - cashflow.ui + cashflow.ui \ + settingsdialog.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin diff --git a/cashflow.cpp b/cashflow.cpp index 8e48d27..8eb46a4 100644 --- a/cashflow.cpp +++ b/cashflow.cpp @@ -1,10 +1,13 @@ #include "cashflow.h" #include "ui_cashflow.h" +#include "settingsdialog.h" #include #include #include #include #include +#include +#include CashFlow::CashFlow(QWidget *parent) : QMainWindow(parent) @@ -13,30 +16,24 @@ CashFlow::CashFlow(QWidget *parent) , currentRecurringId(-1) , startingBalance(0.0) , currentAmountFont("Courier New", 10) + , weekStartDay(1) // Default to Monday { ui->setupUi(this); // Initialize database database = new Database(); - QString dbPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/cashflow.db"; - QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); - if (!database->open(dbPath)) { - QMessageBox::critical(this, "Database Error", "Failed to open database: " + database->lastError()); - return; - } + // Try to open default database + QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir().mkpath(defaultDir); + QString defaultPath = defaultDir + "/default.cashflo.sqlite"; setupConnections(); - loadSettings(); - // Set default date range (current month to 3 months out) - QDate today = QDate::currentDate(); - ui->dateFromEdit->setDate(QDate(today.year(), today.month(), 1)); - ui->dateToEdit->setDate(today.addMonths(3)); - - clearTransactionEntry(); - clearRecurringEntry(); - refreshView(); + if (!openDatabase(defaultPath)) { + QMessageBox::critical(this, "Database Error", "Failed to open default database: " + database->lastError()); + return; + } } CashFlow::~CashFlow() @@ -46,6 +43,14 @@ CashFlow::~CashFlow() } void CashFlow::setupConnections() { + // File menu + connect(ui->actionNew, &QAction::triggered, this, &CashFlow::onNewFile); + connect(ui->actionOpen, &QAction::triggered, this, &CashFlow::onOpenFile); + connect(ui->actionQuit, &QAction::triggered, this, &CashFlow::onQuit); + + // Settings menu + connect(ui->actionPreferences, &QAction::triggered, this, &CashFlow::onPreferences); + // Transaction tab connect(ui->dateFromEdit, &QDateEdit::dateChanged, this, &CashFlow::onDateRangeChanged); connect(ui->dateToEdit, &QDateEdit::dateChanged, this, &CashFlow::onDateRangeChanged); @@ -69,14 +74,6 @@ void CashFlow::setupConnections() { // Set up Delete key shortcut for transaction table ui->deleteBtn->setShortcut(Qt::Key_Delete); - // Auto-save transaction fields on change - connect(ui->entryDateEdit, &QDateEdit::dateChanged, this, &CashFlow::onTransactionFieldChanged); - connect(ui->entryAmountSpin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CashFlow::onTransactionFieldChanged); - connect(ui->entryAccountCombo, &QComboBox::currentTextChanged, this, &CashFlow::onTransactionFieldChanged); - connect(ui->entryCategoryCombo, &QComboBox::currentTextChanged, this, &CashFlow::onTransactionFieldChanged); - connect(ui->entryDescriptionEdit, &QLineEdit::textChanged, this, &CashFlow::onTransactionFieldChanged); - connect(ui->entryTypeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &CashFlow::onTransactionFieldChanged); - // Color-code amount inputs connect(ui->entryAmountSpin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CashFlow::updateAmountColors); connect(ui->recurringAmountSpin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CashFlow::updateAmountColors); @@ -87,10 +84,6 @@ void CashFlow::setupConnections() { connect(ui->newRecurringBtn, &QPushButton::clicked, this, &CashFlow::onNewRecurring); connect(ui->deleteRecurringBtn, &QPushButton::clicked, this, &CashFlow::onDeleteRecurring); - // Settings tab - connect(ui->amountFontBtn, &QPushButton::clicked, this, &CashFlow::onChooseAmountFont); - connect(ui->saveSettingsBtn, &QPushButton::clicked, this, &CashFlow::onSaveSettings); - // Set up Delete key shortcut for recurring table ui->deleteRecurringBtn->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Delete)); } @@ -282,9 +275,14 @@ QDate CashFlow::getPeriodEnd(const QDate &date, PeriodType periodType) { case Daily: return date; case Weekly: { - // End on Sunday (7) - int daysUntilSunday = 7 - date.dayOfWeek(); - return date.addDays(daysUntilSunday); + // End on day before week start day + int weekEndDay = (weekStartDay == 1) ? 7 : weekStartDay - 1; + int currentDay = date.dayOfWeek(); + int daysUntilWeekEnd = (weekEndDay - currentDay + 7) % 7; + if (daysUntilWeekEnd == 0 && currentDay != weekEndDay) { + daysUntilWeekEnd = 7; // If we're past the end, go to next week's end + } + return date.addDays(daysUntilWeekEnd); } case Monthly: return QDate(date.year(), date.month(), date.daysInMonth()); @@ -322,9 +320,10 @@ QDate CashFlow::getPeriodStart(const QDate &date, PeriodType periodType) { case Daily: return date; case Weekly: { - // Start on Monday (1) - int daysFromMonday = date.dayOfWeek() - 1; - return date.addDays(-daysFromMonday); + // Start on configured week start day (1=Monday, 7=Sunday) + int currentDay = date.dayOfWeek(); // 1=Monday, 7=Sunday + int daysFromWeekStart = (currentDay - weekStartDay + 7) % 7; + return date.addDays(-daysFromWeekStart); } case Monthly: return QDate(date.year(), date.month(), 1); @@ -595,6 +594,23 @@ void CashFlow::onTransactionSelected() { } void CashFlow::onSaveTransaction() { + // Skip validation if this is a new empty transaction being auto-saved + bool isEmptyNew = (currentTransactionId == -1 && + ui->entryAccountCombo->currentText().isEmpty() && + ui->entryAmountSpin->value() == 0.0); + + if (!isEmptyNew) { + // Validate required fields + if (ui->entryAccountCombo->currentText().isEmpty()) { + QMessageBox::warning(this, "Required Field", "Account is required."); + return; + } + if (ui->entryAmountSpin->value() == 0.0) { + QMessageBox::warning(this, "Required Field", "Amount cannot be zero."); + return; + } + } + Transaction t; t.id = currentTransactionId; t.date = ui->entryDateEdit->date(); @@ -629,15 +645,6 @@ void CashFlow::onSaveTransaction() { } } -void CashFlow::onTransactionFieldChanged() { - // Auto-save if we're editing an existing transaction or have data entered - if (currentTransactionId != -1 || - !ui->entryAccountCombo->currentText().isEmpty() || - ui->entryAmountSpin->value() != 0.0) { - onSaveTransaction(); - } -} - void CashFlow::onNewTransaction() { clearTransactionEntry(); ui->entryDateEdit->setDate(QDate::currentDate()); @@ -849,59 +856,113 @@ void CashFlow::updateAmountColors() { void CashFlow::loadSettings() { // Load settings from database - QString currency = database->getSetting("currency_symbol", "$"); QString fontFamily = database->getSetting("amount_font", "Courier New"); int fontSize = database->getSetting("amount_font_size", "10").toInt(); - int defaultPeriod = database->getSetting("default_period", "2").toInt(); // 2 = Monthly + int defaultPeriod = database->getSetting("default_period", "2").toInt(); bool showAccountBalances = database->getSetting("show_account_balances", "0").toInt(); + weekStartDay = database->getSetting("week_start_day", "1").toInt(); - // Set settings UI - ui->currencyEdit->setText(currency); + // Apply to member variables and main UI currentAmountFont = QFont(fontFamily, fontSize); - ui->amountFontBtn->setText(QString("%1, %2pt").arg(fontFamily).arg(fontSize)); - ui->defaultPeriodCombo->setCurrentIndex(defaultPeriod); - ui->defaultShowAccountBalancesCheck->setChecked(showAccountBalances); - - // Apply defaults to main UI (only on initial load) ui->periodCombo->setCurrentIndex(defaultPeriod); ui->showAccountBalancesCheck->setChecked(showAccountBalances); } -void CashFlow::applySettings() { - // Currency symbol is now part of the formatted text in line edits - // Font is already stored in currentAmountFont - - // Refresh to apply font changes - refreshView(); +QString CashFlow::formatCurrency(double amount) const { + QLocale locale; + return locale.toString(amount, 'f', 2); } -void CashFlow::onSaveSettings() { - // Save settings to database - database->setSetting("currency_symbol", ui->currencyEdit->text()); - database->setSetting("amount_font", currentAmountFont.family()); - database->setSetting("amount_font_size", QString::number(currentAmountFont.pointSize())); - database->setSetting("default_period", QString::number(ui->defaultPeriodCombo->currentIndex())); - database->setSetting("show_account_balances", QString::number(ui->defaultShowAccountBalancesCheck->isChecked() ? 1 : 0)); +bool CashFlow::openDatabase(const QString &filePath) { + if (database->open(filePath)) { + currentFilePath = filePath; + QFileInfo fileInfo(filePath); + setWindowTitle(QString("CashFlo - %1").arg(fileInfo.fileName())); + + loadSettings(); + + // Set default date range (current month to 3 months out) + QDate today = QDate::currentDate(); + ui->dateFromEdit->setDate(QDate(today.year(), today.month(), 1)); + ui->dateToEdit->setDate(today.addMonths(3)); + + clearTransactionEntry(); + clearRecurringEntry(); + refreshView(); + + return true; + } + return false; +} + +void CashFlow::onNewFile() { + QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir().mkpath(defaultDir); + + QString fileName = QFileDialog::getSaveFileName( + this, + "New CashFlo File", + defaultDir, + "CashFlo Files (*.cashflo.sqlite);;All Files (*)" + ); - // Apply settings - applySettings(); + if (fileName.isEmpty()) { + return; + } + + // Ensure .cashflo.sqlite extension + if (!fileName.endsWith(".cashflo.sqlite", Qt::CaseInsensitive)) { + fileName += ".cashflo.sqlite"; + } + + // Remove file if it exists + if (QFile::exists(fileName)) { + QFile::remove(fileName); + } + + // Close current database + delete database; + database = new Database(); - QMessageBox::information(this, "Settings Saved", "Settings have been saved successfully."); + if (!openDatabase(fileName)) { + QMessageBox::critical(this, "Error", "Failed to create new file: " + database->lastError()); + } } -void CashFlow::onChooseAmountFont() { - bool ok; - QFont selectedFont = QFontDialog::getFont(&ok, currentAmountFont, this, "Choose Amount Font", QFontDialog::MonospacedFonts); +void CashFlow::onOpenFile() { + QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + + QString fileName = QFileDialog::getOpenFileName( + this, + "Open CashFlo File", + defaultDir, + "CashFlo Files (*.cashflo.sqlite);;All Files (*)" + ); + + if (fileName.isEmpty()) { + return; + } + + // Close current database + delete database; + database = new Database(); - if (ok) { - currentAmountFont = selectedFont; - ui->amountFontBtn->setText(QString("%1, %2pt").arg(selectedFont.family()).arg(selectedFont.pointSize())); + if (!openDatabase(fileName)) { + QMessageBox::critical(this, "Error", "Failed to open file: " + database->lastError()); } } -QString CashFlow::formatCurrency(double amount) const { - QLocale locale; - return locale.toString(amount, 'f', 2); +void CashFlow::onQuit() { + QApplication::quit(); } - +void CashFlow::onPreferences() { + SettingsDialog dialog(database, this); + if (dialog.exec() == QDialog::Accepted) { + // Reload settings + currentAmountFont = dialog.getCurrentAmountFont(); + weekStartDay = dialog.getWeekStartDay(); + loadSettings(); + refreshView(); + } +} diff --git a/cashflow.h b/cashflow.h index 9de01ae..adb0494 100644 --- a/cashflow.h +++ b/cashflow.h @@ -23,7 +23,6 @@ private slots: void onDateRangeChanged(); void onTransactionSelected(); void onSaveTransaction(); - void onTransactionFieldChanged(); void onNewTransaction(); void onDeleteTransaction(); void onRecurringSelected(); @@ -31,6 +30,10 @@ private slots: void onNewRecurring(); void onDeleteRecurring(); void onPeriodChanged(); + void onNewFile(); + void onOpenFile(); + void onQuit(); + void onPreferences(); private: Ui::CashFlow *ui; @@ -39,6 +42,8 @@ private: int currentRecurringId; double startingBalance; QFont currentAmountFont; + int weekStartDay; + QString currentFilePath; enum PeriodType { Daily, @@ -64,9 +69,7 @@ private: void insertPeriodEndRow(const QString &label, double balance, const QMap &accountBalances); void updateAmountColors(); void loadSettings(); - void applySettings(); - void onSaveSettings(); - void onChooseAmountFont(); QString formatCurrency(double amount) const; + bool openDatabase(const QString &filePath); }; #endif // CASHFLOW_H diff --git a/cashflow.ui b/cashflow.ui index 6592c0d..5d58574 100644 --- a/cashflow.ui +++ b/cashflow.ui @@ -493,111 +493,6 @@ - - - Settings - - - - - - Application Settings - - - - - - Currency Symbol: - - - - - - - 3 - - - $ - - - - - - - Amount Font: - - - - - - - Choose Font... - - - - - - - Default Period: - - - - - - - - Daily - - - - - Weekly - - - - - Monthly - - - - - Quarterly - - - - - - - - Show Account Balances by Default - - - - - - - Save Settings - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - @@ -611,8 +506,57 @@ 22 + + + File + + + + + + + + + Settings + + + + + + + + New... + + + Ctrl+N + + + + + Open... + + + Ctrl+O + + + + + Quit + + + Ctrl+Q + + + + + Preferences... + + + Ctrl+, + + diff --git a/settingsdialog.cpp b/settingsdialog.cpp new file mode 100644 index 0000000..644fa89 --- /dev/null +++ b/settingsdialog.cpp @@ -0,0 +1,66 @@ +#include "settingsdialog.h" +#include "ui_settingsdialog.h" +#include + +SettingsDialog::SettingsDialog(Database *db, QWidget *parent) + : QDialog(parent) + , ui(new Ui::SettingsDialog) + , database(db) + , currentAmountFont("Courier New", 10) + , weekStartDay(1) +{ + ui->setupUi(this); + + connect(ui->amountFontBtn, &QPushButton::clicked, this, &SettingsDialog::onChooseAmountFont); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::saveSettings); + + loadSettings(); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +void SettingsDialog::loadSettings() +{ + // Load settings from database + QString currency = database->getSetting("currency_symbol", "$"); + QString fontFamily = database->getSetting("amount_font", "Courier New"); + int fontSize = database->getSetting("amount_font_size", "10").toInt(); + int defaultPeriod = database->getSetting("default_period", "2").toInt(); + bool showAccountBalances = database->getSetting("show_account_balances", "0").toInt(); + weekStartDay = database->getSetting("week_start_day", "1").toInt(); + + // Set UI + ui->currencyEdit->setText(currency); + currentAmountFont = QFont(fontFamily, fontSize); + ui->amountFontBtn->setText(QString("%1, %2pt").arg(fontFamily).arg(fontSize)); + ui->defaultPeriodCombo->setCurrentIndex(defaultPeriod); + ui->defaultShowAccountBalancesCheck->setChecked(showAccountBalances); + ui->weekStartDayCombo->setCurrentIndex(weekStartDay - 1); +} + +void SettingsDialog::saveSettings() +{ + // Save to database + database->setSetting("currency_symbol", ui->currencyEdit->text()); + database->setSetting("amount_font", currentAmountFont.family()); + database->setSetting("amount_font_size", QString::number(currentAmountFont.pointSize())); + database->setSetting("default_period", QString::number(ui->defaultPeriodCombo->currentIndex())); + database->setSetting("show_account_balances", QString::number(ui->defaultShowAccountBalancesCheck->isChecked() ? 1 : 0)); + database->setSetting("week_start_day", QString::number(ui->weekStartDayCombo->currentIndex() + 1)); + + weekStartDay = ui->weekStartDayCombo->currentIndex() + 1; +} + +void SettingsDialog::onChooseAmountFont() +{ + bool ok; + QFont selectedFont = QFontDialog::getFont(&ok, currentAmountFont, this, "Choose Amount Font", QFontDialog::MonospacedFonts); + + if (ok) { + currentAmountFont = selectedFont; + ui->amountFontBtn->setText(QString("%1, %2pt").arg(selectedFont.family()).arg(selectedFont.pointSize())); + } +} diff --git a/settingsdialog.h b/settingsdialog.h new file mode 100644 index 0000000..95d43b3 --- /dev/null +++ b/settingsdialog.h @@ -0,0 +1,36 @@ +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include +#include +#include "database.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class SettingsDialog; } +QT_END_NAMESPACE + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog(Database *db, QWidget *parent = nullptr); + ~SettingsDialog(); + + QFont getCurrentAmountFont() const { return currentAmountFont; } + int getWeekStartDay() const { return weekStartDay; } + +private slots: + void onChooseAmountFont(); + +private: + Ui::SettingsDialog *ui; + Database *database; + QFont currentAmountFont; + int weekStartDay; + + void loadSettings(); + void saveSettings(); +}; + +#endif // SETTINGSDIALOG_H diff --git a/settingsdialog.ui b/settingsdialog.ui new file mode 100644 index 0000000..fad4c35 --- /dev/null +++ b/settingsdialog.ui @@ -0,0 +1,178 @@ + + + SettingsDialog + + + + 0 + 0 + 400 + 350 + + + + Preferences + + + + + + Application Settings + + + + + + Currency Symbol: + + + + + + + 3 + + + $ + + + + + + + Amount Font: + + + + + + + Choose Font... + + + + + + + Default Period: + + + + + + + + Daily + + + + + Weekly + + + + + Monthly + + + + + Quarterly + + + + + + + + Show Account Balances by Default + + + + + + + Week Starts On: + + + + + + + + Monday + + + + + Tuesday + + + + + Wednesday + + + + + Thursday + + + + + Friday + + + + + Saturday + + + + + Sunday + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SettingsDialog + accept() + + + buttonBox + rejected() + SettingsDialog + reject() + + + -- cgit v1.2.3