# HG changeset patch # User Matti Hamalainen # Date 1493025159 -10800 # Node ID c8fd927cd2c44b971956ebe0faaf05949f181096 # Parent f48b8fc1de64a36e4545549f72ad5d6027f9117d Restructure the project by placing source code, images into appropriate subdirectories. diff -r f48b8fc1de64 -r c8fd927cd2c4 Makefile.gen --- a/Makefile.gen Wed Apr 12 12:16:00 2017 +0300 +++ b/Makefile.gen Mon Apr 24 12:12:39 2017 +0300 @@ -17,6 +17,8 @@ DEFINES += -DQT_DEPRECATED_WARNINGS -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_SQL_LIB -DQT_CORE_LIB # Application stuff +APP_SRC=src/ +APP_IMG=img/ APP_BIN=$(BINPATH)Syntilista$(EXEEXT) APP_OBJS=main.o resources.o moc_main.o APP_VERSION := $(shell cat VERSION) @@ -27,14 +29,14 @@ LOGO_SVG ?= kampuscafe4.svg ICON_PNGS = icon-64.png icon-48.png icon-32.png icon-16.png -APP_RESOURCES += logo.png $(ICON_PNGS) +APP_RESOURCES += $(addprefix $(APP_IMG),logo.png $(ICON_PNGS)) # And target lists TARGETS = $(APP_BIN) NOBUILD_TARGETS += $(OBJPATH) $(BINPATH) NOINST_TARGETS += -DISTCLEAN_TARGETS += icon.ico moc_*.cpp ui_*.h +DISTCLEAN_TARGETS += icon.ico $(APP_SRC)moc_*.cpp $(APP_SRC)ui_*.h ### @@ -60,11 +62,11 @@ @echo " MKDIR $@" @$(MKDIR_P) $@ -$(OBJPATH)%.o: %.cpp %.h +$(OBJPATH)%.o: $(APP_SRC)%.cpp $(APP_SRC)%.h @echo " CXX $@" @$(CXX) $(CXXFLAGS) $(DEFINES) $(INCPATH) -c -o $@ $< -$(OBJPATH)%.o: %.cpp +$(OBJPATH)%.o: $(APP_SRC)%.cpp @echo " CXX $@" @$(CXX) $(CXXFLAGS) $(DEFINES) $(INCPATH) -c -o $@ $< @@ -76,7 +78,7 @@ %.rc: %.rc.in icon.ico VERSION @sed -e "s/@APP_VERSION@/$(APP_VERSION)/g;s/@APP_VERSION_COM@/$(APP_VERSION_COM)/g;s#@APP_EXE@#$(notdir $(APP_BIN))#g" < $< > $@ -$(OBJPATH)%.o: %.rc +$(OBJPATH)%.o: $(APP_SRC)%.rc @echo " WINDRES $<" @$(WINDRES) $< -O coff -o $@ @@ -84,21 +86,21 @@ ### ### Application rules ### -icon-%.png: $(LOGO_SVG) +$(APP_IMG)icon-%.png: $(addprefix $(APP_IMG),$(LOGO_SVG)) inkscape --export-area-page -w $(patsubst icon-%.png,%,$@) -h $(patsubst icon-%.png,%,$@) -e "$@" "$<" -logo.png: $(LOGO_SVG) +$(APP_IMG)logo.png: $(addprefix $(APP_IMG),$(LOGO_SVG)) inkscape --export-area-page -w 320 -h 280 -e "$@" "$<" -icon.ico: $(ICON_PNGS) +icon.ico: $(addprefix $(APP_IMG),$(ICON_PNGS)) @echo " CONVERT $+ -> $@" @convert $+ $@ -%.cpp: %.qrc $(APP_RESOURCES) +$(APP_SRC)%.cpp: $(APP_SRC)%.qrc $(APP_RESOURCES) @echo " Qt:RCC $@ $<" @$(QT_RCC) -name "Syntilista" $< -o $@ -$(OBJPATH)main.o: main.cpp main.h ui_mainwindow.h ui_editperson.h VERSION +$(OBJPATH)main.o: $(addprefix $(APP_SRC),main.cpp main.h ui_mainwindow.h ui_editperson.h) VERSION @echo " CXX $@" @$(CXX) $(CXXFLAGS) $(DEFINES) $(INCPATH) -c -o $@ $< diff -r f48b8fc1de64 -r c8fd927cd2c4 editperson.ui --- a/editperson.ui Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,120 +0,0 @@ - - - EditPerson - - - Qt::NonModal - - - - 0 - 0 - 564 - 500 - - - - Muokkaa henkilöä - - - - - - Henkilön perustiedot - - - - - - Etunimi - - - - - - - Sukunimi - - - - - - - - - - - - - Lisätietoja: - - - - - - - - - - - - - Tapahtumat: - - - - - - Qt::DefaultContextMenu - - - QAbstractItemView::SelectRows - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Peruuta - - - - - - - Talleta / OK - - - - - - - - - edit_FirstName - edit_LastName - textedit_ExtraInfo - button_OK - button_Cancel - - - - diff -r f48b8fc1de64 -r c8fd927cd2c4 icon-16.png Binary file icon-16.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 icon-32.png Binary file icon-32.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 icon-48.png Binary file icon-48.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 icon-64.png Binary file icon-64.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 img/icon-16.png Binary file img/icon-16.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 img/icon-32.png Binary file img/icon-32.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 img/icon-48.png Binary file img/icon-48.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 img/icon-64.png Binary file img/icon-64.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 img/kampuscafe1.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/img/kampuscafe1.svg Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,119 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + Kampus Cafe + + + diff -r f48b8fc1de64 -r c8fd927cd2c4 img/kampuscafe2.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/img/kampuscafe2.svg Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,132 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + Kampus Cafe + + + + diff -r f48b8fc1de64 -r c8fd927cd2c4 img/kampuscafe3.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/img/kampuscafe3.svg Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + Kampus Cafe + + + Kampus Cafe + + diff -r f48b8fc1de64 -r c8fd927cd2c4 img/kampuscafe4.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/img/kampuscafe4.svg Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + Cafe Kampus + + + + diff -r f48b8fc1de64 -r c8fd927cd2c4 img/logo.png Binary file img/logo.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 kampuscafe1.svg --- a/kampuscafe1.svg Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - Kampus Cafe - - - diff -r f48b8fc1de64 -r c8fd927cd2c4 kampuscafe2.svg --- a/kampuscafe2.svg Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - Kampus Cafe - - - - diff -r f48b8fc1de64 -r c8fd927cd2c4 kampuscafe3.svg --- a/kampuscafe3.svg Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - Kampus Cafe - - - Kampus Cafe - - diff -r f48b8fc1de64 -r c8fd927cd2c4 kampuscafe4.svg --- a/kampuscafe4.svg Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +0,0 @@ - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - Cafe Kampus - - - - diff -r f48b8fc1de64 -r c8fd927cd2c4 logo.png Binary file logo.png has changed diff -r f48b8fc1de64 -r c8fd927cd2c4 main.cpp --- a/main.cpp Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1189 +0,0 @@ -// -// Syntilista - velkalistasovellus Kampus-kahvilaan -// Programmed and designed by Matti Hämäläinen -// (C) Copyright 2017 Tecnic Software productions (TNSP) -// -// Distributed under 3-clause BSD style license, refer to -// included file "COPYING" for exact terms. -// -#include -#include -#include -#include "main.h" -#include "ui_mainwindow.h" -#include "ui_editperson.h" - -double setScale; - - -int errorMsg(QString title, QString msg) -{ - QMessageBox dlg; - - dlg.setText(title); - dlg.setInformativeText(msg); - dlg.setTextFormat(Qt::RichText); - dlg.setIcon(QMessageBox::Critical); - dlg.setStandardButtons(QMessageBox::Ok); - dlg.setDefaultButton(QMessageBox::Ok); - - return dlg.exec(); -} - - -double moneyStrToValue(const QString &str) -{ - QString str2 = str; - return str2.replace(",", ".").toDouble(); -} - - -QString moneyValueToStr(double val) -{ - return QStringLiteral("%1").arg(val, 1, 'f', 2); -} - - -QString cleanupStr(const QString &str) -{ - return str.simplified().trimmed(); -} - - -const QString dateTimeToStr(const QDateTime &val) -{ - QDateTime tmp = val; - tmp.setOffsetFromUtc(0); - return tmp.toLocalTime().toString(QStringLiteral("yyyy-MM-dd hh:mm")); -} - - -bool checkAndReportSQLError(const QString where, const QSqlError &err) -{ - if (err.isValid()) - { - printf("SQL Error in %s: %s\n", - where.toUtf8().constData(), - err.text().toUtf8().constData()); - return false; - } - else - return true; -} - - -void PersonInfo::dump() -{ - printf("PersonInfo() #%lld '%s %s' (added=%s, updated=%s, balance %1.2f)\n#%s#\n", - id, - firstName.toUtf8().constData(), - lastName.toUtf8().constData(), - dateTimeToStr(added).toUtf8().constData(), - dateTimeToStr(updated).toUtf8().constData(), - balance, - extraInfo.toUtf8().constData()); -} - - -// -// Get PersonInfo record from SQL database for specified persn ID # -// -bool getPersonInfo(qint64 id, PersonInfo &info) -{ - QSqlQuery person; - person.prepare( - "SELECT id,first_name,last_name,extra_info,added,updated, " - "(SELECT SUM(value) FROM transactions WHERE transactions.person=people.id) AS balance " - "FROM people WHERE id=?"); - - person.addBindValue(id); - person.exec(); - if (!person.next()) - return false; - - info.id = person.value(0).toInt(); - info.firstName = person.value(1).toString(); - info.lastName = person.value(2).toString(); - info.extraInfo = person.value(3).toString(); - info.added = person.value(4).toDateTime(); - info.updated = person.value(5).toDateTime(); - info.balance = person.value(6).toDouble(); - - person.finish(); - - return true; -} - - -void setCommonStyleSheet(QWidget *widget) -{ - // Clamp scale value - if (setScale < 0.5f) - setScale = 0.5f; - if (setScale > 3.0f) - setScale = 3.0f; - - // Set the stylesheet - widget->setStyleSheet( - QStringLiteral( - "* { font-size: %1pt; }" - "QPushButton { font-size: %2pt; padding: 0.25em; }" - "#button_AddDebt[enabled='true'] { font-size: %3pt; background-color: #900; color: white; }" - "#button_PayDebt[enabled='true'] { font-size: %3pt; background-color: #090; color: white; }" - "#button_PayFullDebt[enabled='true'] { background-color: #060; color: white; }" - - "#button_AddDebt[enabled='false'] { font-size: %3pt; background-color: #622; color: black; }" - "#button_PayDebt[enabled='false'] { font-size: %3pt; background-color: #262; color: black; }" - "#button_PayFullDebt[enabled='false'] { background-color: #131; color: black; }" - - "#label_PersonName { font-size: %5pt; font-weight: bold; }" - "#label_BalanceValue { font-size: %4pt; font-weight: bold; }" - "#label_EUR { font-size: %4pt; font-weight: bold; }" - "#edit_Amount { font-size: %4pt; margin: 0.5em; padding: 0.5em; }" - ). - arg(12 * setScale). - arg(14 * setScale). - arg(16 * setScale). - arg(18 * setScale). - arg(20 * setScale) - ); -} - - -int main(int argc, char *argv[]) -{ - QApplication sapp(argc, argv); - - // - // Initialize / open SQL database connection - // - QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); - db.setDatabaseName(qApp->applicationDirPath() + - QDir::separator() + APP_SQLITE_FILE); - - if (!db.open()) - { - errorMsg( - QObject::tr("Tietokantaa ei voitu avata"), - QObject::tr("Yhteyttä SQL-tietokantaan ei saatu.

Virhe: %1

"). - arg(db.lastError().text()) - ); - return 1; - } - - QSqlQuery query; - if (!db.tables().contains("people")) - { - query.exec(QStringLiteral( - "CREATE TABLE people (id INTEGER PRIMARY KEY, " - "first_name VARCHAR(128) NOT NULL, " - "last_name VARCHAR(128) NOT NULL, " - "extra_info VARCHAR(2048), " - "added DATETIME NOT NULL, " - "updated DATETIME NOT NULL)")); - - checkAndReportSQLError("CREATE TABLE people", query.lastError()); - } - - if (!db.tables().contains("transactions")) - { - query.exec(QStringLiteral( - "CREATE TABLE transactions (" - "id INTEGER PRIMARY KEY, " - "person INT NOT NULL, " - "value REAL, " - "added DATETIME NOT NULL)")); - - checkAndReportSQLError("CREATE TABLE transactions", query.lastError()); - } - - SyntilistaMainWindow swin; - swin.show(); - return sapp.exec(); -} - - -// -// Main application window code -// -SyntilistaMainWindow::SyntilistaMainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::SyntilistaMainWindow) -{ - // Setup UI - ui->setupUi(this); - - // Read config - readSettings(); - - // Setup other UI things - setWindowIcon(QIcon(QPixmap(":/img/icon-64.png"))); - setWindowTitle(tr("%1 versio %3"). - arg(tr(APP_NAME)). - arg(tr(APP_VERSION))); - - QPixmap logoImage(":/img/logo.png"); - ui->button_LogoImage->setPixmap(logoImage); - ui->button_LogoImage->setAlignment(Qt::AlignCenter); - - setCommonStyleSheet(this); - - // Validator for amount input - //ui->edit_Amount->setValidator(new QDoubleValidator(0, 1000, 2, this)); - QRegExp vregex("\\d{0,4}[,.]\\d{0,2}|\\d{0,4}"); - ui->edit_Amount->setValidator(new QRegExpValidator(vregex, this)); - - // Setup person list filtering and sorting - peopleSortIndex = 1; - peopleSortOrder = Qt::AscendingOrder; - peopleFilter = ""; - - model_People = new PersonSQLModel(); - updatePersonList(); - - ui->tableview_People->setModel(model_People); - ui->tableview_People->setColumnHidden(0, true); - ui->tableview_People->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_People)); - ui->tableview_People->verticalHeader()->setVisible(false); - ui->tableview_People->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - ui->tableview_People->setSortingEnabled(true); - ui->tableview_People->sortByColumn(peopleSortIndex, peopleSortOrder); - - connect( - ui->tableview_People->selectionModel(), - SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), - this, - SLOT(selectedPersonChanged(const QModelIndex &, const QModelIndex &))); - - connect( - ui->tableview_People->horizontalHeader(), - SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), - this, - SLOT(updateSortOrder(int, Qt::SortOrder))); - - ui->tableview_People->horizontalHeader()->setSortIndicator(1, Qt::AscendingOrder); - - model_Latest = new TransactionSQLModel(); - ui->tableview_Latest->setModel(model_Latest); - ui->tableview_Latest->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_Latest)); - ui->tableview_Latest->verticalHeader()->setVisible(false); - ui->tableview_Latest->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - - setActivePerson(-1); - - // Keyboard shortcuts - ui->button_Quit->setShortcut(QKeySequence(Qt::Key_F10)); - new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this, SLOT(on_button_Quit_clicked())); - - ui->button_AddPerson->setShortcut(QKeySequence(Qt::Key_F5)); - ui->button_DeletePerson->setShortcut(QKeySequence(Qt::Key_F8)); - ui->button_EditPerson->setShortcut(QKeySequence(Qt::Key_F6)); - ui->button_ClearFilter->setShortcut(QKeySequence(Qt::Key_Escape)); - ui->button_Help->setShortcut(QKeySequence(Qt::Key_F1)); - ui->button_About->setShortcut(QKeySequence(Qt::Key_F2)); - - new QShortcut(QKeySequence(QKeySequence::ZoomIn), this, SLOT(changeUIZoomIn())); - new QShortcut(QKeySequence(QKeySequence::ZoomOut), this, SLOT(changeUIZoomOut())); - new QShortcut(QKeySequence(Qt::CTRL + Qt::KeypadModifier + Qt::Key_Plus), this, SLOT(changeUIZoomIn())); - new QShortcut(QKeySequence(Qt::CTRL + Qt::KeypadModifier + Qt::Key_Minus), this, SLOT(changeUIZoomOut())); - new QShortcut(QKeySequence(Qt::CTRL + Qt::KeypadModifier + Qt::Key_0), this, SLOT(changeUIZoomReset())); - new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_0), this, SLOT(changeUIZoomReset())); - - new QShortcut(QKeySequence(Qt::Key_PageUp), this, SLOT(selectRowPrev())); - new QShortcut(QKeySequence(Qt::Key_PageDown), this, SLOT(selectRowNext())); - - new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(focusDebtEdit())); -} - - -SyntilistaMainWindow::~SyntilistaMainWindow() -{ - saveSettings(); - - delete ui; - delete model_People; - delete model_Latest; -} - - -void SyntilistaMainWindow::statusMsg(const QString &msg) -{ - ui->statusbar->showMessage(msg); -} - - -void SyntilistaMainWindow::readSettings() -{ - QSettings settings(APP_VENDOR, APP_ID); - move(settings.value("pos", QPoint(100, 100)).toPoint()); - resize(settings.value("size", QSize(1000, 600)).toSize()); - setScale = settings.value("scale", 1.0f).toDouble(); -} - - -void SyntilistaMainWindow::saveSettings() -{ - QSettings settings(APP_VENDOR, APP_ID); - settings.setValue("pos", pos()); - settings.setValue("size", size()); - settings.setValue("scale", setScale); -} - - -void SyntilistaMainWindow::changeUIZoomIn() -{ - setScale += 0.1f; - setCommonStyleSheet(this); -} - - -void SyntilistaMainWindow::changeUIZoomOut() -{ - setScale -= 0.1f; - setCommonStyleSheet(this); -} - - -void SyntilistaMainWindow::changeUIZoomReset() -{ - setScale = 1.0f; - setCommonStyleSheet(this); -} - - -void SyntilistaMainWindow::selectedPersonChanged(const QModelIndex &curr, const QModelIndex &prev) -{ - (void) prev; - int row = curr.row(); - if (row >= 0) - { - const QAbstractItemModel *model = curr.model(); - setActivePerson(model->data(model->index(row, 0)).toInt()); - } - else - setActivePerson(-1); -} - - -void SyntilistaMainWindow::updateSortOrder(int index, Qt::SortOrder order) -{ - peopleSortIndex = index; - peopleSortOrder = order; - updatePersonList(); -} - - -void SyntilistaMainWindow::setActivePerson(qint64 id) -{ - currPerson.id = id; - - ui->button_EditPerson->setEnabled(id >= 0); - - if (id >= 0) - { - if (!getPersonInfo(id, currPerson)) - { - statusMsg(tr("Virhe! Ei henkilöä ID:llä #%1").arg(id)); - } - else - { - ui->personGB->setEnabled(true); - ui->label_PersonName->setText(currPerson.lastName +", "+ currPerson.firstName); - - ui->label_BalanceValue->setText(moneyValueToStr(currPerson.balance)); - ui->label_BalanceValue->setStyleSheet(currPerson.balance < 0 ? "color: red;" : "color: green;"); - ui->button_PayFullDebt->setEnabled(currPerson.balance < 0); - - QSqlQuery query; - query.prepare("SELECT id,value,added FROM transactions WHERE person=? ORDER BY added DESC LIMIT 5"); - query.addBindValue(id); - query.exec(); - checkAndReportSQLError("SELECT transactions for tableview_Latest", query.lastError()); - - model_Latest->setQuery(query); - - model_Latest->setHeaderData(0, Qt::Horizontal, tr("ID")); - model_Latest->setHeaderData(1, Qt::Horizontal, tr("Summa")); - model_Latest->setHeaderData(2, Qt::Horizontal, tr("Aika")); - - ui->tableview_Latest->setModel(model_Latest); - ui->tableview_Latest->setColumnHidden(0, true); - ui->tableview_Latest->verticalHeader()->setVisible(false); - ui->tableview_Latest->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - - setCommonStyleSheet(this); - return; // Ugly - } - } - - // In case of id < 0 or errors .. - ui->personGB->setEnabled(false); - ui->edit_Amount->clear(); - ui->label_BalanceValue->setText("--"); - ui->label_BalanceValue->setStyleSheet(NULL); - ui->label_PersonName->setText("???"); - ui->tableview_Latest->setModel(NULL); - setCommonStyleSheet(this); -} - - -// -// Widget slot handlers -// -void SyntilistaMainWindow::on_button_Quit_clicked() -{ - close(); -} - - -void SyntilistaMainWindow::on_button_About_clicked() -{ - QMessageBox dlg; - - setCommonStyleSheet(&dlg); - dlg.setWindowTitle(tr("Tietoja ohjelmasta")); - dlg.setTextFormat(Qt::RichText); - dlg.setIconPixmap(QPixmap(":/img/icon-64.png")); - dlg.setStandardButtons(QMessageBox::Ok); - dlg.setDefaultButton(QMessageBox::Ok); - - //dlg.setInformativeText(tr( - dlg.setText(tr( - "

%1 v%2

" - "

" - "Ohjelmoinut ja kehittänyt Matti Hämäläinen <ccr@tnsp.org>
" - "(C) Copyright 2017 Tecnic Software productions (TNSP)

" - "
" - "Kehitetty Raahen kaupungin Hanketoiminta ja Kehittäminen -yksikön " - "alaisuudessa Café Kampuksen käyttöön.
" - "

" - "

" - "Ohjelma ja sen lähdekoodi ovat uudemman BSD-tyylisen lisenssin alaisia. " - "Lue ohjelman mukana tullut tiedosto \"COPYING\" (tai \"COPYING.txt\") " - "nähdäksesi täydelliset lisenssiehdot." - "

" - ). - arg(tr(APP_NAME)). - arg(tr(APP_VERSION)) - ); - - dlg.exec(); -} - - -void SyntilistaMainWindow::on_button_Help_clicked() -{ - QMessageBox dlg; - - setCommonStyleSheet(&dlg); - dlg.setWindowTitle(tr("Tietoja ohjelmasta")); - dlg.setTextFormat(Qt::RichText); - dlg.setIconPixmap(QPixmap(":/img/icon-64.png")); - dlg.setStandardButtons(QMessageBox::Ok); - dlg.setDefaultButton(QMessageBox::Ok); - - dlg.setText(tr( - "

Pikanäppäimet

" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "
F1Tämä tietoikkuna
F2Tietoja ohjelmasta
CTRL + QOhjelman lopetus
CTRL + Page UpSuurenna ohjelman tekstejä/käyttöliittymää
CTRL + Page DownPienennä ohjelman tekstejä/käyttöliittymää
EscTyhjennä 'Etsi / suodata' kenttä ja siirry siihen
CTRL + EnterSiirry summan syöttökenttään
Page UpSiirry ylös henkilölistassa
Page DownSiirry alas henkilölistassa
F5Lisää uusi henkilö
F6Muokkaa henkilöä
F8Poista henkilö
" - )); - - dlg.exec(); -} - - -void SyntilistaMainWindow::on_button_DeletePerson_clicked() -{ - if (currPerson.id <= 0) - { - statusMsg(tr("Ei valittua henkilöä!")); - return; - } - - PersonInfo info; - if (!getPersonInfo(currPerson.id, info)) - { - statusMsg(tr("Virhe! Ei henkilöä ID:llä #%1").arg(currPerson.id)); - return; - } - - QMessageBox dlg; - setCommonStyleSheet(&dlg); - dlg.setText(tr("Varmistus")); - dlg.setInformativeText( - tr("
Haluatko varmasti poistaa henkilön:
" - "
" - "'%1, %2' (ID #%3)?
" - "
" - "Tämä poistaa sekä henkilön ja hänen koko tapahtumahistoriansa PYSYVÄSTI!
"). - arg(info.lastName).arg(info.firstName).arg(info.id)); - - dlg.setTextFormat(Qt::RichText); - dlg.setIcon(QMessageBox::Question); - dlg.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - dlg.setButtonText(QMessageBox::Yes, tr("Kyllä")); - dlg.setButtonText(QMessageBox::No, tr("Ei / peruuta")); - dlg.setDefaultButton(QMessageBox::No); - - if (dlg.exec() == QMessageBox::Yes) - { - int rv = model_People->deletePerson(info.id); - updatePersonList(); - setActivePerson(-1); - if (rv != 0) - { - errorMsg(tr("SQL-tietokantavirhe"), - tr("Henkilön tietoja poistettaessa tapahtui virhe #%1."). - arg(rv)); - } - else - { - statusMsg(tr("Henkilö '%1 %2' (ID #%3) poistettu."). - arg(info.firstName).arg(info.lastName). - arg(info.id)); - } - } -} - - -void SyntilistaMainWindow::on_button_AddPerson_clicked() -{ - EditPerson *person = new EditPerson(this); - person->setPerson(-1); -} - - -void SyntilistaMainWindow::on_button_EditPerson_clicked() -{ - if (currPerson.id >= 0) - { - EditPerson *person = new EditPerson(this); - person->setPerson(currPerson.id); - } -} - - -void SyntilistaMainWindow::on_tableview_People_doubleClicked(const QModelIndex &curr) -{ - int row = curr.row(); - if (row >= 0) - { - const QAbstractItemModel *model = curr.model(); - setActivePerson(model->data(model->index(row, 0)).toInt()); - - EditPerson *person = new EditPerson(this); - person->setPerson(currPerson.id); - } - else - setActivePerson(-1); -} - - -void SyntilistaMainWindow::on_button_ClearFilter_clicked() -{ - ui->edit_PersonFilter->clear(); - ui->edit_PersonFilter->setFocus(Qt::ShortcutFocusReason); -} - - -void SyntilistaMainWindow::focusDebtEdit() -{ - if (currPerson.id >= 0) - ui->edit_Amount->setFocus(Qt::ShortcutFocusReason); -} - - -void SyntilistaMainWindow::selectRowPrev() -{ - QItemSelectionModel *sel = ui->tableview_People->selectionModel(); - int row = sel->currentIndex().row() - 1; - if (row < 0) - row = 0; - - sel->setCurrentIndex(model_People->index(row, 0), - QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows); -} - - -void SyntilistaMainWindow::selectRowNext() -{ - QItemSelectionModel *sel = ui->tableview_People->selectionModel(); - int row = sel->currentIndex().row() + 1; - if (row >= model_People->rowCount()) - row = model_People->rowCount() - 1; - - sel->setCurrentIndex(model_People->index(row, 0), - QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows); -} - - -// -// Update visible person list/query based on the current -// filtering and sorting settings. -// -void SyntilistaMainWindow::updatePersonList() -{ - static QString queryBase = - "SELECT id,last_name,first_name," - "(SELECT SUM(value) FROM transactions WHERE transactions.person=people.id) AS balance," - "updated FROM people"; - - QSqlQuery query; - QString queryOrderDir, queryOrderBy; - - // Sort order - if (peopleSortOrder == Qt::AscendingOrder) - queryOrderDir = QStringLiteral("ASC"); - else - queryOrderDir = QStringLiteral("DESC"); - - // Sort by which column - switch (peopleSortIndex) - { - case 1: - case 2: - queryOrderBy = QStringLiteral(" ORDER BY last_name ") + queryOrderDir + QStringLiteral(",first_name ") + queryOrderDir; - break; - - case 3: - queryOrderBy = QStringLiteral(" ORDER BY balance ") + queryOrderDir; - break; - - case 4: - queryOrderBy = QStringLiteral(" ORDER BY updated ") + queryOrderDir; - break; - - default: - queryOrderBy = ""; - } - - // Are we filtering or not? - if (peopleFilter != "") - { - // Filter by name(s) - QString tmp = "%"+ peopleFilter +"%"; - query.prepare(queryBase +" WHERE first_name LIKE ? OR last_name LIKE ?" + queryOrderBy); - - query.addBindValue(tmp); - query.addBindValue(tmp); - } - else - { - // No filter - query.prepare(queryBase + queryOrderBy); - } - - // Execute the query and update model - checkAndReportSQLError("updatePersonList() before exec", query.lastError()); - query.exec(); - checkAndReportSQLError("updatePersonList() after exec", query.lastError()); - - model_People->setQuery(query); - - model_People->setHeaderData(0, Qt::Horizontal, tr("ID")); - model_People->setHeaderData(1, Qt::Horizontal, tr("Sukunimi")); - model_People->setHeaderData(2, Qt::Horizontal, tr("Etunimi")); - model_People->setHeaderData(3, Qt::Horizontal, tr("Tase")); - model_People->setHeaderData(4, Qt::Horizontal, tr("Muutettu")); -} - - -// -// Update the list of people when filter parameter changes -// -void SyntilistaMainWindow::on_edit_PersonFilter_textChanged(const QString &str) -{ - peopleFilter = cleanupStr(str); - updatePersonList(); -} - - -// -// Add one transaction to given person id -// -int SyntilistaMainWindow::addTransaction(qint64 id, double value, PersonInfo &info) -{ - if (!getPersonInfo(id, info)) - return -1; - - QSqlDatabase::database().transaction(); - - QSqlQuery query; - query.prepare("INSERT INTO transactions (person,value,added) VALUES (?,?,?)"); - query.addBindValue(id); - query.addBindValue(value); - query.addBindValue(QDateTime::currentDateTimeUtc()); - query.exec(); - if (!checkAndReportSQLError("addTransaction()", query.lastError())) - { - QSqlDatabase::database().rollback(); - return -2; - } - - query.prepare("UPDATE people SET updated=? WHERE id=?"); - query.addBindValue(QDateTime::currentDateTimeUtc()); - query.addBindValue(id); - query.exec(); - if (!checkAndReportSQLError("addTransaction update timestamp", query.lastError())) - { - QSqlDatabase::database().rollback(); - return -3; - } - - QSqlDatabase::database().commit(); - - return 0; -} - - -int SyntilistaMainWindow::addTransactionGUI(qint64 id, bool debt, double value) -{ - PersonInfo info; - - // Check if person is selected - if (id <= 0) - return -1; - - // Check value - if (value == 0) - { - QString tmp = (debt ? "lisätty" : "vähennetty"); - statusMsg("Velkaa ei "+ tmp +" koska summaa ei määritetty."); - return 1; - } - - // Perform transaction insert - int ret = addTransaction(id, debt ? -value : value, info); - if (ret == 0) - { - // All ok, clear amount entry and update person data - ui->edit_Amount->clear(); - if (info.id == currPerson.id) - setActivePerson(info.id); - - model_People->updateModel(); - - QString str; - if (debt) - { - str = tr("Lisättiin velkaa %1 EUR henkilölle '%2 %3' (#%4)."). - arg(moneyValueToStr(value)). - arg(info.firstName). - arg(info.lastName). - arg(info.id); - } - else - { - str = tr("Vähennettiin velkaa %1 EUR henkilöltä '%2 %3' (#%4)."). - arg(moneyValueToStr(value)). - arg(info.firstName). - arg(info.lastName). - arg(info.id); - } - statusMsg(str); - } - else - { - errorMsg( - tr("SQL-tietokantavirhe"), - tr("Tietokantaan tapahtumaa lisättäessa tapahtui virhe #%1."). - arg(ret)); - } - - return ret; -} - - -void SyntilistaMainWindow::on_button_AddDebt_clicked() -{ - addTransactionGUI(currPerson.id, true, moneyStrToValue(ui->edit_Amount->text())); -} - - -void SyntilistaMainWindow::on_button_PayDebt_clicked() -{ - addTransactionGUI(currPerson.id, false, moneyStrToValue(ui->edit_Amount->text())); -} - - -void SyntilistaMainWindow::on_button_PayFullDebt_clicked() -{ - if (currPerson.balance < 0) - addTransactionGUI(currPerson.id, false, -currPerson.balance); - else - { - statusMsg( - tr("Valitulla henkilöllä '%1, %2' ei ole velkaa."). - arg(currPerson.lastName). - arg(currPerson.firstName)); - } -} - - -// -// Edit person dialog -// -EditPerson::EditPerson(QWidget *parent) : - QDialog(parent), - ui(new Ui::EditPerson) -{ - ui->setupUi(this); - - setCommonStyleSheet(this); - - setModal(true); - setAttribute(Qt::WA_DeleteOnClose); - show(); - activateWindow(); - raise(); - setFocus(); - - model_Transactions = new TransactionSQLModel(); - ui->tableview_Transactions->setModel(model_Transactions); - ui->tableview_Transactions->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_Transactions)); - ui->tableview_Transactions->verticalHeader()->setVisible(false); - ui->tableview_Transactions->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - - validateForm(); -} - - -EditPerson::~EditPerson() -{ - delete ui; - delete model_Transactions; -} - - -void EditPerson::statusMsg(const QString &msg) -{ - dynamic_cast(parent())->statusMsg(msg); -} - - -bool EditPerson::validateForm() -{ - selPerson.firstName = cleanupStr(ui->edit_FirstName->text()); - selPerson.lastName = cleanupStr(ui->edit_LastName->text()); - selPerson.extraInfo = ui->textedit_ExtraInfo->document()->toPlainText(); - - ui->edit_FirstName->setStyleSheet(selPerson.firstName == "" ? "background-color: red;" : NULL); - ui->edit_LastName->setStyleSheet(selPerson.lastName == "" ? "background-color: red;" : NULL); - - return selPerson.firstName != "" && selPerson.lastName != ""; -} - - -void EditPerson::on_button_Cancel_clicked() -{ - close(); -} - - -void EditPerson::on_button_OK_clicked() -{ - if (!validateForm()) - { - errorMsg( - tr("Virhe!"), - tr("Vaaditut kentät (etunimi, sukunimi) eivät ole täytetty.")); - - return; - } - - if (selPerson.id >= 0) - { - QSqlQuery person; - person.prepare("SELECT * FROM people WHERE id <> ? AND first_name=? AND last_name=?"); - person.addBindValue(selPerson.id); - person.addBindValue(selPerson.firstName); - person.addBindValue(selPerson.lastName); - person.exec(); - - checkAndReportSQLError("SELECT check for existing person by same name (UPDATE)", person.lastError()); - - if (person.next()) - { - errorMsg( - tr("Virhe!"), - tr("Ei pysty! Samalla nimellä '%1 %2' on olemassa jo henkilö!"). - arg(selPerson.firstName).arg(selPerson.lastName)); - return; - } - - dynamic_cast(parent())->model_People->updatePerson(selPerson); - dynamic_cast(parent())->setActivePerson(selPerson.id); - - statusMsg(tr("Päivitettiin henkilö '%1 %2' (#%3)."). - arg(selPerson.firstName).arg(selPerson.lastName).arg(selPerson.id)); - } - else - { - QSqlQuery person; - person.prepare("SELECT * FROM people WHERE first_name=? AND last_name=?"); - person.addBindValue(selPerson.firstName); - person.addBindValue(selPerson.lastName); - person.exec(); - - checkAndReportSQLError("SELECT check for existing person by same name (ADD)", person.lastError()); - - if (person.next()) - { - errorMsg( - tr("Virhe!"), - tr("Ei pysty! Samalla nimellä '%1 %2' on olemassa jo henkilö!"). - arg(selPerson.firstName).arg(selPerson.lastName)); - - return; - } - - dynamic_cast(parent())->model_People->addPerson(selPerson); - dynamic_cast(parent())->updatePersonList(); - - statusMsg(tr("Lisättiin uusi henkilö '%1 %2'."). - arg(selPerson.firstName).arg(selPerson.lastName)); - } - - close(); -} - - -void EditPerson::on_edit_FirstName_textChanged(const QString &arg1) -{ - (void) arg1; - validateForm(); -} - - -void EditPerson::on_edit_LastName_textChanged(const QString &arg1) -{ - (void) arg1; - validateForm(); -} - - -void EditPerson::clearForm() -{ - ui->edit_FirstName->clear(); - ui->edit_LastName->clear(); - ui->textedit_ExtraInfo->document()->clear(); - ui->edit_FirstName->setFocus(); -} - - -void EditPerson::setPerson(qint64 id) -{ - selPerson.id = id; - - if (id >= 0) - { - PersonInfo pinfo; - if (!getPersonInfo(id, pinfo)) - { - statusMsg(tr("Virhe! Ei henkilöä ID:llä #%1").arg(id)); - } - else - { - ui->edit_FirstName->setText(pinfo.firstName); - ui->edit_LastName->setText(pinfo.lastName); - ui->textedit_ExtraInfo->document()->setPlainText(pinfo.extraInfo); - - QSqlQuery query; - query.prepare("SELECT id,value,added FROM transactions WHERE person=? ORDER BY added DESC"); - query.addBindValue(pinfo.id); - query.exec(); - checkAndReportSQLError("SELECT transactions for tableview_Transactions", query.lastError()); - - model_Transactions->setQuery(query); - - model_Transactions->setHeaderData(0, Qt::Horizontal, tr("ID")); - model_Transactions->setHeaderData(1, Qt::Horizontal, tr("Summa")); - model_Transactions->setHeaderData(2, Qt::Horizontal, tr("Aika")); - - ui->tableview_Transactions->setModel(model_Transactions); - ui->tableview_Transactions->setColumnHidden(0, true); - - return; // Ugly - } - } - - // In case of id < 0 or errors .. - clearForm(); - ui->tableview_Transactions->setModel(NULL); -} - - -// -// Custom SQL models -// -PersonSQLModel::PersonSQLModel(QObject *parent) : QSqlQueryModel(parent) -{ -} - - -QVariant PersonSQLModel::data(const QModelIndex &index, int role) const -{ - QVariant value = QSqlQueryModel::data(index, role); - - if (value.isValid() && role == Qt::DisplayRole) - { - switch (index.column()) - { - case 3: - return moneyValueToStr(value.toDouble()); - - case 4: - return dateTimeToStr(value.toDateTime()); - } - } - - if (index.column() == 3 && role == Qt::ForegroundRole) - { - double val = QSqlQueryModel::data(index, Qt::DisplayRole).toDouble(); - if (val < 0) - return QVariant::fromValue(QColor(Qt::red)); - else - return QVariant::fromValue(QColor(Qt::green)); - } - - return value; -} - - -int PersonSQLModel::updatePerson(const PersonInfo &info) -{ - QSqlQuery np; - - np.prepare("UPDATE people SET first_name=?,last_name=?,extra_info=?,updated=? WHERE id=?"); - np.addBindValue(info.firstName); - np.addBindValue(info.lastName); - np.addBindValue(info.extraInfo); - np.addBindValue(QDateTime::currentDateTimeUtc()); - np.addBindValue(info.id); - np.exec(); - - if (!checkAndReportSQLError("PersonSQLModel::updatePerson()", np.lastError())) - return -1; - - QSqlDatabase::database().commit(); - - updateModel(); - return 0; -} - - -int PersonSQLModel::addPerson(const PersonInfo &info) -{ -// beginInsertRows(QModelIndex(), rowCount(), rowCount()); - - QSqlQuery np; - np.prepare("INSERT INTO people (first_name,last_name,extra_info,added,updated) VALUES (?,?,?,?,?)"); - np.addBindValue(info.firstName); - np.addBindValue(info.lastName); - np.addBindValue(info.extraInfo); - np.addBindValue(QDateTime::currentDateTimeUtc()); - np.addBindValue(QDateTime::currentDateTimeUtc()); - np.exec(); - - if (!checkAndReportSQLError("PersonSQLModel::addPerson()", np.lastError())) - return -1; - - QSqlDatabase::database().commit(); - -// endInsertRows(); - updateModel(); - return 0; -} - - -int PersonSQLModel::deletePerson(qint64 id) -{ - QSqlDatabase::database().transaction(); - QSqlQuery del; - - del.prepare("DELETE FROM people WHERE id=?"); - del.addBindValue(id); - del.exec(); - - if (!checkAndReportSQLError("delete user", del.lastError())) - { - QSqlDatabase::database().rollback(); - return -1; - } - - del.prepare("DELETE FROM transactions WHERE person=?"); - del.addBindValue(id); - del.exec(); - - if (!checkAndReportSQLError("delete user transactions", del.lastError())) - { - QSqlDatabase::database().rollback(); - return -2; - } - - QSqlDatabase::database().commit(); - updateModel(); - return 0; -} - - -void PersonSQLModel::updateModel() -{ - query().exec(); - emit dataChanged(index(0, 0), index(rowCount(), columnCount())); -} - - -TransactionSQLModel::TransactionSQLModel(QObject *parent) : QSqlQueryModel(parent) -{ -} - - -QVariant TransactionSQLModel::data(const QModelIndex &index, int role) const -{ - QVariant value = QSqlQueryModel::data(index, role); - - if (value.isValid() && role == Qt::DisplayRole) - { - switch (index.column()) - { - case 1: - return moneyValueToStr(value.toDouble()); - - case 2: - return dateTimeToStr(value.toDateTime()); - } - } - - if (index.column() == 1 && role == Qt::ForegroundRole) - { - double val = QSqlQueryModel::data(index, Qt::DisplayRole).toDouble(); - if (val < 0) - return QVariant::fromValue(QColor(Qt::red)); - else - return QVariant::fromValue(QColor(Qt::green)); - } - - return value; -} - - -void TransactionSQLModel::updateModel() -{ - query().exec(); - emit dataChanged(QModelIndex(), QModelIndex()); -} diff -r f48b8fc1de64 -r c8fd927cd2c4 main.h --- a/main.h Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,197 +0,0 @@ -// -// Syntilista - velkalistasovellus Kampus-kahvilaan -// Programmed and designed by Matti Hämäläinen -// (C) Copyright 2017 Tecnic Software productions (TNSP) -// -// Distributed under 3-clause BSD style license, refer to -// included file "COPYING" for exact terms. -// -#ifndef SYNTILISTA_H -#define SYNTILISTA_H - -#include -#include -#include -#include -#include - - -// -// Global application defines -// -#define APP_VENDOR "TNSP" // Vendor ID (for settings, etc.) -#define APP_ID "Kampus Syntilista" // Application ID (for settings) -#define APP_NAME "Café Kampus Syntilista" // Application title/name -#define APP_SQLITE_FILE "syntilista.sqlite3" // SQLite3 database file name (without path) - - -// -// Custom SQL models -// -class PersonInfo : public QObject -{ - Q_OBJECT - -public: - explicit PersonInfo() - { - id = -1; - firstName = ""; - lastName = ""; - extraInfo = ""; - balance = 0; - } - - ~PersonInfo() - { - } - - void dump(); - - qint64 id; - QString firstName, lastName, extraInfo; - double balance; - QDateTime added, updated; -}; - - - -class PersonSQLModel : public QSqlQueryModel -{ - Q_OBJECT - -private: - -public: - PersonSQLModel(QObject *parent = 0); - - QVariant data(const QModelIndex &item, int role) const Q_DECL_OVERRIDE; - - int updatePerson(const PersonInfo &person); - int addPerson(const PersonInfo &person); - int deletePerson(qint64 id); - void updateModel(); -}; - - - -class TransactionSQLModel : public QSqlQueryModel -{ - Q_OBJECT - -private: - -public: - TransactionSQLModel(QObject *parent = 0); - - QVariant data(const QModelIndex &item, int role) const Q_DECL_OVERRIDE; - - void updateModel(); -}; - - - -// -// Main window -// -namespace Ui { -class SyntilistaMainWindow; -class EditPerson; -} - -class SyntilistaMainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit SyntilistaMainWindow(QWidget *parent = 0); - ~SyntilistaMainWindow(); - - void statusMsg(const QString &msg); - - void readSettings(); - void saveSettings(); - void setActivePerson(qint64 id); - int addTransaction(qint64 id, double value, PersonInfo &info); - int addTransactionGUI(qint64 id, bool debt, double value); - void updatePersonList(); - - PersonSQLModel *model_People; - -private slots: - void on_button_AddPerson_clicked(); - void on_button_EditPerson_clicked(); - void on_button_DeletePerson_clicked(); - - void on_edit_PersonFilter_textChanged(const QString &arg1); - void on_button_ClearFilter_clicked(); - - void on_button_Quit_clicked(); - void on_button_About_clicked(); - void on_button_Help_clicked(); - - void on_button_AddDebt_clicked(); - void on_button_PayDebt_clicked(); - void on_button_PayFullDebt_clicked(); - - void on_tableview_People_doubleClicked(const QModelIndex &index); - - void selectedPersonChanged(const QModelIndex &, const QModelIndex &); - - void focusDebtEdit(); - void selectRowPrev(); - void selectRowNext(); - - void changeUIZoomIn(); - void changeUIZoomOut(); - void changeUIZoomReset(); - - void updateSortOrder(int index, Qt::SortOrder order); - - -private: - Ui::SyntilistaMainWindow *ui; - - TransactionSQLModel *model_Latest; - PersonInfo currPerson; - - int peopleSortIndex; - Qt::SortOrder peopleSortOrder; - QString peopleFilter; -}; - - -// -// Person edit / new person dialog -// -class EditPerson : public QDialog -{ - Q_OBJECT - -public: - explicit EditPerson(QWidget *parent = 0); - ~EditPerson(); - - void statusMsg(const QString &msg); - - void clearForm(); - bool validateForm(); - void setPerson(qint64 id); - -private slots: - void on_button_OK_clicked(); - - void on_button_Cancel_clicked(); - - void on_edit_FirstName_textChanged(const QString &arg1); - - void on_edit_LastName_textChanged(const QString &arg1); - -private: - Ui::EditPerson *ui; - - PersonInfo selPerson; - TransactionSQLModel *model_Transactions; -}; - -#endif // SYNTILISTA_H diff -r f48b8fc1de64 -r c8fd927cd2c4 mainwindow.ui --- a/mainwindow.ui Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,309 +0,0 @@ - - - SyntilistaMainWindow - - - - 0 - 0 - 835 - 646 - - - - - - - - Henkilöt - - - - - - - - Etsi / suodata - - - - - - - - - - Tyhjennä suodatin - - - - - - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - - - - - - Poista henkilö - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Lisää uusi henkilö - - - - - - - Muokkaa henkilöä - - - - - - - - - - - - QLayout::SetMinimumSize - - - - - true - - - Henkilön syntilista - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - false - - - - QLayout::SetMinimumSize - - - - - Henkilön nimi - - - - - - - 4 - - - Qt::Horizontal - - - - - - - - - Nykyinen tase: - - - - - - - 12345 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - EUR - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - 4 - - - Qt::Horizontal - - - - - - - - - - Qt::AlignCenter - - - - - - - - - Lisää velkaa - - - - - - - Maksa velkaa - - - - - - - - - Maksa koko velka - - - - - - - Viimeisimmät tapahtumat: - - - - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - - - - - - - - 0 - 0 - - - - - - - - - - - QLayout::SetMinimumSize - - - - - - - - - :/img/icon-64.png:/img/icon-64.png - - - - 32 - 32 - - - - - - - - ? - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Poistu ohjelmasta - - - - - - - - - - - - - edit_PersonFilter - button_ClearFilter - button_AddPerson - button_Quit - - - - - - diff -r f48b8fc1de64 -r c8fd927cd2c4 resources.qrc --- a/resources.qrc Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ - - - logo.png - icon-64.png - - diff -r f48b8fc1de64 -r c8fd927cd2c4 src/editperson.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/editperson.ui Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,120 @@ + + + EditPerson + + + Qt::NonModal + + + + 0 + 0 + 564 + 500 + + + + Muokkaa henkilöä + + + + + + Henkilön perustiedot + + + + + + Etunimi + + + + + + + Sukunimi + + + + + + + + + + + + + Lisätietoja: + + + + + + + + + + + + + Tapahtumat: + + + + + + Qt::DefaultContextMenu + + + QAbstractItemView::SelectRows + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Peruuta + + + + + + + Talleta / OK + + + + + + + + + edit_FirstName + edit_LastName + textedit_ExtraInfo + button_OK + button_Cancel + + + + diff -r f48b8fc1de64 -r c8fd927cd2c4 src/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cpp Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,1189 @@ +// +// Syntilista - velkalistasovellus Kampus-kahvilaan +// Programmed and designed by Matti Hämäläinen +// (C) Copyright 2017 Tecnic Software productions (TNSP) +// +// Distributed under 3-clause BSD style license, refer to +// included file "COPYING" for exact terms. +// +#include +#include +#include +#include "main.h" +#include "ui_mainwindow.h" +#include "ui_editperson.h" + +double setScale; + + +int errorMsg(QString title, QString msg) +{ + QMessageBox dlg; + + dlg.setText(title); + dlg.setInformativeText(msg); + dlg.setTextFormat(Qt::RichText); + dlg.setIcon(QMessageBox::Critical); + dlg.setStandardButtons(QMessageBox::Ok); + dlg.setDefaultButton(QMessageBox::Ok); + + return dlg.exec(); +} + + +double moneyStrToValue(const QString &str) +{ + QString str2 = str; + return str2.replace(",", ".").toDouble(); +} + + +QString moneyValueToStr(double val) +{ + return QStringLiteral("%1").arg(val, 1, 'f', 2); +} + + +QString cleanupStr(const QString &str) +{ + return str.simplified().trimmed(); +} + + +const QString dateTimeToStr(const QDateTime &val) +{ + QDateTime tmp = val; + tmp.setOffsetFromUtc(0); + return tmp.toLocalTime().toString(QStringLiteral("yyyy-MM-dd hh:mm")); +} + + +bool checkAndReportSQLError(const QString where, const QSqlError &err) +{ + if (err.isValid()) + { + printf("SQL Error in %s: %s\n", + where.toUtf8().constData(), + err.text().toUtf8().constData()); + return false; + } + else + return true; +} + + +void PersonInfo::dump() +{ + printf("PersonInfo() #%lld '%s %s' (added=%s, updated=%s, balance %1.2f)\n#%s#\n", + id, + firstName.toUtf8().constData(), + lastName.toUtf8().constData(), + dateTimeToStr(added).toUtf8().constData(), + dateTimeToStr(updated).toUtf8().constData(), + balance, + extraInfo.toUtf8().constData()); +} + + +// +// Get PersonInfo record from SQL database for specified persn ID # +// +bool getPersonInfo(qint64 id, PersonInfo &info) +{ + QSqlQuery person; + person.prepare( + "SELECT id,first_name,last_name,extra_info,added,updated, " + "(SELECT SUM(value) FROM transactions WHERE transactions.person=people.id) AS balance " + "FROM people WHERE id=?"); + + person.addBindValue(id); + person.exec(); + if (!person.next()) + return false; + + info.id = person.value(0).toInt(); + info.firstName = person.value(1).toString(); + info.lastName = person.value(2).toString(); + info.extraInfo = person.value(3).toString(); + info.added = person.value(4).toDateTime(); + info.updated = person.value(5).toDateTime(); + info.balance = person.value(6).toDouble(); + + person.finish(); + + return true; +} + + +void setCommonStyleSheet(QWidget *widget) +{ + // Clamp scale value + if (setScale < 0.5f) + setScale = 0.5f; + if (setScale > 3.0f) + setScale = 3.0f; + + // Set the stylesheet + widget->setStyleSheet( + QStringLiteral( + "* { font-size: %1pt; }" + "QPushButton { font-size: %2pt; padding: 0.25em; }" + "#button_AddDebt[enabled='true'] { font-size: %3pt; background-color: #900; color: white; }" + "#button_PayDebt[enabled='true'] { font-size: %3pt; background-color: #090; color: white; }" + "#button_PayFullDebt[enabled='true'] { background-color: #060; color: white; }" + + "#button_AddDebt[enabled='false'] { font-size: %3pt; background-color: #622; color: black; }" + "#button_PayDebt[enabled='false'] { font-size: %3pt; background-color: #262; color: black; }" + "#button_PayFullDebt[enabled='false'] { background-color: #131; color: black; }" + + "#label_PersonName { font-size: %5pt; font-weight: bold; }" + "#label_BalanceValue { font-size: %4pt; font-weight: bold; }" + "#label_EUR { font-size: %4pt; font-weight: bold; }" + "#edit_Amount { font-size: %4pt; margin: 0.5em; padding: 0.5em; }" + ). + arg(12 * setScale). + arg(14 * setScale). + arg(16 * setScale). + arg(18 * setScale). + arg(20 * setScale) + ); +} + + +int main(int argc, char *argv[]) +{ + QApplication sapp(argc, argv); + + // + // Initialize / open SQL database connection + // + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName(qApp->applicationDirPath() + + QDir::separator() + APP_SQLITE_FILE); + + if (!db.open()) + { + errorMsg( + QObject::tr("Tietokantaa ei voitu avata"), + QObject::tr("Yhteyttä SQL-tietokantaan ei saatu.

Virhe: %1

"). + arg(db.lastError().text()) + ); + return 1; + } + + QSqlQuery query; + if (!db.tables().contains("people")) + { + query.exec(QStringLiteral( + "CREATE TABLE people (id INTEGER PRIMARY KEY, " + "first_name VARCHAR(128) NOT NULL, " + "last_name VARCHAR(128) NOT NULL, " + "extra_info VARCHAR(2048), " + "added DATETIME NOT NULL, " + "updated DATETIME NOT NULL)")); + + checkAndReportSQLError("CREATE TABLE people", query.lastError()); + } + + if (!db.tables().contains("transactions")) + { + query.exec(QStringLiteral( + "CREATE TABLE transactions (" + "id INTEGER PRIMARY KEY, " + "person INT NOT NULL, " + "value REAL, " + "added DATETIME NOT NULL)")); + + checkAndReportSQLError("CREATE TABLE transactions", query.lastError()); + } + + SyntilistaMainWindow swin; + swin.show(); + return sapp.exec(); +} + + +// +// Main application window code +// +SyntilistaMainWindow::SyntilistaMainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::SyntilistaMainWindow) +{ + // Setup UI + ui->setupUi(this); + + // Read config + readSettings(); + + // Setup other UI things + setWindowIcon(QIcon(QPixmap(":/img/icon-64.png"))); + setWindowTitle(tr("%1 versio %3"). + arg(tr(APP_NAME)). + arg(tr(APP_VERSION))); + + QPixmap logoImage(":/img/logo.png"); + ui->button_LogoImage->setPixmap(logoImage); + ui->button_LogoImage->setAlignment(Qt::AlignCenter); + + setCommonStyleSheet(this); + + // Validator for amount input + //ui->edit_Amount->setValidator(new QDoubleValidator(0, 1000, 2, this)); + QRegExp vregex("\\d{0,4}[,.]\\d{0,2}|\\d{0,4}"); + ui->edit_Amount->setValidator(new QRegExpValidator(vregex, this)); + + // Setup person list filtering and sorting + peopleSortIndex = 1; + peopleSortOrder = Qt::AscendingOrder; + peopleFilter = ""; + + model_People = new PersonSQLModel(); + updatePersonList(); + + ui->tableview_People->setModel(model_People); + ui->tableview_People->setColumnHidden(0, true); + ui->tableview_People->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_People)); + ui->tableview_People->verticalHeader()->setVisible(false); + ui->tableview_People->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + ui->tableview_People->setSortingEnabled(true); + ui->tableview_People->sortByColumn(peopleSortIndex, peopleSortOrder); + + connect( + ui->tableview_People->selectionModel(), + SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), + this, + SLOT(selectedPersonChanged(const QModelIndex &, const QModelIndex &))); + + connect( + ui->tableview_People->horizontalHeader(), + SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), + this, + SLOT(updateSortOrder(int, Qt::SortOrder))); + + ui->tableview_People->horizontalHeader()->setSortIndicator(1, Qt::AscendingOrder); + + model_Latest = new TransactionSQLModel(); + ui->tableview_Latest->setModel(model_Latest); + ui->tableview_Latest->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_Latest)); + ui->tableview_Latest->verticalHeader()->setVisible(false); + ui->tableview_Latest->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + setActivePerson(-1); + + // Keyboard shortcuts + ui->button_Quit->setShortcut(QKeySequence(Qt::Key_F10)); + new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this, SLOT(on_button_Quit_clicked())); + + ui->button_AddPerson->setShortcut(QKeySequence(Qt::Key_F5)); + ui->button_DeletePerson->setShortcut(QKeySequence(Qt::Key_F8)); + ui->button_EditPerson->setShortcut(QKeySequence(Qt::Key_F6)); + ui->button_ClearFilter->setShortcut(QKeySequence(Qt::Key_Escape)); + ui->button_Help->setShortcut(QKeySequence(Qt::Key_F1)); + ui->button_About->setShortcut(QKeySequence(Qt::Key_F2)); + + new QShortcut(QKeySequence(QKeySequence::ZoomIn), this, SLOT(changeUIZoomIn())); + new QShortcut(QKeySequence(QKeySequence::ZoomOut), this, SLOT(changeUIZoomOut())); + new QShortcut(QKeySequence(Qt::CTRL + Qt::KeypadModifier + Qt::Key_Plus), this, SLOT(changeUIZoomIn())); + new QShortcut(QKeySequence(Qt::CTRL + Qt::KeypadModifier + Qt::Key_Minus), this, SLOT(changeUIZoomOut())); + new QShortcut(QKeySequence(Qt::CTRL + Qt::KeypadModifier + Qt::Key_0), this, SLOT(changeUIZoomReset())); + new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_0), this, SLOT(changeUIZoomReset())); + + new QShortcut(QKeySequence(Qt::Key_PageUp), this, SLOT(selectRowPrev())); + new QShortcut(QKeySequence(Qt::Key_PageDown), this, SLOT(selectRowNext())); + + new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(focusDebtEdit())); +} + + +SyntilistaMainWindow::~SyntilistaMainWindow() +{ + saveSettings(); + + delete ui; + delete model_People; + delete model_Latest; +} + + +void SyntilistaMainWindow::statusMsg(const QString &msg) +{ + ui->statusbar->showMessage(msg); +} + + +void SyntilistaMainWindow::readSettings() +{ + QSettings settings(APP_VENDOR, APP_ID); + move(settings.value("pos", QPoint(100, 100)).toPoint()); + resize(settings.value("size", QSize(1000, 600)).toSize()); + setScale = settings.value("scale", 1.0f).toDouble(); +} + + +void SyntilistaMainWindow::saveSettings() +{ + QSettings settings(APP_VENDOR, APP_ID); + settings.setValue("pos", pos()); + settings.setValue("size", size()); + settings.setValue("scale", setScale); +} + + +void SyntilistaMainWindow::changeUIZoomIn() +{ + setScale += 0.1f; + setCommonStyleSheet(this); +} + + +void SyntilistaMainWindow::changeUIZoomOut() +{ + setScale -= 0.1f; + setCommonStyleSheet(this); +} + + +void SyntilistaMainWindow::changeUIZoomReset() +{ + setScale = 1.0f; + setCommonStyleSheet(this); +} + + +void SyntilistaMainWindow::selectedPersonChanged(const QModelIndex &curr, const QModelIndex &prev) +{ + (void) prev; + int row = curr.row(); + if (row >= 0) + { + const QAbstractItemModel *model = curr.model(); + setActivePerson(model->data(model->index(row, 0)).toInt()); + } + else + setActivePerson(-1); +} + + +void SyntilistaMainWindow::updateSortOrder(int index, Qt::SortOrder order) +{ + peopleSortIndex = index; + peopleSortOrder = order; + updatePersonList(); +} + + +void SyntilistaMainWindow::setActivePerson(qint64 id) +{ + currPerson.id = id; + + ui->button_EditPerson->setEnabled(id >= 0); + + if (id >= 0) + { + if (!getPersonInfo(id, currPerson)) + { + statusMsg(tr("Virhe! Ei henkilöä ID:llä #%1").arg(id)); + } + else + { + ui->personGB->setEnabled(true); + ui->label_PersonName->setText(currPerson.lastName +", "+ currPerson.firstName); + + ui->label_BalanceValue->setText(moneyValueToStr(currPerson.balance)); + ui->label_BalanceValue->setStyleSheet(currPerson.balance < 0 ? "color: red;" : "color: green;"); + ui->button_PayFullDebt->setEnabled(currPerson.balance < 0); + + QSqlQuery query; + query.prepare("SELECT id,value,added FROM transactions WHERE person=? ORDER BY added DESC LIMIT 5"); + query.addBindValue(id); + query.exec(); + checkAndReportSQLError("SELECT transactions for tableview_Latest", query.lastError()); + + model_Latest->setQuery(query); + + model_Latest->setHeaderData(0, Qt::Horizontal, tr("ID")); + model_Latest->setHeaderData(1, Qt::Horizontal, tr("Summa")); + model_Latest->setHeaderData(2, Qt::Horizontal, tr("Aika")); + + ui->tableview_Latest->setModel(model_Latest); + ui->tableview_Latest->setColumnHidden(0, true); + ui->tableview_Latest->verticalHeader()->setVisible(false); + ui->tableview_Latest->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + setCommonStyleSheet(this); + return; // Ugly + } + } + + // In case of id < 0 or errors .. + ui->personGB->setEnabled(false); + ui->edit_Amount->clear(); + ui->label_BalanceValue->setText("--"); + ui->label_BalanceValue->setStyleSheet(NULL); + ui->label_PersonName->setText("???"); + ui->tableview_Latest->setModel(NULL); + setCommonStyleSheet(this); +} + + +// +// Widget slot handlers +// +void SyntilistaMainWindow::on_button_Quit_clicked() +{ + close(); +} + + +void SyntilistaMainWindow::on_button_About_clicked() +{ + QMessageBox dlg; + + setCommonStyleSheet(&dlg); + dlg.setWindowTitle(tr("Tietoja ohjelmasta")); + dlg.setTextFormat(Qt::RichText); + dlg.setIconPixmap(QPixmap(":/img/icon-64.png")); + dlg.setStandardButtons(QMessageBox::Ok); + dlg.setDefaultButton(QMessageBox::Ok); + + //dlg.setInformativeText(tr( + dlg.setText(tr( + "

%1 v%2

" + "

" + "Ohjelmoinut ja kehittänyt Matti Hämäläinen <ccr@tnsp.org>
" + "(C) Copyright 2017 Tecnic Software productions (TNSP)

" + "
" + "Kehitetty Raahen kaupungin Hanketoiminta ja Kehittäminen -yksikön " + "alaisuudessa Café Kampuksen käyttöön.
" + "

" + "

" + "Ohjelma ja sen lähdekoodi ovat uudemman BSD-tyylisen lisenssin alaisia. " + "Lue ohjelman mukana tullut tiedosto \"COPYING\" (tai \"COPYING.txt\") " + "nähdäksesi täydelliset lisenssiehdot." + "

" + ). + arg(tr(APP_NAME)). + arg(tr(APP_VERSION)) + ); + + dlg.exec(); +} + + +void SyntilistaMainWindow::on_button_Help_clicked() +{ + QMessageBox dlg; + + setCommonStyleSheet(&dlg); + dlg.setWindowTitle(tr("Tietoja ohjelmasta")); + dlg.setTextFormat(Qt::RichText); + dlg.setIconPixmap(QPixmap(":/img/icon-64.png")); + dlg.setStandardButtons(QMessageBox::Ok); + dlg.setDefaultButton(QMessageBox::Ok); + + dlg.setText(tr( + "

Pikanäppäimet

" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
F1Tämä tietoikkuna
F2Tietoja ohjelmasta
CTRL + QOhjelman lopetus
CTRL + Page UpSuurenna ohjelman tekstejä/käyttöliittymää
CTRL + Page DownPienennä ohjelman tekstejä/käyttöliittymää
EscTyhjennä 'Etsi / suodata' kenttä ja siirry siihen
CTRL + EnterSiirry summan syöttökenttään
Page UpSiirry ylös henkilölistassa
Page DownSiirry alas henkilölistassa
F5Lisää uusi henkilö
F6Muokkaa henkilöä
F8Poista henkilö
" + )); + + dlg.exec(); +} + + +void SyntilistaMainWindow::on_button_DeletePerson_clicked() +{ + if (currPerson.id <= 0) + { + statusMsg(tr("Ei valittua henkilöä!")); + return; + } + + PersonInfo info; + if (!getPersonInfo(currPerson.id, info)) + { + statusMsg(tr("Virhe! Ei henkilöä ID:llä #%1").arg(currPerson.id)); + return; + } + + QMessageBox dlg; + setCommonStyleSheet(&dlg); + dlg.setText(tr("Varmistus")); + dlg.setInformativeText( + tr("
Haluatko varmasti poistaa henkilön:
" + "
" + "'%1, %2' (ID #%3)?
" + "
" + "Tämä poistaa sekä henkilön ja hänen koko tapahtumahistoriansa PYSYVÄSTI!
"). + arg(info.lastName).arg(info.firstName).arg(info.id)); + + dlg.setTextFormat(Qt::RichText); + dlg.setIcon(QMessageBox::Question); + dlg.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + dlg.setButtonText(QMessageBox::Yes, tr("Kyllä")); + dlg.setButtonText(QMessageBox::No, tr("Ei / peruuta")); + dlg.setDefaultButton(QMessageBox::No); + + if (dlg.exec() == QMessageBox::Yes) + { + int rv = model_People->deletePerson(info.id); + updatePersonList(); + setActivePerson(-1); + if (rv != 0) + { + errorMsg(tr("SQL-tietokantavirhe"), + tr("Henkilön tietoja poistettaessa tapahtui virhe #%1."). + arg(rv)); + } + else + { + statusMsg(tr("Henkilö '%1 %2' (ID #%3) poistettu."). + arg(info.firstName).arg(info.lastName). + arg(info.id)); + } + } +} + + +void SyntilistaMainWindow::on_button_AddPerson_clicked() +{ + EditPerson *person = new EditPerson(this); + person->setPerson(-1); +} + + +void SyntilistaMainWindow::on_button_EditPerson_clicked() +{ + if (currPerson.id >= 0) + { + EditPerson *person = new EditPerson(this); + person->setPerson(currPerson.id); + } +} + + +void SyntilistaMainWindow::on_tableview_People_doubleClicked(const QModelIndex &curr) +{ + int row = curr.row(); + if (row >= 0) + { + const QAbstractItemModel *model = curr.model(); + setActivePerson(model->data(model->index(row, 0)).toInt()); + + EditPerson *person = new EditPerson(this); + person->setPerson(currPerson.id); + } + else + setActivePerson(-1); +} + + +void SyntilistaMainWindow::on_button_ClearFilter_clicked() +{ + ui->edit_PersonFilter->clear(); + ui->edit_PersonFilter->setFocus(Qt::ShortcutFocusReason); +} + + +void SyntilistaMainWindow::focusDebtEdit() +{ + if (currPerson.id >= 0) + ui->edit_Amount->setFocus(Qt::ShortcutFocusReason); +} + + +void SyntilistaMainWindow::selectRowPrev() +{ + QItemSelectionModel *sel = ui->tableview_People->selectionModel(); + int row = sel->currentIndex().row() - 1; + if (row < 0) + row = 0; + + sel->setCurrentIndex(model_People->index(row, 0), + QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows); +} + + +void SyntilistaMainWindow::selectRowNext() +{ + QItemSelectionModel *sel = ui->tableview_People->selectionModel(); + int row = sel->currentIndex().row() + 1; + if (row >= model_People->rowCount()) + row = model_People->rowCount() - 1; + + sel->setCurrentIndex(model_People->index(row, 0), + QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows); +} + + +// +// Update visible person list/query based on the current +// filtering and sorting settings. +// +void SyntilistaMainWindow::updatePersonList() +{ + static QString queryBase = + "SELECT id,last_name,first_name," + "(SELECT SUM(value) FROM transactions WHERE transactions.person=people.id) AS balance," + "updated FROM people"; + + QSqlQuery query; + QString queryOrderDir, queryOrderBy; + + // Sort order + if (peopleSortOrder == Qt::AscendingOrder) + queryOrderDir = QStringLiteral("ASC"); + else + queryOrderDir = QStringLiteral("DESC"); + + // Sort by which column + switch (peopleSortIndex) + { + case 1: + case 2: + queryOrderBy = QStringLiteral(" ORDER BY last_name ") + queryOrderDir + QStringLiteral(",first_name ") + queryOrderDir; + break; + + case 3: + queryOrderBy = QStringLiteral(" ORDER BY balance ") + queryOrderDir; + break; + + case 4: + queryOrderBy = QStringLiteral(" ORDER BY updated ") + queryOrderDir; + break; + + default: + queryOrderBy = ""; + } + + // Are we filtering or not? + if (peopleFilter != "") + { + // Filter by name(s) + QString tmp = "%"+ peopleFilter +"%"; + query.prepare(queryBase +" WHERE first_name LIKE ? OR last_name LIKE ?" + queryOrderBy); + + query.addBindValue(tmp); + query.addBindValue(tmp); + } + else + { + // No filter + query.prepare(queryBase + queryOrderBy); + } + + // Execute the query and update model + checkAndReportSQLError("updatePersonList() before exec", query.lastError()); + query.exec(); + checkAndReportSQLError("updatePersonList() after exec", query.lastError()); + + model_People->setQuery(query); + + model_People->setHeaderData(0, Qt::Horizontal, tr("ID")); + model_People->setHeaderData(1, Qt::Horizontal, tr("Sukunimi")); + model_People->setHeaderData(2, Qt::Horizontal, tr("Etunimi")); + model_People->setHeaderData(3, Qt::Horizontal, tr("Tase")); + model_People->setHeaderData(4, Qt::Horizontal, tr("Muutettu")); +} + + +// +// Update the list of people when filter parameter changes +// +void SyntilistaMainWindow::on_edit_PersonFilter_textChanged(const QString &str) +{ + peopleFilter = cleanupStr(str); + updatePersonList(); +} + + +// +// Add one transaction to given person id +// +int SyntilistaMainWindow::addTransaction(qint64 id, double value, PersonInfo &info) +{ + if (!getPersonInfo(id, info)) + return -1; + + QSqlDatabase::database().transaction(); + + QSqlQuery query; + query.prepare("INSERT INTO transactions (person,value,added) VALUES (?,?,?)"); + query.addBindValue(id); + query.addBindValue(value); + query.addBindValue(QDateTime::currentDateTimeUtc()); + query.exec(); + if (!checkAndReportSQLError("addTransaction()", query.lastError())) + { + QSqlDatabase::database().rollback(); + return -2; + } + + query.prepare("UPDATE people SET updated=? WHERE id=?"); + query.addBindValue(QDateTime::currentDateTimeUtc()); + query.addBindValue(id); + query.exec(); + if (!checkAndReportSQLError("addTransaction update timestamp", query.lastError())) + { + QSqlDatabase::database().rollback(); + return -3; + } + + QSqlDatabase::database().commit(); + + return 0; +} + + +int SyntilistaMainWindow::addTransactionGUI(qint64 id, bool debt, double value) +{ + PersonInfo info; + + // Check if person is selected + if (id <= 0) + return -1; + + // Check value + if (value == 0) + { + QString tmp = (debt ? "lisätty" : "vähennetty"); + statusMsg("Velkaa ei "+ tmp +" koska summaa ei määritetty."); + return 1; + } + + // Perform transaction insert + int ret = addTransaction(id, debt ? -value : value, info); + if (ret == 0) + { + // All ok, clear amount entry and update person data + ui->edit_Amount->clear(); + if (info.id == currPerson.id) + setActivePerson(info.id); + + model_People->updateModel(); + + QString str; + if (debt) + { + str = tr("Lisättiin velkaa %1 EUR henkilölle '%2 %3' (#%4)."). + arg(moneyValueToStr(value)). + arg(info.firstName). + arg(info.lastName). + arg(info.id); + } + else + { + str = tr("Vähennettiin velkaa %1 EUR henkilöltä '%2 %3' (#%4)."). + arg(moneyValueToStr(value)). + arg(info.firstName). + arg(info.lastName). + arg(info.id); + } + statusMsg(str); + } + else + { + errorMsg( + tr("SQL-tietokantavirhe"), + tr("Tietokantaan tapahtumaa lisättäessa tapahtui virhe #%1."). + arg(ret)); + } + + return ret; +} + + +void SyntilistaMainWindow::on_button_AddDebt_clicked() +{ + addTransactionGUI(currPerson.id, true, moneyStrToValue(ui->edit_Amount->text())); +} + + +void SyntilistaMainWindow::on_button_PayDebt_clicked() +{ + addTransactionGUI(currPerson.id, false, moneyStrToValue(ui->edit_Amount->text())); +} + + +void SyntilistaMainWindow::on_button_PayFullDebt_clicked() +{ + if (currPerson.balance < 0) + addTransactionGUI(currPerson.id, false, -currPerson.balance); + else + { + statusMsg( + tr("Valitulla henkilöllä '%1, %2' ei ole velkaa."). + arg(currPerson.lastName). + arg(currPerson.firstName)); + } +} + + +// +// Edit person dialog +// +EditPerson::EditPerson(QWidget *parent) : + QDialog(parent), + ui(new Ui::EditPerson) +{ + ui->setupUi(this); + + setCommonStyleSheet(this); + + setModal(true); + setAttribute(Qt::WA_DeleteOnClose); + show(); + activateWindow(); + raise(); + setFocus(); + + model_Transactions = new TransactionSQLModel(); + ui->tableview_Transactions->setModel(model_Transactions); + ui->tableview_Transactions->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_Transactions)); + ui->tableview_Transactions->verticalHeader()->setVisible(false); + ui->tableview_Transactions->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + validateForm(); +} + + +EditPerson::~EditPerson() +{ + delete ui; + delete model_Transactions; +} + + +void EditPerson::statusMsg(const QString &msg) +{ + dynamic_cast(parent())->statusMsg(msg); +} + + +bool EditPerson::validateForm() +{ + selPerson.firstName = cleanupStr(ui->edit_FirstName->text()); + selPerson.lastName = cleanupStr(ui->edit_LastName->text()); + selPerson.extraInfo = ui->textedit_ExtraInfo->document()->toPlainText(); + + ui->edit_FirstName->setStyleSheet(selPerson.firstName == "" ? "background-color: red;" : NULL); + ui->edit_LastName->setStyleSheet(selPerson.lastName == "" ? "background-color: red;" : NULL); + + return selPerson.firstName != "" && selPerson.lastName != ""; +} + + +void EditPerson::on_button_Cancel_clicked() +{ + close(); +} + + +void EditPerson::on_button_OK_clicked() +{ + if (!validateForm()) + { + errorMsg( + tr("Virhe!"), + tr("Vaaditut kentät (etunimi, sukunimi) eivät ole täytetty.")); + + return; + } + + if (selPerson.id >= 0) + { + QSqlQuery person; + person.prepare("SELECT * FROM people WHERE id <> ? AND first_name=? AND last_name=?"); + person.addBindValue(selPerson.id); + person.addBindValue(selPerson.firstName); + person.addBindValue(selPerson.lastName); + person.exec(); + + checkAndReportSQLError("SELECT check for existing person by same name (UPDATE)", person.lastError()); + + if (person.next()) + { + errorMsg( + tr("Virhe!"), + tr("Ei pysty! Samalla nimellä '%1 %2' on olemassa jo henkilö!"). + arg(selPerson.firstName).arg(selPerson.lastName)); + return; + } + + dynamic_cast(parent())->model_People->updatePerson(selPerson); + dynamic_cast(parent())->setActivePerson(selPerson.id); + + statusMsg(tr("Päivitettiin henkilö '%1 %2' (#%3)."). + arg(selPerson.firstName).arg(selPerson.lastName).arg(selPerson.id)); + } + else + { + QSqlQuery person; + person.prepare("SELECT * FROM people WHERE first_name=? AND last_name=?"); + person.addBindValue(selPerson.firstName); + person.addBindValue(selPerson.lastName); + person.exec(); + + checkAndReportSQLError("SELECT check for existing person by same name (ADD)", person.lastError()); + + if (person.next()) + { + errorMsg( + tr("Virhe!"), + tr("Ei pysty! Samalla nimellä '%1 %2' on olemassa jo henkilö!"). + arg(selPerson.firstName).arg(selPerson.lastName)); + + return; + } + + dynamic_cast(parent())->model_People->addPerson(selPerson); + dynamic_cast(parent())->updatePersonList(); + + statusMsg(tr("Lisättiin uusi henkilö '%1 %2'."). + arg(selPerson.firstName).arg(selPerson.lastName)); + } + + close(); +} + + +void EditPerson::on_edit_FirstName_textChanged(const QString &arg1) +{ + (void) arg1; + validateForm(); +} + + +void EditPerson::on_edit_LastName_textChanged(const QString &arg1) +{ + (void) arg1; + validateForm(); +} + + +void EditPerson::clearForm() +{ + ui->edit_FirstName->clear(); + ui->edit_LastName->clear(); + ui->textedit_ExtraInfo->document()->clear(); + ui->edit_FirstName->setFocus(); +} + + +void EditPerson::setPerson(qint64 id) +{ + selPerson.id = id; + + if (id >= 0) + { + PersonInfo pinfo; + if (!getPersonInfo(id, pinfo)) + { + statusMsg(tr("Virhe! Ei henkilöä ID:llä #%1").arg(id)); + } + else + { + ui->edit_FirstName->setText(pinfo.firstName); + ui->edit_LastName->setText(pinfo.lastName); + ui->textedit_ExtraInfo->document()->setPlainText(pinfo.extraInfo); + + QSqlQuery query; + query.prepare("SELECT id,value,added FROM transactions WHERE person=? ORDER BY added DESC"); + query.addBindValue(pinfo.id); + query.exec(); + checkAndReportSQLError("SELECT transactions for tableview_Transactions", query.lastError()); + + model_Transactions->setQuery(query); + + model_Transactions->setHeaderData(0, Qt::Horizontal, tr("ID")); + model_Transactions->setHeaderData(1, Qt::Horizontal, tr("Summa")); + model_Transactions->setHeaderData(2, Qt::Horizontal, tr("Aika")); + + ui->tableview_Transactions->setModel(model_Transactions); + ui->tableview_Transactions->setColumnHidden(0, true); + + return; // Ugly + } + } + + // In case of id < 0 or errors .. + clearForm(); + ui->tableview_Transactions->setModel(NULL); +} + + +// +// Custom SQL models +// +PersonSQLModel::PersonSQLModel(QObject *parent) : QSqlQueryModel(parent) +{ +} + + +QVariant PersonSQLModel::data(const QModelIndex &index, int role) const +{ + QVariant value = QSqlQueryModel::data(index, role); + + if (value.isValid() && role == Qt::DisplayRole) + { + switch (index.column()) + { + case 3: + return moneyValueToStr(value.toDouble()); + + case 4: + return dateTimeToStr(value.toDateTime()); + } + } + + if (index.column() == 3 && role == Qt::ForegroundRole) + { + double val = QSqlQueryModel::data(index, Qt::DisplayRole).toDouble(); + if (val < 0) + return QVariant::fromValue(QColor(Qt::red)); + else + return QVariant::fromValue(QColor(Qt::green)); + } + + return value; +} + + +int PersonSQLModel::updatePerson(const PersonInfo &info) +{ + QSqlQuery np; + + np.prepare("UPDATE people SET first_name=?,last_name=?,extra_info=?,updated=? WHERE id=?"); + np.addBindValue(info.firstName); + np.addBindValue(info.lastName); + np.addBindValue(info.extraInfo); + np.addBindValue(QDateTime::currentDateTimeUtc()); + np.addBindValue(info.id); + np.exec(); + + if (!checkAndReportSQLError("PersonSQLModel::updatePerson()", np.lastError())) + return -1; + + QSqlDatabase::database().commit(); + + updateModel(); + return 0; +} + + +int PersonSQLModel::addPerson(const PersonInfo &info) +{ +// beginInsertRows(QModelIndex(), rowCount(), rowCount()); + + QSqlQuery np; + np.prepare("INSERT INTO people (first_name,last_name,extra_info,added,updated) VALUES (?,?,?,?,?)"); + np.addBindValue(info.firstName); + np.addBindValue(info.lastName); + np.addBindValue(info.extraInfo); + np.addBindValue(QDateTime::currentDateTimeUtc()); + np.addBindValue(QDateTime::currentDateTimeUtc()); + np.exec(); + + if (!checkAndReportSQLError("PersonSQLModel::addPerson()", np.lastError())) + return -1; + + QSqlDatabase::database().commit(); + +// endInsertRows(); + updateModel(); + return 0; +} + + +int PersonSQLModel::deletePerson(qint64 id) +{ + QSqlDatabase::database().transaction(); + QSqlQuery del; + + del.prepare("DELETE FROM people WHERE id=?"); + del.addBindValue(id); + del.exec(); + + if (!checkAndReportSQLError("delete user", del.lastError())) + { + QSqlDatabase::database().rollback(); + return -1; + } + + del.prepare("DELETE FROM transactions WHERE person=?"); + del.addBindValue(id); + del.exec(); + + if (!checkAndReportSQLError("delete user transactions", del.lastError())) + { + QSqlDatabase::database().rollback(); + return -2; + } + + QSqlDatabase::database().commit(); + updateModel(); + return 0; +} + + +void PersonSQLModel::updateModel() +{ + query().exec(); + emit dataChanged(index(0, 0), index(rowCount(), columnCount())); +} + + +TransactionSQLModel::TransactionSQLModel(QObject *parent) : QSqlQueryModel(parent) +{ +} + + +QVariant TransactionSQLModel::data(const QModelIndex &index, int role) const +{ + QVariant value = QSqlQueryModel::data(index, role); + + if (value.isValid() && role == Qt::DisplayRole) + { + switch (index.column()) + { + case 1: + return moneyValueToStr(value.toDouble()); + + case 2: + return dateTimeToStr(value.toDateTime()); + } + } + + if (index.column() == 1 && role == Qt::ForegroundRole) + { + double val = QSqlQueryModel::data(index, Qt::DisplayRole).toDouble(); + if (val < 0) + return QVariant::fromValue(QColor(Qt::red)); + else + return QVariant::fromValue(QColor(Qt::green)); + } + + return value; +} + + +void TransactionSQLModel::updateModel() +{ + query().exec(); + emit dataChanged(QModelIndex(), QModelIndex()); +} diff -r f48b8fc1de64 -r c8fd927cd2c4 src/main.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.h Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,197 @@ +// +// Syntilista - velkalistasovellus Kampus-kahvilaan +// Programmed and designed by Matti Hämäläinen +// (C) Copyright 2017 Tecnic Software productions (TNSP) +// +// Distributed under 3-clause BSD style license, refer to +// included file "COPYING" for exact terms. +// +#ifndef SYNTILISTA_H +#define SYNTILISTA_H + +#include +#include +#include +#include +#include + + +// +// Global application defines +// +#define APP_VENDOR "TNSP" // Vendor ID (for settings, etc.) +#define APP_ID "Kampus Syntilista" // Application ID (for settings) +#define APP_NAME "Café Kampus Syntilista" // Application title/name +#define APP_SQLITE_FILE "syntilista.sqlite3" // SQLite3 database file name (without path) + + +// +// Custom SQL models +// +class PersonInfo : public QObject +{ + Q_OBJECT + +public: + explicit PersonInfo() + { + id = -1; + firstName = ""; + lastName = ""; + extraInfo = ""; + balance = 0; + } + + ~PersonInfo() + { + } + + void dump(); + + qint64 id; + QString firstName, lastName, extraInfo; + double balance; + QDateTime added, updated; +}; + + + +class PersonSQLModel : public QSqlQueryModel +{ + Q_OBJECT + +private: + +public: + PersonSQLModel(QObject *parent = 0); + + QVariant data(const QModelIndex &item, int role) const Q_DECL_OVERRIDE; + + int updatePerson(const PersonInfo &person); + int addPerson(const PersonInfo &person); + int deletePerson(qint64 id); + void updateModel(); +}; + + + +class TransactionSQLModel : public QSqlQueryModel +{ + Q_OBJECT + +private: + +public: + TransactionSQLModel(QObject *parent = 0); + + QVariant data(const QModelIndex &item, int role) const Q_DECL_OVERRIDE; + + void updateModel(); +}; + + + +// +// Main window +// +namespace Ui { +class SyntilistaMainWindow; +class EditPerson; +} + +class SyntilistaMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit SyntilistaMainWindow(QWidget *parent = 0); + ~SyntilistaMainWindow(); + + void statusMsg(const QString &msg); + + void readSettings(); + void saveSettings(); + void setActivePerson(qint64 id); + int addTransaction(qint64 id, double value, PersonInfo &info); + int addTransactionGUI(qint64 id, bool debt, double value); + void updatePersonList(); + + PersonSQLModel *model_People; + +private slots: + void on_button_AddPerson_clicked(); + void on_button_EditPerson_clicked(); + void on_button_DeletePerson_clicked(); + + void on_edit_PersonFilter_textChanged(const QString &arg1); + void on_button_ClearFilter_clicked(); + + void on_button_Quit_clicked(); + void on_button_About_clicked(); + void on_button_Help_clicked(); + + void on_button_AddDebt_clicked(); + void on_button_PayDebt_clicked(); + void on_button_PayFullDebt_clicked(); + + void on_tableview_People_doubleClicked(const QModelIndex &index); + + void selectedPersonChanged(const QModelIndex &, const QModelIndex &); + + void focusDebtEdit(); + void selectRowPrev(); + void selectRowNext(); + + void changeUIZoomIn(); + void changeUIZoomOut(); + void changeUIZoomReset(); + + void updateSortOrder(int index, Qt::SortOrder order); + + +private: + Ui::SyntilistaMainWindow *ui; + + TransactionSQLModel *model_Latest; + PersonInfo currPerson; + + int peopleSortIndex; + Qt::SortOrder peopleSortOrder; + QString peopleFilter; +}; + + +// +// Person edit / new person dialog +// +class EditPerson : public QDialog +{ + Q_OBJECT + +public: + explicit EditPerson(QWidget *parent = 0); + ~EditPerson(); + + void statusMsg(const QString &msg); + + void clearForm(); + bool validateForm(); + void setPerson(qint64 id); + +private slots: + void on_button_OK_clicked(); + + void on_button_Cancel_clicked(); + + void on_edit_FirstName_textChanged(const QString &arg1); + + void on_edit_LastName_textChanged(const QString &arg1); + +private: + Ui::EditPerson *ui; + + PersonInfo selPerson; + TransactionSQLModel *model_Transactions; +}; + +#endif // SYNTILISTA_H diff -r f48b8fc1de64 -r c8fd927cd2c4 src/mainwindow.ui --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainwindow.ui Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,309 @@ + + + SyntilistaMainWindow + + + + 0 + 0 + 835 + 646 + + + + + + + + Henkilöt + + + + + + + + Etsi / suodata + + + + + + + + + + Tyhjennä suodatin + + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + + + + + + Poista henkilö + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Lisää uusi henkilö + + + + + + + Muokkaa henkilöä + + + + + + + + + + + + QLayout::SetMinimumSize + + + + + true + + + Henkilön syntilista + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + + QLayout::SetMinimumSize + + + + + Henkilön nimi + + + + + + + 4 + + + Qt::Horizontal + + + + + + + + + Nykyinen tase: + + + + + + + 12345 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + EUR + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 4 + + + Qt::Horizontal + + + + + + + + + + Qt::AlignCenter + + + + + + + + + Lisää velkaa + + + + + + + Maksa velkaa + + + + + + + + + Maksa koko velka + + + + + + + Viimeisimmät tapahtumat: + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + + + + + + + + 0 + 0 + + + + + + + + + + + QLayout::SetMinimumSize + + + + + + + + + :/img/icon-64.png:/img/icon-64.png + + + + 32 + 32 + + + + + + + + ? + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Poistu ohjelmasta + + + + + + + + + + + + + edit_PersonFilter + button_ClearFilter + button_AddPerson + button_Quit + + + + + + diff -r f48b8fc1de64 -r c8fd927cd2c4 src/resources.qrc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/resources.qrc Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,6 @@ + + + ../img/logo.png + ../img/icon-64.png + + diff -r f48b8fc1de64 -r c8fd927cd2c4 src/winres.rc.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/winres.rc.in Mon Apr 24 12:12:39 2017 +0300 @@ -0,0 +1,20 @@ +MAINICON ICON "icon.ico" +1 VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION @APP_VERSION_COM@ +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "TNSP" + VALUE "FileDescription", "Cafe Kampus Syntilista" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "Syntilista" + VALUE "LegalCopyright", "(C) Copyright 2017 Tecnic Software productions" + VALUE "OriginalFilename", "@APP_EXE@" + VALUE "ProductName", "Syntilista" + VALUE "ProductVersion", "@APP_VERSION@" + END + END +END diff -r f48b8fc1de64 -r c8fd927cd2c4 winres.rc.in --- a/winres.rc.in Wed Apr 12 12:16:00 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -MAINICON ICON "icon.ico" -1 VERSIONINFO -FILEVERSION 1,0,0,0 -PRODUCTVERSION @APP_VERSION_COM@ -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - BEGIN - VALUE "CompanyName", "TNSP" - VALUE "FileDescription", "Cafe Kampus Syntilista" - VALUE "FileVersion", "1.0" - VALUE "InternalName", "Syntilista" - VALUE "LegalCopyright", "(C) Copyright 2017 Tecnic Software productions" - VALUE "OriginalFilename", "@APP_EXE@" - VALUE "ProductName", "Syntilista" - VALUE "ProductVersion", "@APP_VERSION@" - END - END -END