Initial commit
This commit is contained in:
commit
872bb95acf
20 changed files with 1369 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*.pro.user
|
||||||
|
Makefile
|
||||||
32
TagListDock.ui
Normal file
32
TagListDock.ui
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>TagListDock</class>
|
||||||
|
<widget class="QDockWidget" name="TagListDock">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Tags</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="dockWidgetContents">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="QListView" name="tagList"/>
|
||||||
|
<widget class="QTableView" name="propertyTable"/>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
20
aboutdialog.cpp
Normal file
20
aboutdialog.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "aboutdialog.h"
|
||||||
|
#include "ui_aboutdialog.h"
|
||||||
|
|
||||||
|
AboutDialog::AboutDialog(QWidget *parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
ui(new Ui::AboutDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AboutDialog::~AboutDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AboutDialog::on_buttonBox_clicked(QAbstractButton *button)
|
||||||
|
{
|
||||||
|
Q_UNUSED(button); /* There is only one button */
|
||||||
|
this->accept();
|
||||||
|
}
|
||||||
26
aboutdialog.h
Normal file
26
aboutdialog.h
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#ifndef ABOUTDIALOG_H
|
||||||
|
#define ABOUTDIALOG_H
|
||||||
|
|
||||||
|
#include <QAbstractButton>
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class AboutDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AboutDialog(QWidget *parent = nullptr);
|
||||||
|
~AboutDialog();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void on_buttonBox_clicked(QAbstractButton *button);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::AboutDialog *ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ABOUTDIALOG_H
|
||||||
78
aboutdialog.ui
Normal file
78
aboutdialog.ui
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AboutDialog</class>
|
||||||
|
<widget class="QDialog" name="AboutDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Numberator v0.1
|
||||||
|
©2020 Jan Goette <code@jaseg.de></string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
<property name="centerButtons">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>AboutDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>AboutDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
11
main.cpp
Normal file
11
main.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "numberator.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
Numberator w;
|
||||||
|
w.show();
|
||||||
|
return a.exec();
|
||||||
|
}
|
||||||
86
numberator.cpp
Normal file
86
numberator.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
#include "numberator.h"
|
||||||
|
#include "ui_numberator.h"
|
||||||
|
#include "ui_TagListDock.h"
|
||||||
|
|
||||||
|
Numberator::Numberator(QWidget *parent)
|
||||||
|
: QMainWindow(parent)
|
||||||
|
, ui(new Ui::Numberator)
|
||||||
|
, tagsDockUi(new Ui::TagListDock)
|
||||||
|
, settings("jaseg.de", "Numberator")
|
||||||
|
, loadImageDialog(this)
|
||||||
|
, proj()
|
||||||
|
, tagListModel(proj)
|
||||||
|
, tagPropTableModel(proj)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
QDockWidget *dock = new QDockWidget(this);
|
||||||
|
tagsDockUi->setupUi(dock);
|
||||||
|
addDockWidget(Qt::LeftDockWidgetArea, dock);
|
||||||
|
ui->menuView->addAction(dock->toggleViewAction());
|
||||||
|
connect(ui->actionReload_Image, &QAction::triggered,
|
||||||
|
&proj, &SQLiteSaveFile::reloadImageFromDisk);
|
||||||
|
|
||||||
|
tagsDockUi->tagList->setModel(&tagListModel);
|
||||||
|
tagsDockUi->propertyTable->setModel(&tagPropTableModel);
|
||||||
|
|
||||||
|
loadImageDialog.setWindowModality(Qt::ApplicationModal);
|
||||||
|
loadImageDialog.setWindowTitle("Load Image...");
|
||||||
|
loadImageDialog.setNameFilter("Images (*.png, *.xpm, *.jpg)");
|
||||||
|
loadImageDialog.setFileMode(QFileDialog::ExistingFile);
|
||||||
|
loadImageDialog.restoreState(settings.value("MainWindow/LoadImageFileDialogState").toByteArray());
|
||||||
|
connect(&loadImageDialog, &QFileDialog::accepted, [=]() {
|
||||||
|
settings.setValue("MainWindow/LoadImageFileDialogState", this->loadImageDialog.saveState());
|
||||||
|
});
|
||||||
|
connect(&loadImageDialog, &QFileDialog::fileSelected, &proj, &SQLiteSaveFile::loadImageFromDisk);
|
||||||
|
connect(ui->actionImport_Image, &QAction::triggered, [=](bool checked){
|
||||||
|
Q_UNUSED(checked);
|
||||||
|
this->loadImageDialog.open();
|
||||||
|
});
|
||||||
|
|
||||||
|
saveOpenDialog.setWindowModality(Qt::ApplicationModal);
|
||||||
|
saveOpenDialog.setNameFilter("Project Files (*.npr);;Any File (*)");
|
||||||
|
saveOpenDialog.setFileMode(QFileDialog::AnyFile);
|
||||||
|
saveOpenDialog.restoreState(settings.value("MainWindow/SaveAsFileDialogState").toByteArray());
|
||||||
|
connect(&saveOpenDialog, &QFileDialog::accepted, [=]() {
|
||||||
|
settings.setValue("MainWindow/SaveAsFileDialogState", this->saveOpenDialog.saveState());
|
||||||
|
});
|
||||||
|
connect(ui->actionSave_Project, &QAction::triggered, [=](bool checked){
|
||||||
|
Q_UNUSED(checked);
|
||||||
|
this->saveOpenDialog.setWindowTitle("Save Project as...");
|
||||||
|
disconnect(&this->saveOpenDialog, &QFileDialog::fileSelected, nullptr, nullptr);
|
||||||
|
connect(&this->saveOpenDialog, &QFileDialog::fileSelected,
|
||||||
|
&this->proj, &SQLiteSaveFile::saveAs);
|
||||||
|
this->saveOpenDialog.open();
|
||||||
|
});
|
||||||
|
connect(ui->actionOpen_Project, &QAction::triggered, [=](bool checked){
|
||||||
|
Q_UNUSED(checked);
|
||||||
|
this->saveOpenDialog.setWindowTitle("Open Project...");
|
||||||
|
disconnect(&this->saveOpenDialog, &QFileDialog::fileSelected, nullptr, nullptr);
|
||||||
|
connect(&this->saveOpenDialog, &QFileDialog::fileSelected,
|
||||||
|
this, &Numberator::openFile);
|
||||||
|
this->saveOpenDialog.open();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(ui->actionNew_Project, &QAction::triggered,
|
||||||
|
&proj, &SQLiteSaveFile::clearNew);
|
||||||
|
connect(ui->actionQuit, &QAction::triggered, &QApplication::quit);
|
||||||
|
connect(ui->actionAbout, &QAction::triggered, &aboutDialog, &AboutDialog::open);
|
||||||
|
|
||||||
|
connect(tagsDockUi->tagList->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||||
|
[=](const QModelIndex ¤t, const QModelIndex &previous) {
|
||||||
|
Q_UNUSED(previous);
|
||||||
|
tagPropTableModel.showTag(tagListModel.getTag(current));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Numberator::~Numberator()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Numberator::openFile(const QString &path)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
43
numberator.h
Normal file
43
numberator.h
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef NUMBERATOR_H
|
||||||
|
#define NUMBERATOR_H
|
||||||
|
|
||||||
|
#include "aboutdialog.h"
|
||||||
|
#include "sqlitebackend.h"
|
||||||
|
#include "taglistmodel.h"
|
||||||
|
#include "tagproptablemodel.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
namespace Ui {
|
||||||
|
class Numberator;
|
||||||
|
class TagListDock;
|
||||||
|
}
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
class Numberator : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Numberator(QWidget *parent = nullptr);
|
||||||
|
~Numberator();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void openFile(const QString &path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::Numberator *ui;
|
||||||
|
Ui::TagListDock *tagsDockUi;
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
QFileDialog loadImageDialog,
|
||||||
|
saveOpenDialog;
|
||||||
|
AboutDialog aboutDialog;
|
||||||
|
|
||||||
|
SQLiteSaveFile proj;
|
||||||
|
TagListModel tagListModel;
|
||||||
|
TagPropTableModel tagPropTableModel;
|
||||||
|
};
|
||||||
|
#endif // NUMBERATOR_H
|
||||||
50
numberator.pro
Normal file
50
numberator.pro
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
QT += core gui sql
|
||||||
|
|
||||||
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
|
|
||||||
|
CONFIG += c++2a
|
||||||
|
|
||||||
|
|
||||||
|
win32:INCLUDEPATH += $$[QT_INSTALL_PREFIX]/../Src/qtbase/src/3rdparty/sqlite
|
||||||
|
win32:SOURCES += $$[QT_INSTALL_PREFIX]/../Src/qtbase/src/3rdparty/sqlite/sqlite3.c
|
||||||
|
unix:LIBS += -lsqlite3
|
||||||
|
|
||||||
|
# The following define makes your compiler emit warnings if you use
|
||||||
|
# any Qt feature that has been marked deprecated (the exact warnings
|
||||||
|
# depend on your compiler). Please consult the documentation of the
|
||||||
|
# deprecated API in order to know how to port your code away from it.
|
||||||
|
DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
|
|
||||||
|
# You can also make your code fail to compile if it uses deprecated APIs.
|
||||||
|
# In order to do so, uncomment the following line.
|
||||||
|
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||||
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
aboutdialog.cpp \
|
||||||
|
main.cpp \
|
||||||
|
numberator.cpp \
|
||||||
|
sqlitebackend.cpp \
|
||||||
|
taglistmodel.cpp \
|
||||||
|
tagproptablemodel.cpp \
|
||||||
|
tagview.cpp \
|
||||||
|
tagscene.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
aboutdialog.h \
|
||||||
|
numberator.h \
|
||||||
|
sqlitebackend.h \
|
||||||
|
taglistmodel.h \
|
||||||
|
tagproptablemodel.h \
|
||||||
|
tagview.h \
|
||||||
|
tagscene.h
|
||||||
|
|
||||||
|
FORMS += \
|
||||||
|
TagListDock.ui \
|
||||||
|
aboutdialog.ui \
|
||||||
|
numberator.ui
|
||||||
|
|
||||||
|
# Default rules for deployment.
|
||||||
|
qnx: target.path = /tmp/$${TARGET}/bin
|
||||||
|
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||||
|
!isEmpty(target.path): INSTALLS += target
|
||||||
157
numberator.ui
Normal file
157
numberator.ui
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Numberator</class>
|
||||||
|
<widget class="QMainWindow" name="Numberator">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>600</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Numberator</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGraphicsView" name="graphicsView"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>21</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<widget class="QMenu" name="menuFile">
|
||||||
|
<property name="title">
|
||||||
|
<string>File</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionNew_Project"/>
|
||||||
|
<addaction name="actionOpen_Project"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionImport_Image"/>
|
||||||
|
<addaction name="actionReload_Image"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionSave_Project"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionExport_PDF"/>
|
||||||
|
<addaction name="actionExport_PNG"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionQuit"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuEdit">
|
||||||
|
<property name="title">
|
||||||
|
<string>Edit</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionUndo"/>
|
||||||
|
<addaction name="actionRedo"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionCopy"/>
|
||||||
|
<addaction name="actionCut"/>
|
||||||
|
<addaction name="actionPaste"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionNew_Tag"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuHelp">
|
||||||
|
<property name="title">
|
||||||
|
<string>Help</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionAbout"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuView">
|
||||||
|
<property name="title">
|
||||||
|
<string>View</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<addaction name="menuFile"/>
|
||||||
|
<addaction name="menuEdit"/>
|
||||||
|
<addaction name="menuView"/>
|
||||||
|
<addaction name="menuHelp"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
<action name="actionAbout">
|
||||||
|
<property name="text">
|
||||||
|
<string>About</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionUndo">
|
||||||
|
<property name="text">
|
||||||
|
<string>Undo</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionRedo">
|
||||||
|
<property name="text">
|
||||||
|
<string>Redo</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionCopy">
|
||||||
|
<property name="text">
|
||||||
|
<string>Copy</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionCut">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cut</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionPaste">
|
||||||
|
<property name="text">
|
||||||
|
<string>Paste</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionNew_Tag">
|
||||||
|
<property name="text">
|
||||||
|
<string>New Tag</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionNew_Project">
|
||||||
|
<property name="text">
|
||||||
|
<string>New Project</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionOpen_Project">
|
||||||
|
<property name="text">
|
||||||
|
<string>Open Project</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionImport_Image">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import Image</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionReload_Image">
|
||||||
|
<property name="text">
|
||||||
|
<string>Reload Image</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSave_Project">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save Project</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionExport_PDF">
|
||||||
|
<property name="text">
|
||||||
|
<string>Export PDF</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionExport_PNG">
|
||||||
|
<property name="text">
|
||||||
|
<string>Export Image</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionQuit">
|
||||||
|
<property name="text">
|
||||||
|
<string>Exit</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
322
sqlitebackend.cpp
Normal file
322
sqlitebackend.cpp
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include<sqlite3.h>
|
||||||
|
|
||||||
|
#include "sqlitebackend.h"
|
||||||
|
|
||||||
|
SQLiteSaveFile::SQLiteSaveFile(QObject *parent, QString filename) :
|
||||||
|
QObject(parent)
|
||||||
|
, lastError(NoError)
|
||||||
|
, lastErrorString(QString())
|
||||||
|
, db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()))
|
||||||
|
, filename(filename)
|
||||||
|
, m_isOpen(false)
|
||||||
|
{
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::initDb(bool setCreationDate)
|
||||||
|
{
|
||||||
|
for (auto const &q: {
|
||||||
|
"CREATE TABLE IF NOT EXISTS metadata (key TEXT, value TEXT)",
|
||||||
|
"CREATE TABLE IF NOT EXISTS tags (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, anchor_x REAL, anchor_y REAL, meta TEXT)",
|
||||||
|
"CREATE TABLE IF NOT EXISTS blobs (name TEXT, data BLOB)"}) {
|
||||||
|
if (!runSql(q))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setCreationDate) {
|
||||||
|
if (!setMetaLocked("creationTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::connect()
|
||||||
|
{
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
resetError();
|
||||||
|
m_isOpen = false;
|
||||||
|
imageData = QByteArray();
|
||||||
|
|
||||||
|
bool newlyCreated = QFile(filename).exists();
|
||||||
|
|
||||||
|
db.setDatabaseName(filename);
|
||||||
|
if (!db.open()) {
|
||||||
|
setDatabaseError(db);
|
||||||
|
db.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initDb(newlyCreated)) {
|
||||||
|
db.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_isOpen = true;
|
||||||
|
|
||||||
|
/* Try to load image, ignore if image is unset */
|
||||||
|
QSqlQuery q("SELECT data FROM blobs WHERE name = 'image'", db);
|
||||||
|
if (!q.next())
|
||||||
|
return setDatabaseError(q);
|
||||||
|
|
||||||
|
imageData = q.value(0).toByteArray();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sqlite3 *getSqliteHandle(QSqlDatabase &db) {
|
||||||
|
QVariant v = db.driver()->handle();
|
||||||
|
assert (v.isValid());
|
||||||
|
assert (!qstrcmp(v.typeName(), "sqlite3*"));
|
||||||
|
return *static_cast<sqlite3 **>(v.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::saveAs(const QString &filename)
|
||||||
|
{
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
QFile f(filename);
|
||||||
|
QSqlDatabase old_db = db;
|
||||||
|
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
|
||||||
|
new_db.setDatabaseName(f.fileName());
|
||||||
|
if (!new_db.open()) {
|
||||||
|
setDatabaseError(new_db);
|
||||||
|
new_db.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3 *old_handle = getSqliteHandle(db);
|
||||||
|
sqlite3 *new_handle = getSqliteHandle(new_db);
|
||||||
|
sqlite3_backup *bck = sqlite3_backup_init(new_handle, "main", old_handle, "main");
|
||||||
|
if (!bck)
|
||||||
|
goto err_cleanup;
|
||||||
|
|
||||||
|
if (sqlite3_backup_step(bck, -1) != SQLITE_DONE)
|
||||||
|
goto err_cleanup;
|
||||||
|
|
||||||
|
if (sqlite3_backup_finish(bck) != SQLITE_DONE)
|
||||||
|
goto err_cleanup;
|
||||||
|
|
||||||
|
db = new_db;
|
||||||
|
old_db.close();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
err_cleanup:
|
||||||
|
setDatabaseError(new_db);
|
||||||
|
new_db.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::clearNew()
|
||||||
|
{
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
|
||||||
|
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
|
||||||
|
new_db.setDatabaseName(":memory:");
|
||||||
|
if (!new_db.open()) {
|
||||||
|
setDatabaseError(new_db);
|
||||||
|
new_db.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initDb()) {
|
||||||
|
db.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
imageData = QByteArray();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Tag> SQLiteSaveFile::getAllTags()
|
||||||
|
{
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
resetError();
|
||||||
|
QList<Tag> rv;
|
||||||
|
QSqlQuery q("SELECT (id, name, anchor_x, anchor_y, meta) FROM tags", db);
|
||||||
|
|
||||||
|
while (q.next()) {
|
||||||
|
rv << Tag {
|
||||||
|
q.value(0).toLongLong(),
|
||||||
|
q.value(1).toString(),
|
||||||
|
q.value(2).toFloat(),
|
||||||
|
q.value(3).toFloat(),
|
||||||
|
q.value(4).toByteArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!setDatabaseError(q))
|
||||||
|
return QList<Tag>();
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::updateTag(Tag tag)
|
||||||
|
{
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
if (!runSql("UPDATE tags SET name=?, anchor_x=?, anchor_y=?, meta=? WHERE id=?", {
|
||||||
|
tag.name, tag.anchor.x(), tag.anchor.y(), QJsonDocument::fromVariant(tag.metadata).toJson(), tag.id
|
||||||
|
}))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
tagChange(TagChange::CHANGED, tag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::deleteTag(Tag tag)
|
||||||
|
{
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
tagChange(TagChange::DELETED, tag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::createTag(Tag tag)
|
||||||
|
{
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
resetError();
|
||||||
|
QSqlQuery q("INSERT INTO tags(name, anchor_x, anchor_y, meta) VALUES (?, ?, ?, ?)", db);
|
||||||
|
q.addBindValue(tag.name);
|
||||||
|
q.addBindValue(tag.anchor.x());
|
||||||
|
q.addBindValue(tag.anchor.y());
|
||||||
|
q.addBindValue(QJsonDocument::fromVariant(tag.metadata).toJson());
|
||||||
|
if (!q.exec())
|
||||||
|
return setDatabaseError(q);
|
||||||
|
|
||||||
|
Tag created_tag(q.lastInsertId().toLongLong(), tag);
|
||||||
|
tagChange(TagChange::CREATED, created_tag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::setMetaLocked(const QString &key, const QVariant &value)
|
||||||
|
{
|
||||||
|
return runSql("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)", {key, value});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::setMetaLocked(std::initializer_list<QPair<QString, QVariant>> metas)
|
||||||
|
{
|
||||||
|
for (const auto &meta : metas) {
|
||||||
|
if (!setMetaLocked(meta.first, meta.second))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::setMeta(const QString &key, const QVariant &value) {
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
return setMetaLocked(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::setMeta(std::initializer_list<QPair<QString, QVariant>> metas) {
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
return setMetaLocked(metas);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SQLiteSaveFile::getMeta(const QString &key) {
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
return getMetaLocked(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SQLiteSaveFile::getMetaLocked(const QString &key)
|
||||||
|
{
|
||||||
|
resetError();
|
||||||
|
QSqlQuery q("SELECT value FROM metadata WHERE key=?", db);
|
||||||
|
q.addBindValue(key);
|
||||||
|
if (!q.next()) {
|
||||||
|
setDatabaseError(q);
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.value(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::runSql(QString query, std::initializer_list<QVariant> bindings)
|
||||||
|
{
|
||||||
|
resetError();
|
||||||
|
QSqlQuery q(query, db);
|
||||||
|
for (const QVariant &v : bindings)
|
||||||
|
q.addBindValue(v);
|
||||||
|
|
||||||
|
q.exec();
|
||||||
|
return setDatabaseError(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
|
||||||
|
{
|
||||||
|
QMutexLocker l(&dbMut);
|
||||||
|
QFile f(filename);
|
||||||
|
resetError();
|
||||||
|
|
||||||
|
if (!f.open(QIODevice::ReadOnly)) {
|
||||||
|
setError(ImageOpenError, QString("Failed to open image: %1").arg(f.errorString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
imageData = f.readAll();
|
||||||
|
if (f.error() != QFileDevice::NoError) {
|
||||||
|
setError(ImageReadError, QString("Failed to read image: %1").arg(f.errorString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!setMetaLocked({
|
||||||
|
{"imagePathOriginal", f.fileName()},
|
||||||
|
{"imagePathAbsolute", QFileInfo(f).absoluteFilePath()},
|
||||||
|
{"imageLoadedTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()}}))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return runSql("INSERT OR REPLACE INTO blobs(name, data) VALUES ('image', ?)", {imageData});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::reloadImageFromDisk()
|
||||||
|
{
|
||||||
|
const QString &p = getMeta("imagePathOriginal").toString();
|
||||||
|
if (QFile(p).exists())
|
||||||
|
return loadImageFromDisk(p);
|
||||||
|
|
||||||
|
const QString &q = getMeta("imagePathAbsolute").toString();
|
||||||
|
if (QFile(q).exists())
|
||||||
|
return loadImageFromDisk(q);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag::Tag(long long id, QString name, qreal anchor_x, qreal anchor_y, QByteArray metadata)
|
||||||
|
: id(id)
|
||||||
|
, name(name)
|
||||||
|
, anchor(QPointF(anchor_x, anchor_y))
|
||||||
|
, metadata(QJsonDocument::fromJson(metadata).object().toVariantMap())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag::Tag()
|
||||||
|
: id(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag::Tag(long long id, const Tag &other)
|
||||||
|
: id(id)
|
||||||
|
, name(other.name)
|
||||||
|
, anchor(other.anchor)
|
||||||
|
, metadata(other.metadata)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q)
|
||||||
|
{
|
||||||
|
if (!q.lastError().isValid())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
setError(SQLiteError, QString("Project file database error: %1").arg(q.lastError().text()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::setDatabaseError(const QSqlDatabase &db)
|
||||||
|
{
|
||||||
|
if (!db.lastError().isValid())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
setError(SQLiteError, QString("Project file database error: %1").arg(db.lastError().text()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
102
sqlitebackend.h
Normal file
102
sqlitebackend.h
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
#ifndef SQLITEBACKEND_H
|
||||||
|
#define SQLITEBACKEND_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointF>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QtSql>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
enum TagChange {
|
||||||
|
CREATED,
|
||||||
|
CHANGED,
|
||||||
|
DELETED
|
||||||
|
};
|
||||||
|
|
||||||
|
class Tag
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Tag(long long int id, QString name, qreal anchor_x, qreal anchor_y, QByteArray metadata);
|
||||||
|
Tag();
|
||||||
|
Tag(long long int id, const Tag &other);
|
||||||
|
|
||||||
|
long long int id;
|
||||||
|
QString name;
|
||||||
|
QPointF anchor;
|
||||||
|
QVariantMap metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SQLiteSaveFile : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit SQLiteSaveFile(QObject *parent = nullptr, QString filename = ":memory:");
|
||||||
|
bool connect();
|
||||||
|
bool isOpen() { return m_isOpen; }
|
||||||
|
|
||||||
|
QList<Tag> getAllTags();
|
||||||
|
|
||||||
|
QByteArray getImage();
|
||||||
|
|
||||||
|
bool updateTag(Tag tag);
|
||||||
|
bool deleteTag(Tag tag);
|
||||||
|
bool createTag(Tag tag);
|
||||||
|
|
||||||
|
bool setMeta(const QString &key, const QVariant &value);
|
||||||
|
bool setMeta(std::initializer_list<QPair<QString, QVariant>> metas);
|
||||||
|
QVariant getMeta(const QString &key);
|
||||||
|
|
||||||
|
QString errorString() { return lastErrorString; }
|
||||||
|
enum Error {
|
||||||
|
NoError = 0,
|
||||||
|
SQLiteError,
|
||||||
|
ImageOpenError,
|
||||||
|
ImageReadError
|
||||||
|
};
|
||||||
|
inline const static QString errorNames[] = {
|
||||||
|
[NoError] = "No Error",
|
||||||
|
[SQLiteError] = "Database Error",
|
||||||
|
[ImageOpenError] = "Error Opening Image",
|
||||||
|
[ImageReadError] = "Error Reading Image"
|
||||||
|
};
|
||||||
|
|
||||||
|
Error error();
|
||||||
|
void resetError() { lastError = NoError; lastErrorString = QString(); }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/** Save this project file under a new name. This changes the backend database this project file object points to, and copies all data.
|
||||||
|
* Callers can continue to use the same project file object afterwards.
|
||||||
|
*/
|
||||||
|
bool saveAs(const QString &filename);
|
||||||
|
bool reloadImageFromDisk();
|
||||||
|
bool loadImageFromDisk(const QString &filename);
|
||||||
|
bool clearNew();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void tagChange(TagChange change, const Tag &tag);
|
||||||
|
void fileReload();
|
||||||
|
void fileIOError(Error e, QString errorName, QString description);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initDb(bool setCreationDate=true);
|
||||||
|
bool runSql(QString query, std::initializer_list<QVariant> bindings={});
|
||||||
|
|
||||||
|
bool setMetaLocked(const QString &key, const QVariant &value);
|
||||||
|
bool setMetaLocked(std::initializer_list<QPair<QString, QVariant>> metas);
|
||||||
|
QVariant getMetaLocked(const QString &key);
|
||||||
|
|
||||||
|
void setError(Error e, QString desc) { lastError = e; lastErrorString = desc; fileIOError(e, errorNames[e], desc); }
|
||||||
|
bool setDatabaseError(const QSqlQuery &q);
|
||||||
|
bool setDatabaseError(const QSqlDatabase &db);
|
||||||
|
|
||||||
|
Error lastError;
|
||||||
|
QString lastErrorString;
|
||||||
|
QSqlDatabase db;
|
||||||
|
QMutex dbMut;
|
||||||
|
QString filename;
|
||||||
|
QByteArray imageData;
|
||||||
|
bool m_isOpen;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // SQLITEBACKEND_H
|
||||||
76
taglistmodel.cpp
Normal file
76
taglistmodel.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include "taglistmodel.h"
|
||||||
|
|
||||||
|
TagListModel::TagListModel(SQLiteSaveFile &backend)
|
||||||
|
: backend(backend)
|
||||||
|
, cached_tags(backend.getAllTags())
|
||||||
|
{
|
||||||
|
connect(&backend, &SQLiteSaveFile::tagChange,
|
||||||
|
[=](TagChange change, const Tag &tag) { Q_UNUSED(change); Q_UNUSED(tag); reloadTags(); });
|
||||||
|
connect(&backend, &SQLiteSaveFile::fileReload,
|
||||||
|
[=]() { reloadTags(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
int TagListModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
return cached_tags.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagListModel::reloadTags()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
cached_tags = backend.getAllTags();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TagListModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (role != Qt::DisplayRole)
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
return cached_tags.at(index.row()).name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TagListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
assert(section == 0);
|
||||||
|
assert(orientation == Qt::Horizontal);
|
||||||
|
|
||||||
|
if (role != Qt::DisplayRole)
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
return QString("Tag");
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags TagListModel::flags(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(index);
|
||||||
|
/* TODO: Add drag&drop from tag list to graphics view */
|
||||||
|
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagListModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (role != Qt::EditRole)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Tag t = cached_tags.at(index.row());
|
||||||
|
t.name = value.toString();
|
||||||
|
|
||||||
|
backend.updateTag(t);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag TagListModel::getTag(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return Tag();
|
||||||
|
|
||||||
|
return cached_tags.at(index.row());
|
||||||
|
}
|
||||||
30
taglistmodel.h
Normal file
30
taglistmodel.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef TAGLISTMODEL_H
|
||||||
|
#define TAGLISTMODEL_H
|
||||||
|
|
||||||
|
#include "sqlitebackend.h"
|
||||||
|
|
||||||
|
#include <qabstractitemmodel.h>
|
||||||
|
|
||||||
|
class TagListModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TagListModel(SQLiteSaveFile &backend);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent=QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
|
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override;
|
||||||
|
|
||||||
|
Tag getTag(const QModelIndex &index) const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void reloadTags();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SQLiteSaveFile &backend;
|
||||||
|
QList<Tag> cached_tags;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGLISTMODEL_H
|
||||||
144
tagproptablemodel.cpp
Normal file
144
tagproptablemodel.cpp
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
#include "tagproptablemodel.h"
|
||||||
|
|
||||||
|
TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend, const Tag tag)
|
||||||
|
: backend(backend)
|
||||||
|
, tag_cached(tag)
|
||||||
|
, tagIsValid(true)
|
||||||
|
{
|
||||||
|
tag_keys = tag_cached.metadata.keys();
|
||||||
|
tag_keys.sort();
|
||||||
|
connect(&backend, &SQLiteSaveFile::tagChange,
|
||||||
|
this, &TagPropTableModel::tagChange);
|
||||||
|
connect(&backend, &SQLiteSaveFile::fileReload,
|
||||||
|
[=]() {
|
||||||
|
beginResetModel();
|
||||||
|
tagIsValid = false;
|
||||||
|
endResetModel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend)
|
||||||
|
: backend(backend)
|
||||||
|
, tagIsValid(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int TagPropTableModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
if (!tagIsValid)
|
||||||
|
return 0;
|
||||||
|
return 3 + tag_cached.metadata.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int TagPropTableModel::columnCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TagPropTableModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (role != Qt::DisplayRole)
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
bool label = index.column() == 0;
|
||||||
|
|
||||||
|
switch (index.row()) {
|
||||||
|
case 0: return label ? QVariant("Database ID") : tag_cached.id; break;
|
||||||
|
case 1: return label ? QVariant("Label") : tag_cached.name; break;
|
||||||
|
case 2: return label ? QVariant("Anchor") : tag_cached.anchor; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = index.row() - 3;
|
||||||
|
return label ? tag_keys[idx] : tag_cached.metadata[tag_keys[idx]];
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TagPropTableModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
if (orientation != Qt::Horizontal)
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (role != Qt::DisplayRole)
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (section == 0)
|
||||||
|
return "Property";
|
||||||
|
else
|
||||||
|
return "Value";
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags TagPropTableModel::flags(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (index.row() == 0)
|
||||||
|
return Qt::NoItemFlags;
|
||||||
|
|
||||||
|
if (index.column() == 0) {
|
||||||
|
if (index.row() < 3)
|
||||||
|
return Qt::ItemIsEnabled;
|
||||||
|
|
||||||
|
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index.row() == 1)
|
||||||
|
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||||
|
|
||||||
|
if (index.row() == 2) /* anchor */
|
||||||
|
return Qt::ItemIsEnabled;
|
||||||
|
|
||||||
|
return Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagPropTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (role != Qt::EditRole)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (index.row() == 1)
|
||||||
|
tag_cached.name = value.toString();
|
||||||
|
else if (index.row() < 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int idx = index.row() - 3;
|
||||||
|
if (index.column() == 0) { /* key changed */
|
||||||
|
/* move value to new key and delete old key */
|
||||||
|
tag_cached.metadata[value.toString()] = tag_cached.metadata[tag_keys[idx]];
|
||||||
|
tag_cached.metadata.remove(tag_keys[idx]);
|
||||||
|
} else {
|
||||||
|
tag_cached.metadata[tag_keys[idx]] = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
backend.updateTag(tag_cached);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagPropTableModel::tagChange(TagChange change, const Tag &tag)
|
||||||
|
{
|
||||||
|
if (tag.id != tag_cached.id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(change != TagChange::CREATED);
|
||||||
|
if (change == TagChange::CHANGED) {
|
||||||
|
showTag(tag);
|
||||||
|
} else if (change == TagChange::DELETED) {
|
||||||
|
beginResetModel();
|
||||||
|
tagIsValid = false;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TagPropTableModel::showTag(const Tag &tag)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
tag_cached = tag;
|
||||||
|
tag_keys = tag_cached.metadata.keys();
|
||||||
|
tag_keys.sort();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
36
tagproptablemodel.h
Normal file
36
tagproptablemodel.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef TAGPROPTABLEMODEL_H
|
||||||
|
#define TAGPROPTABLEMODEL_H
|
||||||
|
|
||||||
|
#include "sqlitebackend.h"
|
||||||
|
|
||||||
|
#include <qabstractitemmodel.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TagPropTableModel : public QAbstractTableModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TagPropTableModel(SQLiteSaveFile &backend);
|
||||||
|
TagPropTableModel(SQLiteSaveFile &backend, const Tag tag);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent=QModelIndex()) const override;
|
||||||
|
int columnCount(const QModelIndex &parent=QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
|
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override;
|
||||||
|
|
||||||
|
void showTag(const Tag &tag);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void tagChange(TagChange change, const Tag &tag);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SQLiteSaveFile &backend;
|
||||||
|
Tag tag_cached;
|
||||||
|
QStringList tag_keys;
|
||||||
|
bool tagIsValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGPROPTABLEMODEL_H
|
||||||
6
tagscene.cpp
Normal file
6
tagscene.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "tagscene.h"
|
||||||
|
|
||||||
|
TagScene::TagScene()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
14
tagscene.h
Normal file
14
tagscene.h
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef TAGSCENE_H
|
||||||
|
#define TAGSCENE_H
|
||||||
|
|
||||||
|
#include <QGraphicsScene>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TagScene : public QGraphicsScene
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TagScene();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGSCENE_H
|
||||||
96
tagview.cpp
Normal file
96
tagview.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
#include "tagview.h"
|
||||||
|
|
||||||
|
#include <QWheelEvent>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
TagView::TagView(SQLiteSaveFile &proj)
|
||||||
|
: proj(proj)
|
||||||
|
, saveCenterTimer()
|
||||||
|
{
|
||||||
|
setDragMode(QGraphicsView::ScrollHandDrag);
|
||||||
|
setScene(&scene);
|
||||||
|
|
||||||
|
saveCenterTimer.setSingleShot(true);
|
||||||
|
saveCenterTimer.setInterval(500);
|
||||||
|
connect(&saveCenterTimer, &QTimer::timeout,
|
||||||
|
this, &TagView::saveCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagView::zoomToFit()
|
||||||
|
{
|
||||||
|
QTransform tx = QTransform().rotate(-rotation);
|
||||||
|
QRectF rect = tx.mapRect(scene.itemsBoundingRect());
|
||||||
|
QRectF vp = viewport()->rect();
|
||||||
|
|
||||||
|
setZoom(qMin(vp.width()/rect.width(), vp.height()/rect.height()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagView::setZoom(qreal zoom)
|
||||||
|
{
|
||||||
|
this->zoom = zoom;
|
||||||
|
proj.setMeta("view_zoom", zoom);
|
||||||
|
setTransform(QTransform::fromScale(zoom, zoom).rotate(rotation));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagView::zoomIn(qreal delta)
|
||||||
|
{
|
||||||
|
setZoom(qMax(1.0/16, qMin(4.0, zoom * qPow(1.2, delta/120))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagView::rotate(int angle)
|
||||||
|
{
|
||||||
|
QGraphicsView::rotate(angle);
|
||||||
|
int tmp = (rotation + angle) % 360;
|
||||||
|
if (tmp < 0)
|
||||||
|
tmp += 360;
|
||||||
|
rotation = tmp;
|
||||||
|
proj.setMeta("view_rotation", rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagView::wheelEvent(QWheelEvent *evt)
|
||||||
|
{
|
||||||
|
if (evt->modifiers() == Qt::ControlModifier) {
|
||||||
|
zoomIn(evt->angleDelta().y());
|
||||||
|
} else {
|
||||||
|
if (qAbs(evt->angleDelta().x()) > qAbs(evt->angleDelta().y())) {
|
||||||
|
QCoreApplication::sendEvent(horizontalScrollBar(), evt);
|
||||||
|
} else {
|
||||||
|
QCoreApplication::sendEvent(verticalScrollBar(), evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagView::saveCenter()
|
||||||
|
{
|
||||||
|
QPointF p = mapToScene(viewport()->rect().center());
|
||||||
|
proj.setMeta("view_center", QJsonDocument(QJsonArray({p.x(), p.y()})).toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagView::restoreViewport()
|
||||||
|
{
|
||||||
|
QVariant v_rot = proj.getMeta("view_rotation");
|
||||||
|
if (v_rot.isValid()) {
|
||||||
|
rotation = v_rot.toInt();
|
||||||
|
} else {
|
||||||
|
rotation = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant v_zoom = proj.getMeta("view_zoom");
|
||||||
|
if (v_zoom.isValid()) {
|
||||||
|
zoom = v_zoom.toDouble();
|
||||||
|
setTransform(QTransform::fromScale(zoom, zoom).rotate(rotation));
|
||||||
|
} else {
|
||||||
|
zoomToFit();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant v_center = proj.getMeta("view_center");
|
||||||
|
if (v_center.isValid()) {
|
||||||
|
QJsonArray arr = QJsonDocument::fromJson(v_center.toByteArray()).toVariant().toJsonArray();
|
||||||
|
assert(arr.size() == 2);
|
||||||
|
assert(arr[0].isDouble() && arr[1].isDouble());
|
||||||
|
centerOn(QPointF(arr[0].toDouble(), arr[1].toDouble()));
|
||||||
|
} else {
|
||||||
|
centerOn(scene.itemsBoundingRect().center());
|
||||||
|
}
|
||||||
|
}
|
||||||
38
tagview.h
Normal file
38
tagview.h
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef TAGVIEW_H
|
||||||
|
#define TAGVIEW_H
|
||||||
|
|
||||||
|
#include "sqlitebackend.h"
|
||||||
|
#include "tagscene.h"
|
||||||
|
|
||||||
|
#include <QGraphicsView>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
|
||||||
|
class TagView : public QGraphicsView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TagView(SQLiteSaveFile &proj);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void zoomToFit();
|
||||||
|
void setZoom(qreal zoom);
|
||||||
|
void zoomIn(qreal delta);
|
||||||
|
void rotate(int angle);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void wheelEvent(QWheelEvent *evt) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void saveCenter();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void restoreViewport();
|
||||||
|
|
||||||
|
TagScene scene;
|
||||||
|
SQLiteSaveFile &proj;
|
||||||
|
QTimer saveCenterTimer;
|
||||||
|
int rotation;
|
||||||
|
double zoom;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGVIEW_H
|
||||||
Loading…
Add table
Add a link
Reference in a new issue