Commit 552a89ae authored by André Hoarau's avatar André Hoarau 🍆
Browse files

reorganize code (to be continued)

parent d6f44404
This files describes how to contribute to Logimaraich. Please note that theses
rules and recommendations will certainly change over time. If you have any ideas
for improving them, please file an issue or open a merge request!
# How to contribute
# Core team
# Review and merge rules
This diff is collapsed.
# Qrop
A crop planning and recordkeeping free software Available on GNU/Linux, Windows and Mac.
**Warning:** this is alpha software with known bugs. It runs, and work at least
some of the times, but use at your own risk.
![Screenshot of Logimaraich](logimaraich.png)
## Features
* **Crop planning:** build crop plans including expected yields and harvest
windows. Easily create, duplicate, delete, batch-edit, sort and filter
successions.
* **Task management and recordkeeping:** plan your tasks (weeding, seeding,
planting, stale seed bed, cultivating, etc.) and dynamically keep track of
them.
* *(Planned)* **Harvests tracking:** keep track of each harvest and get
real-time crop yields.
## Built With
* C++, QML and Javascript
* Qt5
* SQLite
## Contributing
Please read the [contribution guide](CONTRIBUTING.md) for details of our code of
conduct, and the process for submitting bug reports and merge requests to us.
## Authors
* **André Hoarau** @ah − initial coding
## Licence
This project is licensed under the GNU GPLv3 − see [LICENSE](LICENCE) for more details.
QT += sql
QT -= gui
TARGET = core
TEMPLATE = lib
CONFIG += lib c++11
DEFINES += CORE_LIBRARY
SOURCES += \
sqltaskmodel.cpp \
sqlnotemodel.cpp \
sqlplantingmodel.cpp
HEADERS += \
core_global.h \
sqltaskmodel.h \
sqlnotemodel.h \
sqlplantingmodel.h
#ifndef CORE_GLOBAL_H
#define CORE_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(CORE_LIBRARY)
# define CORESHARED_EXPORT Q_DECL_EXPORT
#else
# define CORESHARED_EXPORT Q_DECL_IMPORT
#endif
#endif // CORE_GLOBAL_H
#include <QGuiApplication>
#include <QStandardPaths>
#include <QSqlDatabase>
#include <QSqlError>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QIcon>
#include "sqlplantingmodel.h"
#include "sqltaskmodel.h"
#include "sqlnotemodel.h"
static void connectToDatabase()
{
QSqlDatabase database = QSqlDatabase::database();
if (!database.isValid()) {
database = QSqlDatabase::addDatabase("QSQLITE");
if (!database.isValid())
qFatal("Cannot add database: %s",
qPrintable(database.lastError().text()));
}
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
if (!writeDir.mkpath("."))
qFatal("Failed to create writable directory at %s",
qPrintable(writeDir.absolutePath()));
// Ensure that we have a writable location on all devices.
//const QString fileName = writeDir.absolutePath() + "/croplan.sqlite3";
const QString fileName = "/home/ah/.logimaraich/db.sqlite";
// When using the SQLite driver, open() will create the SQLite database if it doesn't exist.
database.setDatabaseName(fileName);
if (!database.open()) {
QFile::remove(fileName);
qFatal("Cannot open database: %s", qPrintable(database.lastError().text()));
}
qInfo("database open!");
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setApplicationName("Qrop");
QCoreApplication::setOrganizationName("AH");
QCoreApplication::setOrganizationDomain("io.qrop");
QGuiApplication app(argc, argv);
qmlRegisterType<SqlPlantingModel>("io.croplan.components", 1, 0, "SqlPlantingModel");
qmlRegisterType<SqlTaskModel>("io.croplan.components", 1, 0, "SqlTaskModel");
qmlRegisterType<SqlNoteModel>("io.croplan.components", 1, 0, "SqlNoteModel");
connectToDatabase();
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "sqlnotemodel.h"
#include <QDebug>
#include <QSqlRecord>
#include <QSqlError>
#include <QSqlQuery>
static const char *noteTableName = "comment";
SqlNoteModel::SqlNoteModel(QObject *parent)
: QSqlTableModel(parent)
{
m_date = QDate();
setTable(noteTableName);
setSort(2, Qt::AscendingOrder);
setEditStrategy(QSqlTableModel::OnManualSubmit);
select();
}
QDate SqlNoteModel::date() const
{
return m_date;
}
void SqlNoteModel::setDate(const QDate &date)
{
if (date == m_date)
return;
m_date = date;
const QString filterString = QString::fromLatin1(
"date_assigned = %1").arg("2018-06-16");
// "date_assigned = %1").arg(date.toString(Qt.ISODate));
setFilter(filterString);
select();
emit dateChanged();
}
QVariant SqlNoteModel::data(const QModelIndex &index, int role) const
{
if (role < Qt::UserRole)
return QSqlTableModel::data(index, role);
const QSqlRecord sqlRecord = record(index.row());
return sqlRecord.value(role - Qt::UserRole);
}
QHash<int, QByteArray> SqlNoteModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole] = "comment_id";
names[Qt::UserRole + 1] = "text";
names[Qt::UserRole + 2] = "date_modified";
return names;
}
void SqlNoteModel::addNote(const QString &content, const QDate &date)
{
QSqlRecord newRecord = record();
newRecord.setValue("text", content);
newRecord.setValue("date_modified", date);
if (!insertRecord(rowCount(), newRecord)) {
qWarning() << "Failed to send message:" << lastError().text();
return;
}
submitAll();
}
#ifndef SQLNOTEMODEL_H
#define SQLNOTEMODEL_H
#include <QSqlTableModel>
#include <QDate>
class SqlNoteModel : public QSqlTableModel
{
Q_OBJECT
Q_PROPERTY(QDate date READ date WRITE setDate NOTIFY dateChanged)
public:
SqlNoteModel(QObject *parent = 0);
QDate date() const;
void setDate(const QDate &date);
QVariant data(const QModelIndex &idx, int role) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
Q_INVOKABLE void addNote(const QString &content, const QDate &date);
signals:
void dateChanged();
private:
QDate m_date;
};
#endif // SQLNOTEMODEL_H
/*
* Copyright (C) 2018 André Hoarau <ah@ouvaton.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "sqlplantingmodel.h"
#include <QSqlRecord>
#include <QDebug>
#include <QSqlError>
#include <QSqlQuery>
#include <QDate>
static const char *plantingTableName = "planting";
SqlPlantingModel::SqlPlantingModel(QObject *parent)
: QSqlTableModel(parent)
{
setTable(plantingTableName);
for (int i = 0; i < this->record().count(); i++) {
m_rolesIndexes.insert(record().fieldName(i).toUtf8(), i);
}
// setSort(1, Qt::AscendingOrder);
setSortColumn("seeding_date", "ascending");
setEditStrategy(QSqlTableModel::OnManualSubmit);
select();
}
void SqlPlantingModel::setSortColumn(const QString fieldName, const QString order)
{
if (!m_rolesIndexes.contains(fieldName)) {
qDebug() << "m_rolesIndexes doesn't have key" << fieldName;
return;
}
qDebug() << "New sort column: " << fieldName << m_rolesIndexes[fieldName];
setSort(m_rolesIndexes[fieldName], order == "ascending" ? Qt::AscendingOrder : Qt::DescendingOrder);
select();
}
QVariant SqlPlantingModel::data(const QModelIndex &index, int role) const
{
QVariant value;
if (role < Qt::UserRole)
return QSqlTableModel::data(index, role);
const QSqlRecord sqlRecord = record(index.row());
value = sqlRecord.value(role - Qt::UserRole);
if ((Qt::UserRole + 9 <= role) && (role <= Qt::UserRole + 12))
return QDate::fromString(value.toString(), Qt::ISODate);
else
return value;
}
QString SqlPlantingModel::crop() const
{
return m_crop;
}
void SqlPlantingModel::setCrop(const QString &crop)
{
if (crop == m_crop)
return;
m_crop = crop;
if (m_crop == "") {
qInfo("null!");
setFilter("");
} else {
const QString filterString = QString::fromLatin1(
"(crop LIKE '%%%1%%')").arg(crop);
setFilter(filterString);
}
select();
emit cropChanged();
}
QHash<int, QByteArray> SqlPlantingModel::roleNames() const
{
QHash<int, QByteArray> roles;
for (int i = 0; i < this->record().count(); i ++)
roles.insert(Qt::UserRole + i, record().fieldName(i).toUtf8());
return roles;
}
#ifndef SQLPLANTINGMODEL_H
#define SQLPLANTINGMODEL_H
#include <QSqlTableModel>
class SqlPlantingModel : public QSqlTableModel
{
Q_OBJECT
Q_PROPERTY(QString crop READ crop WRITE setCrop NOTIFY cropChanged)
public:
SqlPlantingModel(QObject *parent = 0);
QString crop() const;
void setCrop(const QString &crop);
QVariant data(const QModelIndex &idx, int role) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
Q_INVOKABLE void setSortColumn(const QString fieldName, const QString order);
signals:
void cropChanged();
private:
QString m_crop;
QHash<QString, int> m_rolesIndexes;
QHash<QModelIndex, bool> m_selected;
};
#endif // SQLPLANTINGMODEL_H
#include "sqltaskmodel.h"
#include <QSqlRecord>
#include <QDebug>
#include <QSqlError>
#include <QSqlQuery>
static const char *taskTableName = "tasks_view";
SqlTaskModel::SqlTaskModel(QObject *parent)
: QSqlTableModel(parent)
{
m_date = QDate();
setTable(taskTableName);
setSort(2, Qt::AscendingOrder);
setEditStrategy(QSqlTableModel::OnManualSubmit);
// setDate(QDate::currentDate());
select();
qInfo("%d", rowCount());
}
QDate SqlTaskModel::date() const
{
return m_date;
}
void SqlTaskModel::setDate(const QDate &date)
{
if (date == m_date)
return;
m_date = date;
const QString filterString = QString::fromLatin1(
"date_assigned = %1").arg("2018-06-16");
// "date_assigned = %1").arg(date.toString(Qt.ISODate));
setFilter(filterString);
select();
emit dateChanged();
}
QVariant SqlTaskModel::data(const QModelIndex &index, int role) const
{
if (role < Qt::UserRole)
return QSqlTableModel::data(index, role);
const QSqlRecord sqlRecord = record(index.row());
return sqlRecord.value(role - Qt::UserRole);
}
QHash<int, QByteArray> SqlTaskModel::roleNames() const
{
QHash<int, QByteArray> roles;
for (int i = 0; i < this->record().count(); i ++)
roles.insert(Qt::UserRole + i, record().fieldName(i).toUtf8());
return roles;
}
#ifndef SQLTASKGMODEL_H
#define SQLTASKMODEL_H
#include <QSqlTableModel>
#include <QDate>
class SqlTaskModel : public QSqlTableModel
{
Q_OBJECT
Q_PROPERTY(QDate date READ date WRITE setDate NOTIFY dateChanged)
public:
SqlTaskModel(QObject *parent = 0);
QDate date() const;
void setDate(const QDate &date);
QVariant data(const QModelIndex &idx, int role) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
signals:
void dateChanged();
private:
QDate m_date;
};
#endif // SQLPLANTINGMODEL_H
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.1
import QtQuick.Controls.Universal 2.1
TabButton {
width: implicitWidth
icon.name: "back"
display: checked ? AbstractButton.TextBesideIcon : AbstractButton.IconOnly
}
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.2
import io.croplan.components 1.0
Page {
id: page
title: "Calendar"
padding: 8
property int rowHeight: 47
property bool filterMode: false
property string filterText: ""
property int checks: 0
property var tableHeaderModel: [
{ name: qsTr("Task"), columnName: "task", width: 100 },
{ name: qsTr("Description"), columnName: "descr", width: 100 },
{ name: qsTr("Plantings"), columnName: "planting_ids", width: 150 },
{ name: qsTr("Locations"), columnName: "place_ids", width: 80 }
]
property int tableSortColumn: 0
property string tableSortOrder: "descending"
onTableSortColumnChanged: {
var columnName = tableHeaderModel[tableSortColumn].columnName
tableSortOrder = "descending"
listView.model.setSortColumn(columnName, tableSortOrder)
}
onTableSortOrderChanged: {
var columnName = tableHeaderModel[tableSortColumn].columnName
listView.model.setSortColumn(columnName, tableSortOrder)
}
TaskDialog {
id: taskDialog
width: parent.width / 2
height: parent.height
x: (parent.width - width) / 2
}
Pane {
width: parent.width
height: parent.height
anchors.fill: parent
padding: 0
Material.elevation: 1
Rectangle {
id: buttonRectangle
color: checks > 0 ? Material.color(Material.Cyan, Material.Shade100) : "white"
visible: true
width: parent.width
height: 48
RowLayout {
id: filterRow
anchors.fill: parent
spacing: 0
visible: filterMode
TextField {
id: filterField
leftPadding: 16 + largeDisplay ? 50 : 0
font.family: "Roboto Regular"
verticalAlignment: Qt.AlignVCenter
font.pixelSize: 20
color: "black"
placeholderText: qsTr("Search")
Layout.fillWidth: true
anchors.verticalCenter: parent.verticalCenter
Shortcut {
sequence: "Escape"
onActivated: {
filterMode = false
filterField.text = ""
}
}
background: Rectangle {
anchors.verticalCenter: parent.verticalCenter
height: parent.height * 0.7
Label {
leftPadding: 16
color: "black"
anchors.verticalCenter: parent.verticalCenter
text: "\ue8b6" // search
font.family: "Material Icons"
font.pixelSize: 24
}
}
}
IconButton {
text: "\ue5cd" // delete
onClicked: {
filterMode = false
filterField.text = ""
}
}
}
RowLayout {
id: buttonRow
anchors.fill: parent
spacing: 0
visible: !filterMode
Label {
anchors.verticalCenter: parent.verticalCenter
text: checks + " task" + (checks > 1 ? "s" : "") + " selected"
leftPadding: 16
color: Material.color(Material.Blue)
Layout.fillWidth: true
visible: checks > 0
font.family: "Roboto Regular"
font.pixelSize: 16
horizontalAlignment: Qt.AlignLeft
verticalAlignment: Qt.AlignVCenter
}