see changelog

This commit is contained in:
Dorian Zedler 2018-07-22 16:47:55 +02:00
parent 04293363e2
commit 3cfca3a083
15 changed files with 561 additions and 81 deletions

View file

@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added
- cancel button during start sequence
- new screen in landscape mode
- buttons for settings and profiles
## [0.02] - 2018-07-18 ## [0.02] - 2018-07-18
### Fixed ### Fixed

View file

@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<manifest package="com.itsblue.speedclimbing_stopwatch" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.02" android:versionCode="2" android:installLocation="auto"> <manifest package="com.itsblue.speedclimbing_stopwatchtest" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.02" android:versionCode="2" android:installLocation="auto">
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="speedclimbing stw" android:icon="@drawable/icon"> <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="speedclimbing stw" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="speedclimbing stw" android:screenOrientation="unspecified" android:launchMode="singleTop"> <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="com.itsblue.StayAwake" android:label="speedclimbing stw" android:screenOrientation="unspecified" android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
@ -59,6 +59,7 @@
--> -->
<meta-data android:name="android.app.extract_android_style" android:value="full"/> <meta-data android:name="android.app.extract_android_style" android:value="full"/>
<!-- extract android style --> <!-- extract android style -->
</activity> </activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices --> <!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->

View file

@ -0,0 +1,8 @@
package com.itsblue;
public class StayAwake extends org.qtproject.qt5.android.bindings.QtActivity {
@Override
public void onCreate(android.os.Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}

BIN
graphics/icons/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

BIN
graphics/icons/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -1,5 +1,41 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QSqlDatabase>
#include <QSqlError>
#include <QDebug>
#include <QFile>
#include <QDir>
#include <QStandardPaths>
#include <QtAndroidExtras>
#include "sqlstoragemodel.h"
#include "sqlprofilemodel.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);
qDebug() << writeDir;
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() + "/chat-database.sqlite3";
//QFile::remove(fileName);
// 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()));
}
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -7,6 +43,13 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
connectToDatabase();
//setup the sql storage model as a qml model
qmlRegisterType<SqlProfileModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlProfileModel");
qmlRegisterType<SqlStorageModel>("com.itsblue.speedclimbingstopwatch", 1, 0, "SqlStorageModel");
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty()) if (engine.rootObjects().isEmpty())

399
main.qml
View file

@ -2,6 +2,9 @@ import QtQuick 2.9
import QtMultimedia 5.8 import QtMultimedia 5.8
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtQuick.Controls 2.2 import QtQuick.Controls 2.2
import QtQuick.Layouts 1.11
import com.itsblue.speedclimbingstopwatch 1.0
Window { Window {
visible: true visible: true
@ -20,7 +23,10 @@ Window {
property double stoppedTime: 0 property double stoppedTime: 0
property double currTime: new Date().getTime() property double currTime: new Date().getTime()
state: "IDLE"
Timer { Timer {
//timer that updates the currTime variable
running: true running: true
repeat: true repeat: true
interval: 1 interval: 1
@ -29,36 +35,11 @@ Window {
} }
} }
Item {
id: time_container
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: parent.height * 0.15
Label {
id: time
text: "Click start to start"
anchors.centerIn: parent
font.pixelSize: parent.height * 0.3
}
}
Rectangle {
width: parent.width
height: 1
color: "grey"
anchors.left: parent.left
anchors.top: time_container.bottom
}
SoundEffect { SoundEffect {
//start sound
id: startSound id: startSound
source: "OFFICAL_IFSC_STARTIGNAL.wav" source: "OFFICAL_IFSC_STARTIGNAL.wav"
onPlayingChanged: { onPlayingChanged: {
if(!playing){ if(!playing){
root.startTime = new Date().getTime() root.startTime = new Date().getTime()
@ -69,18 +50,56 @@ Window {
} }
} }
/*------------------------
Timer text an upper line
------------------------*/
Item {
id: time_container
anchors {
top: parent.top
left: parent.left
right: root.landscape() ? startButt.left:parent.right
bottom: root.landscape() ? parent.bottom:startButt.top
bottomMargin: root.landscape() ? undefined:parent.height * 0.1
rightMargin: root.landscape() ? parent.width * 0.05:0
}
//height: root.landscape() ? undefined:parent.height * 0.15
Label {
id: time
text: "Click start to start"
anchors.centerIn: parent
//font.pixelSize: root.landscape() ? parent.width * 0.1:parent.height * 0.3
elide: "ElideRight"
}
}
Rectangle {
width: root.landscape() ? 1:parent.width
height: root.landscape() ? parent.height:1
color: "grey"
anchors.left: root.landscape() ? time_container.right:parent.left
anchors.top: root.landscape() ? parent.top:time_container.bottom
anchors.bottom: root.landscape() ? parent.bottom:undefined
}
/*----------------------
Start button
----------------------*/
Rectangle { Rectangle {
id: startButt id: startButt
property string text: "start" property string text: "start"
property int size: root.landscape() ? parent.width * 0.5:parent.height * 0.5
anchors { anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom bottom: parent.bottom
bottomMargin: parent.height * 0.5 - height * 0.5 bottomMargin: parent.height * 0.5 - height * 0.5
right: parent.right
rightMargin: parent.width * 0.5 - width * 0.5
} }
height: parent.height - (parent.height * 0.5) height: root.landscape() ? size > parent.height * 0.9 ? parent.height * 0.9:size : size
width: height > parent.width ? parent.width * 0.8:height width: root.landscape() ? size : size > parent.width * 0.9 ? parent.width * 0.9:size
color: "white" color: "white"
border.color: "grey" border.color: "grey"
border.width: 1 border.width: 1
@ -119,48 +138,333 @@ Window {
} }
} }
} }
/*----------------------
Cancel button
----------------------*/
Rectangle {
id: cancelButt
property string text: "cancel"
anchors {
right: startButt.right
bottom: startButt.bottom
}
height: startButt.height * 0.3
scale: 0
width: height
color: "white"
border.color: "grey"
border.width: 1
radius: width / 2
Label {
id: cancelButt_text
text: parent.text
anchors.centerIn: parent
font.pixelSize: parent.height * 0.16
font.family: "Helvetica"
}
MouseArea {
enabled: startSound.playing
anchors.fill: parent
onPressed: parent.color = "lightgrey"
onReleased: parent.color = "white"
onClicked: {
startSound.stop()
root.stoppedTime = 0
time.text = "false start"
root.state = "STOPPED"
}
}
Behavior on scale {
PropertyAnimation {
duration: 200
}
}
}
/*-------------------
lower line and menu
-------------------*/
Rectangle {
width: root.landscape() ? 1:parent.width
height: root.landscape() ? parent.height:1
color: "grey"
anchors.right: root.landscape() ? menu_container.left:parent.right
anchors.bottom: root.landscape() ? parent.bottom:menu_container.top
anchors.top: root.landscape() ? parent.top:undefined
}
Item {
id: menu_container
anchors {
bottom: parent.bottom
right: parent.right
left: root.landscape() ? startButt.right:parent.left
top: root.landscape() ? parent.top:startButt.bottom
topMargin: root.landscape() ? undefined:parent.height * 0.1
leftMargin: root.landscape() ? parent.width * 0.05:0
}
Rectangle {
id: settingsButt
property string text: "cancel"
anchors {
//center
verticalCenter: root.landscape() ? undefined:parent.verticalCenter
horizontalCenter: root.landscape() ? parent.horizontalCenter:undefined
//set anchors
left: root.landscape() ? undefined:parent.left
top: root.landscape() ? parent.top:undefined
//align in landscape mode
topMargin: root.landscape() ? (parent.height - (height * 2)) / 3:undefined
//align in portrait mode
leftMargin: root.landscape() ? undefined:(parent.width - width * 2) / 3
}
height: root.landscape() ? parent.width * 0.7:parent.height * 0.7
width: height
color: "white"
border.color: "grey"
border.width: 1
radius: width / 2
Image {
id: settungsButt_Image
source: "qrc:/graphics/icons/settings.png"
anchors.centerIn: parent
height: parent.height * 0.7
width: parent.width * 0.7
mipmap: true
}
MouseArea {
enabled: root.state === "IDLE"
anchors.fill: parent
onPressed: parent.color = "lightgrey"
onReleased: parent.color = "white"
onClicked: {
}
}
Behavior on scale {
PropertyAnimation {
duration: 200
}
}
}
Rectangle {
id: profilesButt
property string text: "cancel"
anchors {
verticalCenter: root.landscape() ? undefined:parent.verticalCenter
horizontalCenter: root.landscape() ? parent.horizontalCenter:undefined
left: root.landscape() ? undefined:settingsButt.right
top: root.landscape() ? settingsButt.bottom:undefined
topMargin: root.landscape() ? (parent.height - (height * 2)) / 3:undefined
leftMargin: root.landscape() ? undefined:(parent.width - width * 2) / 3
}
height: root.landscape() ? parent.width * 0.7:parent.height * 0.7
width: height
color: "white"
border.color: "grey"
border.width: 1
radius: width / 2
Image {
id: profilesButt_Image
source: "qrc:/graphics/icons/user.png"
anchors.centerIn: parent
height: parent.height * 0.5
width: parent.width * 0.5
mipmap: true
}
MouseArea {
enabled: root.state === "IDLE"
anchors.fill: parent
onPressed: parent.color = "lightgrey"
onReleased: parent.color = "white"
onClicked: {
}
}
Behavior on scale {
PropertyAnimation {
duration: 200
}
}
}
}
/*
// ComboBox {
// id: profileBox
// property int profileIndex: -1
// model: SqlProfileModel{}
// textRole: "name"
// width: parent.width
// anchors {
// bottom: parent.bottom
// horizontalCenter: parent.horizontalCenter
// }
// height: parent.height * 0.05
// background: Rectangle {
// color: profileBox.down ? "lightgrey":"white"
// border.width: 1
// border.color: "grey"
// }
// popup: Popup {
// id: profileBox_popup
// property bool popup_open: false
// y: profileBox.height - 1
// width: profileBox.width
// implicitHeight: contentItem.implicitHeight
// padding: 1
// contentItem: ListView {
// clip: true
// implicitHeight: contentHeight
// model: profileBox.popup.visible ? profileBox.delegateModel : null
// currentIndex: profileBox.highlightedIndex
// ScrollIndicator.vertical: ScrollIndicator { }
// }
// onVisibleChanged: {
// if(visible){
// if(popup_open){
// popup_open = false
// return
// }
// popup_open = true
// height=implicitHeight
// }
// else {
// height = 0
// visible = true
// }
// }
// background: Rectangle {
// border.color: "grey"
// radius: 2
// }
// Behavior on height {
// NumberAnimation
// {
// onRunningChanged: {
// if(!running && !profileBox_popup.popup_pen){
// profileBox_popup.popup_open = false
// profileBox_popup.visible = false
// }
// }
// duration: 200
// }
// }
// }
// onDownChanged: {
// if(profileIndex !== currentIndex)
// profileIndex = currentIndex
// }
// }
*/
/*----------------------
Timer states
----------------------*/
states: [ states: [
State { State {
name: "IDLE" name: "IDLE"
//state for the start page //state for the start page
PropertyChanges { target: time; text: "Click start to start"; font.pixelSize: parent.height * 0.3 } PropertyChanges { target: time; text: "Click start to start"; font.pixelSize: root.landscape() ? parent.width * 0.1:parent.height * 0.3; scale: 1 }
PropertyChanges { target: time_container; height: parent.height * 0.15 } PropertyChanges {
PropertyChanges { target: startButt; enabled: true; text: "start"; height: parent.height - (parent.height * 0.5); anchors.bottomMargin: parent.height * 0.5 - startButt.height * 0.5 } target: time_container;
anchors.bottomMargin: root.landscape() ? undefined:parent.height * 0.1;
anchors.rightMargin: root.landscape() ? parent.height * 0.05:0
}
PropertyChanges {
target: startButt;
enabled: true; text: "start";
size: root.landscape() ? parent.width * 0.5:parent.height * 0.5
anchors.bottomMargin: parent.height * 0.5 - startButt.height * 0.5
anchors.rightMargin: parent.width * 0.5 - startButt.width * 0.5
}
}, },
State { State {
name: "STARTING" name: "STARTING"
//state for the start sequence //state for the start sequence
PropertyChanges { target: startButt; enabled: false; text: "starting..." } PropertyChanges { target: startButt; enabled: false; text: "starting...";
PropertyChanges { target: time; text: "0.000 sec" } anchors.rightMargin: root.landscape() ? parent.width * 0.05:none //put the button more to the right to hide the menu (only in landscape mode)
anchors.bottomMargin: root.landscape() ? none:parent.height * 0.1 //put the button lower to hide the menu (only in portrait mode)
}
PropertyChanges { target: time; text: "0.000 sec"; font.pixelSize: root.landscape() ? parent.width * 0.2:parent.height * 0.3; scale: 1 }
PropertyChanges { target: cancelButt; scale: 1}
PropertyChanges { target: menu_container; }
}, },
State { State {
name: "RUNNING" name: "RUNNING"
//state when the timer is running //state when the timer is running
PropertyChanges { target: time; text: ( ( root.currTime - root.startTime ) / 1000 ).toFixed(3) + " sec" } PropertyChanges { target: time; text: ( ( root.currTime - root.startTime ) / 1000 ).toFixed(3) + " sec"; font.pixelSize: root.landscape() ? parent.width * 0.2:parent.height * 0.3; scale: 1 }
PropertyChanges { target: startButt; enabled: true; text: "stop" } PropertyChanges { target: startButt; enabled: true;
text: "stop"
anchors.rightMargin: root.landscape() ? parent.width * 0.05:none //put the button more to the right to hide the menu (only in landscape mode)
anchors.bottomMargin: root.landscape() ? none:parent.height * 0.1 //put the button lower to hide the menu (only in portrait mode)
}
}, },
State { State {
name: "STOPPED" name: "STOPPED"
//state when the meassuring is over //state when the meassuring is over
PropertyChanges { target: time; text: ( root.stoppedTime / 1000 ).toFixed(3) + " sec"; font.pixelSize: parent.height * 0.1 } PropertyChanges {
PropertyChanges { target: startButt; enabled: true; text: "reset"; height: parent.height - (parent.height * 0.8); anchors.bottomMargin: parent.height * 0.2 - startButt.height * 0.5 } target: time; text: root.stoppedTime > 0 ? ( root.stoppedTime / 1000 ).toFixed(3) + " sec":"false start";
PropertyChanges { target: time_container; height: parent.height * 0.8 } font.pixelSize: root.landscape() ? parent.width * 0.15:parent.height * 0.1;
scale: 1
}
PropertyChanges {
target: startButt;
enabled: true; text: "reset";
size: root.landscape() ? parent.height * 0.35:parent.height * 0.2;
anchors.bottomMargin: root.landscape() ? parent.height * 0.5 - startButt.height * 0.5:parent.height * 0.2 - startButt.height * 0.5
anchors.rightMargin: root.landscape() ? parent.height * 0.2 - startButt.height * 0.5:parent.width * 0.5 - startButt.width * 0.5
}
PropertyChanges {
target: time_container;
anchors.rightMargin: root.landscape() ? 0-startButt.width/2:undefined
anchors.bottomMargin: root.landscape() ? undefined:0-startButt.height/2
}
} }
] ]
/*----------------------
Timer animations
----------------------*/
transitions: [ transitions: [
Transition { Transition {
NumberAnimation { properties: "height,width,bottomMargin,font.pixelSize"; easing.type: Easing.InOutQuad; duration: 700 } NumberAnimation { properties: "size,rightMargin,height,width,bottomMargin,font.pixelSize"; easing.type: Easing.InOutQuad; duration: 700 }
FadeAnimation { target: time; fadeDuration_in: 0; fadeDuration_out: 0 }
}, },
Transition { Transition {
to: "STOPPED" to: "STOPPED"
NumberAnimation { properties: "height,width,bottomMargin,font.pixelSize"; easing.type: Easing.InOutQuad; duration: 700 } NumberAnimation { properties: "size,rightMargin,height,width,bottomMargin,font.pixelSize"; easing.type: Easing.InOutQuad; duration: 700 }
}, },
Transition { Transition {
to: "IDLE" to: "IDLE"
NumberAnimation { properties: "height,width,bottomMargin,font.pixelSize"; easing.type: Easing.InOutQuad; duration: 700 } NumberAnimation { properties: "size,rightMargin,height,width,bottomMargin,font.pixelSize"; easing.type: Easing.InOutQuad; duration: 700 }
FadeAnimation { target: time; fadeDuration_out: 1000; fadeDuration_in: 0} FadeAnimation { target: time; fadeDuration_out: 1000; fadeDuration_in: 0}
}, },
@ -169,5 +473,12 @@ Window {
//disable transitions for the RUNNING state //disable transitions for the RUNNING state
} }
] ]
/*----------------------
Timer functions
----------------------*/
function landscape(){
return(root.height < root.width)
}
} }
} }

View file

@ -1,5 +1,7 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>OFFICAL_IFSC_STARTIGNAL.wav</file> <file>OFFICAL_IFSC_STARTIGNAL.wav</file>
<file>graphics/icons/settings.png</file>
<file>graphics/icons/user.png</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -1,4 +1,4 @@
QT += quick QT += quick sql androidextras
CONFIG += c++11 CONFIG += c++11
# The following define makes your compiler emit warnings if you use # The following define makes your compiler emit warnings if you use
@ -15,7 +15,9 @@ DEFINES += QT_DEPRECATED_WARNINGS
TARGET = speedclimbing_stw TARGET = speedclimbing_stw
SOURCES += \ SOURCES += \
main.cpp main.cpp \
sqlstoragemodel.cpp \
sqlprofilemodel.cpp
RESOURCES += qml.qrc \ RESOURCES += qml.qrc \
shared.qrc shared.qrc
@ -35,8 +37,13 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target !isEmpty(target.path): INSTALLS += target
DISTFILES += \ DISTFILES += \
android-sources/AndroidManifest.xml android-sources/AndroidManifest.xml \
android-sources/src/StayAwake.java
android { android {
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources
} }
HEADERS += \
sqlstoragemodel.h \
sqlprofilemodel.h

56
sqlprofilemodel.cpp Normal file
View file

@ -0,0 +1,56 @@
#include "sqlprofilemodel.h"
static void createTable()
{
if (QSqlDatabase::database().tables().contains(QStringLiteral("Contacts"))) {
// The table already exists; we don't need to do anything.
return;
}
QSqlQuery query;
//creat eth etable to store the profiles
if (!query.exec(
"CREATE TABLE IF NOT EXISTS `profiles` ( "
" `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,"
" `name` TEXT NOT NULL "
" );")) {
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
//create the table to store the times
if (!query.exec(
"CREATE TABLE IF NOT EXISTS `times` ("
" `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,"
" `profileid` INTEGER NOT NULL,"
" `time` INTEGER NOT NULL"
" );")) {
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
}
SqlProfileModel::SqlProfileModel(QObject *parent) : QSqlTableModel(parent)
{
qDebug("ProfileModel constructor");
createTable();
setTable("profiles");
select();
}
QVariant SqlProfileModel::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> SqlProfileModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole + 0] = "id";
names[Qt::UserRole + 1] = "name";
return names;
}

25
sqlprofilemodel.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef SQLPROFILEMODEL_H
#define SQLPROFILEMODEL_H
#include <QObject>
#include <QDebug>
#include <QSqlError>
#include <QSqlQuery>
#include <QDateTime>
#include <QSqlRecord>
#include <QSqlTableModel>
class SqlProfileModel : public QSqlTableModel
{
Q_OBJECT
public:
explicit SqlProfileModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
signals:
public slots:
};
#endif // SQLPROFILEMODEL_H

6
sqlstoragemodel.cpp Normal file
View file

@ -0,0 +1,6 @@
#include "sqlstoragemodel.h"
SqlStorageModel::SqlStorageModel(QObject *parent) : QObject(parent)
{
}

17
sqlstoragemodel.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef SQLSTORAGEMODEL_H
#define SQLSTORAGEMODEL_H
#include <QObject>
class SqlStorageModel : public QObject
{
Q_OBJECT
public:
explicit SqlStorageModel(QObject *parent = nullptr);
signals:
public slots:
};
#endif // SQLSTORAGEMODEL_H