diff --git a/src/databasetree.cpp b/src/databasetree.cpp index 06642a1..698499e 100644 --- a/src/databasetree.cpp +++ b/src/databasetree.cpp @@ -1,20 +1,31 @@ #include "databasetree.h" #include "database.h" +#include "databaseview.h" #include #include +#include #include #include #include +#include #include -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")); QVBoxLayout *vlayout = new QVBoxLayout(this); 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++) { @@ -27,8 +38,15 @@ DatabaseTreeSettings::DatabaseTreeSettings(const QString &filter, const QStringL 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); - connect(buttonBox, &QDialogButtonBox::accepted, this, &DatabaseTreeSettings::accept); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DatabaseTreeSettings::acceptButton); connect(buttonBox, &QDialogButtonBox::rejected, this, &DatabaseTreeSettings::reject); vlayout->addWidget(buttonBox); } @@ -44,6 +62,24 @@ QString DatabaseTreeSettings::keywords() const 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 { public: @@ -130,6 +166,11 @@ void DatabaseTree::setKeys(const QStringList &keys) endResetModel(); } +QStringList DatabaseTree::keys() const +{ + return _keys; +} + QModelIndex DatabaseTree::index(int row, int column, const QModelIndex &parent) const { 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() { if(!_loaded)return; @@ -276,22 +362,42 @@ void DatabaseTree::prepareQueries() 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); - qDebug() << _keys[i] << sql; + QString 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()); for(auto &val : _keys) - query.addBindValue(val); + { + if(val.startsWith("DATE-OBS_")) + query.addBindValue("DATE-OBS"); + else + query.addBindValue(val); + } if(where.isEmpty()) - where += QString(" WHERE h%1.value IS ?").arg(i); + where += QString(" WHERE %1 IS ?").arg(col); else - where += QString(" AND h%1.value IS ?").arg(i); + where += QString(" AND %1 IS ?").arg(col); _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) - files.addBindValue(val); + { + if(val.startsWith("DATE-OBS_")) + files.addBindValue("DATE-OBS"); + else + files.addBindValue(val); + } qDebug() << files.lastQuery(); _queries.append(std::move(files)); } @@ -322,7 +428,7 @@ void DatabaseTree::fillNode(TreeNode *node) q.bindValue(i + _keys.size(), vals[i]); if(!q.exec()) { - qWarning() << "Failed to execute query" << q.lastError(); + qWarning() << "Failed to execute query" << q.lastError() << q.lastQuery() << q.boundValues(); node->fill({}); return; } @@ -345,25 +451,44 @@ DatabaseTreeView::DatabaseTreeView(Database *database, QWidget *parent) : QWidge _treeView->setModel(_model); _treeView->setHeaderHidden(true); + _tableView = new CopyTableView(this); + _sqlModel = new QSqlQueryModel(this); + _tableView->setModel(_sqlModel); + 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(); _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); - connect(_filters, &QComboBox::currentTextChanged, this, &DatabaseTreeView::filterChanged); - filterChanged(_filters->currentText()); + connect(_filters, &QComboBox::currentIndexChanged, this, &DatabaseTreeView::filterChanged); + filterChanged(_filters->currentIndex()); + + QStackedWidget *stackedWidget = new QStackedWidget; + stackedWidget->addWidget(_treeView); + stackedWidget->addWidget(_tableView); QPushButton *addButton = new QPushButton(tr("Add"), 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(addButton); hlayout->addWidget(removeButton); + hlayout->addWidget(treeTableButton); vlayout->addLayout(hlayout); - vlayout->addWidget(_treeView); + vlayout->addWidget(stackedWidget); connect(_treeView, &QTreeView::activated, [this](const QModelIndex &index){ if(!_model->hasChildren(index)) @@ -380,27 +505,36 @@ DatabaseTreeView::DatabaseTreeView(Database *database, QWidget *parent) : QWidge DatabaseTreeView::~DatabaseTreeView() { QStringList filters; + QStringList aggrFuncs; 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; settings.setValue("databasetreeview/filters", filters); + settings.setValue("databasetreeview/aggrFuncs", aggrFuncs); settings.setValue("databasetreeview/selectedFilter", _filters->currentIndex()); } void DatabaseTreeView::addFilter() { QStringList keywords = _database->getFitsKeywords(); - DatabaseTreeSettings settings(_filters->currentText(), keywords, this); + QStringList data = _filters->currentData().toStringList(); + DatabaseTreeSettings settings(data, keywords, this); int result = settings.exec(); if(result == QDialog::Accepted) { 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) { - _filters->addItem(keywords); - _filters->setCurrentText(keywords); + _filters->addItem(text, QStringList{keywords, aggrFunc}); + _filters->setCurrentText(text); } else { @@ -415,14 +549,37 @@ void DatabaseTreeView::removeFilter() _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); + setQuery(data[1]); } void DatabaseTreeView::visible(bool visible) { - if(visible) + if(visible && !_loaded) + { + _loaded = true; _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(); } diff --git a/src/databasetree.h b/src/databasetree.h index f515d13..9226d29 100644 --- a/src/databasetree.h +++ b/src/databasetree.h @@ -2,11 +2,13 @@ #define DATABASETREE_H #include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include class Database; @@ -14,11 +16,16 @@ class TreeNode; class DatabaseTreeSettings : public QDialog { + Q_OBJECT 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 aggregrationFunc() const; +public slots: + void acceptButton(); private: QVector _keywordsSelect; + QComboBox *_aggregateFunction; }; class DatabaseTree : public QAbstractItemModel @@ -26,6 +33,7 @@ class DatabaseTree : public QAbstractItemModel public: explicit DatabaseTree(Database *database, QObject *parent = nullptr); void setKeys(const QStringList &keys); + QStringList keys() const; QModelIndex index(int row, int column, const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &index) const override; int rowCount(const QModelIndex &index) const override; @@ -35,6 +43,7 @@ public: void fetchMore(const QModelIndex &parent) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; void load(); + QSqlQuery getGroupQuery(const QString &aggregateFunc) const; private: void prepareQueries(); void fillNode(TreeNode *node); @@ -55,15 +64,20 @@ public: public slots: void addFilter(); void removeFilter(); - void filterChanged(const QString &filter); + void filterChanged(int index); void visible(bool visible); +private: + void setQuery(const QString &func); signals: void loadFile(const QString &file); private: QComboBox *_filters = nullptr; QTreeView *_treeView = nullptr; + QTableView *_tableView = nullptr; DatabaseTree *_model = nullptr; + QSqlQueryModel *_sqlModel = nullptr; Database *_database = nullptr; + bool _loaded = false; }; #endif // DATABASETREE_H