unify-grid #88

Merged
eelke merged 5 commits from unify-grid into main 2026-10-18 09:47:33 +00:00
9 changed files with 153 additions and 12 deletions
Showing only changes of commit ee1a8ba48d - Show all commits

6
.idea/QtSettings.xml generated Normal file
View 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
View 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
View file

@ -1,8 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
<module classpath="CIDR" type="CPP_MODULE" version="4" />

View file

@ -244,6 +244,7 @@ void CrudModel::loadData()
void CrudModel::loadIntoModel(std::shared_ptr<Pgsql::Result> data)
{
beginResetModel();
bool was_dirty = hasPendingChanges();
m_pendingRowList.clear();
m_roData = data;
initializeColumnList();
@ -253,6 +254,8 @@ void CrudModel::loadIntoModel(std::shared_ptr<Pgsql::Result> data)
if (m_wholeTable)
appendNewRowInternal();
endResetModel();
if (was_dirty)
emit pendingChangesChanged(false);
}
void CrudModel::initRowMapping()
@ -321,7 +324,10 @@ bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int rol
s.clear();
val = s;
}
bool was_dirty = hasPendingChanges();
m_pendingRowList.setValue(col, row_mapping.rowKey, val);
if (!was_dirty)
emit pendingChangesChanged(true);
emit dataChanged(index, index, QVector<int>() << role);
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()
@ -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
}
// Then from model
bool was_dirty = hasPendingChanges();
RemoveRangesOfRowsFromModel(row_ranges);
if (was_dirty != hasPendingChanges())
emit pendingChangesChanged(hasPendingChanges());
return { true, "" };
}
catch (const Pgsql::PgResultError &error)
@ -713,6 +735,8 @@ void CrudModel::RemoveRangeOfRowsFromModel(IntegerRange<int> range)
beginRemoveRows(QModelIndex(), range.start(), range.end() - 1);
SCOPE_EXIT { endRemoveRows(); };
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());
}

View file

@ -46,12 +46,21 @@ public:
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);
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:
virtual bool submit() override;
virtual void revert() override;
private:

View file

@ -60,6 +60,13 @@ void CrudTab::setConfig(Oid oid) //std::shared_ptr<OpenDatabase> db, const PgCla
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();
}

View file

@ -1,9 +1,13 @@
#include "DataGridWidget.h"
#include "CrudModel.h"
#include "CustomDataRole.h"
#include "ResultTableModelUtil.h"
#include "util/PgLabItemDelegate.h"
#include <QAction>
#include <QSortFilterProxyModel>
#include <QStyle>
#include <QTableView>
#include <QToolBar>
#include <QVBoxLayout>
@ -13,6 +17,25 @@ DataGridWidget::DataGridWidget(QWidget *parent)
auto layout = new QVBoxLayout(this);
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);
layout->addWidget(m_view);
@ -27,8 +50,52 @@ void DataGridWidget::setSourceModel(QAbstractItemModel *model)
m_proxy = new QSortFilterProxyModel(this);
m_proxy->setSortRole(CustomSortRole);
}
disconnect(m_pendingChangesConnection);
m_proxy->setSourceModel(model);
m_view->setModel(m_proxy);
m_view->setSortingEnabled(true);
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();
}

View file

@ -4,8 +4,10 @@
#include <QWidget>
class QAbstractItemModel;
class QAction;
class QSortFilterProxyModel;
class QTableView;
class QToolBar;
/** 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; }
QSortFilterProxyModel *proxy() const { return m_proxy; }
private slots:
void onPendingChangesChanged(bool hasChanges);
void onSave();
void onRevert();
private:
/// Closes an open cell editor so its value is written to the model before saving or reverting
void commitOpenEditor();
QTableView *m_view = nullptr;
QSortFilterProxyModel *m_proxy = nullptr;
QToolBar *m_toolbar = nullptr;
QAction *m_actSave = nullptr;
QAction *m_actRevert = nullptr;
QMetaObject::Connection m_pendingChangesConnection;
};
#endif // DATAGRIDWIDGET_H

View file

@ -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.