Add database tree view
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
#include "databasetree.h"
|
||||
|
||||
#include "database.h"
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QSettings>
|
||||
#include <QSqlError>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DatabaseTreeSettings::DatabaseTreeSettings(const QString &filter, const QStringList &keywords, QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Add tree filter"));
|
||||
QVBoxLayout *vlayout = new QVBoxLayout(this);
|
||||
setLayout(vlayout);
|
||||
|
||||
QStringList key = filter.split('/');
|
||||
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
QComboBox *comboxBox = new QComboBox(this);
|
||||
comboxBox->addItem("");
|
||||
comboxBox->addItems(keywords);
|
||||
vlayout->addWidget(comboxBox);
|
||||
_keywordsSelect.append(comboxBox);
|
||||
if(i < key.size() && keywords.contains(key[i]))
|
||||
comboxBox->setCurrentText(key[i]);
|
||||
}
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &DatabaseTreeSettings::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &DatabaseTreeSettings::reject);
|
||||
vlayout->addWidget(buttonBox);
|
||||
}
|
||||
|
||||
QString DatabaseTreeSettings::keywords() const
|
||||
{
|
||||
QStringList keywords;
|
||||
for(QComboBox *box : _keywordsSelect)
|
||||
{
|
||||
if(box->currentIndex() > 0)
|
||||
keywords.append(box->currentText());
|
||||
}
|
||||
return keywords.join('/');
|
||||
}
|
||||
|
||||
class TreeNode
|
||||
{
|
||||
public:
|
||||
TreeNode() = default;
|
||||
TreeNode(TreeNode *parent, const QVariant value, int level)
|
||||
:_parent(parent)
|
||||
,_value(value)
|
||||
,_level(level)
|
||||
{}
|
||||
const TreeNode* child(size_t idx) const
|
||||
{
|
||||
if(idx >= 0 && idx < _children.size())
|
||||
return _children[idx].get();
|
||||
return nullptr;
|
||||
}
|
||||
TreeNode* child(size_t idx)
|
||||
{
|
||||
if(idx >= 0 && idx < _children.size())
|
||||
return _children[idx].get();
|
||||
return nullptr;
|
||||
}
|
||||
TreeNode* parent() const
|
||||
{
|
||||
return _parent;
|
||||
}
|
||||
int row() const
|
||||
{
|
||||
if(_parent)
|
||||
return _parent->indexOf(this);
|
||||
return 0;
|
||||
}
|
||||
int childCount() const
|
||||
{
|
||||
if(!_init)return 1;
|
||||
return _children.size();
|
||||
}
|
||||
const QVariant& value() const
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
void fill(const QVariantList &list)
|
||||
{
|
||||
_init = true;
|
||||
for(auto &item : list)
|
||||
_children.push_back(std::make_unique<TreeNode>(this, item, _level + 1));
|
||||
}
|
||||
bool filled() const
|
||||
{
|
||||
return _init;
|
||||
}
|
||||
int level() const
|
||||
{
|
||||
return _level;
|
||||
}
|
||||
private:
|
||||
int indexOf(const TreeNode *child) const
|
||||
{
|
||||
auto f = [child](const std::unique_ptr<TreeNode> &i){ return i.get() == child; };
|
||||
auto it = std::find_if(_children.begin(), _children.end(), f);
|
||||
if(it != _children.end())return std::distance(_children.begin(), it);
|
||||
return -1;
|
||||
}
|
||||
TreeNode *_parent = nullptr;
|
||||
QVariant _value;
|
||||
std::vector<std::unique_ptr<TreeNode>> _children;
|
||||
bool _init = false;
|
||||
int _level = 0;
|
||||
};
|
||||
|
||||
DatabaseTree::DatabaseTree(Database *database, QObject *parent) : QAbstractItemModel(parent)
|
||||
,_database(database)
|
||||
{
|
||||
_italicFont.setItalic(true);
|
||||
}
|
||||
|
||||
void DatabaseTree::setKeys(const QStringList &keys)
|
||||
{
|
||||
_keys = keys;
|
||||
if(!_loaded)return;
|
||||
beginResetModel();
|
||||
prepareQueries();
|
||||
_rootNode = std::make_unique<TreeNode>();
|
||||
fillNode(_rootNode.get());
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QModelIndex DatabaseTree::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if(!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
TreeNode *node;
|
||||
|
||||
if(!parent.isValid())
|
||||
node = _rootNode.get();
|
||||
else
|
||||
node = static_cast<TreeNode*>(parent.internalPointer());
|
||||
|
||||
if(node)
|
||||
{
|
||||
TreeNode *child = node->child(row);
|
||||
if(child)return createIndex(row, column, child);
|
||||
}
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex DatabaseTree::parent(const QModelIndex &index) const
|
||||
{
|
||||
if(!index.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
TreeNode *childNode = static_cast<TreeNode*>(index.internalPointer());
|
||||
const TreeNode *parentNode = childNode->parent();
|
||||
|
||||
if (parentNode == _rootNode.get())
|
||||
return QModelIndex();
|
||||
|
||||
return createIndex(parentNode->row(), 0, parentNode);
|
||||
}
|
||||
|
||||
int DatabaseTree::rowCount(const QModelIndex &index) const
|
||||
{
|
||||
if(index.column() > 0)return 0;
|
||||
|
||||
TreeNode *node;
|
||||
if(!index.isValid())
|
||||
node = _rootNode.get();
|
||||
else
|
||||
node = static_cast<TreeNode*>(index.internalPointer());
|
||||
|
||||
if(node && node->level() <= _keys.size())
|
||||
return node->childCount();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DatabaseTree::columnCount(const QModelIndex &index) const
|
||||
{
|
||||
Q_UNUSED(index);
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant DatabaseTree::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if(!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
TreeNode *node = static_cast<TreeNode*>(index.internalPointer());
|
||||
if(node == nullptr)
|
||||
return QVariant();
|
||||
|
||||
switch(role)
|
||||
{
|
||||
case Qt::FontRole:
|
||||
{
|
||||
if(node->value().toString().isNull())
|
||||
return _italicFont;
|
||||
return QVariant();
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
QString str = node->value().toString();
|
||||
if(str.isNull())return "NULL";
|
||||
else return str;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseTree::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
if(!parent.isValid())
|
||||
return false;
|
||||
|
||||
TreeNode *node = static_cast<TreeNode*>(parent.internalPointer());
|
||||
//qDebug() << "Can Fetch more" << node->value();
|
||||
if(node)
|
||||
return !node->filled();
|
||||
return false;
|
||||
}
|
||||
|
||||
void DatabaseTree::fetchMore(const QModelIndex &parent)
|
||||
{
|
||||
if(!parent.isValid())
|
||||
return;
|
||||
|
||||
TreeNode *node = static_cast<TreeNode*>(parent.internalPointer());
|
||||
//qDebug() << "Fetch more" << node->value();
|
||||
if(node)
|
||||
{
|
||||
fillNode(node);
|
||||
|
||||
if(node->childCount() > 0)
|
||||
{
|
||||
beginInsertRows(parent, 0, node->childCount() - 1);
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant DatabaseTree::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0)
|
||||
return _keys.join('/');
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void DatabaseTree::load()
|
||||
{
|
||||
if(!_loaded)
|
||||
{
|
||||
_loaded = true;
|
||||
setKeys(_keys);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseTree::prepareQueries()
|
||||
{
|
||||
if(!_loaded)return;
|
||||
|
||||
_queries.clear();
|
||||
|
||||
QString join;
|
||||
QString where;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
QSqlQuery query(sql, _database->db());
|
||||
for(auto &val : _keys)
|
||||
query.addBindValue(val);
|
||||
|
||||
if(where.isEmpty())
|
||||
where += QString(" WHERE h%1.value IS ?").arg(i);
|
||||
else
|
||||
where += QString(" AND h%1.value IS ?").arg(i);
|
||||
_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());
|
||||
for(auto &val : _keys)
|
||||
files.addBindValue(val);
|
||||
qDebug() << files.lastQuery();
|
||||
_queries.append(std::move(files));
|
||||
}
|
||||
|
||||
void DatabaseTree::fillNode(TreeNode *node)
|
||||
{
|
||||
if(node->filled())
|
||||
return;
|
||||
|
||||
TreeNode *n = node;
|
||||
QVariantList vals;
|
||||
while(n->parent())
|
||||
{
|
||||
vals.prepend(n->value());
|
||||
n = n->parent();
|
||||
}
|
||||
|
||||
int level = vals.size();
|
||||
if(level >= _queries.size())
|
||||
{
|
||||
qWarning() << "Level is too deep";
|
||||
node->fill({});
|
||||
return;
|
||||
}
|
||||
QSqlQuery &q = _queries[level];
|
||||
|
||||
for(int i = 0; i < level; i++)
|
||||
q.bindValue(i + _keys.size(), vals[i]);
|
||||
if(!q.exec())
|
||||
{
|
||||
qWarning() << "Failed to execute query" << q.lastError();
|
||||
node->fill({});
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantList list;
|
||||
while(q.next())
|
||||
list.append(q.value(0));
|
||||
|
||||
node->fill(list);
|
||||
}
|
||||
|
||||
DatabaseTreeView::DatabaseTreeView(Database *database, QWidget *parent) : QWidget(parent)
|
||||
,_database(database)
|
||||
{
|
||||
QVBoxLayout *vlayout = new QVBoxLayout(this);
|
||||
QHBoxLayout *hlayout = new QHBoxLayout(this);
|
||||
|
||||
_model = new DatabaseTree(database, this);
|
||||
_treeView = new QTreeView(this);
|
||||
_treeView->setModel(_model);
|
||||
_treeView->setHeaderHidden(true);
|
||||
|
||||
QSettings settings;
|
||||
QStringList filters = settings.value("databasetreeview/filters", QStringList{"OBJECT", "OBJECT/IMAGETYP", "OBJECT/IMAGETYP/FILTER", "OBJECT/IMAGETYP/FILTER/EXPTIME"}).toStringList();
|
||||
int selectedFilter = settings.value("databasetreeview/selectedFilter", 2).toInt();
|
||||
|
||||
_filters = new QComboBox(this);
|
||||
_filters->addItems(filters);
|
||||
_filters->setCurrentIndex(selectedFilter);
|
||||
connect(_filters, &QComboBox::currentTextChanged, this, &DatabaseTreeView::filterChanged);
|
||||
filterChanged(_filters->currentText());
|
||||
|
||||
QPushButton *addButton = new QPushButton(tr("Add"), this);
|
||||
QPushButton *removeButton = new QPushButton(tr("Remove"), this);
|
||||
|
||||
hlayout->addWidget(_filters, 1);
|
||||
hlayout->addWidget(addButton);
|
||||
hlayout->addWidget(removeButton);
|
||||
|
||||
vlayout->addLayout(hlayout);
|
||||
vlayout->addWidget(_treeView);
|
||||
|
||||
connect(_treeView, &QTreeView::activated, [this](const QModelIndex &index){
|
||||
if(!_model->hasChildren(index))
|
||||
{
|
||||
QString path = _model->data(index).toString();
|
||||
emit loadFile(path);
|
||||
}
|
||||
});
|
||||
|
||||
connect(addButton, &QPushButton::clicked, this, &DatabaseTreeView::addFilter);
|
||||
connect(removeButton, &QPushButton::clicked, this, &DatabaseTreeView::removeFilter);
|
||||
}
|
||||
|
||||
DatabaseTreeView::~DatabaseTreeView()
|
||||
{
|
||||
QStringList filters;
|
||||
for(int i = 0; i < _filters->count(); i++)
|
||||
filters.append(_filters->itemText(i));
|
||||
|
||||
QSettings settings;
|
||||
settings.setValue("databasetreeview/filters", filters);
|
||||
settings.setValue("databasetreeview/selectedFilter", _filters->currentIndex());
|
||||
}
|
||||
|
||||
void DatabaseTreeView::addFilter()
|
||||
{
|
||||
QStringList keywords = _database->getFitsKeywords();
|
||||
DatabaseTreeSettings settings(_filters->currentText(), keywords, this);
|
||||
int result = settings.exec();
|
||||
if(result == QDialog::Accepted)
|
||||
{
|
||||
QString keywords = settings.keywords();
|
||||
int idx = _filters->findText(keywords);
|
||||
if(idx == -1)
|
||||
{
|
||||
_filters->addItem(keywords);
|
||||
_filters->setCurrentText(keywords);
|
||||
}
|
||||
else
|
||||
{
|
||||
_filters->setCurrentIndex(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseTreeView::removeFilter()
|
||||
{
|
||||
if(_filters->count() > 1)
|
||||
_filters->removeItem(_filters->currentIndex());
|
||||
}
|
||||
|
||||
void DatabaseTreeView::filterChanged(const QString &filter)
|
||||
{
|
||||
QStringList keys = filter.split('/');
|
||||
_model->setKeys(keys);
|
||||
}
|
||||
|
||||
void DatabaseTreeView::visible(bool visible)
|
||||
{
|
||||
if(visible)
|
||||
_model->load();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#ifndef DATABASETREE_H
|
||||
#define DATABASETREE_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QTreeView>
|
||||
#include <QSqlQuery>
|
||||
#include <QFont>
|
||||
#include <QDialog>
|
||||
#include <QComboBox>
|
||||
#include <memory>
|
||||
|
||||
class Database;
|
||||
class TreeNode;
|
||||
|
||||
class DatabaseTreeSettings : public QDialog
|
||||
{
|
||||
public:
|
||||
explicit DatabaseTreeSettings(const QString &filter, const QStringList &keywords, QWidget *parent = nullptr);
|
||||
QString keywords() const;
|
||||
private:
|
||||
QVector<QComboBox*> _keywordsSelect;
|
||||
};
|
||||
|
||||
class DatabaseTree : public QAbstractItemModel
|
||||
{
|
||||
public:
|
||||
explicit DatabaseTree(Database *database, QObject *parent = nullptr);
|
||||
void setKeys(const QStringList &keys);
|
||||
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;
|
||||
int columnCount(const QModelIndex &index) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
void load();
|
||||
private:
|
||||
void prepareQueries();
|
||||
void fillNode(TreeNode *node);
|
||||
Database *_database = nullptr;
|
||||
std::unique_ptr<TreeNode> _rootNode;
|
||||
QVector<QSqlQuery> _queries;
|
||||
QStringList _keys;
|
||||
QFont _italicFont;
|
||||
bool _loaded = false;
|
||||
};
|
||||
|
||||
class DatabaseTreeView : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DatabaseTreeView(Database *database, QWidget *parent = nullptr);
|
||||
virtual ~DatabaseTreeView();
|
||||
public slots:
|
||||
void addFilter();
|
||||
void removeFilter();
|
||||
void filterChanged(const QString &filter);
|
||||
void visible(bool visible);
|
||||
signals:
|
||||
void loadFile(const QString &file);
|
||||
private:
|
||||
QComboBox *_filters = nullptr;
|
||||
QTreeView *_treeView = nullptr;
|
||||
DatabaseTree *_model = nullptr;
|
||||
Database *_database = nullptr;
|
||||
};
|
||||
|
||||
#endif // DATABASETREE_H
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>511</width>
|
||||
<height>487</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>120</x>
|
||||
<y>390</y>
|
||||
<width>341</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>60</x>
|
||||
<y>30</y>
|
||||
<width>86</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>180</x>
|
||||
<y>30</y>
|
||||
<width>113</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</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>Dialog</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>
|
||||
@@ -128,6 +128,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
_plateSolving->hide();
|
||||
#endif
|
||||
|
||||
_databaseTree = new DatabaseTree(m_database, this);
|
||||
|
||||
QToolBar *navigationToolbar = new QToolBar(tr("Navigation toolbar"), this);
|
||||
navigationToolbar->setObjectName("navigationtoolbar");
|
||||
navigationToolbar->hide();
|
||||
@@ -173,6 +175,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
histogramDock->hide();
|
||||
addDockWidget(Qt::LeftDockWidgetArea, histogramDock);
|
||||
|
||||
DatabaseTreeView *databaseTreeView = new DatabaseTreeView(m_database, this);
|
||||
QDockWidget *databaseTreeDock = new QDockWidget(tr("Database Tree"), this);
|
||||
databaseTreeDock->setObjectName("databasetreeDock");
|
||||
databaseTreeDock->setWidget(databaseTreeView);
|
||||
databaseTreeDock->hide();
|
||||
connect(databaseTreeDock, &QDockWidget::visibilityChanged, databaseTreeView, &DatabaseTreeView::visible);
|
||||
connect(databaseTreeView, &DatabaseTreeView::loadFile, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
|
||||
addDockWidget(Qt::BottomDockWidgetArea, databaseTreeDock);
|
||||
|
||||
setWindowTitle(tr("Tenmon"));
|
||||
|
||||
connect(m_ringList, &ImageRingList::pixmapLoaded, m_image, &ImageScrollArea::imageLoaded);
|
||||
@@ -337,6 +348,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
dockMenu->addAction(navigationToolbar->toggleViewAction());
|
||||
dockMenu->addAction(filesystemDock->toggleViewAction());
|
||||
dockMenu->addAction(databaseViewDock->toggleViewAction());
|
||||
dockMenu->addAction(databaseTreeDock->toggleViewAction());
|
||||
if(filetreeDock)dockMenu->addAction(filetreeDock->toggleViewAction());
|
||||
dockMenu->addAction(histogramDock->toggleViewAction());
|
||||
#ifdef PLATESOLVER
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "stretchtoolbar.h"
|
||||
#include "databaseview.h"
|
||||
#include "platesolving.h"
|
||||
#include "databasetree.h"
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
@@ -24,6 +25,7 @@ class MainWindow : public QMainWindow
|
||||
Filetree *m_filetree;
|
||||
DataBaseView *m_databaseView;
|
||||
PlateSolving *_plateSolving = nullptr;
|
||||
DatabaseTree *_databaseTree = nullptr;
|
||||
static int socketPair[2];
|
||||
QSocketNotifier *socketNotifier;
|
||||
QString _lastDir;
|
||||
@@ -69,6 +71,7 @@ public slots:
|
||||
void exportCSV();
|
||||
void checkNewVersion();
|
||||
void openFileManager();
|
||||
void runScript(const QString &script, const QString &outdir, const QStringList &paths, bool exit);
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
Reference in New Issue
Block a user