Fix UTF-8 issue with CSV files opened in Excel

This commit is contained in:
Alex Spataru 2020-12-25 12:53:56 -06:00
parent d2690de4f9
commit cf839fd8e7
12 changed files with 177 additions and 82 deletions

View File

@ -42,6 +42,8 @@ Widgets.Window {
// //
Settings { Settings {
category: "Device Manager" category: "Device Manager"
property alias dmAuto: commAuto.checked
property alias dmManual: commManual.checked
property alias dmParity: parity.currentIndex property alias dmParity: parity.currentIndex
property alias dmStopBits: stopBits.currentIndex property alias dmStopBits: stopBits.currentIndex
property alias dmBaudRate: baudRate.currentIndex property alias dmBaudRate: baudRate.currentIndex

View File

@ -30,5 +30,9 @@ import Dataset 1.0
import "../Widgets" import "../Widgets"
Item { Item {
AccelerometerDelegate {
width: 250
height: 250
anchors.centerIn: parent
}
} }

View File

@ -127,15 +127,10 @@ Page {
Layout.fillHeight: true Layout.fillHeight: true
} }
Item { Components.WidgetGrid {
id: widgetGrid
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Widgets.MapDelegate {
id: gpsMap
anchors.fill: parent
anchors.margins: app.spacing * 2
}
} }
} }

View File

@ -21,7 +21,91 @@
*/ */
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Layouts 1.0
Item { import Group 1.0
import Dataset 1.0
RowLayout {
id: accel
spacing: app.spacing
//
// Custom properties
//
property int index: 0
property real accX: 0
property real accY: 0
property real accZ: 0
property string title: ""
property real meanGForce: 0
property real gConstant: 9.80665
//
// Calculates the mean g force for all three axes using the pythagorean theorem
//
function calculateMeanGForce() {
if (CppWidgets.accelerometerGroupCount() > accel.index) {
accel.accX = CppWidgets.accelerometerX(accel.index)
accel.accY = CppWidgets.accelerometerY(accel.index)
accel.accZ = CppWidgets.accelerometerZ(accel.index)
accel.title = CppWidgets.accelerometerGroupAt(accel.index).title
}
else {
accel.accX = 0
accel.accY = 0
accel.accZ = 0
accel.title = 0
}
var gX = accel.accX / accel.gConstant
var gY = accel.accY / accel.gConstant
var gZ = accel.accZ / accel.gConstant
accel.meanGForce = Math.sqrt(Math.pow(gX, 2) + Math.pow(gY, 2) + Math.pow(gZ, 2))
}
//
// Accelerometer circle
//
Rectangle {
width: height
color: "#444"
border.width: 2
radius: width / 2
border.color: "#bebebe"
Layout.fillHeight: true
}
//
// Indicator controls
//
ColumnLayout {
spacing: app.spacing
Layout.fillHeight: true
Item {
Layout.fillHeight: true
}
LED {
onColor: "#f00"
text: qsTr("Cont. value")
}
LED {
onColor: "#0f0"
text: qsTr("Max. value")
}
LED {
onColor: "#00f"
text: qsTr("Min. value")
}
Item {
Layout.fillHeight: true
}
}
} }

View File

@ -60,29 +60,6 @@ ColumnLayout {
// //
readonly property var gpsCoordinates: QtPositioning.coordinate(latitude, longitude) readonly property var gpsCoordinates: QtPositioning.coordinate(latitude, longitude)
//
// Center map when connecting with CanSat
//
Connections {
target: CppQmlBridge
function onUpdated() {
if (oldCoordinates === QtPositioning.coordinate(0,0)) {
map.center = gpsCoordinates
oldCoordinates = gpsCoordinates
}
gps.latitude = CppQmlBridge.gpsLatitude
gps.longitude = CppQmlBridge.gpsLongitude
}
} Connections {
target: CppSerialManager
function onPortChanged() {
oldCoordinates = QtPositioning.coordinate(0,0)
}
}
// //
// Centers the map to Queretaro if the GPS is not working, // Centers the map to Queretaro if the GPS is not working,
// otherwise, centers the map to the CanSat's position // otherwise, centers the map to the CanSat's position

View File

@ -106,6 +106,9 @@ ApplicationWindow {
Timer { Timer {
id: timer id: timer
interval: 500 interval: 500
onTriggered: app.visible = true onTriggered: {
app.visible = true
CppJsonParser.readSettings()
}
} }
} }

View File

@ -138,6 +138,8 @@ void Export::closeFile()
writeValues(); writeValues();
m_csvFile.close(); m_csvFile.close();
m_textStream.setDevice(Q_NULLPTR);
emit openChanged(); emit openChanged();
} }
} }
@ -228,7 +230,7 @@ void Export::writeValues()
// Open file // Open file
m_csvFile.setFileName(dir.filePath(fileName)); m_csvFile.setFileName(dir.filePath(fileName));
if (!m_csvFile.open(QFile::WriteOnly)) if (!m_csvFile.open(QIODevice::WriteOnly | QIODevice::Text))
{ {
QMessageBox::critical(Q_NULLPTR, tr("CSV File Error"), tr("Cannot open CSV file for writing!"), QMessageBox::critical(Q_NULLPTR, tr("CSV File Error"), tr("Cannot open CSV file for writing!"),
QMessageBox::Ok); QMessageBox::Ok);
@ -236,14 +238,17 @@ void Export::writeValues()
break; break;
} }
// Add cell titles // Add cell titles & force UTF-8 codec
m_textStream.setDevice(&m_csvFile);
m_textStream.setCodec("UTF-8");
m_textStream.setGenerateByteOrderMark(true);
for (int i = 0; i < titles.count(); ++i) for (int i = 0; i < titles.count(); ++i)
{ {
m_csvFile.write(titles.at(i).toUtf8()); m_textStream << titles.at(i).toUtf8();
if (i < titles.count() - 1) if (i < titles.count() - 1)
m_csvFile.write(","); m_textStream << ",";
else else
m_csvFile.write("\n"); m_textStream << "\n";
} }
// Update UI // Update UI
@ -253,11 +258,11 @@ void Export::writeValues()
// Write cell values // Write cell values
for (int i = 0; i < values.count(); ++i) for (int i = 0; i < values.count(); ++i)
{ {
m_csvFile.write(values.at(i).toUtf8()); m_textStream << values.at(i).toUtf8();
if (i < values.count() - 1) if (i < values.count() - 1)
m_csvFile.write(","); m_textStream << ",";
else else
m_csvFile.write("\n"); m_textStream << "\n";
} }
// Remove JSON from list // Remove JSON from list

View File

@ -47,15 +47,16 @@ private:
public slots: public slots:
void openCsv(); void openCsv();
void closeFile();
void openCurrentCsv(); void openCurrentCsv();
private slots: private slots:
void closeFile();
void writeValues(); void writeValues();
void updateValues(); void updateValues();
private: private:
QFile m_csvFile; QFile m_csvFile;
QTextStream m_textStream;
QList<QPair<QDateTime, QJsonObject>> m_jsonList; QList<QPair<QDateTime, QJsonObject>> m_jsonList;
}; };

View File

@ -139,7 +139,7 @@ void JsonParser::loadJsonMap()
/** /**
* Opens, validates & loads into memory the JSON file in the given @a path. * Opens, validates & loads into memory the JSON file in the given @a path.
*/ */
void JsonParser::loadJsonMap(const QString &path) void JsonParser::loadJsonMap(const QString &path, const bool silent)
{ {
// Validate path // Validate path
if (path.isEmpty()) if (path.isEmpty())
@ -163,23 +163,26 @@ void JsonParser::loadJsonMap(const QString &path)
if (error.error != QJsonParseError::NoError) if (error.error != QJsonParseError::NoError)
{ {
m_jsonMap.close(); m_jsonMap.close();
writeSettings("");
NiceMessageBox(tr("JSON parse error"), error.errorString()); NiceMessageBox(tr("JSON parse error"), error.errorString());
} }
// JSON contains no errors // JSON contains no errors, load data & save settings
else else
{ {
writeSettings(path);
m_jsonMapData = QString::fromUtf8(data); m_jsonMapData = QString::fromUtf8(data);
NiceMessageBox(tr("JSON map file loaded successfully!"), if (!silent)
tr("File \"%1\" loaded into memory").arg(jsonMapFilename())); NiceMessageBox(tr("JSON map file loaded successfully!"),
tr("File \"%1\" loaded into memory").arg(jsonMapFilename()));
} }
} }
// Open error // Open error
else else
{ {
NiceMessageBox(tr("Cannot read file contents"), writeSettings("");
tr("Failed to read contents of \"%1\", check file permissions").arg(jsonMapFilename())); NiceMessageBox(tr("Cannot read JSON file"), tr("Please check file permissions & location"));
m_jsonMap.close(); m_jsonMap.close();
} }
@ -204,6 +207,24 @@ void JsonParser::setOperationMode(const OperationMode mode)
emit operationModeChanged(); emit operationModeChanged();
} }
/**
* Loads the last saved JSON map file (if any)
*/
void JsonParser::readSettings()
{
auto path = m_settings.value("json_map_location", "").toString();
if (!path.isEmpty())
loadJsonMap(path, true);
}
/**
* Saves the location of the last valid JSON map file that was opened (if any)
*/
void JsonParser::writeSettings(const QString &path)
{
m_settings.setValue("json_map_location", path);
}
/** /**
* Tries to parse the given data as a JSON document according to the selected * Tries to parse the given data as a JSON document according to the selected
* operation mode. * operation mode.

View File

@ -25,6 +25,7 @@
#include <QFile> #include <QFile>
#include <QObject> #include <QObject>
#include <QSettings>
#include <QQmlEngine> #include <QQmlEngine>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonValue> #include <QJsonValue>
@ -62,17 +63,22 @@ public:
public slots: public slots:
void loadJsonMap(); void loadJsonMap();
void loadJsonMap(const QString &path);
void setOperationMode(const OperationMode mode); void setOperationMode(const OperationMode mode);
void loadJsonMap(const QString &path, const bool silent = false);
private: private:
JsonParser(); JsonParser();
public slots:
void readSettings();
void writeSettings(const QString &path);
private slots: private slots:
void readData(const QByteArray &data); void readData(const QByteArray &data);
private: private:
QFile m_jsonMap; QFile m_jsonMap;
QSettings m_settings;
QString m_jsonMapData; QString m_jsonMapData;
OperationMode m_opMode; OperationMode m_opMode;
QJsonDocument m_document; QJsonDocument m_document;

View File

@ -43,8 +43,5 @@ ModuleManager::ModuleManager()
*/ */
void ModuleManager::deleteModules() void ModuleManager::deleteModules()
{ {
Export::getInstance()->deleteLater(); Export::getInstance()->closeFile();
QmlBridge::getInstance()->deleteLater();
GraphProvider::getInstance()->deleteLater();
SerialManager::getInstance()->deleteLater();
} }

View File

@ -44,40 +44,40 @@ public:
QList<Group *> gaugeGroup() const; QList<Group *> gaugeGroup() const;
QList<Group *> accelerometerGroup() const; QList<Group *> accelerometerGroup() const;
int barGroupCount() const; Q_INVOKABLE int barGroupCount() const;
int mapGroupCount() const; Q_INVOKABLE int mapGroupCount() const;
int gyroGroupCount() const; Q_INVOKABLE int gyroGroupCount() const;
int tankGroupCount() const; Q_INVOKABLE int tankGroupCount() const;
int gaugeGroupCount() const; Q_INVOKABLE int gaugeGroupCount() const;
int accelerometerGroupCount() const; Q_INVOKABLE int accelerometerGroupCount() const;
Group *barGroupAt(const int index); Q_INVOKABLE Group *barGroupAt(const int index);
Group *mapGroupAt(const int index); Q_INVOKABLE Group *mapGroupAt(const int index);
Group *gyroGroupAt(const int index); Q_INVOKABLE Group *gyroGroupAt(const int index);
Group *tankGroupAt(const int index); Q_INVOKABLE Group *tankGroupAt(const int index);
Group *gaugeGroupAt(const int index); Q_INVOKABLE Group *gaugeGroupAt(const int index);
Group *accelerometerGroupAt(const int index); Q_INVOKABLE Group *accelerometerGroupAt(const int index);
double gyroX(const int index); Q_INVOKABLE double gyroX(const int index);
double gyroY(const int index); Q_INVOKABLE double gyroY(const int index);
double gyroZ(const int index); Q_INVOKABLE double gyroZ(const int index);
double accelerometerX(const int index); Q_INVOKABLE double accelerometerX(const int index);
double accelerometerY(const int index); Q_INVOKABLE double accelerometerY(const int index);
double accelerometerZ(const int index); Q_INVOKABLE double accelerometerZ(const int index);
double bar(const int index); Q_INVOKABLE double bar(const int index);
double tank(const int index); Q_INVOKABLE double tank(const int index);
double gauge(const int index); Q_INVOKABLE double gauge(const int index);
double barMin(const int index); Q_INVOKABLE double barMin(const int index);
double barMax(const int index); Q_INVOKABLE double barMax(const int index);
double tankMin(const int index); Q_INVOKABLE double tankMin(const int index);
double tankMax(const int index); Q_INVOKABLE double tankMax(const int index);
double gaugeMin(const int index); Q_INVOKABLE double gaugeMin(const int index);
double gaugeMax(const int index); Q_INVOKABLE double gaugeMax(const int index);
double mapLatitude(const int index); Q_INVOKABLE double mapLatitude(const int index);
double mapLongitude(const int index); Q_INVOKABLE double mapLongitude(const int index);
private slots: private slots:
void updateModels(); void updateModels();