diff --git a/pglab/CMakeLists.txt b/pglab/CMakeLists.txt index ab4dda5..fb3d3f2 100644 --- a/pglab/CMakeLists.txt +++ b/pglab/CMakeLists.txt @@ -30,9 +30,9 @@ set(PGLAB_SOURCES codeeditor/GutterPainter.cpp crud/CrudModel.cpp crud/CrudTab.cpp + crud/DataGridWidget.cpp querytool/QueryExplainModel.cpp querytool/QueryParamListController.cpp - querytool/QueryResultModel.cpp querytool/QueryTool.cpp serverinspector/DatabasesPage.cpp serverinspector/RolesPage.cpp diff --git a/pglab/crud/CrudModel.cpp b/pglab/crud/CrudModel.cpp index 8a8ce00..a8bcae1 100644 --- a/pglab/crud/CrudModel.cpp +++ b/pglab/crud/CrudModel.cpp @@ -10,6 +10,7 @@ #include "CustomDataRole.h" #include #include +#include #include #include "Pgsql_oids.h" #include "Pgsql_PgException.h" @@ -217,6 +218,8 @@ QVariant CrudModel::data(const QModelIndex &index, int role) const if (value) return StringToVariant(value->c_str(), typ); } + else if (role == Qt::ForegroundRole && !m_wholeTable && columnIsReadOnly(index.column())) + return QBrush(Qt::gray); return {}; } diff --git a/pglab/crud/CrudTab.cpp b/pglab/crud/CrudTab.cpp index a776879..88b0ac2 100644 --- a/pglab/crud/CrudTab.cpp +++ b/pglab/crud/CrudTab.cpp @@ -3,16 +3,19 @@ #include "CrudModel.h" #include "CustomDataRole.h" #include "ResultTableModelUtil.h" -#include "util/PgLabItemDelegate.h" +#include "DataGridWidget.h" #include "IntegerRange.h" #include "OpenDatabase.h" #include "catalog/PgClassContainer.h" #include "catalog/PgDatabaseCatalog.h" #include +#include #include +#include #include #include #include +#include CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent) @@ -24,25 +27,18 @@ CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent) m_db = context->openDatabase(); - SetTableViewDefault(ui->tableView); - - auto delegate = new PgLabItemDelegate(ui->tableView); - ui->tableView->setItemDelegate(delegate); - m_crudModel = new CrudModel(nullptr); - m_SortFilterProxy = new QSortFilterProxyModel(this); - m_SortFilterProxy->setSourceModel(m_crudModel); - m_SortFilterProxy->setSortRole(CustomSortRole); - ui->tableView->setModel(m_SortFilterProxy); - ui->tableView->setSortingEnabled(true); - m_SortFilterProxy->sort(0, Qt::AscendingOrder); + m_grid = new DataGridWidget(this); + ui->verticalLayout->addWidget(m_grid); + m_grid->setSourceModel(m_crudModel); + m_grid->view()->verticalHeader()->setVisible(false); - ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); - ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows); - ui->tableView->addAction(ui->actionRemove_rows); + m_grid->view()->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_grid->view()->setSelectionBehavior(QAbstractItemView::SelectRows); + m_grid->view()->addAction(ui->actionRemove_rows); - auto horizontal_header = ui->tableView->horizontalHeader(); + auto horizontal_header = m_grid->view()->horizontalHeader(); horizontal_header->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); connect(horizontal_header, &QHeaderView::customContextMenuRequested, this, &CrudTab::headerCustomContextMenu); @@ -70,7 +66,7 @@ void CrudTab::refresh() void CrudTab::on_actionRemove_rows_triggered() { std::set> row_ranges; - auto selection = m_SortFilterProxy->mapSelectionToSource(ui->tableView->selectionModel()->selection()); + auto selection = m_grid->proxy()->mapSelectionToSource(m_grid->view()->selectionModel()->selection()); for (auto& range : selection) { row_ranges.emplace(range.top(), range.height()); } @@ -110,7 +106,7 @@ void CrudTab::headerCustomContextMenu(const QPoint &pos) if (headerContextMenu) { - auto horizontal_header = ui->tableView->horizontalHeader(); + auto horizontal_header = m_grid->view()->horizontalHeader(); headerContextMenu->popup(horizontal_header->mapToGlobal(pos)); } } @@ -122,9 +118,9 @@ void CrudTab::gotoColumn(QString column) auto name = m_crudModel->headerData(i, Qt::Horizontal).toString(); if (name == column) { - auto index = ui->tableView->currentIndex(); - index = ui->tableView->model()->index(index.row(), i); - ui->tableView->setCurrentIndex(index); + auto index = m_grid->view()->currentIndex(); + index = m_grid->view()->model()->index(index.row(), i); + m_grid->view()->setCurrentIndex(index); break; } } diff --git a/pglab/crud/CrudTab.h b/pglab/crud/CrudTab.h index ef91325..a6bf0fc 100644 --- a/pglab/crud/CrudTab.h +++ b/pglab/crud/CrudTab.h @@ -1,7 +1,6 @@ #ifndef CRUDTAB_H #define CRUDTAB_H -#include #include "catalog/PgClass.h" #include "IDatabaseWindow.h" #include @@ -14,6 +13,7 @@ namespace Ui { class OpenDatabase; class CrudModel; +class DataGridWidget; class QMenu; class CrudTab : public QWidget { @@ -33,7 +33,7 @@ private: std::optional m_table; CrudModel *m_crudModel = nullptr; - QSortFilterProxyModel *m_SortFilterProxy = nullptr; + DataGridWidget *m_grid = nullptr; QMenu *headerContextMenu = nullptr; diff --git a/pglab/crud/CrudTab.ui b/pglab/crud/CrudTab.ui index 8c58abd..f3118a4 100644 --- a/pglab/crud/CrudTab.ui +++ b/pglab/crud/CrudTab.ui @@ -13,15 +13,7 @@ Form - - - - - false - - - - + Remove rows diff --git a/pglab/crud/DataGridWidget.cpp b/pglab/crud/DataGridWidget.cpp new file mode 100644 index 0000000..64f19a8 --- /dev/null +++ b/pglab/crud/DataGridWidget.cpp @@ -0,0 +1,34 @@ +#include "DataGridWidget.h" +#include "CustomDataRole.h" +#include "ResultTableModelUtil.h" +#include "util/PgLabItemDelegate.h" +#include +#include +#include + + +DataGridWidget::DataGridWidget(QWidget *parent) + : QWidget(parent) +{ + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + m_view = new QTableView(this); + layout->addWidget(m_view); + + SetTableViewDefault(m_view); + auto delegate = new PgLabItemDelegate(m_view); + m_view->setItemDelegate(delegate); +} + +void DataGridWidget::setSourceModel(QAbstractItemModel *model) +{ + if (!m_proxy) { + m_proxy = new QSortFilterProxyModel(this); + m_proxy->setSortRole(CustomSortRole); + } + m_proxy->setSourceModel(model); + m_view->setModel(m_proxy); + m_view->setSortingEnabled(true); + m_proxy->sort(0, Qt::AscendingOrder); +} diff --git a/pglab/crud/DataGridWidget.h b/pglab/crud/DataGridWidget.h new file mode 100644 index 0000000..11d6745 --- /dev/null +++ b/pglab/crud/DataGridWidget.h @@ -0,0 +1,28 @@ +#ifndef DATAGRIDWIDGET_H +#define DATAGRIDWIDGET_H + +#include + +class QAbstractItemModel; +class QSortFilterProxyModel; +class QTableView; + +/** Sortable and editable grid shared by the CRUD tab and the query result tab. + */ +class DataGridWidget : public QWidget { + Q_OBJECT +public: + explicit DataGridWidget(QWidget *parent = nullptr); + + /// Sets the model to show, sorting is done by a proxy that uses CustomSortRole + void setSourceModel(QAbstractItemModel *model); + + QTableView *view() const { return m_view; } + QSortFilterProxyModel *proxy() const { return m_proxy; } + +private: + QTableView *m_view = nullptr; + QSortFilterProxyModel *m_proxy = nullptr; +}; + +#endif // DATAGRIDWIDGET_H diff --git a/pglab/querytool/QueryResultModel.cpp b/pglab/querytool/QueryResultModel.cpp deleted file mode 100644 index 360a00c..0000000 --- a/pglab/querytool/QueryResultModel.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "QueryResultModel.h" -#include "ResultTableModelUtil.h" -#include "Pgsql_declare.h" -#include "Pgsql_oids.h" -#include "catalog/PgDatabaseCatalog.h" -#include "CustomDataRole.h" -#include -#include - -using namespace Pgsql; - -QueryResultModel::QueryResultModel(QObject *parent, std::shared_ptr r - , std::shared_ptr cat) - : QAbstractTableModel(parent) - , result(std::move(r)) - , catalog(cat) -{} - - -int QueryResultModel::rowCount(const QModelIndex &) const -{ - int r = result->rows(); - return r; -} - -int QueryResultModel::columnCount(const QModelIndex &) const -{ - int r = result->cols(); - return r; -} - -QVariant QueryResultModel::data(const QModelIndex &index, int role) const -{ -// static const QString null_str("null"); - QVariant v; - - const int col = index.column(); - const int rij = index.row(); - const Oid oid = result->type(col); - if (role == Qt::DisplayRole || role == Qt::EditRole) { - if (result->null(col, rij)) { - //v = nullptr; - } - else { - Value value = result->get(col, rij); - if (oid == bool_oid) { - bool b = value; - v = b; - } - else { - QString s = value; - v = s; - } - } - } - else if (role == CustomDataTypeRole) { - v = oid; - } - - - return v; -} - -QVariant QueryResultModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - QVariant r; - if (role == Qt::DisplayRole) { - if (orientation == Qt::Horizontal) { - QString s(result->getColName(section)); - s += "\n"; - s += getTypeDisplayString(*catalog, result->type(section)); - r = s; - } - else { - r = QString::number(section + 1); - } - } - Qt::ItemFlags f; - return r; -} - -Qt::ItemFlags QueryResultModel::flags(const QModelIndex &) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; -} diff --git a/pglab/querytool/QueryResultModel.h b/pglab/querytool/QueryResultModel.h deleted file mode 100644 index 7de2f70..0000000 --- a/pglab/querytool/QueryResultModel.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef QUERYRESULTMODEL_H -#define QUERYRESULTMODEL_H - -#include -#include "catalog/models/BaseTableModel.h" -#include "Pgsql_Connection.h" - -class PgDatabaseCatalog; - -class QueryResultModel : public QAbstractTableModel -{ - Q_OBJECT -public: - QueryResultModel(QObject *parent, std::shared_ptr r, std::shared_ptr cat); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - - std::shared_ptr GetPgsqlResult() const { return result; } -protected: -// virtual Oid getType(int column) const override; -// virtual QVariant getData(const QModelIndex &index) const override; -private: - std::shared_ptr result; - std::shared_ptr catalog; -}; - -#endif // QUERYRESULTMODEL_H diff --git a/pglab/querytool/QueryTool.cpp b/pglab/querytool/QueryTool.cpp index 153b19c..f808335 100644 --- a/pglab/querytool/QueryTool.cpp +++ b/pglab/querytool/QueryTool.cpp @@ -476,10 +476,8 @@ void QueryTool::query_ready(std::shared_ptr dbres, qint64 elapsed //int n_rows = dbres->getRows(); //QString rowcount_str = QString("rows: %1").arg(dbres->getRows()); - auto result_model = std::make_shared(nullptr , dbres, - m_catalog); TuplesResultWidget *trw = new TuplesResultWidget; - trw->setResult(result_model, elapsedms); + trw->setResult(m_context->openDatabase(), dbres, elapsedms); resultList.push_back(trw); ui->tabWidget->addTab(trw, "Data"); if (resultList.size() == 1) diff --git a/pglab/querytool/QueryTool.h b/pglab/querytool/QueryTool.h index 5cb98f0..1274642 100644 --- a/pglab/querytool/QueryTool.h +++ b/pglab/querytool/QueryTool.h @@ -3,7 +3,6 @@ #include "ManagedPage.h" #include "ASyncDBConnection.h" -#include "QueryResultModel.h" #include "QueryExplainModel.h" #include "stopwatch.h" #include "tuplesresultwidget.h" @@ -26,7 +25,6 @@ class QTableView; class QTabWidget; class SqlSyntaxHighlighter; class ExplainRoot; -class QueryResultModel; class QueryExplainModel; class PgTypeContainer; class OpenDatabase; diff --git a/pglab/tuplesresultwidget.cpp b/pglab/tuplesresultwidget.cpp index 1e2dd5b..21b090a 100644 --- a/pglab/tuplesresultwidget.cpp +++ b/pglab/tuplesresultwidget.cpp @@ -1,11 +1,15 @@ -#include "tuplesresultwidget.h" +#include "tuplesresultwidget.h" #include "ui_tuplesresultwidget.h" #include "ResultTableModelUtil.h" #include "util.h" +#include "crud/CrudModel.h" +#include "crud/DataGridWidget.h" +#include "OpenDatabase.h" #include #include -#include "util/PgLabItemDelegate.h" +#include +#include TuplesResultWidget::TuplesResultWidget(QWidget *parent) : @@ -15,9 +19,13 @@ TuplesResultWidget::TuplesResultWidget(QWidget *parent) : ui->setupUi(this); ui->lblRowCount->setText(QString()); - SetTableViewDefault(ui->ResultView); - auto delegate = new PgLabItemDelegate(ui->ResultView); - ui->ResultView->setItemDelegate(delegate); + m_grid = new DataGridWidget(this); + ui->verticalLayout->insertWidget(0, m_grid); + m_grid->view()->setFont(QFont("Source Sans Pro", 10)); + m_grid->view()->setAlternatingRowColors(true); + m_grid->view()->setWordWrap(false); + m_grid->view()->verticalHeader()->setDefaultSectionSize(21); + m_grid->view()->verticalHeader()->setMinimumSectionSize(16); } TuplesResultWidget::~TuplesResultWidget() @@ -25,14 +33,17 @@ TuplesResultWidget::~TuplesResultWidget() delete ui; } -void TuplesResultWidget::setResult(std::shared_ptr res, float ms) +void TuplesResultWidget::setResult(std::shared_ptr db, std::shared_ptr res, float ms) { - resultModel = res; - ui->ResultView->setModel(resultModel.get()); + m_result = res; - ui->ResultView->resizeColumnsToContents(); + m_model = new CrudModel(m_grid); + m_model->setResult(db, res); + m_grid->setSourceModel(m_model); - QString rowcount_str = QString("rows: %1").arg(resultModel->rowCount()); + m_grid->view()->resizeColumnsToContents(); + + QString rowcount_str = QString("rows: %1").arg(m_model->rowCount()); ui->lblRowCount->setText(rowcount_str); ui->lblElapsedTime->setText(msfloatToHumanReadableString(ms)); } @@ -42,8 +53,6 @@ void TuplesResultWidget::exportData(const QString &file_name) const QFile file(file_name); if (file.open(QIODevice::WriteOnly)) { QTextStream out(&file); - ::exportTable(ui->ResultView, out); + ::exportTable(m_grid->view(), out); } } - - diff --git a/pglab/tuplesresultwidget.h b/pglab/tuplesresultwidget.h index 89a55d9..19fc083 100644 --- a/pglab/tuplesresultwidget.h +++ b/pglab/tuplesresultwidget.h @@ -1,13 +1,21 @@ #ifndef TUPLESRESULTWIDGET_H #define TUPLESRESULTWIDGET_H -#include "querytool/QueryResultModel.h" #include +#include namespace Ui { class TuplesResultWidget; } +namespace Pgsql { + class Result; +} + +class OpenDatabase; +class CrudModel; +class DataGridWidget; + class TuplesResultWidget : public QWidget { Q_OBJECT @@ -16,14 +24,15 @@ public: explicit TuplesResultWidget(QWidget *parent = 0); ~TuplesResultWidget(); - void setResult(std::shared_ptr res, float ms); + void setResult(std::shared_ptr db, std::shared_ptr res, float ms); void exportData(const QString &file_name) const; - std::shared_ptr GetPgsqlResult() const { return resultModel->GetPgsqlResult(); } + std::shared_ptr GetPgsqlResult() const { return m_result; } private: Ui::TuplesResultWidget *ui; - - std::shared_ptr resultModel; + DataGridWidget *m_grid = nullptr; + CrudModel *m_model = nullptr; + std::shared_ptr m_result; }; #endif // TUPLESRESULTWIDGET_H diff --git a/pglab/tuplesresultwidget.ui b/pglab/tuplesresultwidget.ui index 677a464..b13909e 100644 --- a/pglab/tuplesresultwidget.ui +++ b/pglab/tuplesresultwidget.ui @@ -29,37 +29,6 @@ 2 - - - - - Source Sans Pro - 10 - - - - QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed - - - true - - - QAbstractItemView::ScrollPerPixel - - - QAbstractItemView::ScrollPerPixel - - - false - - - 21 - - - 16 - - - diff --git a/releasenotes/notes/query-results-editable-grid-b265a193a39f3245.yaml b/releasenotes/notes/query-results-editable-grid-b265a193a39f3245.yaml new file mode 100644 index 0000000..14be544 --- /dev/null +++ b/releasenotes/notes/query-results-editable-grid-b265a193a39f3245.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Query results are now shown in the same grid as the table data tab. Columns + can be sorted by clicking their headers, and cells that belong to a table + with a primary key present in the result can be edited directly. Changes + are saved with an ``UPDATE`` by primary key. +other: + - | + Read-only cells in query results, such as expression columns or columns of + tables without a primary key in the result, are now shown in grey so they + can be told apart from editable cells.