diff --git a/CMakeLists.txt b/CMakeLists.txt index ab0ba28e..f2619dab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ set(SQLB_MOC_HDR src/CondFormat.h src/RunSql.h src/ProxyDialog.h + src/SelectItemsPopup.h ) set(SQLB_SRC @@ -230,6 +231,7 @@ set(SQLB_SRC src/RunSql.cpp src/ProxyDialog.cpp src/IconCache.cpp + src/SelectItemsPopup.cpp ) set(SQLB_FORMS @@ -254,6 +256,7 @@ set(SQLB_FORMS src/FileExtensionManager.ui src/CondFormatManager.ui src/ProxyDialog.ui + src/SelectItemsPopup.ui ) set(SQLB_RESOURCES diff --git a/src/EditTableDialog.cpp b/src/EditTableDialog.cpp index 635f9a50..c82933dc 100644 --- a/src/EditTableDialog.cpp +++ b/src/EditTableDialog.cpp @@ -4,6 +4,7 @@ #include "ui_EditTableDialog.h" #include "sqlitetablemodel.h" #include "sqlitedb.h" +#include "SelectItemsPopup.h" #include #include @@ -81,6 +82,45 @@ EditTableDialog::EditTableDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier& ui->editTableName->setText(QString::fromStdString(curTable.name())); updateColumnWidth(); + // Allow editing of constraint columns by double clicking the columns column of the constraints table + connect(ui->tableConstraints, &QTableWidget::itemDoubleClicked, [this](QTableWidgetItem* item) { + // Check whether the double clicked item is in the columns column + if(item->column() == kConstraintColumns) + { + sqlb::ConstraintPtr constraint = ui->tableConstraints->item(item->row(), kConstraintName)->data(Qt::UserRole).value(); + + // Do not allow editing the columns list of a CHECK constraint because CHECK constraints are independent of column lists + if(constraint->type() == sqlb::Constraint::CheckConstraintType) + return; + + // Show the select items popup dialog + SelectItemsPopup* dialog = new SelectItemsPopup(m_table.fieldNames(), item->data(Qt::UserRole).value(), this); + QRect item_rect = ui->tableConstraints->visualItemRect(item); + dialog->move(ui->tableConstraints->mapToGlobal(QPoint(ui->tableConstraints->x() + item_rect.x(), + ui->tableConstraints->y() + item_rect.y() + item_rect.height() / 2))); + dialog->show(); + + // When clicking the Apply button in the popup dialog, save the new columns list + connect(dialog, &SelectItemsPopup::accepted, [this, dialog, item, constraint]() { + // Check if column selection changed at all + sqlb::StringVector columns_before = ui->tableConstraints->item(item->row(), kConstraintColumns)->data(Qt::UserRole).value(); + sqlb::StringVector columns_after = dialog->selectedItems(); + if(columns_before != columns_after) + { + // Remove the constraint with the old columns and add a new one with the new columns + m_table.removeConstraint(columns_before, constraint); + m_table.addConstraint(columns_after, constraint); + + // Update the UI + populateFields(); + populateConstraints(); + updateSqlText(); + } + }); + } + }); + + // (De-)activate fields checkInput(); } diff --git a/src/SelectItemsPopup.cpp b/src/SelectItemsPopup.cpp new file mode 100644 index 00000000..6d054f11 --- /dev/null +++ b/src/SelectItemsPopup.cpp @@ -0,0 +1,136 @@ +#include "SelectItemsPopup.h" +#include "ui_SelectItemsPopup.h" + +#include + +SelectItemsPopup::SelectItemsPopup(const std::vector& available, const std::vector& selected, QWidget* parent) : + QDialog(parent), + ui(new Ui::SelectItemsPopup) +{ + ui->setupUi(this); + setWindowFlags(Qt::Popup); + + // Load initial items + for(const auto& s : available) + { + if(std::find(selected.begin(), selected.end(), s) == selected.end()) + new QListWidgetItem(QString::fromStdString(s), ui->listAvailable); + } + for(const auto& s : selected) + new QListWidgetItem(QString::fromStdString(s), ui->listSelected); +} + +SelectItemsPopup::~SelectItemsPopup() +{ + delete ui; +} + +std::vector SelectItemsPopup::selectedItems() const +{ + std::vector result; + for(int i=0;ilistSelected->count();i++) + result.push_back(ui->listSelected->item(i)->text().toStdString()); + return result; +} + +void SelectItemsPopup::selectItem(const QModelIndex& idx) +{ + // Get currently selected iitem if none was provided + QListWidgetItem* item; + if(idx.isValid()) + item = ui->listAvailable->item(idx.row()); + else + item = ui->listAvailable->currentItem(); + + if(!item) + return; + + // Add it to the selected items list + new QListWidgetItem(item->text(), ui->listSelected); + + // Remove it from available items list + delete item; +} + +void SelectItemsPopup::unselectItem(const QModelIndex& idx) +{ + // Get currently selected iitem if none was provided + QListWidgetItem* item; + if(idx.isValid()) + item = ui->listSelected->item(idx.row()); + else + item = ui->listSelected->currentItem(); + + if(!item) + return; + + // Add it to the available items list + new QListWidgetItem(item->text(), ui->listAvailable); + + // Remove it from selected items list + delete item; +} + +void SelectItemsPopup::resizeEvent(QResizeEvent*) +{ + // We modify the shape of the dialog to add an arrow shaped edge. See the ascii art image below for details. The edges + // are numbered, their order is the same as in the polygon definition. + + /* + /3\ + / \ + 1---2 4--------5 + | | + | | + 7------------------6 + */ + + const int arrow_height = ui->spacer->geometry().height(); + const int arrow_width = arrow_height * 3; + const int arrow_position_div = 5; + + QPolygon poly; + poly << QPoint(rect().x(), rect().y() + arrow_height) + << QPoint(rect().x() + rect().width() / arrow_position_div - arrow_width / 2, rect().y() + arrow_height) + << QPoint(rect().x() + rect().width() / arrow_position_div, rect().y()) + << QPoint(rect().x() + rect().width() / arrow_position_div + arrow_width / 2, rect().y() + arrow_height) + << QPoint(rect().x() + rect().width(), rect().y() + arrow_height) + << QPoint(rect().x() + rect().width(), rect().y() + rect().height()) + << QPoint(rect().x(), rect().y() + rect().height()); + setMask(QRegion(poly)); +} + +void SelectItemsPopup::buttonBoxClicked(QAbstractButton* button) +{ + if(button == ui->buttonBox->button(QDialogButtonBox::Apply)) + accept(); +} + +void SelectItemsPopup::moveItemUp() +{ + moveCurrentItem(false); +} + +void SelectItemsPopup::moveItemDown() +{ + moveCurrentItem(true); +} + +void SelectItemsPopup::moveCurrentItem(bool down) +{ + // Get current row number and calculate row number after the movement. Check the values + int currentRow = ui->listSelected->currentRow(); + if(currentRow == -1) + return; + int newRow = currentRow + (down ? 1 : -1); + if(newRow < 0) + return; + if(newRow >= ui->listSelected->count()) + return; + + // Swap items + ui->listSelected->insertItem(newRow, ui->listSelected->takeItem(currentRow)); + + // Select old item at new position + ui->listSelected->setCurrentRow(newRow); +} diff --git a/src/SelectItemsPopup.h b/src/SelectItemsPopup.h new file mode 100644 index 00000000..e7fb9e9d --- /dev/null +++ b/src/SelectItemsPopup.h @@ -0,0 +1,44 @@ +#ifndef SELECTITEMS_H +#define SELECTITEMS_H + +#include +#include + +#include +#include + +class QAbstractButton; + +namespace Ui { +class SelectItemsPopup; +} + +class SelectItemsPopup : public QDialog +{ + Q_OBJECT + +public: + explicit SelectItemsPopup(const std::vector& available, const std::vector& selected = {}, QWidget* parent = nullptr); + ~SelectItemsPopup(); + + std::vector selectedItems() const; + +private slots: + void buttonBoxClicked(QAbstractButton* button); + + void selectItem(const QModelIndex& idx = QModelIndex()); + void unselectItem(const QModelIndex& idx = QModelIndex()); + + void moveItemUp(); + void moveItemDown(); + +protected: + void resizeEvent(QResizeEvent* ev); + +private: + Ui::SelectItemsPopup* ui; + + void moveCurrentItem(bool down); +}; + +#endif diff --git a/src/SelectItemsPopup.ui b/src/SelectItemsPopup.ui new file mode 100644 index 00000000..89b0d4ef --- /dev/null +++ b/src/SelectItemsPopup.ui @@ -0,0 +1,331 @@ + + + SelectItemsPopup + + + + 0 + 0 + 537 + 290 + + + + + + + Qt::Vertical + + + + 0 + 15 + + + + + + + + + + + + A&vailable + + + listAvailable + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::RightArrow + + + + + + + Qt::LeftArrow + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Sele&cted + + + listSelected + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::InternalMove + + + true + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::UpArrow + + + + + + + Qt::DownArrow + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel + + + + + + + listAvailable + listSelected + buttonSelect + buttonUnselect + + + + + buttonSelect + clicked() + SelectItemsPopup + selectItem() + + + 263 + 115 + + + 2 + 203 + + + + + buttonUnselect + clicked() + SelectItemsPopup + unselectItem() + + + 257 + 159 + + + 515 + 186 + + + + + listAvailable + doubleClicked(QModelIndex) + SelectItemsPopup + selectItem(QModelIndex) + + + 124 + 45 + + + 115 + 0 + + + + + listSelected + doubleClicked(QModelIndex) + SelectItemsPopup + unselectItem(QModelIndex) + + + 377 + 96 + + + 383 + 4 + + + + + buttonBox + rejected() + SelectItemsPopup + reject() + + + 262 + 258 + + + 262 + 140 + + + + + buttonBox + clicked(QAbstractButton*) + SelectItemsPopup + buttonBoxClicked(QAbstractButton*) + + + 262 + 258 + + + 262 + 140 + + + + + buttonDown + clicked() + SelectItemsPopup + moveItemDown() + + + 513 + 153 + + + 268 + 144 + + + + + buttonUp + clicked() + SelectItemsPopup + moveItemUp() + + + 513 + 124 + + + 268 + 144 + + + + + + selectItem(QModelIndex) + unselectItem(QModelIndex) + selectItem() + unselectItem() + buttonBoxClicked(QAbstractButton*) + moveItemUp() + moveItemDown() + + diff --git a/src/src.pro b/src/src.pro index 5943421f..7dd95c46 100644 --- a/src/src.pro +++ b/src/src.pro @@ -76,7 +76,8 @@ HEADERS += \ RunSql.h \ sql/ObjectIdentifier.h \ ProxyDialog.h \ - IconCache.h + IconCache.h \ + SelectItemsPopup.h SOURCES += \ sqlitedb.cpp \ @@ -129,7 +130,8 @@ SOURCES += \ RunSql.cpp \ sql/ObjectIdentifier.cpp \ ProxyDialog.cpp \ - IconCache.cpp + IconCache.cpp \ + SelectItemsPopup.cpp RESOURCES += icons/icons.qrc \ translations/flags/flags.qrc \ @@ -158,7 +160,8 @@ FORMS += \ FindReplaceDialog.ui \ FileExtensionManager.ui \ CondFormatManager.ui \ - ProxyDialog.ui + ProxyDialog.ui \ + SelectItemsPopup.ui TRANSLATIONS += \ translations/sqlb_ar_SA.ts \