Add table view to database tree

This commit is contained in:
2026-04-12 13:41:29 +02:00
parent 28016ada8d
commit 27afb2ea5f
2 changed files with 201 additions and 30 deletions
+181 -24
View File
@@ -1,20 +1,31 @@
#include "databasetree.h" #include "databasetree.h"
#include "database.h" #include "database.h"
#include "databaseview.h"
#include <QComboBox> #include <QComboBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QSettings> #include <QSettings>
#include <QSqlError> #include <QSqlError>
#include <QStackedWidget>
#include <QVBoxLayout> #include <QVBoxLayout>
DatabaseTreeSettings::DatabaseTreeSettings(const QString &filter, const QStringList &keywords, QWidget *parent) : QDialog(parent) DatabaseTreeSettings::DatabaseTreeSettings(const QStringList &data, QStringList keywords, QWidget *parent) : QDialog(parent)
{ {
setWindowTitle(tr("Add tree filter")); setWindowTitle(tr("Add tree filter"));
QVBoxLayout *vlayout = new QVBoxLayout(this); QVBoxLayout *vlayout = new QVBoxLayout(this);
setLayout(vlayout); setLayout(vlayout);
QStringList key = filter.split('/'); QStringList key = data[0].split('/');
qsizetype dateobsindex = keywords.indexOf("DATE-OBS");
if(dateobsindex != -1)
{
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR-MONTH-DAY");
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR-MONTH");
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR");
}
for(int i = 0; i < 10; i++) for(int i = 0; i < 10; i++)
{ {
@@ -27,8 +38,15 @@ DatabaseTreeSettings::DatabaseTreeSettings(const QString &filter, const QStringL
comboxBox->setCurrentText(key[i]); comboxBox->setCurrentText(key[i]);
} }
vlayout->addWidget(new QLabel(tr("Aggregate function"), this));
_aggregateFunction = new QComboBox(this);
_aggregateFunction->addItems({"", "SUM", "COUNT", "AVG", "MIN", "MAX", "MEDIAN"});
vlayout->addWidget(_aggregateFunction);
_aggregateFunction->setToolTip(tr("This aggregate function will be applied to last level of grouping"));
_aggregateFunction->setCurrentText(data[1]);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DatabaseTreeSettings::accept); connect(buttonBox, &QDialogButtonBox::accepted, this, &DatabaseTreeSettings::acceptButton);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DatabaseTreeSettings::reject); connect(buttonBox, &QDialogButtonBox::rejected, this, &DatabaseTreeSettings::reject);
vlayout->addWidget(buttonBox); vlayout->addWidget(buttonBox);
} }
@@ -44,6 +62,24 @@ QString DatabaseTreeSettings::keywords() const
return keywords.join('/'); return keywords.join('/');
} }
QString DatabaseTreeSettings::aggregrationFunc() const
{
return _aggregateFunction->currentText();
}
void DatabaseTreeSettings::acceptButton()
{
for(QComboBox *box : _keywordsSelect)
{
if(box->currentIndex() > 0)
{
QDialog::accept();
return;
}
}
QDialog::reject();
}
class TreeNode class TreeNode
{ {
public: public:
@@ -130,6 +166,11 @@ void DatabaseTree::setKeys(const QStringList &keys)
endResetModel(); endResetModel();
} }
QStringList DatabaseTree::keys() const
{
return _keys;
}
QModelIndex DatabaseTree::index(int row, int column, const QModelIndex &parent) const QModelIndex DatabaseTree::index(int row, int column, const QModelIndex &parent) const
{ {
if(!hasIndex(row, column, parent)) if(!hasIndex(row, column, parent))
@@ -262,6 +303,51 @@ void DatabaseTree::load()
} }
} }
QSqlQuery DatabaseTree::getGroupQuery(const QString &aggregateFunc) const
{
QStringList cols;
QString join;
QString sum;
for(int i = 0; i < _keys.size(); i++)
{
join += QString(" LEFT JOIN fits_headers AS h%1 ON f.id = h%1.id_file AND h%1.key = ?").arg(i);
if(_keys[i] == "DATE-OBS_YEAR")
cols.append(QString("STRFTIME('%Y', h%1.value)").arg(i));
else if(_keys[i] == "DATE-OBS_YEAR-MONTH")
cols.append(QString("STRFTIME('%Y-%m', h%1.value)").arg(i));
else if(_keys[i] == "DATE-OBS_YEAR-MONTH-DAY")
cols.append(QString("STRFTIME('%Y-%m-%d', h%1.value)").arg(i));
else
cols.append(QString("h%1.value").arg(i));
if(i == _keys.size() - 1)
{
QString tmp = aggregateFunc + "(" + cols.last() + ")";
cols.last() = tmp;
}
}
QStringList group = cols;
group.removeLast();
QString sql = "SELECT " + cols.join(',') + " FROM fits_files AS f" + join + " GROUP BY " + group.join(',');
QSqlQuery query(sql, _database->db());
for(auto &val : _keys)
{
if(val.startsWith("DATE-OBS_"))
query.addBindValue("DATE-OBS");
else
query.addBindValue(val);
}
qDebug() << "Group query" << sql;
if(!query.exec())
qWarning() << "Group query failed" << query.lastError();
return query;
}
void DatabaseTree::prepareQueries() void DatabaseTree::prepareQueries()
{ {
if(!_loaded)return; if(!_loaded)return;
@@ -276,22 +362,42 @@ void DatabaseTree::prepareQueries()
for(int i = 0; i < _keys.size(); i++) for(int i = 0; i < _keys.size(); i++)
{ {
QString sql = QString("SELECT h%1.value FROM fits_files AS f").arg(i) + join + where + QString(" GROUP BY h%1.value ORDER BY h%1.value").arg(i); QString sql;
qDebug() << _keys[i] << sql; QString col = QString("h%1.value").arg(i);
if(_keys[i] == "DATE-OBS_YEAR")
col = QString("STRFTIME('%Y', h%1.value)").arg(i);
else if(_keys[i] == "DATE-OBS_YEAR-MONTH")
col = QString("STRFTIME('%Y-%m', h%1.value)").arg(i);
else if(_keys[i] == "DATE-OBS_YEAR-MONTH-DAY")
col = QString("STRFTIME('%Y-%m-%d', h%1.value)").arg(i);
sql = QString("SELECT %1 FROM fits_files AS f").arg(col) + join + where + QString(" GROUP BY %1 ORDER BY %1").arg(col);
qDebug() << "Tree query for" << _keys[i] << sql;
QSqlQuery query(sql, _database->db()); QSqlQuery query(sql, _database->db());
for(auto &val : _keys) for(auto &val : _keys)
query.addBindValue(val); {
if(val.startsWith("DATE-OBS_"))
query.addBindValue("DATE-OBS");
else
query.addBindValue(val);
}
if(where.isEmpty()) if(where.isEmpty())
where += QString(" WHERE h%1.value IS ?").arg(i); where += QString(" WHERE %1 IS ?").arg(col);
else else
where += QString(" AND h%1.value IS ?").arg(i); where += QString(" AND %1 IS ?").arg(col);
_queries.append(std::move(query)); _queries.append(std::move(query));
} }
QSqlQuery files("SELECT f.file FROM fits_files AS f " + join + where + " GROUP BY f.id ORDER BY f.file", _database->db()); QSqlQuery files("SELECT f.file FROM fits_files AS f" + join + where + " GROUP BY f.id ORDER BY f.file", _database->db());
for(auto &val : _keys) for(auto &val : _keys)
files.addBindValue(val); {
if(val.startsWith("DATE-OBS_"))
files.addBindValue("DATE-OBS");
else
files.addBindValue(val);
}
qDebug() << files.lastQuery(); qDebug() << files.lastQuery();
_queries.append(std::move(files)); _queries.append(std::move(files));
} }
@@ -322,7 +428,7 @@ void DatabaseTree::fillNode(TreeNode *node)
q.bindValue(i + _keys.size(), vals[i]); q.bindValue(i + _keys.size(), vals[i]);
if(!q.exec()) if(!q.exec())
{ {
qWarning() << "Failed to execute query" << q.lastError(); qWarning() << "Failed to execute query" << q.lastError() << q.lastQuery() << q.boundValues();
node->fill({}); node->fill({});
return; return;
} }
@@ -345,25 +451,44 @@ DatabaseTreeView::DatabaseTreeView(Database *database, QWidget *parent) : QWidge
_treeView->setModel(_model); _treeView->setModel(_model);
_treeView->setHeaderHidden(true); _treeView->setHeaderHidden(true);
_tableView = new CopyTableView(this);
_sqlModel = new QSqlQueryModel(this);
_tableView->setModel(_sqlModel);
QSettings settings; QSettings settings;
QStringList filters = settings.value("databasetreeview/filters", QStringList{"OBJECT", "OBJECT/IMAGETYP", "OBJECT/IMAGETYP/FILTER", "OBJECT/IMAGETYP/FILTER/EXPTIME", "IMAGETYP/OBJECT/IMAGETYP/FILTER/EXPTIME"}).toStringList(); QStringList filters = settings.value("databasetreeview/filters", QStringList{"OBJECT", "OBJECT/IMAGETYP", "OBJECT/IMAGETYP/FILTER", "OBJECT/IMAGETYP/FILTER/EXPTIME",
"IMAGETYP/OBJECT/IMAGETYP/FILTER/EXPTIME", "IMAGETYP/DATE-OBS_YEAR/EXPTIME"}).toStringList();
QStringList aggrFuncs = settings.value("databasetreeview/aggrFuncs", QStringList{"", "", "", "SUM", "SUM", "SUM"}).toStringList();
int selectedFilter = settings.value("databasetreeview/selectedFilter", 2).toInt(); int selectedFilter = settings.value("databasetreeview/selectedFilter", 2).toInt();
_filters = new QComboBox(this); _filters = new QComboBox(this);
_filters->addItems(filters); for(int i = 0; i < std::min(filters.size(), aggrFuncs.size()); i++)
{
_filters->addItem(filters[i] + " " + aggrFuncs[i], QStringList{filters[i], aggrFuncs[i]});
}
_filters->setCurrentIndex(selectedFilter); _filters->setCurrentIndex(selectedFilter);
connect(_filters, &QComboBox::currentTextChanged, this, &DatabaseTreeView::filterChanged); connect(_filters, &QComboBox::currentIndexChanged, this, &DatabaseTreeView::filterChanged);
filterChanged(_filters->currentText()); filterChanged(_filters->currentIndex());
QStackedWidget *stackedWidget = new QStackedWidget;
stackedWidget->addWidget(_treeView);
stackedWidget->addWidget(_tableView);
QPushButton *addButton = new QPushButton(tr("Add"), this); QPushButton *addButton = new QPushButton(tr("Add"), this);
QPushButton *removeButton = new QPushButton(tr("Remove"), this); QPushButton *removeButton = new QPushButton(tr("Remove"), this);
QPushButton *treeTableButton = new QPushButton(tr("Tree/Table"), this);
treeTableButton->setCheckable(true);
connect(treeTableButton, &QPushButton::clicked, [stackedWidget](bool checked){
stackedWidget->setCurrentIndex(checked ? 1 : 0);
});
hlayout->addWidget(_filters, 1); hlayout->addWidget(_filters, 1);
hlayout->addWidget(addButton); hlayout->addWidget(addButton);
hlayout->addWidget(removeButton); hlayout->addWidget(removeButton);
hlayout->addWidget(treeTableButton);
vlayout->addLayout(hlayout); vlayout->addLayout(hlayout);
vlayout->addWidget(_treeView); vlayout->addWidget(stackedWidget);
connect(_treeView, &QTreeView::activated, [this](const QModelIndex &index){ connect(_treeView, &QTreeView::activated, [this](const QModelIndex &index){
if(!_model->hasChildren(index)) if(!_model->hasChildren(index))
@@ -380,27 +505,36 @@ DatabaseTreeView::DatabaseTreeView(Database *database, QWidget *parent) : QWidge
DatabaseTreeView::~DatabaseTreeView() DatabaseTreeView::~DatabaseTreeView()
{ {
QStringList filters; QStringList filters;
QStringList aggrFuncs;
for(int i = 0; i < _filters->count(); i++) for(int i = 0; i < _filters->count(); i++)
filters.append(_filters->itemText(i)); {
QStringList data = _filters->itemData(i).toStringList();
filters.append(data[0]);
aggrFuncs.append(data[1]);
}
QSettings settings; QSettings settings;
settings.setValue("databasetreeview/filters", filters); settings.setValue("databasetreeview/filters", filters);
settings.setValue("databasetreeview/aggrFuncs", aggrFuncs);
settings.setValue("databasetreeview/selectedFilter", _filters->currentIndex()); settings.setValue("databasetreeview/selectedFilter", _filters->currentIndex());
} }
void DatabaseTreeView::addFilter() void DatabaseTreeView::addFilter()
{ {
QStringList keywords = _database->getFitsKeywords(); QStringList keywords = _database->getFitsKeywords();
DatabaseTreeSettings settings(_filters->currentText(), keywords, this); QStringList data = _filters->currentData().toStringList();
DatabaseTreeSettings settings(data, keywords, this);
int result = settings.exec(); int result = settings.exec();
if(result == QDialog::Accepted) if(result == QDialog::Accepted)
{ {
QString keywords = settings.keywords(); QString keywords = settings.keywords();
int idx = _filters->findText(keywords); QString aggrFunc = settings.aggregrationFunc();
QString text = keywords + " " + aggrFunc;
int idx = _filters->findText(text);
if(idx == -1) if(idx == -1)
{ {
_filters->addItem(keywords); _filters->addItem(text, QStringList{keywords, aggrFunc});
_filters->setCurrentText(keywords); _filters->setCurrentText(text);
} }
else else
{ {
@@ -415,14 +549,37 @@ void DatabaseTreeView::removeFilter()
_filters->removeItem(_filters->currentIndex()); _filters->removeItem(_filters->currentIndex());
} }
void DatabaseTreeView::filterChanged(const QString &filter) void DatabaseTreeView::filterChanged(int index)
{ {
QStringList keys = filter.split('/'); QStringList data = _filters->itemData(index).toStringList();
QStringList keys = data[0].split('/');
_model->setKeys(keys); _model->setKeys(keys);
setQuery(data[1]);
} }
void DatabaseTreeView::visible(bool visible) void DatabaseTreeView::visible(bool visible)
{ {
if(visible) if(visible && !_loaded)
{
_loaded = true;
_model->load(); _model->load();
QStringList data = _filters->currentData().toStringList();
setQuery(data[1]);
}
}
void DatabaseTreeView::setQuery(const QString &func)
{
QStringList keys = _model->keys();
int i = 0;
_sqlModel->setQuery(_model->getGroupQuery(func));
if(!func.isEmpty())
{
QString tmp = func + "(" + keys.last() + ")";
keys.last() = tmp;
}
for(auto &key : keys)
_sqlModel->setHeaderData(i++, Qt::Horizontal, key);
_tableView->resizeColumnsToContents();
} }
+20 -6
View File
@@ -2,11 +2,13 @@
#define DATABASETREE_H #define DATABASETREE_H
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QTreeView>
#include <QSqlQuery>
#include <QFont>
#include <QDialog>
#include <QComboBox> #include <QComboBox>
#include <QDialog>
#include <QFont>
#include <QSqlQuery>
#include <QSqlQueryModel>
#include <QTableView>
#include <QTreeView>
#include <memory> #include <memory>
class Database; class Database;
@@ -14,11 +16,16 @@ class TreeNode;
class DatabaseTreeSettings : public QDialog class DatabaseTreeSettings : public QDialog
{ {
Q_OBJECT
public: public:
explicit DatabaseTreeSettings(const QString &filter, const QStringList &keywords, QWidget *parent = nullptr); explicit DatabaseTreeSettings(const QStringList &data, QStringList keywords, QWidget *parent = nullptr);
QString keywords() const; QString keywords() const;
QString aggregrationFunc() const;
public slots:
void acceptButton();
private: private:
QVector<QComboBox*> _keywordsSelect; QVector<QComboBox*> _keywordsSelect;
QComboBox *_aggregateFunction;
}; };
class DatabaseTree : public QAbstractItemModel class DatabaseTree : public QAbstractItemModel
@@ -26,6 +33,7 @@ class DatabaseTree : public QAbstractItemModel
public: public:
explicit DatabaseTree(Database *database, QObject *parent = nullptr); explicit DatabaseTree(Database *database, QObject *parent = nullptr);
void setKeys(const QStringList &keys); void setKeys(const QStringList &keys);
QStringList keys() const;
QModelIndex index(int row, int column, const QModelIndex &parent) const override; QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &index) const override; QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &index) const override; int rowCount(const QModelIndex &index) const override;
@@ -35,6 +43,7 @@ public:
void fetchMore(const QModelIndex &parent) override; void fetchMore(const QModelIndex &parent) override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
void load(); void load();
QSqlQuery getGroupQuery(const QString &aggregateFunc) const;
private: private:
void prepareQueries(); void prepareQueries();
void fillNode(TreeNode *node); void fillNode(TreeNode *node);
@@ -55,15 +64,20 @@ public:
public slots: public slots:
void addFilter(); void addFilter();
void removeFilter(); void removeFilter();
void filterChanged(const QString &filter); void filterChanged(int index);
void visible(bool visible); void visible(bool visible);
private:
void setQuery(const QString &func);
signals: signals:
void loadFile(const QString &file); void loadFile(const QString &file);
private: private:
QComboBox *_filters = nullptr; QComboBox *_filters = nullptr;
QTreeView *_treeView = nullptr; QTreeView *_treeView = nullptr;
QTableView *_tableView = nullptr;
DatabaseTree *_model = nullptr; DatabaseTree *_model = nullptr;
QSqlQueryModel *_sqlModel = nullptr;
Database *_database = nullptr; Database *_database = nullptr;
bool _loaded = false;
}; };
#endif // DATABASETREE_H #endif // DATABASETREE_H