Add save and revert button to datagrid no implicit saves any more.
This commit is contained in:
parent
66267334c8
commit
ee1a8ba48d
9 changed files with 153 additions and 12 deletions
6
.idea/QtSettings.xml
generated
Normal file
6
.idea/QtSettings.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="QtSettings">
|
||||||
|
<option name="myCurrentProfile" value="Debug" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CMakePythonSetting">
|
||||||
|
<option name="pythonIntegrationState" value="YES" />
|
||||||
|
</component>
|
||||||
|
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||||
|
</project>
|
||||||
8
.idea/pgLab.iml
generated
8
.idea/pgLab.iml
generated
|
|
@ -1,8 +1,2 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="EMPTY_MODULE" version="4">
|
<module classpath="CIDR" type="CPP_MODULE" version="4" />
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
|
|
@ -244,6 +244,7 @@ void CrudModel::loadData()
|
||||||
void CrudModel::loadIntoModel(std::shared_ptr<Pgsql::Result> data)
|
void CrudModel::loadIntoModel(std::shared_ptr<Pgsql::Result> data)
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
bool was_dirty = hasPendingChanges();
|
||||||
m_pendingRowList.clear();
|
m_pendingRowList.clear();
|
||||||
m_roData = data;
|
m_roData = data;
|
||||||
initializeColumnList();
|
initializeColumnList();
|
||||||
|
|
@ -253,6 +254,8 @@ void CrudModel::loadIntoModel(std::shared_ptr<Pgsql::Result> data)
|
||||||
if (m_wholeTable)
|
if (m_wholeTable)
|
||||||
appendNewRowInternal();
|
appendNewRowInternal();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
if (was_dirty)
|
||||||
|
emit pendingChangesChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrudModel::initRowMapping()
|
void CrudModel::initRowMapping()
|
||||||
|
|
@ -321,7 +324,10 @@ bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int rol
|
||||||
s.clear();
|
s.clear();
|
||||||
val = s;
|
val = s;
|
||||||
}
|
}
|
||||||
|
bool was_dirty = hasPendingChanges();
|
||||||
m_pendingRowList.setValue(col, row_mapping.rowKey, val);
|
m_pendingRowList.setValue(col, row_mapping.rowKey, val);
|
||||||
|
if (!was_dirty)
|
||||||
|
emit pendingChangesChanged(true);
|
||||||
|
|
||||||
emit dataChanged(index, index, QVector<int>() << role);
|
emit dataChanged(index, index, QVector<int>() << role);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -611,14 +617,27 @@ void CrudModel::initializeColumnList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CrudModel::submit()
|
bool CrudModel::saveChanges()
|
||||||
{
|
{
|
||||||
return savePendingChanges();
|
bool was_dirty = hasPendingChanges();
|
||||||
|
bool ok = savePendingChanges();
|
||||||
|
if (was_dirty != hasPendingChanges())
|
||||||
|
emit pendingChangesChanged(hasPendingChanges());
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrudModel::revert()
|
void CrudModel::revertChanges()
|
||||||
{
|
{
|
||||||
|
if (!hasPendingChanges())
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_pendingRowList.clear();
|
||||||
|
for (auto &row_mapping : m_rowMapping)
|
||||||
|
row_mapping.pending = false;
|
||||||
|
|
||||||
|
if (rowCount() > 0)
|
||||||
|
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||||
|
emit pendingChangesChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrudModel::appendNewRow()
|
void CrudModel::appendNewRow()
|
||||||
|
|
@ -678,7 +697,10 @@ std::tuple<bool, QString> CrudModel::removeRows(const std::set<IntegerRange<int>
|
||||||
// If something goes wrong after this commit we should reload contents of model
|
// If something goes wrong after this commit we should reload contents of model
|
||||||
}
|
}
|
||||||
// Then from model
|
// Then from model
|
||||||
|
bool was_dirty = hasPendingChanges();
|
||||||
RemoveRangesOfRowsFromModel(row_ranges);
|
RemoveRangesOfRowsFromModel(row_ranges);
|
||||||
|
if (was_dirty != hasPendingChanges())
|
||||||
|
emit pendingChangesChanged(hasPendingChanges());
|
||||||
return { true, "" };
|
return { true, "" };
|
||||||
}
|
}
|
||||||
catch (const Pgsql::PgResultError &error)
|
catch (const Pgsql::PgResultError &error)
|
||||||
|
|
@ -713,6 +735,8 @@ void CrudModel::RemoveRangeOfRowsFromModel(IntegerRange<int> range)
|
||||||
beginRemoveRows(QModelIndex(), range.start(), range.end() - 1);
|
beginRemoveRows(QModelIndex(), range.start(), range.end() - 1);
|
||||||
SCOPE_EXIT { endRemoveRows(); };
|
SCOPE_EXIT { endRemoveRows(); };
|
||||||
auto first = m_rowMapping.begin() + range.start();
|
auto first = m_rowMapping.begin() + range.start();
|
||||||
|
for (auto iter = first; iter != first + range.length(); ++iter)
|
||||||
|
m_pendingRowList.m_rows.erase(iter->rowKey);
|
||||||
m_rowMapping.erase(first, first + range.length());
|
m_rowMapping.erase(first, first + range.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,21 @@ public:
|
||||||
|
|
||||||
void loadData();
|
void loadData();
|
||||||
|
|
||||||
|
/// True when there are edits that have not been saved to the database
|
||||||
|
bool hasPendingChanges() const { return !m_pendingRowList.m_rows.empty(); }
|
||||||
|
/// Writes all pending rows to the database, rows that fail stay pending
|
||||||
|
bool saveChanges();
|
||||||
|
/// Discards all pending edits, the grid shows the last saved values again
|
||||||
|
void revertChanges();
|
||||||
|
|
||||||
std::tuple<bool, QString> removeRows(const std::set<IntegerRange<int>> &row_ranges);
|
std::tuple<bool, QString> removeRows(const std::set<IntegerRange<int>> &row_ranges);
|
||||||
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
|
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/// Emitted when the model goes from clean to having pending changes or back
|
||||||
|
void pendingChangesChanged(bool hasChanges);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual bool submit() override;
|
|
||||||
virtual void revert() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,13 @@ void CrudTab::setConfig(Oid oid) //std::shared_ptr<OpenDatabase> db, const PgCla
|
||||||
|
|
||||||
void CrudTab::refresh()
|
void CrudTab::refresh()
|
||||||
{
|
{
|
||||||
|
if (m_crudModel->hasPendingChanges()) {
|
||||||
|
auto res = QMessageBox::question(this, "pgLab",
|
||||||
|
tr("There are unsaved changes. Refreshing discards them. Do you want to continue?"),
|
||||||
|
QMessageBox::Yes, QMessageBox::No);
|
||||||
|
if (res != QMessageBox::Yes)
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_crudModel->loadData();
|
m_crudModel->loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
#include "DataGridWidget.h"
|
#include "DataGridWidget.h"
|
||||||
|
#include "CrudModel.h"
|
||||||
#include "CustomDataRole.h"
|
#include "CustomDataRole.h"
|
||||||
#include "ResultTableModelUtil.h"
|
#include "ResultTableModelUtil.h"
|
||||||
#include "util/PgLabItemDelegate.h"
|
#include "util/PgLabItemDelegate.h"
|
||||||
|
#include <QAction>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QStyle>
|
||||||
#include <QTableView>
|
#include <QTableView>
|
||||||
|
#include <QToolBar>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -13,6 +17,25 @@ DataGridWidget::DataGridWidget(QWidget *parent)
|
||||||
auto layout = new QVBoxLayout(this);
|
auto layout = new QVBoxLayout(this);
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
|
m_toolbar = new QToolBar(this);
|
||||||
|
m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||||
|
layout->addWidget(m_toolbar);
|
||||||
|
|
||||||
|
m_actSave = new QAction(style()->standardIcon(QStyle::SP_DialogSaveButton), tr("Save"), this);
|
||||||
|
m_actSave->setShortcut(QKeySequence::Save);
|
||||||
|
m_actSave->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
||||||
|
m_actSave->setToolTip(tr("Save all pending changes to the database (Ctrl+S)"));
|
||||||
|
m_actSave->setEnabled(false);
|
||||||
|
addAction(m_actSave);
|
||||||
|
m_toolbar->addAction(m_actSave);
|
||||||
|
connect(m_actSave, &QAction::triggered, this, &DataGridWidget::onSave);
|
||||||
|
|
||||||
|
m_actRevert = new QAction(style()->standardIcon(QStyle::SP_DialogResetButton), tr("Revert"), this);
|
||||||
|
m_actRevert->setToolTip(tr("Discard all pending changes"));
|
||||||
|
m_actRevert->setEnabled(false);
|
||||||
|
m_toolbar->addAction(m_actRevert);
|
||||||
|
connect(m_actRevert, &QAction::triggered, this, &DataGridWidget::onRevert);
|
||||||
|
|
||||||
m_view = new QTableView(this);
|
m_view = new QTableView(this);
|
||||||
layout->addWidget(m_view);
|
layout->addWidget(m_view);
|
||||||
|
|
||||||
|
|
@ -27,8 +50,52 @@ void DataGridWidget::setSourceModel(QAbstractItemModel *model)
|
||||||
m_proxy = new QSortFilterProxyModel(this);
|
m_proxy = new QSortFilterProxyModel(this);
|
||||||
m_proxy->setSortRole(CustomSortRole);
|
m_proxy->setSortRole(CustomSortRole);
|
||||||
}
|
}
|
||||||
|
disconnect(m_pendingChangesConnection);
|
||||||
m_proxy->setSourceModel(model);
|
m_proxy->setSourceModel(model);
|
||||||
m_view->setModel(m_proxy);
|
m_view->setModel(m_proxy);
|
||||||
m_view->setSortingEnabled(true);
|
m_view->setSortingEnabled(true);
|
||||||
m_proxy->sort(0, Qt::AscendingOrder);
|
m_proxy->sort(0, Qt::AscendingOrder);
|
||||||
|
|
||||||
|
auto crud = qobject_cast<CrudModel *>(model);
|
||||||
|
m_toolbar->setVisible(crud != nullptr);
|
||||||
|
if (crud) {
|
||||||
|
m_pendingChangesConnection = connect(crud, &CrudModel::pendingChangesChanged, this, &DataGridWidget::onPendingChangesChanged);
|
||||||
|
onPendingChangesChanged(crud->hasPendingChanges());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
onPendingChangesChanged(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataGridWidget::onPendingChangesChanged(bool hasChanges)
|
||||||
|
{
|
||||||
|
m_actSave->setEnabled(hasChanges);
|
||||||
|
m_actRevert->setEnabled(hasChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataGridWidget::onSave()
|
||||||
|
{
|
||||||
|
auto crud = qobject_cast<CrudModel *>(m_proxy->sourceModel());
|
||||||
|
if (!crud)
|
||||||
|
return;
|
||||||
|
|
||||||
|
commitOpenEditor();
|
||||||
|
crud->saveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataGridWidget::onRevert()
|
||||||
|
{
|
||||||
|
auto crud = qobject_cast<CrudModel *>(m_proxy->sourceModel());
|
||||||
|
if (!crud)
|
||||||
|
return;
|
||||||
|
|
||||||
|
commitOpenEditor();
|
||||||
|
crud->revertChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataGridWidget::commitOpenEditor()
|
||||||
|
{
|
||||||
|
// Moving focus away from the editor makes the delegate commit its value, a toolbar click does not take focus
|
||||||
|
if (m_view->indexWidget(m_view->currentIndex()))
|
||||||
|
m_view->setFocus();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class QAbstractItemModel;
|
class QAbstractItemModel;
|
||||||
|
class QAction;
|
||||||
class QSortFilterProxyModel;
|
class QSortFilterProxyModel;
|
||||||
class QTableView;
|
class QTableView;
|
||||||
|
class QToolBar;
|
||||||
|
|
||||||
/** Sortable and editable grid shared by the CRUD tab and the query result tab.
|
/** Sortable and editable grid shared by the CRUD tab and the query result tab.
|
||||||
*/
|
*/
|
||||||
|
|
@ -20,9 +22,21 @@ public:
|
||||||
QTableView *view() const { return m_view; }
|
QTableView *view() const { return m_view; }
|
||||||
QSortFilterProxyModel *proxy() const { return m_proxy; }
|
QSortFilterProxyModel *proxy() const { return m_proxy; }
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPendingChangesChanged(bool hasChanges);
|
||||||
|
void onSave();
|
||||||
|
void onRevert();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Closes an open cell editor so its value is written to the model before saving or reverting
|
||||||
|
void commitOpenEditor();
|
||||||
|
|
||||||
QTableView *m_view = nullptr;
|
QTableView *m_view = nullptr;
|
||||||
QSortFilterProxyModel *m_proxy = nullptr;
|
QSortFilterProxyModel *m_proxy = nullptr;
|
||||||
|
QToolBar *m_toolbar = nullptr;
|
||||||
|
QAction *m_actSave = nullptr;
|
||||||
|
QAction *m_actRevert = nullptr;
|
||||||
|
QMetaObject::Connection m_pendingChangesConnection;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DATAGRIDWIDGET_H
|
#endif // DATAGRIDWIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Data grids in the table editor and in query results now have a toolbar
|
||||||
|
with :menuselection:`Save` and :menuselection:`Revert` buttons, Save can
|
||||||
|
also be triggered with :kbd:`Ctrl+S`. Both buttons are only enabled while
|
||||||
|
there are unsaved changes.
|
||||||
|
other:
|
||||||
|
- |
|
||||||
|
Edits in data grids are no longer saved automatically when a cell editor
|
||||||
|
is closed or when :kbd:`Escape` is pressed. Use the :menuselection:`Save`
|
||||||
|
button to write changes to the database, or :menuselection:`Revert` to
|
||||||
|
discard them. Refreshing a table with unsaved changes asks for confirmation.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue