unify-grid #88
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"?>
|
||||
<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" />
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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