diff options
| author | 2025-01-16 01:58:22 -0500 | |
|---|---|---|
| committer | 2025-01-16 01:58:22 -0500 | |
| commit | a5ae79be08125b31bb6b8d9703090a98c6fd2e30 (patch) | |
| tree | 569ee612c9de85b2bb423efa485688ef1d43852e /SQLiteStudio3/guiSQLiteStudio | |
| parent | 21966b4f924b0a1933d9662e75ff253bd154fdb7 (diff) | |
| parent | 81a21e6ce040e7740de86340c8ea4dba30e69bc3 (diff) | |
Update upstream source from tag 'upstream/3.4.13+dfsg'
Update to upstream version '3.4.13+dfsg'
with Debian dir bf81ee0219cb8e4562a4751df17d75814772d2d6
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio')
77 files changed, 1018 insertions, 289 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/common/dialogsizehandler.cpp b/SQLiteStudio3/guiSQLiteStudio/common/dialogsizehandler.cpp new file mode 100644 index 0000000..ad283ca --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/dialogsizehandler.cpp @@ -0,0 +1,59 @@ +#include "dialogsizehandler.h" +#include "services/config.h" +#include <QEvent> +#include <QTimer> +#include <QWidget> +#include <QScreen> +#include <QGuiApplication> + +DialogSizeHandler::DialogSizeHandler(QObject *parent) : + DialogSizeHandler(parent->objectName(), parent) +{ +} + +DialogSizeHandler::DialogSizeHandler(const QString &key, QObject *parent) : + QObject(parent), configKey(key) +{ + saveTimer = new QTimer(this); + saveTimer->setInterval(500); + saveTimer->setSingleShot(true); + connect(saveTimer, SIGNAL(timeout()), this, SLOT(doSave())); + + QRect geom = CFG->get(CONFIG_GROUP, configKey).toRect(); + if (geom.isValid() && qApp->primaryScreen()->geometry().contains(geom)) + { + QWidget* w = qobject_cast<QWidget*>(parent); + w->setGeometry(geom); + } +} + +DialogSizeHandler::~DialogSizeHandler() +{ +} + +void DialogSizeHandler::applyFor(QObject *parent) +{ + applyFor(parent->objectName(), parent); +} + +void DialogSizeHandler::applyFor(const QString &key, QObject *parent) +{ + DialogSizeHandler* handler = new DialogSizeHandler(key, parent); + parent->installEventFilter(handler); +} + +bool DialogSizeHandler::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::Resize || event->type() == QEvent::Move) + { + QWidget* w = qobject_cast<QWidget*>(obj); + recentGeometry = w->geometry(); + saveTimer->start(); + } + return false; +} + +void DialogSizeHandler::doSave() +{ + CFG->set(CONFIG_GROUP, configKey, recentGeometry); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/common/dialogsizehandler.h b/SQLiteStudio3/guiSQLiteStudio/common/dialogsizehandler.h new file mode 100644 index 0000000..916f65d --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/common/dialogsizehandler.h @@ -0,0 +1,34 @@ +#ifndef DIALOGSIZEHANDLER_H +#define DIALOGSIZEHANDLER_H + +#include <QObject> +#include <QRect> + +class QTimer; + +class DialogSizeHandler : public QObject +{ + Q_OBJECT +public: + explicit DialogSizeHandler(QObject *parent); + DialogSizeHandler(const QString& key, QObject *parent); + virtual ~DialogSizeHandler(); + + static void applyFor(QObject *parent); + static void applyFor(const QString& key, QObject *parent); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + static const constexpr char* CONFIG_GROUP = "DialogDimensions"; + + QString configKey; + QTimer* saveTimer = nullptr; + QRect recentGeometry; + +public slots: + void doSave(); +}; + +#endif // DIALOGSIZEHANDLER_H diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp index 168c7f9..7866888 100644 --- a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.cpp @@ -142,7 +142,13 @@ void WidgetCover::hide() void WidgetCover::setProgress(int value) { - busyBar->setValue(value); + if (undetermined) + { + busyBar->setRange(0, value); + busyBar->setValue(value); + } + else + busyBar->setValue(value); } QEasingCurve WidgetCover::getEasingCurve() const @@ -203,6 +209,7 @@ void WidgetCover::displayProgress(int maxValue, const QString& format) return; busyBar->setRange(0, maxValue); + undetermined = maxValue == 0; if (!format.isNull()) busyBar->setFormat(format); @@ -224,6 +231,7 @@ void WidgetCover::initWithProgressBarOnly(const QString& format) busyBar->setRange(0, 100); busyBar->setFormat(format); busyBar->setTextVisible(true); + undetermined = false; containerLayout->addWidget(busyBar, 0, 0); } @@ -236,6 +244,7 @@ void WidgetCover::initWithInterruptContainer(const QString& interruptButtonText) busyBar = new QProgressBar(); busyBar->setRange(0, 0); busyBar->setTextVisible(false); + undetermined = true; containerLayout->addWidget(busyBar, 0, 0); containerLayout->addWidget(cancelButton, 1, 0); diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h index 0b7a2f5..3d64ef5 100644 --- a/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetcover.h @@ -55,6 +55,7 @@ class GUI_API_EXPORT WidgetCover : public QWidget QGridLayout* containerLayout = nullptr; QPushButton* cancelButton = nullptr; QProgressBar* busyBar = nullptr; + bool undetermined = false; signals: void cancelClicked(); diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp index b5b0264..a81508d 100644 --- a/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.cpp @@ -157,6 +157,11 @@ void WidgetStateIndicator::setVisible(bool visible, const QString& msg) hide(); } +bool WidgetStateIndicator::isVisible() const +{ + return labelParent->isVisible(); +} + void WidgetStateIndicator::release() { setVisible(false); diff --git a/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h index 28c6e0b..065c87a 100644 --- a/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h +++ b/SQLiteStudio3/guiSQLiteStudio/common/widgetstateindicator.h @@ -34,6 +34,7 @@ class GUI_API_EXPORT WidgetStateIndicator : public QObject void show(const QString& msg = QString()); void hide(); void setVisible(bool visible, const QString& msg = QString()); + bool isVisible() const; void release(); void info(const QString& msg); void warn(const QString& msg); diff --git a/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp index 68ca58d..fb5dac9 100644 --- a/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/completer/completerwindow.cpp @@ -236,9 +236,9 @@ QString CompleterWindow::getStatusMsg(const QModelIndex& index) case ExpectedToken::OPERATOR: return tr("Operator: %1", "completer statusbar").arg(value); case ExpectedToken::STRING: - return tr("String", "completer statusbar"); + return tr("String", "completer statusbar").arg(value); case ExpectedToken::NUMBER: - return tr("Number", "completer statusbar").arg(value); + return tr("Number", "completer statusbar"); case ExpectedToken::BLOB: return tr("Binary data", "completer statusbar").arg(value); case ExpectedToken::COLLATION: diff --git a/SQLiteStudio3/guiSQLiteStudio/configmapper.cpp b/SQLiteStudio3/guiSQLiteStudio/configmapper.cpp index 704104b..ea176ad 100644 --- a/SQLiteStudio3/guiSQLiteStudio/configmapper.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/configmapper.cpp @@ -441,7 +441,6 @@ void ConfigMapper::saveFromWidget(QWidget* widget, CfgEntry* cfgEntry) saveCommonConfigFromWidget(widget, cfgEntry); } - bool ConfigMapper::saveCustomConfigFromWidget(QWidget* widget, CfgEntry* key) { QList<CustomConfigWidgetPlugin*> handlers; diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp index de78b2b..d10dc5a 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columncollatepanel.cpp @@ -102,6 +102,8 @@ void ColumnCollatePanel::storeConfiguration() if (ui->namedCheck->isChecked()) constr->name = ui->namedEdit->text(); + else + constr->name.clear(); constr->collationName = ui->collationCombo->currentText(); } diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp index bb70c81..79b6b6a 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columndefaultpanel.cpp @@ -139,6 +139,8 @@ void ColumnDefaultPanel::storeConfiguration() if (ui->namedCheck->isChecked()) constr->name = ui->namedEdit->text(); + else + constr->name.clear(); } void ColumnDefaultPanel::storeExpr(SqliteCreateTable::Column::Constraint* constr) diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columngeneratedpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columngeneratedpanel.cpp index 308b868..8e49c93 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/columngeneratedpanel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columngeneratedpanel.cpp @@ -133,6 +133,8 @@ void ColumnGeneratedPanel::storeConfiguration() if (ui->namedCheck->isChecked()) constr->name = ui->namedEdit->text(); + else + constr->name.clear(); } void ColumnGeneratedPanel::storeExpr(SqliteCreateTable::Column::Constraint* constr) diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp index fa7b7c3..2c70eb9 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnprimarykeypanel.cpp @@ -61,7 +61,7 @@ void ColumnPrimaryKeyPanel::readConstraint() if (!constr->name.isNull()) { - ui->namedCheck->setEnabled(true); + ui->namedCheck->setChecked(true); ui->namedEdit->setText(constr->name); } @@ -122,6 +122,8 @@ void ColumnPrimaryKeyPanel::storeConfiguration() if (ui->namedCheck->isChecked()) constr->name = ui->namedEdit->text(); + else + constr->name.clear(); if (ui->conflictCheck->isChecked() && ui->conflictCombo->currentIndex() > -1) constr->onConflict = sqliteConflictAlgo(ui->conflictCombo->currentText()); diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp index 7c0f5a8..3b455e9 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/columnuniqueandnotnullpanel.cpp @@ -93,6 +93,8 @@ void ColumnUniqueAndNotNullPanel::storeConfiguration() SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constraint.data()); if (ui->namedCheck->isChecked()) constr->name = ui->namedEdit->text(); + else + constr->name.clear(); if (ui->conflictCheck->isChecked() && ui->conflictCombo->currentIndex() > -1) constr->onConflict = sqliteConflictAlgo(ui->conflictCombo->currentText()); diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp index 4f3e350..3296532 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.cpp @@ -29,6 +29,16 @@ void ConstraintPanel::setConstraint(SqliteStatement* stmt) constraintAvailable(); } +void ConstraintPanel::setCreateTableStmt(SqliteCreateTable *stmt) +{ + createTableStmt = stmt; +} + +void ConstraintPanel::setColumnStmt(SqliteCreateTable::Column *stmt) +{ + columnStmt = stmt; +} + void ConstraintPanel::storeDefinition() { storeConfiguration(); diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h index 9f875a9..f7ed9d5 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/constraintpanel.h @@ -16,6 +16,8 @@ class GUI_API_EXPORT ConstraintPanel : public QWidget virtual ~ConstraintPanel(); void setConstraint(SqliteStatement* stmt); + void setCreateTableStmt(SqliteCreateTable* stmt); + void setColumnStmt(SqliteCreateTable::Column* stmt); void storeDefinition(); virtual void setDb(Db* value); @@ -68,6 +70,8 @@ class GUI_API_EXPORT ConstraintPanel : public QWidget Db* db = nullptr; QPointer<SqliteStatement> constraint; + QPointer<SqliteCreateTable> createTableStmt; + QPointer<SqliteCreateTable::Column> columnStmt; public slots: diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp index cecf5f4..5e85915 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.cpp @@ -53,6 +53,8 @@ bool TableForeignKeyPanel::validate() setValidState(combo, idxOk, tr("Pick the foreign column.")); if (!idxOk) columnsSelected = false; + else + handleFkTypeMatched(combo, check->property(UI_PROP_COLUMN).toString(), combo->currentText()); break; } @@ -386,6 +388,36 @@ void TableForeignKeyPanel::storeMatchCondition(const QString& reaction) constr->foreignKey->conditions << condition; } +void TableForeignKeyPanel::handleFkTypeMatched(QWidget* indicatorParent, const QString &localColumn, const QString fkColumn) +{ + SqliteCreateTable::Column* column = createTableStmt->getColumn(localColumn); + if (!column || !column->type) + return; + + QString localType = column->type->toDataType().toString(); + + // FK column type + QString fkTable = ui->fkTableCombo->currentText(); + if (!fkTableTypesCache.contains(fkTable, Qt::CaseInsensitive)) + { + SchemaResolver resolver(db); + fkTableTypesCache[fkTable] = resolver.getTableColumnDataTypesByName(fkTable); + } + + StrHash<DataType> fkTypes = fkTableTypesCache.value(fkTable, Qt::CaseInsensitive); + if (!fkTypes.contains(fkColumn, Qt::CaseInsensitive)) + return; + + QString fkType = fkTypes.value(fkColumn, Qt::CaseInsensitive).toString(); + + if (localType.toLower().trimmed() != fkType.toLower().trimmed()) + { + setValidStateWarning(indicatorParent, + tr("Referenced column type (%1) is different than type declared for local column (%2). It may cause issues while inserting or updating data.") + .arg(fkType, localType)); + } +} + void TableForeignKeyPanel::readTables() { SchemaResolver resolver(db); diff --git a/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h index cecc04a..a6d48e6 100644 --- a/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h +++ b/SQLiteStudio3/guiSQLiteStudio/constraints/tableforeignkeypanel.h @@ -4,6 +4,7 @@ #include "constraintpanel.h" #include "parser/ast/sqlitecreatetable.h" #include "guiSQLiteStudio_global.h" +#include "common/strhash.h" #include <QStringListModel> #include <QWidget> @@ -40,12 +41,14 @@ class GUI_API_EXPORT TableForeignKeyPanel : public ConstraintPanel int getColumnIndex(const QString& colName); void storeCondition(SqliteForeignKey::Condition::Action action, const QString& reaction); void storeMatchCondition(const QString& reaction); + void handleFkTypeMatched(QWidget* indicatorParent, const QString& localColumn, const QString fkColumn); Ui::TableForeignKeyPanel *ui = nullptr; QGridLayout* columnsLayout = nullptr; int totalColumns = 0; QStringListModel fkColumnsModel; QSignalMapper* columnSignalMapping = nullptr; + StrHash<StrHash<DataType>> fkTableTypesCache; private slots: void updateState(); diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/fkcombobox.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/fkcombobox.cpp index b9ca7b7..a3ba2ab 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/fkcombobox.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/fkcombobox.cpp @@ -10,6 +10,7 @@ #include <QScreen> #include <QLineEdit> #include <QScrollBar> +#include <QCompleter> FkComboBox::FkComboBox(QWidget* parent, int dropDownViewMinWidth) : QComboBox(parent), dropDownViewMinWidth(dropDownViewMinWidth) @@ -45,8 +46,8 @@ QString FkComboBox::getSqlForFkEditor(Db* db, SqlQueryModelColumn* columnModel, src = wrapObjIfNeeded(fk->foreignTable); if (i == 0) { - firstSrcCol = col; fullSrcCol = src + "." + col; + firstSrcCol = fullSrcCol; selectedCols << dbColTpl.arg(fullSrcCol, wrapObjIfNeeded(columnModel->column)); } @@ -89,7 +90,7 @@ QString FkComboBox::getSqlForFkEditor(Db* db, SqlQueryModelColumn* columnModel, QString currValueColName = generateUniqueName("curr", usedNames); QString currValueExpr = currentValue.isNull() ? currNullValueTpl.arg(firstSrcCol, currValueColName) : - currValueTpl.arg(firstSrcCol, wrapValueIfNeeded(currentValue), currValueColName); + currValueTpl.arg(firstSrcCol, valueToSqlLiteral(currentValue), currValueColName); return sql.arg( selectedCols.join(", "), @@ -110,8 +111,6 @@ void FkComboBox::setValue(const QVariant& value) bool doExecQuery = (sourceValue != value || comboModel->getQuery().isNull()); sourceValue = value; setCurrentText(value.toString()); - if (!value.isNull() && isEditable()) - lineEdit()->selectAll(); if (doExecQuery) { @@ -221,6 +220,13 @@ void FkComboBox::init() comboView->horizontalHeader()->setVisible(true); comboView->setSelectionMode(QAbstractItemView::SingleSelection); comboView->setSelectionBehavior(QAbstractItemView::SelectRows); + + connect(completer(), QOverload<const QString &>::of(&QCompleter::highlighted), + [=](const QString &value) + { + // #5083 In case of case-sensitive mismatch, we need to sync case, so that next/prev navigation with keybord works correctly. + setCurrentText(value.left(currentText().length())); + }); } void FkComboBox::updateComboViewGeometry(bool initial) const @@ -244,6 +250,19 @@ void FkComboBox::updateComboViewGeometry(bool initial) const } } +void FkComboBox::updateCurrentItemIndex(const QString& value) +{ + QModelIndex startIdx = comboModel->index(0, modelColumn()); + QModelIndex endIdx = comboModel->index(comboModel->rowCount() - 1, modelColumn()); + QModelIndexList idxList = comboModel->findIndexes(startIdx, endIdx, SqlQueryItem::DataRole::VALUE, value.isNull() ? currentText() : value, 1, true); + + if (idxList.size() > 0) + { + setCurrentIndex(idxList.first().row()); + view()->selectionModel()->setCurrentIndex(idxList.first(), QItemSelectionModel::SelectCurrent); + } +} + int FkComboBox::getFkViewHeaderWidth(bool includeScrollBar) const { int wd = comboView->horizontalHeader()->length(); @@ -289,7 +308,9 @@ void FkComboBox::fkDataReady() } } else + { setEditText(beforeLoadValue); + } disableValueChangeNotifications = false; } @@ -305,6 +326,10 @@ void FkComboBox::notifyValueModified() return; oldValue = currentText(); + disableValueChangeNotifications = true; + updateCurrentItemIndex(); + disableValueChangeNotifications = false; + emit valueModified(); } diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/fkcombobox.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/fkcombobox.h index 8cb22a5..647bbb6 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/fkcombobox.h +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/fkcombobox.h @@ -35,6 +35,7 @@ class FkComboBox : public QComboBox void init(); void updateComboViewGeometry(bool initial) const; + void updateCurrentItemIndex(const QString& value = QString()); int getFkViewHeaderWidth(bool includeScrollBar) const; QString getSql() const; diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqldatasourcequerymodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqldatasourcequerymodel.cpp index a1a6cee..e02e77a 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqldatasourcequerymodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqldatasourcequerymodel.cpp @@ -28,7 +28,6 @@ void SqlDataSourceQueryModel::updateTablesInUse(const QString& inUse) void SqlDataSourceQueryModel::applyFilter(const QString& value, FilterValueProcessor valueProc) { -// static_qstring(sql, "SELECT * FROM %1 WHERE %2"); if (value.isEmpty()) { resetFilter(); @@ -39,14 +38,12 @@ void SqlDataSourceQueryModel::applyFilter(const QString& value, FilterValueProce for (SqlQueryModelColumnPtr& column : columns) conditions << wrapObjIfNeeded(column->getAliasedName())+" "+valueProc(value); -// setQuery(sql.arg(getDataSource(), conditions.join(" OR "))); queryExecutor->setFilters(conditions.join(" OR ")); - executeQuery(); + executeQuery(true); } void SqlDataSourceQueryModel::applyFilter(const QStringList& values, FilterValueProcessor valueProc) { -// static_qstring(sql, "SELECT * FROM %1 WHERE %2"); if (values.isEmpty()) { resetFilter(); @@ -69,7 +66,6 @@ void SqlDataSourceQueryModel::applyFilter(const QStringList& values, FilterValue conditions << wrapObjIfNeeded(columns[i]->getAliasedName())+" "+valueProc(values[i]); } -// setQuery(sql.arg(getDataSource(), conditions.join(" AND "))); queryExecutor->setFilters(conditions.join(" AND ")); executeQuery(); } @@ -139,7 +135,7 @@ void SqlDataSourceQueryModel::resetFilter() { // setQuery("SELECT * FROM "+getDataSource()); queryExecutor->setFilters(QString()); - executeQuery(); + executeQuery(true); } QString SqlDataSourceQueryModel::getDatabasePrefix() diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp index 771e890..81fda44 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.cpp @@ -143,8 +143,9 @@ void SqlQueryItem::setValue(const QVariant &value, bool loadedFromDb) // - this item was already marked as uncommitted bool modified = ( ( - newValue != origValue || - origValue.isNull() != newValue.isNull() + newValue != origValue || + origValue.isNull() != newValue.isNull() || + newValue.type() != origValue.type() ) && !loadedFromDb ) || @@ -331,6 +332,18 @@ QVariant SqlQueryItem::data(int role) const if (value.isNull()) return "NULL"; + if (value.type() == QVariant::String) + { + QString str = value.toString(); + return str.length() > DISPLAY_LEN_LIMIT ? QVariant(str.left(DISPLAY_LEN_LIMIT) + "...") : value; + } + + if (value.type() == QVariant::ByteArray) + { + QByteArray bytes = value.toByteArray(); + return bytes.size() > DISPLAY_LEN_LIMIT ? QVariant(bytes.left(DISPLAY_LEN_LIMIT) + "...") : value; + } + return value; } case Qt::ForegroundRole: @@ -377,3 +390,19 @@ QVariant SqlQueryItem::data(int role) const return QStandardItem::data(role); } + +void SqlQueryItem::resetInitialFocusSelection() +{ + QStandardItem::setData(QVariant(), DataRole::EDIT_SKIP_INITIAL_SELECT); + +} + +void SqlQueryItem::skipInitialFocusSelection() +{ + QStandardItem::setData(true, DataRole::EDIT_SKIP_INITIAL_SELECT); +} + +bool SqlQueryItem::shoulSkipInitialFocusSelection() const +{ + return QStandardItem::data(DataRole::EDIT_SKIP_INITIAL_SELECT).toBool(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h index 0de01c1..85dcda9 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitem.h @@ -26,7 +26,8 @@ class GUI_API_EXPORT SqlQueryItem : public QObject, public QStandardItem DELETED = 1007, OLD_VALUE = 1008, JUST_INSERTED_WITHOUT_ROWID = 1009, - COMMITTING_ERROR_MESSAGE = 1010 + COMMITTING_ERROR_MESSAGE = 1010, + EDIT_SKIP_INITIAL_SELECT = 1011 // to prevent content selection initially when editing with double-click }; }; @@ -72,12 +73,18 @@ class GUI_API_EXPORT SqlQueryItem : public QObject, public QStandardItem void setData(const QVariant& value, int role = Qt::UserRole + 1); QVariant data(int role = Qt::UserRole + 1) const; + void resetInitialFocusSelection(); + void skipInitialFocusSelection(); + bool shoulSkipInitialFocusSelection() const; + private: QVariant adjustVariantType(const QVariant& value); QString getToolTip() const; void rememberOldValue(); void clearOldValue(); + static constexpr int DISPLAY_LEN_LIMIT = 1000; + QMutex valueSettingLock; }; diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp index 7b01084..55f2161 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.cpp @@ -8,6 +8,7 @@ #include "common/utils_sql.h" #include "fkcombobox.h" #include "schemaresolver.h" +#include "sqlqueryitemlineedit.h" #include <QHeaderView> #include <QPainter> #include <QEvent> @@ -71,10 +72,22 @@ QWidget* SqlQueryItemDelegate::createEditor(QWidget* parent, const QStyleOptionV return nullptr; } + bool skipInitSelection = item->shoulSkipInitialFocusSelection(); if (!item->getColumn()->getFkConstraints().isEmpty()) - return getFkEditor(item, parent, model); + return getFkEditor(item, skipInitSelection, parent, model); - return getEditor(item->getValue().userType(), parent); + return getEditor(item->getValue().userType(), skipInitSelection, parent); +} + +void SqlQueryItemDelegate::destroyEditor(QWidget* editor, const QModelIndex& index) const +{ + QStyledItemDelegate::destroyEditor(editor, index); + if (!index.isValid()) + return; + + const SqlQueryModel* model = dynamic_cast<const SqlQueryModel*>(index.model()); + SqlQueryItem* item = model->itemFromIndex(index); + item->resetInitialFocusSelection(); } QString SqlQueryItemDelegate::displayText(const QVariant& value, const QLocale& locale) const @@ -216,100 +229,16 @@ SqlQueryItem* SqlQueryItemDelegate::getItem(const QModelIndex &index) const return queryModel->itemFromIndex(index); } -QWidget* SqlQueryItemDelegate::getEditor(int type, QWidget* parent) const +QWidget* SqlQueryItemDelegate::getEditor(int type, bool shouldSkipInitialSelection, QWidget* parent) const { UNUSED(type); - QLineEdit *editor = new QLineEdit(parent); + SqlQueryItemLineEdit *editor = new SqlQueryItemLineEdit(shouldSkipInitialSelection, parent); editor->setMaxLength(std::numeric_limits<int>::max()); editor->setFrame(editor->style()->styleHint(QStyle::SH_ItemView_DrawDelegateFrame, 0, editor)); return editor; } -QString SqlQueryItemDelegate::getSqlForFkEditor(SqlQueryItem* item) const -{ - static_qstring(sql, "SELECT %4, %1 FROM %2%3"); - static_qstring(currValueTpl, "(%1 == %2) AS %3"); - static_qstring(currNullValueTpl, "(%1 IS NULL) AS %2"); - static_qstring(dbColTpl, "%1 AS %2"); - static_qstring(conditionTpl, "%1.%2 = %3.%4"); - static_qstring(conditionPrefixTpl, " WHERE %1"); - - QStringList selectedCols; - QStringList fkConditionTables; - QStringList fkConditionCols; - QStringList srcCols; - Db* db = item->getModel()->getDb(); - SchemaResolver resolver(db); - - QList<SqlQueryModelColumn::ConstraintFk*> fkList = item->getColumn()->getFkConstraints(); - int i = 0; - QString src; - QString fullSrcCol; - QString col; - QString firstSrcCol; - QStringList usedNames; - for (SqlQueryModelColumn::ConstraintFk*& fk : fkList) - { - col = wrapObjIfNeeded(fk->foreignColumn); - src = wrapObjIfNeeded(fk->foreignTable); - if (i == 0) - { - firstSrcCol = col; - fullSrcCol = src + "." + col; - selectedCols << dbColTpl.arg(fullSrcCol, wrapObjIfNeeded(item->getColumn()->column)); - } - - if (fkConditionTables.contains(src, Qt::CaseInsensitive)) - continue; - - srcCols = resolver.getTableColumns(src); - for (QString& srcCol : srcCols) - { - if (fk->foreignColumn.compare(srcCol, Qt::CaseInsensitive) == 0) - continue; // Exclude matching column. We don't want the same column several times. - - fullSrcCol = src + "." + wrapObjIfNeeded(srcCol); - selectedCols << fullSrcCol; - usedNames << srcCol; - } - - fkConditionCols << col; - fkConditionTables << src; - - i++; - } - - QStringList conditions; - QString firstSrc = fkConditionTables.first(); - QString firstCol = fkConditionCols.first(); - for (i = 1; i < fkConditionTables.size(); i++) - { - src = fkConditionTables[i]; - col = fkConditionCols[i]; - conditions << conditionTpl.arg(firstSrc, firstCol, src, col); - } - - QString conditionsStr; - if (!conditions.isEmpty()) { - conditionsStr = conditionPrefixTpl.arg(conditions.join(", ")); - } - - // Current value column (will be 1 for row which matches current cell value) - QVariant value = item->getValue(); - QString currValueColName = generateUniqueName("curr", usedNames); - QString currValueExpr = value.isNull() ? - currNullValueTpl.arg(firstSrcCol, currValueColName) : - currValueTpl.arg(firstSrcCol, wrapValueIfNeeded(value), currValueColName); - - return sql.arg( - selectedCols.join(", "), - fkConditionTables.join(", "), - conditionsStr, - currValueExpr - ); -} - -QWidget* SqlQueryItemDelegate::getFkEditor(SqlQueryItem* item, QWidget* parent, const SqlQueryModel* model) const +QWidget* SqlQueryItemDelegate::getFkEditor(SqlQueryItem* item, bool shouldSkipInitialSelection, QWidget* parent, const SqlQueryModel* model) const { Db* db = model->getDb(); bool countingError = false; @@ -320,7 +249,7 @@ QWidget* SqlQueryItemDelegate::getFkEditor(SqlQueryItem* item, QWidget* parent, notifyWarn(tr("Foreign key for column %2 has more than %1 possible values. It's too much to display in drop down list. You need to edit value manually.") .arg(FkComboBox::MAX_ROWS_FOR_FK).arg(item->getColumn()->column)); - return getEditor(item->getValue().userType(), parent); + return getEditor(item->getValue().userType(), item->shoulSkipInitialFocusSelection(), parent); } if (rowCount == 0 && countingError && model->isStructureOutOfDate()) @@ -333,5 +262,8 @@ QWidget* SqlQueryItemDelegate::getFkEditor(SqlQueryItem* item, QWidget* parent, FkComboBox* cb = new FkComboBox(parent, dropDownViewMinWidth); cb->init(db, item->getColumn()); cb->setValue(item->getValue()); + if (!shouldSkipInitialSelection) + cb->lineEdit()->selectAll(); + return cb; } diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h index 655eae2..b703a18 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemdelegate.h @@ -21,6 +21,7 @@ class GUI_API_EXPORT SqlQueryItemDelegate : public QStyledItemDelegate void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; + void destroyEditor(QWidget *editor, const QModelIndex &index) const; QString displayText(const QVariant & value, const QLocale & locale) const; void setEditorData(QWidget * editor, const QModelIndex & index) const; void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const; @@ -39,13 +40,12 @@ class GUI_API_EXPORT SqlQueryItemDelegate : public QStyledItemDelegate }; SqlQueryItem* getItem(const QModelIndex &index) const; - QWidget* getEditor(int type, QWidget* parent) const; - QWidget* getFkEditor(SqlQueryItem* item, QWidget* parent, const SqlQueryModel *model) const; + QWidget* getEditor(int type, bool shouldSkipInitialSelection, QWidget* parent) const; + QWidget* getFkEditor(SqlQueryItem* item, bool shouldSkipInitialSelection, QWidget* parent, const SqlQueryModel *model) const; void setEditorDataForLineEdit(QLineEdit* le, const QModelIndex& index) const; void setEditorDataForFk(QComboBox* cb, const QModelIndex& index) const; void setModelDataForFk(FkComboBox* editor, QAbstractItemModel* model, const QModelIndex& index) const; void setModelDataForLineEdit(QLineEdit* editor, QAbstractItemModel* model, const QModelIndex& index) const; - QString getSqlForFkEditor(SqlQueryItem* item) const; qlonglong getRowCountForFkEditor(Db* db, const QString& query, bool *isError) const; int getFkViewHeaderWidth(SqlQueryView* fkView, bool includeScrollBar) const; diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemlineedit.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemlineedit.cpp new file mode 100644 index 0000000..8f981bc --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemlineedit.cpp @@ -0,0 +1,14 @@ +#include "sqlqueryitemlineedit.h" + +SqlQueryItemLineEdit::SqlQueryItemLineEdit(bool shouldSkipInitialSelection, QWidget *parent) + : QLineEdit{parent}, shouldSkipInitialSelection(shouldSkipInitialSelection) +{ + +} + +void SqlQueryItemLineEdit::focusInEvent(QFocusEvent* e) +{ + QLineEdit::focusInEvent(e); + if (shouldSkipInitialSelection) + deselect(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemlineedit.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemlineedit.h new file mode 100644 index 0000000..76a79af --- /dev/null +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryitemlineedit.h @@ -0,0 +1,20 @@ +#ifndef SQLQUERYITEMLINEEDIT_H +#define SQLQUERYITEMLINEEDIT_H + +#include <QLineEdit> + +class SqlQueryItemLineEdit : public QLineEdit +{ + Q_OBJECT + + public: + explicit SqlQueryItemLineEdit(bool shouldSkipInitialSelection, QWidget *parent = nullptr); + + protected: + void focusInEvent(QFocusEvent *e); + + private: + bool shouldSkipInitialSelection; +}; + +#endif // SQLQUERYITEMLINEEDIT_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp index 2982415..3c44603 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp @@ -81,7 +81,7 @@ void SqlQueryModel::setAsyncMode(bool enabled) queryExecutor->setAsyncMode(enabled); } -void SqlQueryModel::executeQuery() +void SqlQueryModel::executeQuery(bool enforcePage0) { if (queryExecutor->isExecutionInProgress()) { @@ -91,7 +91,7 @@ void SqlQueryModel::executeQuery() queryExecutor->setSkipRowCounting(false); queryExecutor->setSortOrder(sortOrder); - queryExecutor->setPage(0); + queryExecutor->setPage(page > -1 && !enforcePage0 ? page : 0); queryExecutor->setForceSimpleMode(simpleExecutionMode); reloading = false; @@ -157,6 +157,23 @@ bool SqlQueryModel::isEmptyQuery() const return true; } +void SqlQueryModel::restoreFocusedCell() +{ + if (!storedFocus.isValid() || getCurrentPage() != storedFocus.forPage || getRowsPerPage() != storedFocus.forRowsPerPage || + queryExecutor->getFilters() != storedFocus.forFilter) + { + forgetFocusedCell(); + return; + } + + QModelIndex idx = index(storedFocus.row, storedFocus.column); + if (idx.isValid()) + { + view->setCurrentIndex(idx); + view->scrollTo(idx, QAbstractItemView::EnsureVisible); + } +} + void SqlQueryModel::internalExecutionStopped() { reloading = false; @@ -712,6 +729,21 @@ void SqlQueryModel::detachDependencyTables() dbListToDetach.clear(); } +void SqlQueryModel::rememberFocusedCell() +{ + QModelIndex idx = getView()->currentIndex(); + storedFocus.row = idx.row(); + storedFocus.column = idx.column(); + storedFocus.forPage = getCurrentPage(); + storedFocus.forRowsPerPage = getRowsPerPage(); + storedFocus.forFilter = queryExecutor->getFilters(); +} + +void SqlQueryModel::forgetFocusedCell() +{ + storedFocus.reset(); +} + QString SqlQueryModel::generateSelectQueryForItems(const QList<SqlQueryItem*>& items) { QHash<QString, QVariantList> values = toValuesGroupedByColumns(items); @@ -1439,6 +1471,7 @@ void SqlQueryModel::handleExecFinished(SqlQueryPtr results) results.clear(); detachDatabases(); } + restoreFocusedCell(); } void SqlQueryModel::handleExecFailed(int code, QString errorMessage) @@ -1622,7 +1655,11 @@ void SqlQueryModel::storeStep1NumbersFromExecution() { lastExecutionTime = queryExecutor->getLastExecutionTime(); page = queryExecutor->getPage(); - sortOrder = queryExecutor->getSortOrder(); + + QueryExecutor::SortList newSortOrder = queryExecutor->getSortOrder(); + if (!sortOrder.isEmpty() && newSortOrder.isEmpty()) + notifyWarn(tr("There are less columns in the new query, sort order has been reset.")); + sortOrder = newSortOrder; rowsAffected = queryExecutor->getRowsAffected(); if (!queryExecutor->getSkipRowCounting()) @@ -2294,3 +2331,17 @@ QString SqlQueryModel::SelectCellsQueryBuilder::getDatabase() const { return database; } + +bool SqlQueryModel::StoredFocus::isValid() +{ + return row > -1 && column > -1; +} + +void SqlQueryModel::StoredFocus::reset() +{ + row = -1; + column = -1; + forFilter.clear(); + forRowsPerPage = -1; + forPage = -1; +} diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h index 43222f1..7fb6938 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h @@ -65,6 +65,8 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel bool isExecutionInProgress() const; StrHash<QString> attachDependencyTables(); void detachDependencyTables(); + void rememberFocusedCell(); + void forgetFocusedCell(); /** * @brief Disables or re-enables async query execution @@ -244,6 +246,18 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel int argSquence = 0; }; + struct StoredFocus + { + int row = -1; + int column = -1; + int forPage = -1; + int forRowsPerPage = -1; + QString forFilter; + + bool isValid(); + void reset(); + }; + /** * @brief commitAddedRow Inserts new row to a table. * @param itemsInRow All cells for the new row. @@ -316,6 +330,7 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel QueryExecutor* queryExecutor = nullptr; Db* db = nullptr; QList<SqlQueryModelColumnPtr> columns; + StoredFocus storedFocus; /** * @brief tablesInUse @@ -385,6 +400,7 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel void notifyItemEditionEnded(const QModelIndex& idx); int getRowsPerPage() const; bool isEmptyQuery() const; + void restoreFocusedCell(); QString query; QHash<QString, QVariant> queryParams; @@ -522,7 +538,7 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel void prevPage(); void nextPage(); void lastPage(); - void executeQuery(); + void executeQuery(bool enforcePage0 = false); void interrupt(); void commit(); void rollback(); diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp index 68287bd..cd53773 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.cpp @@ -58,6 +58,8 @@ SqlQueryModelColumn::EditionForbiddenReason SqlQueryModelColumn::convert(QueryEx return EditionForbiddenReason::COMMON_TABLE_EXPRESSION; case QueryExecutor::ColumnEditionForbiddenReason::VIEW_NOT_EXPANDED: return EditionForbiddenReason::VIEW_NOT_EXPANDED; + case QueryExecutor::ColumnEditionForbiddenReason::RES_INLINE_SUBSEL: + return EditionForbiddenReason::RESULT_INLINE_SUBSELECT; } return static_cast<EditionForbiddenReason>(-1); } @@ -66,6 +68,8 @@ QString SqlQueryModelColumn::resolveMessage(SqlQueryModelColumn::EditionForbidde { switch (reason) { + case SqlQueryModelColumn::EditionForbiddenReason::RESULT_INLINE_SUBSELECT: + return QObject::tr("Cannot edit columns that are result of an inline subquery."); case EditionForbiddenReason::COMPOUND_SELECT: return QObject::tr("Cannot edit columns that are result of compound %1 statements (one that includes %2, %3 or %4 keywords).") .arg("SELECT", "UNION", "INTERSECT", "EXCEPT"); diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h index 1a347e5..042e336 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodelcolumn.h @@ -28,6 +28,7 @@ class GUI_API_EXPORT SqlQueryModelColumn EXPRESSION, SMART_EXECUTION_FAILED, DISTINCT_RESULTS, + RESULT_INLINE_SUBSELECT, COMMON_TABLE_EXPRESSION, GENERATED_COLUMN, VIEW_NOT_EXPANDED diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp index d9695d7..d815654 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp @@ -47,7 +47,6 @@ void SqlQueryView::init() itemDelegate = new SqlQueryItemDelegate(); setItemDelegate(itemDelegate); setMouseTracking(true); -// setEditTriggers(QAbstractItemView::AnyKeyPressed); setEditTriggers(QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed); setContextMenuPolicy(Qt::CustomContextMenu); @@ -303,6 +302,7 @@ void SqlQueryView::itemActivated(const QModelIndex& index) if (!editInEditorIfNecessary(item)) return; + item->skipInitialFocusSelection(); edit(getCurrentIndex()); } @@ -335,7 +335,13 @@ void SqlQueryView::editCurrent() { QModelIndex idx = getCurrentIndex(); if (idx.isValid()) + { + SqlQueryItem* item = getModel()->itemFromIndex(idx); + if (item) + item->skipInitialFocusSelection(); + edit(idx); + } } void SqlQueryView::toggleRowsHeightAdjustment(bool enabled) @@ -547,7 +553,7 @@ void SqlQueryView::goToReferencedRow(const QString& table, const QString& column QString wrappedTable = wrapObjIfNeeded(table); QString wrappedColumn = wrapObjIfNeeded(column); - QString valueStr = wrapValueIfNeeded(value.toString()); + QString valueStr = valueToSqlLiteral(value.toString()); EditorWindow* win = MAINWINDOW->openSqlEditor(db, sqlTpl.arg(wrappedTable, wrappedColumn, valueStr)); if (!win) return; diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp index 50b0c5b..198a45f 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqltablemodel.cpp @@ -355,10 +355,10 @@ void SqlTableModel::updateColumnsAndValues(const QList<SqlQueryItem*>& itemsInRo int i = 0; for (SqlQueryModelColumnPtr modelColumn : modelColumns) { + item = itemsInRow[i++]; if (!modelColumn->canEdit()) continue; - item = itemsInRow[i++]; if (item->getValue().isNull()) { if (CFG_UI.General.UseDefaultValueForNull.get() && modelColumn->isDefault()) diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlviewmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlviewmodel.cpp index 1f8422c..23eac36 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlviewmodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlviewmodel.cpp @@ -20,8 +20,6 @@ void SqlViewModel::setDatabaseAndView(const QString& database, const QString& vi this->view = view; //setQuery("SELECT * FROM "+getDataSource()); updateTablesInUse(view); - - SchemaResolver resolver(db); } QString SqlViewModel::getDataSource() diff --git a/SQLiteStudio3/guiSQLiteStudio/dataview.cpp b/SQLiteStudio3/guiSQLiteStudio/dataview.cpp index e7ae9b5..8ec00d0 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dataview.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dataview.cpp @@ -682,12 +682,6 @@ void DataView::setFormViewEnabled(bool enabled) setTabEnabled(1, enabled); } -void DataView::readData() -{ - setNavigationState(false); - model->executeQuery(); -} - bool DataView::isUncommitted() const { return uncommittedGrid || uncommittedForm; @@ -718,10 +712,16 @@ void DataView::totalRowsAndPagesAvailable() updateNavigationState(); } -void DataView::refreshData() +void DataView::refreshData(bool keepFocus) { totalPagesAvailable = false; - readData(); + if (keepFocus) + model->rememberFocusedCell(); + else + model->forgetFocusedCell(); + + setNavigationState(false); + model->executeQuery(!keepFocus); } void DataView::insertRow() diff --git a/SQLiteStudio3/guiSQLiteStudio/dataview.h b/SQLiteStudio3/guiSQLiteStudio/dataview.h index d8f71dc..43b6507 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dataview.h +++ b/SQLiteStudio3/guiSQLiteStudio/dataview.h @@ -138,7 +138,6 @@ class GUI_API_EXPORT DataView : public QTabWidget, public ExtActionContainer void updateResultsCount(int resultsCount); void updateCurrentFormViewRow(); void setFormViewEnabled(bool enabled); - void readData(); void initFormViewForNewRow(); void formViewFocusFirstEditor(); void recreateFilterInputs(); @@ -179,7 +178,7 @@ class GUI_API_EXPORT DataView : public QTabWidget, public ExtActionContainer signals: public slots: - void refreshData(); + void refreshData(bool keepFocus = true); private slots: void dataLoadingEnded(bool successful); diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp index 6514aa8..c118eb6 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp @@ -58,6 +58,11 @@ DbTreeItem* DbTreeItem::findItem(DbTreeItem::Type type, const QString& name) return DbTreeModel::findItem(this, type, name); } +DbTreeItem* DbTreeItem::findFirstItem(Type type) +{ + return DbTreeModel::findFirstItem(this, type); +} + QStandardItem* DbTreeItem::clone() const { return new DbTreeItem(*this); @@ -293,6 +298,16 @@ void DbTreeItem::setIcon(const Icon& icon) QStandardItem::setIcon(icon); } +bool DbTreeItem::isSchemaReady() const +{ + return data(DataRole::SCHEMA_READY).toBool(); +} + +void DbTreeItem::setSchemaReady(bool ready) +{ + setData(ready, DataRole::SCHEMA_READY); +} + void DbTreeItem::init() { Type type = getType(); diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h index ba230f2..7cce7a2 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h @@ -38,6 +38,7 @@ class GUI_API_EXPORT DbTreeItem : public QObject, public QStandardItem int type() const; DbTreeItem* findItem(Type type, const QString& name); + DbTreeItem* findFirstItem(Type type); QStandardItem* clone() const; QList<QStandardItem*> childs() const; QStringList childNames() const; @@ -75,6 +76,8 @@ class GUI_API_EXPORT DbTreeItem : public QObject, public QStandardItem void setHidden(bool hidden); bool isHidden() const; void setIcon(const Icon& icon); + bool isSchemaReady() const; + void setSchemaReady(bool ready); private: struct DataRole // not 'enum class' because we need autocasting to int for this one @@ -84,7 +87,8 @@ class GUI_API_EXPORT DbTreeItem : public QObject, public QStandardItem TYPE = 1001, DB = 1002, ICON_PTR = 1003, - HIDDEN = 1004 + HIDDEN = 1004, + SCHEMA_READY = 1005 }; }; diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp index 6620290..29bf652 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp @@ -280,7 +280,14 @@ void DbTreeModel::expanded(const QModelIndex &index) return; } - if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR) + DbTreeItem* dbTreeItem = dynamic_cast<DbTreeItem*>(item); + if (dbTreeItem->getType() == DbTreeItem::Type::TABLE) + loadTableSchema(dbTreeItem); + + if (dbTreeItem->getType() == DbTreeItem::Type::VIEW) + loadViewSchema(dbTreeItem); + + if (dbTreeItem->getType() == DbTreeItem::Type::DIR) itemFromIndex(index)->setIcon(ICONS.DIRECTORY_OPEN); } @@ -443,8 +450,9 @@ QString DbTreeModel::getDbToolTip(DbTreeItem* item) const QString DbTreeModel::getTableToolTip(DbTreeItem* item) const { - QStringList rows; + const_cast<DbTreeModel*>(this)->loadTableSchema(item); // not nice to const_cast, but nothing better we can do about this + QStringList rows; rows << toolTipHdrRowTmp.arg(ICONS.TABLE.getPath()).arg(tr("Table : %1", "dbtree tooltip").arg(item->text())); QStandardItem* columnsItem = item->child(0); @@ -499,20 +507,33 @@ void DbTreeModel::refreshSchema(Db* db, QStandardItem *item) // Collect all db objects and build the db branch bool sort = CFG_UI.General.SortObjects.get(); - QStringList tables = resolver.getTables(); - QStringList virtualTables; - for (const QString& table : tables) + QList<SchemaResolver::TableListItem> tableListItems = resolver.getAllTableListItems(); + QStringList tables; + QStringList views; + QSet<QString> virtualTables; + + for (SchemaResolver::TableListItem& tableListItem : tableListItems) { - if (resolver.isVirtualTable(table)) - virtualTables << table; + switch (tableListItem.type) + { + case SchemaResolver::TableListItem::VIRTUAL_TABLE: + virtualTables << tableListItem.name; + [[fallthrough]]; + case SchemaResolver::TableListItem::TABLE: + case SchemaResolver::TableListItem::SHADOW_TABLE: + tables << tableListItem.name; + break; + case SchemaResolver::TableListItem::VIEW: + views << tableListItem.name; + break; + case SchemaResolver::TableListItem::UNKNOWN: + break; + } } QList<QStandardItem*> tableItems = refreshSchemaTables(tables, virtualTables, sort); - StrHash<QList<QStandardItem*>> allTableColumns = refreshSchemaTableColumns(resolver.getAllTableColumns()); - StrHash<QList<QStandardItem*>> indexItems = refreshSchemaIndexes(resolver.getGroupedIndexes(), sort); - StrHash<QList<QStandardItem*>> triggerItems = refreshSchemaTriggers(resolver.getGroupedTriggers(), sort); - QList<QStandardItem*> viewItems = refreshSchemaViews(resolver.getViews(), sort); - refreshSchemaBuild(item, tableItems, indexItems, triggerItems, viewItems, allTableColumns); + QList<QStandardItem*> viewItems = refreshSchemaViews(views, sort); + refreshSchemaBuild(item, tableItems, viewItems); populateChildItemsWithDb(item, db); restoreExpandedState(expandedState, item); } @@ -530,7 +551,7 @@ void DbTreeModel::collectExpandedState(QHash<QString, bool> &state, QStandardIte collectExpandedState(state, parentItem->child(i)); } -QList<QStandardItem *> DbTreeModel::refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort) +QList<QStandardItem *> DbTreeModel::refreshSchemaTables(const QStringList &tables, const QSet<QString>& virtualTables, bool sort) { QStringList sortedTables = tables; if (sort) @@ -548,52 +569,44 @@ QList<QStandardItem *> DbTreeModel::refreshSchemaTables(const QStringList &table return items; } -StrHash<QList<QStandardItem*>> DbTreeModel::refreshSchemaTableColumns(const StrHash<QStringList> &columns) +QList<QStandardItem*> DbTreeModel::refreshSchemaTableColumns(const QStringList& columns) { - QStringList sortedColumns; bool doSort = CFG_UI.General.SortColumns.get(); - StrHash<QList<QStandardItem*>> items; - for (const QString& key : columns.keys()) - { - sortedColumns = columns[key]; - if (doSort) - ::sSort(sortedColumns); - for (const QString& column : sortedColumns) - items[key] += DbTreeItemFactory::createColumn(column, this); - } + QStringList sortedColumns = columns; + if (doSort) + ::sSort(sortedColumns); + + QList<QStandardItem*> items; + for (const QString& column : sortedColumns) + items += DbTreeItemFactory::createColumn(column, this); + return items; } -StrHash<QList<QStandardItem *> > DbTreeModel::refreshSchemaIndexes(const StrHash<QStringList> &indexes, bool sort) +QList<QStandardItem*> DbTreeModel::refreshSchemaIndexes(const QStringList& indexes, bool sort) { - StrHash<QList<QStandardItem *> > items; - QStringList sortedIndexes; - for (const QString& key : indexes.keys()) - { - sortedIndexes = indexes[key]; - if (sort) - sortedIndexes.sort(Qt::CaseInsensitive); + QStringList sortedIndexes = indexes; + if (sort) + sortedIndexes.sort(Qt::CaseInsensitive); + + QList<QStandardItem*> items; + for (const QString& index : sortedIndexes) + items += DbTreeItemFactory::createIndex(index, this); - for (const QString& index : sortedIndexes) - items[key] += DbTreeItemFactory::createIndex(index, this); - } return items; } -StrHash<QList<QStandardItem*>> DbTreeModel::refreshSchemaTriggers(const StrHash<QStringList> &triggers, bool sort) +QList<QStandardItem*> DbTreeModel::refreshSchemaTriggers(const QStringList& triggers, bool sort) { - StrHash<QList<QStandardItem*>> items; - QStringList sortedTriggers; - for (const QString& key : triggers.keys()) - { - sortedTriggers = triggers[key]; - if (sort) - sortedTriggers.sort(Qt::CaseInsensitive); + QStringList sortedTriggers = triggers; + if (sort) + sortedTriggers.sort(Qt::CaseInsensitive); + + QList<QStandardItem*> items; + for (const QString& trigger : sortedTriggers) + items += DbTreeItemFactory::createTrigger(trigger, this); - for (const QString& trigger : sortedTriggers) - items[key] += DbTreeItemFactory::createTrigger(trigger, this); - } return items; } @@ -621,12 +634,68 @@ void DbTreeModel::populateChildItemsWithDb(QStandardItem *parentItem, Db* db) } } +void DbTreeModel::loadTableSchema(DbTreeItem* tableItem) +{ + if (tableItem->isSchemaReady()) + return; + + Db* db = tableItem->getDb(); + QString table = tableItem->text(); + + SchemaResolver resolver(db); + resolver.setIgnoreSystemObjects(!CFG_UI.General.ShowSystemObjects.get()); + + bool sort = CFG_UI.General.SortObjects.get(); + + DbTreeItem* columnsItem = tableItem->findFirstItem(DbTreeItem::Type::COLUMNS); + DbTreeItem* indexesItem = tableItem->findFirstItem(DbTreeItem::Type::INDEXES); + DbTreeItem* triggersItem = tableItem->findFirstItem(DbTreeItem::Type::TRIGGERS); + + QList<QStandardItem*> tableColumns = refreshSchemaTableColumns(resolver.getTableColumns(table)); + QList<QStandardItem*> indexItems = refreshSchemaIndexes(resolver.getIndexesForTable(table), sort); + QList<QStandardItem*> triggerItems = refreshSchemaTriggers(resolver.getTriggersForTable(table), sort); + + for (QStandardItem* columnItem : tableColumns) + columnsItem->appendRow(columnItem); + + for (QStandardItem* indexItem : indexItems) + indexesItem->appendRow(indexItem); + + for (QStandardItem* triggerItem : triggerItems) + triggersItem->appendRow(triggerItem); + + populateChildItemsWithDb(columnsItem, db); + populateChildItemsWithDb(indexesItem, db); + populateChildItemsWithDb(triggersItem, db); + + tableItem->setSchemaReady(true); +} + +void DbTreeModel::loadViewSchema(DbTreeItem* viewItem) +{ + if (viewItem->isSchemaReady()) + return; + + Db* db = viewItem->getDb(); + QString view = viewItem->text(); + + SchemaResolver resolver(db); + resolver.setIgnoreSystemObjects(!CFG_UI.General.ShowSystemObjects.get()); + + bool sort = CFG_UI.General.SortObjects.get(); + + DbTreeItem* triggersItem = viewItem->findFirstItem(DbTreeItem::Type::TRIGGERS); + + QList<QStandardItem*> triggerItems = refreshSchemaTriggers(resolver.getTriggersForView(view), sort); + for (QStandardItem* triggerItem : triggerItems) + triggersItem->appendRow(triggerItem); + + viewItem->setSchemaReady(true); +} + void DbTreeModel::refreshSchemaBuild(QStandardItem *dbItem, QList<QStandardItem*> tables, - StrHash<QList<QStandardItem*> > indexes, - StrHash<QList<QStandardItem*> > triggers, - QList<QStandardItem*> views, - StrHash<QList<QStandardItem*> > allTableColumns) + QList<QStandardItem*> views) { DbTreeItem* tablesItem = DbTreeItemFactory::createTables(this); DbTreeItem* viewsItem = DbTreeItemFactory::createViews(this); @@ -649,14 +718,7 @@ void DbTreeModel::refreshSchemaBuild(QStandardItem *dbItem, tableItem->appendRow(indexesItem); tableItem->appendRow(triggersItem); - for (QStandardItem* columnItem : allTableColumns[tableItem->text()]) - columnsItem->appendRow(columnItem); - - for (QStandardItem* indexItem : indexes[tableItem->text()]) - indexesItem->appendRow(indexItem); - - for (QStandardItem* triggerItem : triggers[tableItem->text()]) - triggersItem->appendRow(triggerItem); + dynamic_cast<DbTreeItem*>(tableItem)->setSchemaReady(false); } for (QStandardItem* viewItem : views) { @@ -664,8 +726,8 @@ void DbTreeModel::refreshSchemaBuild(QStandardItem *dbItem, triggersItem = DbTreeItemFactory::createTriggers(this); viewItem->appendRow(triggersItem); - for (QStandardItem* triggerItem : triggers[viewItem->text()]) - triggersItem->appendRow(triggerItem); + + dynamic_cast<DbTreeItem*>(viewItem)->setSchemaReady(false); } } @@ -680,22 +742,6 @@ void DbTreeModel::restoreExpandedState(const QHash<QString, bool>& expandedState restoreExpandedState(expandedState, child); } -DbTreeItem* DbTreeModel::findFirstItemOfType(DbTreeItem::Type type, QStandardItem* parentItem) -{ - DbTreeItem* child = nullptr; - for (int i = 0; i < parentItem->rowCount(); i++) - { - child = dynamic_cast<DbTreeItem*>(parentItem->child(i)); - if (child->getType() == type) - return child; - - child = findFirstItemOfType(type, child); - if (child) - return child; - } - return nullptr; -} - void DbTreeModel::dbConnected(Db* db, bool expandItem) { QStandardItem* item = findItem(DbTreeItem::Type::DB, db); @@ -819,7 +865,7 @@ DbTreeItem *DbTreeModel::findItem(DbTreeItem::Type type, Db* db) DbTreeItem* DbTreeModel::findFirstItemOfType(DbTreeItem::Type type) { - return findFirstItemOfType(type, root()); + return findFirstItem(root(), type); } DbTreeItem *DbTreeModel::findItemBySignature(const QString &signature) @@ -883,7 +929,7 @@ QList<DbTreeItem*> DbTreeModel::findItems(QStandardItem* parentItem, DbTreeItem: item = dynamic_cast<DbTreeItem*>(parentItem->child(i)); // Search recursively - if (item->getType() == DbTreeItem::Type::DIR) + if (item->hasChildren()) items += findItems(item, type); if (item->getType() != type) @@ -895,6 +941,28 @@ QList<DbTreeItem*> DbTreeModel::findItems(QStandardItem* parentItem, DbTreeItem: return items; } +DbTreeItem* DbTreeModel::findFirstItem(QStandardItem* parentItem, DbTreeItem::Type type) +{ + for (int i = 0; i < parentItem->rowCount(); i++) + { + DbTreeItem* item = dynamic_cast<DbTreeItem*>(parentItem->child(i)); + + if (item->hasChildren()) + { + DbTreeItem* child = findFirstItem(item, type); + if (child) + return child; + } + + if (item->getType() != type) + continue; + + return item; + } + + return nullptr; +} + QStandardItem* DbTreeModel::root() const { return invisibleRootItem(); diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h index 86dc8a6..5fb91ba 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h +++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h @@ -57,6 +57,7 @@ class GUI_API_EXPORT DbTreeModel : public QStandardItemModel static DbTreeItem* findItem(QStandardItem *parentItem, DbTreeItem::Type type, const QString &name); static DbTreeItem* findItem(QStandardItem* parentItem, DbTreeItem::Type type, Db* db); static QList<DbTreeItem*> findItems(QStandardItem* parentItem, DbTreeItem::Type type); + static DbTreeItem* findFirstItem(QStandardItem* parentItem, DbTreeItem::Type type); static void staticInit(); static const constexpr char* MIMETYPE = "application/x-sqlitestudio-dbtreeitem"; @@ -69,16 +70,16 @@ class GUI_API_EXPORT DbTreeModel : public QStandardItemModel void refreshSchema(Db* db, QStandardItem* item); void collectExpandedState(QHash<QString, bool>& state, QStandardItem* parentItem = nullptr); QStandardItem* refreshSchemaDb(Db* db); - QList<QStandardItem*> refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort); - StrHash<QList<QStandardItem*>> refreshSchemaTableColumns(const StrHash<QStringList>& columns); - StrHash<QList<QStandardItem*>> refreshSchemaIndexes(const StrHash<QStringList>& indexes, bool sort); - StrHash<QList<QStandardItem*>> refreshSchemaTriggers(const StrHash<QStringList>& triggers, bool sort); + QList<QStandardItem*> refreshSchemaTables(const QStringList &tables, const QSet<QString>& virtualTables, bool sort); + QList<QStandardItem*> refreshSchemaTableColumns(const QStringList& columns); + QList<QStandardItem*> refreshSchemaIndexes(const QStringList& indexes, bool sort); + QList<QStandardItem*> refreshSchemaTriggers(const QStringList& triggers, bool sort); QList<QStandardItem*> refreshSchemaViews(const QStringList &views, bool sort); void populateChildItemsWithDb(QStandardItem* parentItem, Db* db); - void refreshSchemaBuild(QStandardItem* dbItem, QList<QStandardItem*> tables, StrHash<QList<QStandardItem*> > indexes, - StrHash<QList<QStandardItem*> > triggers, QList<QStandardItem*> views, StrHash<QList<QStandardItem*> > allTableColumns); + void loadTableSchema(DbTreeItem* tableItem); + void loadViewSchema(DbTreeItem* viewItem); + void refreshSchemaBuild(QStandardItem* dbItem, QList<QStandardItem*> tables, QList<QStandardItem*> views); void restoreExpandedState(const QHash<QString, bool>& expandedState, QStandardItem* parentItem); - DbTreeItem* findFirstItemOfType(DbTreeItem::Type type, QStandardItem* parentItem); QString getToolTip(DbTreeItem *item) const; QString getDbToolTip(DbTreeItem *item) const; QString getTableToolTip(DbTreeItem *item) const; diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp index a217da6..cf5848a 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp @@ -9,11 +9,13 @@ #include "constraints/constraintpanel.h" #include "datatype.h" #include "uiutils.h" +#include "common/dialogsizehandler.h" #include <QDebug> #include <QCheckBox> #include <QMessageBox> #include <QDebug> #include <QPushButton> +#include <schemaresolver.h> ColumnDialog::ColumnDialog(Db* db, QWidget *parent) : QDialog(parent), @@ -33,6 +35,7 @@ void ColumnDialog::init() ui->setupUi(this); limitDialogWidth(this); setWindowIcon(ICONS.COLUMN); + DialogSizeHandler::applyFor(this); ui->scale->setStrict(true, true); ui->precision->setStrict(true, true); @@ -403,7 +406,7 @@ void ColumnDialog::updateTypeValidations() setValidState(ui->typeCombo, typeOk, typeErrorMsg); if (typeOk && integerTypeEnforced) - setValidStateTooltip(ui->typeCombo, integerEnforcedMsg); + setValidStateTooltip(ui->typeCombo, integerEnforcedMsg); if (!scaleOk || !precisionOk || !typeOk) { @@ -437,6 +440,43 @@ bool ColumnDialog::hasAutoIncr() const return false; } +void ColumnDialog::validateFkTypeMatch() +{ + QString fkTypeWarningMsg = tr("Referenced column type (%1) is different than type declared in this column. It may cause issues while inserting or updating data."); + + for (SqliteCreateTable::Column::Constraint*& constr : column->getConstraints(SqliteCreateTable::Column::Constraint::FOREIGN_KEY)) + { + if (!constr->foreignKey || constr->foreignKey->indexedColumns.isEmpty() || constr->foreignKey->foreignTable.isNull()) + continue; + + QString fkTable = constr->foreignKey->foreignTable; + QString fkColumn = constr->foreignKey->indexedColumns.first()->name; + if (!fkTableTypesCache.contains(fkTable, Qt::CaseInsensitive)) + { + SchemaResolver resolver(db); + fkTableTypesCache[fkTable] = resolver.getTableColumnDataTypesByName(fkTable);; + } + + StrHash<DataType> fkTypes = fkTableTypesCache.value(fkTable, Qt::CaseInsensitive); + if (fkTypes.isEmpty()) + continue; + + DataType fkType = fkTypes.value(fkColumn, Qt::CaseInsensitive); + if (fkType.toString().toLower().trimmed() != ui->typeCombo->currentText().toLower().trimmed()) + { + auto fkButton = getToolButtonForConstraint(constr); + if (!fkButton) + continue; + + if (isValidStateIndicatorVisible(fkButton)) + continue; + + setValidStateWarning(fkButton, fkTypeWarningMsg.arg(fkType.toString())); + break; + } + } +} + void ColumnDialog::moveConstraintUp() { QModelIndex idx = ui->constraintsView->currentIndex(); @@ -618,6 +658,7 @@ void ColumnDialog::updateValidations() updateConstraint(constr); updateTypeValidations(); + validateFkTypeMatch(); } void ColumnDialog::updateConstraint(SqliteCreateTable::Column::Constraint* constraint) diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h index c9faf23..bc02d14 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h @@ -5,6 +5,7 @@ #include "common/extactioncontainer.h" #include "constraintdialog.h" #include "guiSQLiteStudio_global.h" +#include "common/strhash.h" #include <QDialog> #include <QPointer> @@ -71,6 +72,7 @@ class GUI_API_EXPORT ColumnDialog : public QDialog, public ExtActionContainer void updateTypeValidations(); void updateTypeForAutoIncr(); bool hasAutoIncr() const; + void validateFkTypeMatch(); Ui::ColumnDialog *ui = nullptr; SqliteCreateTable::ColumnPtr column; @@ -79,6 +81,7 @@ class GUI_API_EXPORT ColumnDialog : public QDialog, public ExtActionContainer Db* db = nullptr; bool integerTypeEnforced = false; QSet<ConstraintDialog::Constraint> disabledConstraints; + StrHash<StrHash<DataType>> fkTableTypesCache; private slots: void updateConstraintsToolbarState(); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp index 8c80f6d..425acad 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp @@ -28,6 +28,7 @@ #include "syntaxhighlighterplugin.h"
#include "sqleditor.h"
#include "style.h"
+#include "common/dialogsizehandler.h"
#include <QSignalMapper>
#include <QLineEdit>
#include <QSpinBox>
@@ -201,6 +202,7 @@ void ConfigDialog::init() {
ui->setupUi(this);
setWindowIcon(ICONS.CONFIGURE);
+ DialogSizeHandler::applyFor(this);
ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(0));
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp index 0333cbc..84c4cdc 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp @@ -79,6 +79,8 @@ void ConstraintDialog::init() } currentPanel->setDb(db); + currentPanel->setCreateTableStmt(createTable.data()); + currentPanel->setColumnStmt(columnStmt.data()); currentPanel->setConstraint(constrStatement); connect(currentPanel, SIGNAL(updateValidation()), this, SLOT(validate())); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp index e6a53db..2b85be3 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp @@ -145,6 +145,8 @@ void DbDialog::init() ui->testConnIcon->setVisible(false); + connect(ui->existingDatabaseRadio, SIGNAL(clicked()), this, SLOT(updateCreateMode())); + connect(ui->createDatabaseRadio, SIGNAL(clicked()), this, SLOT(updateCreateMode())); connect(ui->fileEdit, SIGNAL(textChanged(QString)), this, SLOT(fileChanged(QString))); connect(ui->nameEdit, SIGNAL(textEdited(QString)), this, SLOT(nameModified(QString))); connect(ui->browseOpenButton, SIGNAL(clicked()), this, SLOT(browseClicked())); @@ -172,7 +174,9 @@ void DbDialog::updateOptions() customBrowseHandler = nullptr; ui->pathGroup->setTitle(tr("File")); - ui->browseOpenButton->setToolTip(tr("Select new or existing file on local computer")); + ui->existingDatabaseRadio->setChecked(true); + ui->createDatabaseRadio->setChecked(false); + updateCreateMode(); optionWidgets.clear(); optionKeyToWidget.clear(); @@ -209,6 +213,10 @@ void DbDialog::addOption(const DbPluginOption& option, int& row) // This option does not add any editor, but has it's own label for path edit. row--; ui->pathGroup->setTitle(option.label); + ui->existingDatabaseRadio->setChecked(true); + ui->createDatabaseRadio->setChecked(false); + ui->createDatabaseRadio->setVisible(false); + updateCreateMode(); if (!option.toolTip.isEmpty()) ui->browseOpenButton->setToolTip(option.toolTip); @@ -567,6 +575,13 @@ bool DbDialog::validate() setValidState(ui->fileEdit, false, tr("Enter a database file path.")); fileState = false; } + else if (QFileInfo(getPath()).isRelative()) + { + setValidStateWarning(ui->fileEdit, + tr("You're using a relative file path, which will be resolved to \"%1\" according to the application's working directory. It's always better to use absolute file path to avoid unexpected database location.") + .arg(QFileInfo(getPath()).absoluteFilePath())); + fileState = false; + } if (fileState) { @@ -689,7 +704,7 @@ void DbDialog::browseClicked() else dir = getFileDialogInitPath(); - QString path = getDbPath(dir); + QString path = getDbPath(createMode, dir); if (path.isNull()) return; @@ -728,6 +743,15 @@ void DbDialog::nameModified(const QString &value) updateState(); } +void DbDialog::updateCreateMode() +{ + createMode = ui->createDatabaseRadio->isChecked(); + ui->browseOpenButton->setToolTip( + createMode ? tr("Choose a location for the new database file") + : tr("Browse for existing database file on local computer") + ); +} + void DbDialog::accept() { QString name = getName(); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h index 76cd3ec..a52734b 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h @@ -74,6 +74,7 @@ class GUI_API_EXPORT DbDialog : public QDialog bool disableTypeAutodetection = false; bool doAutoTest = false; bool nameManuallyEdited = false; + bool createMode = false; ImmediateTooltip* connIconTooltip = nullptr; static const constexpr int ADDITIONAL_ROWS_BEGIN_INDEX = 1; @@ -88,6 +89,7 @@ class GUI_API_EXPORT DbDialog : public QDialog void propertyChanged(); void dbTypeChanged(int index); void nameModified(const QString &value); + void updateCreateMode(); public slots: void accept(); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui index f0ebe2a..880175f 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui @@ -44,21 +44,42 @@ <property name="title"> <string>File</string> </property> - <layout class="QHBoxLayout" name="horizontalLayout_5"> + <layout class="QVBoxLayout" name="verticalLayout_3"> <item> - <widget class="QLineEdit" name="fileEdit"/> + <widget class="QRadioButton" name="existingDatabaseRadio"> + <property name="text"> + <string>Select an existing database file</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> </item> <item> - <widget class="QToolButton" name="browseOpenButton"> + <widget class="QRadioButton" name="createDatabaseRadio"> <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../icons.qrc"> - <normaloff>:/icons/img/open_sql_file.png</normaloff>:/icons/img/open_sql_file.png</iconset> + <string>Create a new database file</string> </property> </widget> </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QLineEdit" name="fileEdit"/> + </item> + <item> + <widget class="QToolButton" name="browseOpenButton"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../icons.qrc"> + <normaloff>:/icons/img/open_sql_file.png</normaloff>:/icons/img/open_sql_file.png</iconset> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp index 3af221f..f3863c8 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp @@ -4,6 +4,7 @@ #include "uiconfig.h" #include "sqlitestudio.h" #include "db/db.h" +#include "common/dialogsizehandler.h" DdlPreviewDialog::DdlPreviewDialog(Db* db, QWidget *parent) : QDialog(parent), @@ -11,6 +12,7 @@ DdlPreviewDialog::DdlPreviewDialog(Db* db, QWidget *parent) : db(db) { ui->setupUi(this); + DialogSizeHandler::applyFor(this); } DdlPreviewDialog::~DdlPreviewDialog() diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp index 135bc9d..820e4cc 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp @@ -16,6 +16,7 @@ #include "services/notifymanager.h" #include "themetuner.h" #include "uiconfig.h" +#include "common/dialogsizehandler.h" #include <QClipboard> #include <QDebug> #include <QDir> @@ -52,6 +53,7 @@ void ExportDialog::init() ui->setupUi(this); THEME_TUNER->darkThemeFix(this); limitDialogWidth(this); + DialogSizeHandler::applyFor(this); #ifdef Q_OS_MACX resize(width() + 150, height()); @@ -61,7 +63,9 @@ void ExportDialog::init() widgetCover = new WidgetCover(this); widgetCover->initWithInterruptContainer(tr("Cancel")); connect(widgetCover, SIGNAL(cancelClicked()), EXPORT_MANAGER, SLOT(interrupt())); + connect(EXPORT_MANAGER, SIGNAL(finishedStep(int)), widgetCover, SLOT(setProgress(int))); widgetCover->setVisible(false); + widgetCover->displayProgress(0, "%v"); initPageOrder(); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp index f443f18..6c9c8c7 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp @@ -13,6 +13,7 @@ #include "themetuner.h" #include "iconmanager.h" #include "mainwindow.h" +#include "common/dialogsizehandler.h" #include <QDir> #include <QDebug> #include <QFileDialog> @@ -102,6 +103,7 @@ void ImportDialog::init() ui->setupUi(this); THEME_TUNER->darkThemeFix(this); limitDialogWidth(this); + DialogSizeHandler::applyFor(this); #ifdef Q_OS_MACX resize(width() + 150, height()); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp index d5249d0..ad56f64 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp @@ -13,6 +13,7 @@ #include "indexexprcolumndialog.h" #include "windows/editorwindow.h" #include "services/codeformatter.h" +#include "common/dialogsizehandler.h" #include <QDebug> #include <QGridLayout> #include <QSignalMapper> @@ -64,6 +65,7 @@ void IndexDialog::init() { ui->setupUi(this); limitDialogWidth(this); + DialogSizeHandler::applyFor(this); if (!db || !db->isOpen()) { qCritical() << "Created IndexDialog for null or closed database."; diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp index 89fff04..734f813 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp @@ -11,6 +11,7 @@ #include "services/populatemanager.h" #include "common/widgetcover.h" #include "common/compatibility.h" +#include "common/dialogsizehandler.h" #include <QPushButton> #include <QGridLayout> #include <QCheckBox> @@ -45,6 +46,7 @@ void PopulateDialog::init() { ui->setupUi(this); limitDialogWidth(this); + DialogSizeHandler::applyFor(this); ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Populate", "populate dialog button")); plugins = PLUGINS->getLoadedPlugins<PopulatePlugin>(); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp index 578a253..b8992b4 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp @@ -2,12 +2,14 @@ #include "ui_searchtextdialog.h" #include "searchtextlocator.h" #include "common/unused.h" +#include "common/dialogsizehandler.h" SearchTextDialog::SearchTextDialog(SearchTextLocator* textLocator, QWidget *parent) : QDialog(parent), ui(new Ui::SearchTextDialog), textLocator(textLocator) { ui->setupUi(this); + DialogSizeHandler::applyFor(this); connect(textLocator, SIGNAL(replaceAvailable(bool)), this, SLOT(setReplaceAvailable(bool))); connect(ui->findEdit, SIGNAL(textChanged(QString)), this, SLOT(markModifiedState())); connect(ui->caseSensitiveCheck, SIGNAL(toggled(bool)), this, SLOT(markModifiedState())); diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp index 5091613..61fdadd 100644 --- a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp @@ -13,6 +13,7 @@ #include "services/config.h" #include "uiutils.h" #include "services/codeformatter.h" +#include "common/dialogsizehandler.h" #include <QDebug> #include <QMessageBox> #include <QPushButton> @@ -83,6 +84,7 @@ void TriggerDialog::init() { ui->setupUi(this); limitDialogWidth(this); + DialogSizeHandler::applyFor(this); connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(updateDdlTab(int))); connect(ui->actionColumns, SIGNAL(clicked()), this, SLOT(showColumnsDialog())); diff --git a/SQLiteStudio3/guiSQLiteStudio/extendedpalette.cpp b/SQLiteStudio3/guiSQLiteStudio/extendedpalette.cpp index eba2f4c..28edbe3 100644 --- a/SQLiteStudio3/guiSQLiteStudio/extendedpalette.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/extendedpalette.cpp @@ -31,10 +31,11 @@ bool ExtendedPalette::styleChanged(QStyle *style, const QString &themeName) { UNUSED(themeName); QPalette stdPalette = style->standardPalette(); - if (stdPalette == lastPalette) + QVariant paletteVariant = stdPalette; + if (paletteVariant == initializedForPalette) return false; - lastPalette = stdPalette; + initializedForPalette = paletteVariant; bool isDark = stdPalette.base().color().lightness() < 128; static const QColor stdStrColor = QColor(Qt::green); diff --git a/SQLiteStudio3/guiSQLiteStudio/extendedpalette.h b/SQLiteStudio3/guiSQLiteStudio/extendedpalette.h index 6afaeba..0891ce7 100644 --- a/SQLiteStudio3/guiSQLiteStudio/extendedpalette.h +++ b/SQLiteStudio3/guiSQLiteStudio/extendedpalette.h @@ -3,6 +3,7 @@ #include <QBrush> #include <QPalette> +#include <QVariant> class QStyle; @@ -39,7 +40,7 @@ class ExtendedPalette QBrush editorCurrentQueryBrush; QBrush mdiAreaBaseBrush; - QPalette lastPalette; + QVariant initializedForPalette; }; #endif // EXTENDEDPALETTE_H diff --git a/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro index 9a436de..fde6802 100644 --- a/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro +++ b/SQLiteStudio3/guiSQLiteStudio/guiSQLiteStudio.pro @@ -33,10 +33,12 @@ DEFINES += GUISQLITESTUDIO_LIBRARY SOURCES +=\ common/dbcombobox.cpp \ + common/dialogsizehandler.cpp \ common/immediatetooltip.cpp \ common/mouseshortcut.cpp \ constraints/columngeneratedpanel.cpp \ datagrid/fkcombobox.cpp \ + datagrid/sqlqueryitemlineedit.cpp \ extendedpalette.cpp \ mainwindow.cpp \ iconmanager.cpp \ @@ -193,10 +195,12 @@ SOURCES +=\ HEADERS += mainwindow.h \ common/dbcombobox.h \ + common/dialogsizehandler.h \ common/immediatetooltip.h \ common/mouseshortcut.h \ constraints/columngeneratedpanel.h \ datagrid/fkcombobox.h \ + datagrid/sqlqueryitemlineedit.h \ extendedpalette.h \ iconmanager.h \ dbtree/dbtreemodel.h \ diff --git a/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp b/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp index d1f1a47..1fb57ec 100644 --- a/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/sqleditor.cpp @@ -25,6 +25,8 @@ #include "dbtree/dbtreeview.h" #include "common/lazytrigger.h" #include "common/extaction.h" +#include "db/dbsqlite3.h" +#include "dialogs/dbdialog.h" #include <QAction> #include <QMenu> #include <QTimer> @@ -35,6 +37,7 @@ #include <QScrollBar> #include <QFileDialog> #include <QtConcurrent/QtConcurrent> +#include <QMessageBox> #include <QStyle> CFG_KEYS_DEFINE(SqlEditor) @@ -727,6 +730,9 @@ void SqlEditor::highlightParenthesis(QList<QTextEdit::ExtraSelection>& selection void SqlEditor::highlightCurrentQuery(QList<QTextEdit::ExtraSelection>& selections) { + if (!richFeaturesEnabled) + return; + QTextCursor cursor = textCursor(); int curPos = cursor.position(); QString contents = cursor.document()->toPlainText(); @@ -735,7 +741,7 @@ void SqlEditor::highlightCurrentQuery(QList<QTextEdit::ExtraSelection>& selectio return; QTextEdit::ExtraSelection selection; - selection.format.setBackground(STYLE->extendedPalette().editorCurrentQueryBase()); + selection.format.setBackground(Cfg::getSyntaxCurrentQueryBg()); cursor.setPosition(boundries.first); cursor.setPosition(boundries.second, QTextCursor::KeepAnchor); @@ -1100,7 +1106,7 @@ void SqlEditor::highlightCurrentLine(QList<QTextEdit::ExtraSelection>& selection if (!isReadOnly() && isEnabled()) { QTextEdit::ExtraSelection selection; - selection.format.setBackground(STYLE->extendedPalette().editorLineBase()); + selection.format.setBackground(Cfg::getSyntaxCurrentLineBg()); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = textCursor(); selection.cursor.clearSelection(); @@ -1197,6 +1203,24 @@ void SqlEditor::loadFromFile() setFileDialogInitPathByFile(fName); + if (DbSqlite3::isDbFile(fName)) + { + DbDialog dialog(DbDialog::ADD, MAINWINDOW); + dialog.setPath(fName); + dialog.setDoAutoTest(true); + dialog.exec(); + return; + } + + if (QFile(fName).size() > HUGE_QUERY_LENGTH) + { + QMessageBox::StandardButton resp = QMessageBox::question(this, tr("Open file"), + tr("This file is huge (over %1 MB). Are you sure you want to load it into SQL query editor?") + .arg(HUGE_QUERY_LENGTH / 1024 / 1024)); + if (resp != QMessageBox::Yes) + return; + } + QString err; QString sql = readFileContents(fName, &err); if (sql.isNull() && !err.isNull()) @@ -1205,9 +1229,13 @@ void SqlEditor::loadFromFile() return; } - setPlainText(sql); - - loadedFile = fName; + if (toPlainText().trimmed().isEmpty()) + { + setPlainText(sql); + loadedFile = fName; + } + else + MAINWINDOW->openSqlEditor(db, sql); } void SqlEditor::deleteLine() @@ -1715,10 +1743,11 @@ void SqlEditor::saveSelection() } void SqlEditor::restoreSelection() -{ +{ QTextCursor cur = textCursor(); cur.setPosition(storedSelectionStart); cur.setPosition(storedSelectionEnd, QTextCursor::KeepAnchor); + setTextCursor(cur); } QToolBar* SqlEditor::getToolBar(int toolbar) const diff --git a/SQLiteStudio3/guiSQLiteStudio/sqleditor.h b/SQLiteStudio3/guiSQLiteStudio/sqleditor.h index 4af89c1..b78b46b 100644 --- a/SQLiteStudio3/guiSQLiteStudio/sqleditor.h +++ b/SQLiteStudio3/guiSQLiteStudio/sqleditor.h @@ -124,6 +124,8 @@ class GUI_API_EXPORT SqlEditor : public QPlainTextEdit, public ExtActionContaine bool getAlwaysEnforceErrorsChecking() const; void setAlwaysEnforceErrorsChecking(bool newAlwaysEnforceErrorsChecking); + static constexpr int HUGE_QUERY_LENGTH = 10 * 1024 * 1024; // 10MB of SQL + protected: void setupDefShortcuts(); void createActions(); diff --git a/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp b/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp index 2e6a93a..8f3936e 100644 --- a/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/taskbar.cpp @@ -55,6 +55,18 @@ QList<QAction*> TaskBar::getTasks() const void TaskBar::init() { setAcceptDrops(true); + setStyleSheet(R"( + QToolButton:checked { + color: white; + background-color: #0078D7; + border: 2px solid #005A9E; + border-radius: 4px; + } + QToolButton:hover { + background-color: #005A9E; + color: white; + } + )"); } void TaskBar::mousePressed() diff --git a/SQLiteStudio3/guiSQLiteStudio/translations/guiSQLiteStudio_de_DE.ts b/SQLiteStudio3/guiSQLiteStudio/translations/guiSQLiteStudio_de_DE.ts index de05638..a732e3d 100644 --- a/SQLiteStudio3/guiSQLiteStudio/translations/guiSQLiteStudio_de_DE.ts +++ b/SQLiteStudio3/guiSQLiteStudio/translations/guiSQLiteStudio_de_DE.ts @@ -5321,13 +5321,13 @@ find next</source> <location filename="../sqleditor.cpp" line="165"/> <source>Select file to save SQL</source> <comment>sql editor</comment> - <translation>SQL aus Datei laden</translation> + <translation>Datei zum Speichern von SQL auswählen</translation> </message> <message> <location filename="../sqleditor.cpp" line="166"/> <source>Load SQL from file</source> <comment>sql editor</comment> - <translation>Zeile löschen</translation> + <translation>SQL aus Datei laden</translation> </message> <message> <location filename="../sqleditor.cpp" line="167"/> diff --git a/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp b/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp index a73b8ee..cdad77c 100644 --- a/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/uiutils.cpp @@ -35,7 +35,7 @@ const QStringList pageSizes = map<QPageSize::PageSizeId, QString>(pageSizeIds, [ const QStringList pageSizesWithDimensions; -QString getDbPath(const QString &startWith) +QString getDbPath(bool newFileMode, const QString &startWith) { QString dir = startWith; if (dir.isNull()) @@ -48,8 +48,15 @@ QString getDbPath(const QString &startWith) }); QFileDialog dialog(nullptr, QObject::tr("Select database file"), dir, QString()); - dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setAcceptMode(newFileMode ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen); + + /* As we don't actually overwrite a selected existing database file, switch off the + * overwrite warning. + * FIXME: QFileDialog::DontConfirmOverwrite does not work on MacOS native dialogs. + * Probably some better UX is needed. + */ dialog.setOption(QFileDialog::DontConfirmOverwrite, true); + dialog.setLabelText(QFileDialog::Accept, QObject::tr("Select")); dialog.setLabelText(QFileDialog::FileType, QObject::tr("File type")); dialog.setNameFilters(filters); @@ -97,6 +104,11 @@ void setValidStateTooltip(QWidget* widget, const QString& tip) INDICATOR(widget)->setVisible(widget->isEnabled(), tip); } +bool isValidStateIndicatorVisible(QWidget* widget) +{ + return EXISTS_INDICATOR(widget) && INDICATOR(widget)->isVisible(); +} + QString convertPageSize(QPageSize::PageSizeId size) { return QPageSize::name(size); diff --git a/SQLiteStudio3/guiSQLiteStudio/uiutils.h b/SQLiteStudio3/guiSQLiteStudio/uiutils.h index 324ed9e..c08cb0b 100644 --- a/SQLiteStudio3/guiSQLiteStudio/uiutils.h +++ b/SQLiteStudio3/guiSQLiteStudio/uiutils.h @@ -8,12 +8,13 @@ class QWidget; class QToolBar; -GUI_API_EXPORT QString getDbPath(const QString& startWith = QString()); +GUI_API_EXPORT QString getDbPath(bool newFileMode, const QString& startWith = QString()); GUI_API_EXPORT void setValidState(QWidget* widget, bool valid, const QString& message = QString()); GUI_API_EXPORT void setValidStateWihtTooltip(QWidget* widget, const QString& tooltip, bool valid, const QString& message = QString()); GUI_API_EXPORT void setValidStateWarning(QWidget* widget, const QString& warning); GUI_API_EXPORT void setValidStateInfo(QWidget* widget, const QString& info); GUI_API_EXPORT void setValidStateTooltip(QWidget* widget, const QString& tip); +GUI_API_EXPORT bool isValidStateIndicatorVisible(QWidget* widget); GUI_API_EXPORT const QStringList& getAllPageSizes(); GUI_API_EXPORT QString convertPageSize(QPageSize::PageSizeId size); GUI_API_EXPORT QPageSize convertPageSize(const QString& size); diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp index df72db2..6624bc5 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp @@ -105,14 +105,14 @@ void CollationsEditor::init() connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateModified())); connect(ui->allDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified())); connect(ui->selectedDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified())); + connect(ui->functionBasedRadio, SIGNAL(clicked()), this, SLOT(updateModified())); + connect(ui->extensionBasedRadio, SIGNAL(clicked()), this, SLOT(updateModified())); connect(ui->langCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified())); connect(dbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified())); connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant))); - // Language plugins - for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>()) - ui->langCombo->addItem(plugin->getLanguage()); + updateLangCombo(); // Syntax highlighting plugins for (SyntaxHighlighterPlugin* plugin : PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>()) @@ -130,9 +130,16 @@ int CollationsEditor::getCurrentCollationRow() const return idxList.first().row(); } +CollationManager::CollationType CollationsEditor::getCurrentType() const +{ + return ui->extensionBasedRadio->isChecked() ? CollationManager::CollationType::EXTENSION_BASED + : CollationManager::CollationType::FUNCTION_BASED; +} + void CollationsEditor::collationDeselected(int row) { model->setName(row, ui->nameEdit->text()); + model->setType(row, getCurrentType()); model->setLang(row, ui->langCombo->currentText()); model->setAllDatabases(row, ui->allDatabasesRadio->isChecked()); model->setCode(row, ui->codeEdit->toPlainText()); @@ -149,6 +156,9 @@ void CollationsEditor::collationSelected(int row) updatesForSelection = true; ui->nameEdit->setText(model->getName(row)); ui->codeEdit->setPlainText(model->getCode(row)); + ui->functionBasedRadio->setChecked(model->getType(row) == CollationManager::CollationType::FUNCTION_BASED); + ui->extensionBasedRadio->setChecked(model->getType(row) == CollationManager::CollationType::EXTENSION_BASED); + updateLangCombo(); ui->langCombo->setCurrentText(model->getLang(row)); // Databases @@ -238,7 +248,7 @@ void CollationsEditor::newCollation() CollationManager::CollationPtr coll = CollationManager::CollationPtr::create(); coll->name = generateUniqueName("collation", model->getCollationNames()); - + coll->type = getCurrentType(); if (ui->langCombo->currentIndex() > -1) coll->lang = ui->langCombo->currentText(); @@ -287,6 +297,8 @@ void CollationsEditor::updateCurrentCollationState() bool nameOk = model->isAllowedName(row, name) && !name.trimmed().isEmpty(); setValidState(ui->nameEdit, nameOk, tr("Enter a non-empty, unique name of the collation.")); + updateLangCombo(); + bool langOk = ui->langCombo->currentIndex() >= 0; ui->codeGroup->setEnabled(langOk); ui->databasesGroup->setEnabled(langOk); @@ -296,7 +308,16 @@ void CollationsEditor::updateCurrentCollationState() setValidState(ui->langCombo, langOk, tr("Pick the implementation language.")); bool codeOk = !ui->codeEdit->toPlainText().trimmed().isEmpty(); - setValidState(ui->codeEdit, codeOk, tr("Enter a non-empty implementation code.")); + if (ui->extensionBasedRadio->isChecked()) + { + ui->codeGroup->setTitle(tr("Registration code")); + setValidState(ui->codeEdit, codeOk, tr("Enter a non-empty registration code.")); + } + else + { + ui->codeGroup->setTitle(tr("Implementation code")); + setValidState(ui->codeEdit, codeOk, tr("Enter a non-empty implementation code.")); + } // Syntax highlighter QString lang = ui->langCombo->currentText(); @@ -343,6 +364,34 @@ void CollationsEditor::collationSelected(const QItemSelection& selected, const Q } } +void CollationsEditor::updateLangCombo() +{ + QComboBox *combo = ui->langCombo; + bool alreadyInternalUpdate = updatesForSelection; + updatesForSelection = true; + if (ui->extensionBasedRadio->isChecked()) + { + if (combo->isEnabled()) + { + combo->setEnabled(false); + combo->clear(); + combo->addItem("SQL"); + combo->setCurrentIndex(0); + } + } + else + { + if (!combo->isEnabled()) + { + combo->clear(); + for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>()) + combo->addItem(plugin->getLanguage()); + combo->setEnabled(true); + } + } + updatesForSelection = alreadyInternalUpdate; +} + void CollationsEditor::updateModified() { if (updatesForSelection) @@ -353,11 +402,12 @@ void CollationsEditor::updateModified() { bool nameDiff = model->getName(row) != ui->nameEdit->text(); bool codeDiff = model->getCode(row) != ui->codeEdit->toPlainText(); + bool typeDiff = model->getType(row) != getCurrentType(); bool langDiff = model->getLang(row) != ui->langCombo->currentText(); bool allDatabasesDiff = model->getAllDatabases(row) != ui->allDatabasesRadio->isChecked(); bool dbDiff = toSet(getCurrentDatabases()) != toSet(model->getDatabases(row)); // QSet to ignore order - currentModified = (nameDiff || codeDiff || langDiff || allDatabasesDiff || dbDiff); + currentModified = (nameDiff || codeDiff || typeDiff || langDiff || allDatabasesDiff || dbDiff); } updateCurrentCollationState(); diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h index 61c9165..7f61a82 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h @@ -3,6 +3,7 @@ #include "mdichild.h" #include "common/extactioncontainer.h" +#include "services/collationmanager.h" #include <QItemSelection> #include <QModelIndex> #include <QWidget> @@ -61,12 +62,14 @@ class GUI_API_EXPORT CollationsEditor : public MdiChild private: void init(); int getCurrentCollationRow() const; + CollationManager::CollationType getCurrentType() const; void collationDeselected(int row); void collationSelected(int row); void clearEdits(); void selectCollation(int row); QStringList getCurrentDatabases() const; void setFont(const QFont& font); + void updateLangCombo(); Ui::CollationsEditor *ui = nullptr; CollationsEditorModel* model = nullptr; diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui index 454c12a..123c2ba 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui @@ -203,13 +203,38 @@ <widget class="QLineEdit" name="nameEdit"/> </item> <item row="0" column="1"> + <widget class="QLabel" name="typeLabel"> + <property name="text"> + <string>Collation type:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <widget class="QRadioButton" name="functionBasedRadio"> + <property name="text"> + <string>Function-based</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="extensionBasedRadio"> + <property name="text"> + <string>Extension-based</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="2"> <widget class="QLabel" name="langLabel"> <property name="text"> <string>Implementation language:</string> </property> </widget> </item> - <item row="1" column="1"> + <item row="1" column="2"> <widget class="QComboBox" name="langCombo"/> </item> </layout> diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp index f04e023..0587d76 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp @@ -1,6 +1,7 @@ #include "collationseditormodel.h" #include "common/unused.h" #include "common/strhash.h" +#include "iconmanager.h" #include "services/pluginmanager.h" #include "plugins/scriptingplugin.h" #include "icon.h" @@ -69,6 +70,16 @@ QString CollationsEditorModel::getName(int row) const GETTER(collationList[row]->data->name, QString()); } +void CollationsEditorModel::setType(int row, CollationManager::CollationType type) +{ + SETTER(collationList[row]->data->type, type); +} + +CollationManager::CollationType CollationsEditorModel::getType(int row) const +{ + GETTER(collationList[row]->data->type, CollationManager::CollationType::FUNCTION_BASED); +} + void CollationsEditorModel::setLang(int row, const QString& lang) { SETTER(collationList[row]->data->lang, lang); @@ -248,13 +259,17 @@ QVariant CollationsEditorModel::data(const QModelIndex& index, int role) const if (role == Qt::DisplayRole) return collationList[index.row()]->data->name; - if (role == Qt::DecorationRole && langToIcon.contains(collationList[index.row()]->data->lang)) + if (role == Qt::DecorationRole) { - QIcon icon = langToIcon[collationList[index.row()]->data->lang]; - if (!collationList[index.row()]->valid) - icon = Icon::merge(icon, Icon::ERROR); - - return icon; + auto coll = collationList[index.row()]->data; + bool isExtension = coll->type == CollationManager::CollationType::EXTENSION_BASED; + if (isExtension || langToIcon.contains(coll->lang)) + { + QIcon icon = isExtension ? ICONS.EXTENSION : langToIcon[coll->lang]; + if (!collationList[index.row()]->valid) + icon = Icon::merge(icon, Icon::ERROR); + return icon; + } } return QVariant(); diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h index 46f7ab5..47b8b08 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h +++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h @@ -21,6 +21,8 @@ class GUI_API_EXPORT CollationsEditorModel : public QAbstractListModel void setModified(int row, bool modified); void setName(int row, const QString& name); QString getName(int row) const; + void setType(int row, CollationManager::CollationType type); + CollationManager::CollationType getType(int row) const; void setLang(int row, const QString& lang); QString getLang(int row) const; void setAllDatabases(int row, bool allDatabases); diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp index 23cb651..aa59bb0 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp @@ -211,7 +211,7 @@ QAction* EditorWindow::getAction(EditorWindow::Action action) return ExtActionContainer::getAction(action); } -QString EditorWindow::getQueryToExecute(bool doSelectCurrentQuery, QueryExecMode querySelectionMode) +QString EditorWindow::getQueryToExecute(QueryExecMode querySelectionMode) { QString sql; if (ui->sqlEdit->textCursor().hasSelection()) @@ -228,9 +228,8 @@ QString EditorWindow::getQueryToExecute(bool doSelectCurrentQuery, QueryExecMode ui->sqlEdit->saveSelection(); selectCurrentQuery(true); sql = ui->sqlEdit->textCursor().selectedText(); + ui->sqlEdit->restoreSelection(); fixTextCursorSelectedText(sql); - if (!doSelectCurrentQuery) - ui->sqlEdit->restoreSelection(); } else { @@ -463,7 +462,7 @@ void EditorWindow::updateShortcutTips() void EditorWindow::execQuery(bool explain, QueryExecMode querySelectionMode) { - QString sql = getQueryToExecute(true, querySelectionMode); + QString sql = getQueryToExecute(querySelectionMode); QHash<QString, QVariant> bindParams; bool proceed = processBindParams(sql, bindParams); if (!proceed) @@ -474,7 +473,7 @@ void EditorWindow::execQuery(bool explain, QueryExecMode querySelectionMode) resultsModel->setQuery(sql); resultsModel->setParams(bindParams); resultsModel->setQueryCountLimitForSmartMode(queryLimitForSmartExecution); - ui->dataView->refreshData(); + ui->dataView->refreshData(false); updateState(); if (resultsDisplayMode == ResultsDisplayMode::SEPARATE_TAB) @@ -741,7 +740,7 @@ void EditorWindow::createViewFromQuery() return; } - QString sql = getQueryToExecute(true); + QString sql = getQueryToExecute(); DbObjectDialogs dialogs(getCurrentDb()); dialogs.addView(sql); } diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h index 977784e..0ca2cdf 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h +++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h @@ -100,7 +100,7 @@ class GUI_API_EXPORT EditorWindow : public MdiChild QSize sizeHint() const; QAction* getAction(Action action); - QString getQueryToExecute(bool doSelectCurrentQuery = false, QueryExecMode querySelectionMode = DEFAULT); + QString getQueryToExecute(QueryExecMode querySelectionMode = DEFAULT); bool setCurrentDb(Db* db); void setContents(const QString& sql); QString getContents() const; diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp index 4d964c8..e349525 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp @@ -170,9 +170,12 @@ int FunctionsEditor::getCurrentFunctionRow() const void FunctionsEditor::functionDeselected(int row) { model->setName(row, ui->nameEdit->text()); + model->setUndefinedArgs(row, ui->undefArgsCheck->isChecked()); + if (!ui->undefArgsCheck->isChecked()) + model->setArguments(row, getCurrentArgList()); + model->setLang(row, ui->langCombo->currentText()); model->setType(row, getCurrentFunctionType()); - model->setUndefinedArgs(row, ui->undefArgsCheck->isChecked()); model->setAllDatabases(row, ui->allDatabasesRadio->isChecked()); model->setCode(row, ui->mainCodeEdit->toPlainText()); model->setDeterministic(row, ui->deterministicCheck->isChecked()); @@ -189,9 +192,6 @@ void FunctionsEditor::functionDeselected(int row) model->setFinalCode(row, QString()); } - if (!ui->undefArgsCheck->isChecked()) - model->setArguments(row, getCurrentArgList()); - if (ui->selDatabasesRadio->isChecked()) model->setDatabases(row, getCurrentDatabases()); @@ -416,8 +416,10 @@ void FunctionsEditor::updateCurrentFunctionState() } QString name = ui->nameEdit->text(); - bool nameOk = model->isAllowedName(row, name) && !name.trimmed().isEmpty(); - setValidState(ui->nameEdit, nameOk, tr("Enter a non-empty, unique name of the function.")); + QStringList argList = getCurrentArgList(); + bool undefArgs = ui->undefArgsCheck->isChecked(); + bool nameOk = model->isAllowedName(row, name, argList, undefArgs) && !name.trimmed().isEmpty(); + setValidState(ui->nameEdit, nameOk, tr("Enter a unique, non-empty function name. Duplicate names are allowed if the number of input parameters differs.")); bool langOk = ui->langCombo->currentIndex() >= 0; ui->initCodeGroup->setEnabled(langOk); @@ -488,8 +490,8 @@ void FunctionsEditor::updateCurrentFunctionState() currentHighlighterLang = lang; } - updateArgsState(); - model->setValid(row, langOk && codeOk && finalCodeOk && nameOk); + bool argsOk = updateArgsState(); + model->setValid(row, langOk && codeOk && finalCodeOk && nameOk && argsOk); updateState(); } @@ -576,7 +578,7 @@ void FunctionsEditor::moveFunctionArgDown() ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); } -void FunctionsEditor::updateArgsState() +bool FunctionsEditor::updateArgsState() { bool argsEnabled = !ui->undefArgsCheck->isChecked(); QModelIndexList indexes = ui->argsList->selectionModel()->selectedIndexes(); @@ -596,6 +598,32 @@ void FunctionsEditor::updateArgsState() actionMap[ARG_MOVE_UP]->setEnabled(argsEnabled && canMoveUp); actionMap[ARG_MOVE_DOWN]->setEnabled(argsEnabled && canMoveDown); ui->argsList->setEnabled(argsEnabled); + + if (argsEnabled) + { + bool argsOk = true; + QSet<QString> usedNames; + for (int rowIdx = 0; rowIdx < ui->argsList->model()->rowCount(); rowIdx++) + { + QListWidgetItem* item = ui->argsList->item(rowIdx); + QString argName = item->text().toLower(); + if (argName.isEmpty()) + { + argsOk = false; + break; + } + if (usedNames.contains(argName)) + { + argsOk = false; + break; + } + usedNames << argName; + } + setValidState(ui->argsList, argsOk, tr("Function argument cannot be empty and it cannot have duplicated name.")); + return argsOk; + } + else + return true; } void FunctionsEditor::applyFilter(const QString& value) diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h index d3fcf12..d513234 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h @@ -105,7 +105,7 @@ class GUI_API_EXPORT FunctionsEditor : public MdiChild void delFunctionArg(); void moveFunctionArgUp(); void moveFunctionArgDown(); - void updateArgsState(); + bool updateArgsState(); void applyFilter(const QString& value); void help(); void changeFont(const QVariant& font); diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp index 46ae8d9..13ec34f 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp @@ -266,16 +266,17 @@ QStringList FunctionsEditorModel::getFunctionNames() const void FunctionsEditorModel::validateNames() { - StrHash<QList<int>> counter; + QHash<UniqueFunctionName, QList<int>> counter; int row = 0; for (Function*& func : functionList) { func->valid &= true; - counter[func->data.name] << row++; + UniqueFunctionName uniqueName = func->toUniqueName(); + counter[uniqueName] << row++; } - QHashIterator<QString,QList<int>> cntIt = counter.iterator(); + QHashIterator<UniqueFunctionName, QList<int>> cntIt(counter); while (cntIt.hasNext()) { cntIt.next(); @@ -294,11 +295,18 @@ void FunctionsEditorModel::validateNames() } } -bool FunctionsEditorModel::isAllowedName(int rowToSkip, const QString& nameToValidate) +bool FunctionsEditorModel::isAllowedName(int rowToSkip, const QString& nameToValidate, const QStringList& argList, bool undefinedArgs) { - QStringList names = getFunctionNames(); + QList<UniqueFunctionName> names = getUniqueFunctionNames(); names.removeAt(rowToSkip); - return !names.contains(nameToValidate, Qt::CaseInsensitive); + + UniqueFunctionName validatedName; + validatedName.name = nameToValidate.toLower(); + validatedName.undefArg = undefinedArgs; + if (!undefinedArgs) + validatedName.arguments = argList; + + return !names.contains(validatedName); } int FunctionsEditorModel::rowCount(const QModelIndex& parent) const @@ -347,6 +355,15 @@ void FunctionsEditorModel::emitDataChanged(int row) emit dataChanged(idx, idx); } +QList<FunctionsEditorModel::UniqueFunctionName> FunctionsEditorModel::getUniqueFunctionNames() const +{ + QList<UniqueFunctionName> names; + for (Function* func : functionList) + names << func->toUniqueName(); + + return names; +} + FunctionsEditorModel::Function::Function() { } @@ -356,3 +373,29 @@ FunctionsEditorModel::Function::Function(FunctionManager::ScriptFunction* other) data = FunctionManager::ScriptFunction(*other); originalName = data.name; } + +FunctionsEditorModel::UniqueFunctionName FunctionsEditorModel::Function::toUniqueName() const +{ + UniqueFunctionName uniqName; + uniqName.name = data.name.toLower(); + uniqName.undefArg = data.undefinedArgs; + if (!data.undefinedArgs) + uniqName.arguments = data.arguments; + + return uniqName; +} + +int FunctionsEditorModel::UniqueFunctionName::argCount() const +{ + return undefArg ? -1 : arguments.size(); +} + +bool FunctionsEditorModel::UniqueFunctionName::operator==(const UniqueFunctionName &other) const +{ + return name == other.name && argCount() == other.argCount(); +} + +int qHash(FunctionsEditorModel::UniqueFunctionName fnName) +{ + return qHash(fnName.name) ^ fnName.argCount(); +} diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h index 7caf06c..2f90bcc 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h +++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h @@ -60,17 +60,28 @@ class GUI_API_EXPORT FunctionsEditorModel : public QAbstractListModel QList<FunctionManager::ScriptFunction*> generateFunctions() const; QStringList getFunctionNames() const; void validateNames(); - bool isAllowedName(int rowToSkip, const QString& nameToValidate); + bool isAllowedName(int rowToSkip, const QString& nameToValidate, const QStringList &argList, bool undefinedArgs); bool isValidRowIndex(int row) const; int rowCount(const QModelIndex& parent = QModelIndex()) const; QVariant data(const QModelIndex& index, int role) const; private: + struct UniqueFunctionName + { + QString name; + QStringList arguments; + bool undefArg; + + int argCount() const; + bool operator==(const UniqueFunctionName& other) const; + }; + struct Function { Function(); Function(FunctionManager::ScriptFunction* other); + UniqueFunctionName toUniqueName() const; FunctionManager::ScriptFunction data; bool modified = false; @@ -80,6 +91,9 @@ class GUI_API_EXPORT FunctionsEditorModel : public QAbstractListModel void init(); void emitDataChanged(int row); + QList<UniqueFunctionName> getUniqueFunctionNames() const; + + friend int qHash(FunctionsEditorModel::UniqueFunctionName fnName); QList<Function*> functionList; @@ -96,4 +110,6 @@ class GUI_API_EXPORT FunctionsEditorModel : public QAbstractListModel bool listModified = false; }; +int qHash(FunctionsEditorModel::UniqueFunctionName fnName); + #endif // FUNCTIONSEDITORMODEL_H diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.cpp index f8dd621..66e6682 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.cpp @@ -123,6 +123,7 @@ void SqliteExtensionEditor::init() connect(dbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified())); probingDb = DBLIST->createInMemDb(true); + if (!probingDb->openQuiet()) qWarning() << "Could not open in-memory dtabase for Extension manager window. Probing files will be impossible."; @@ -308,6 +309,7 @@ void SqliteExtensionEditor::rollback() if (model->isValidRowIndex(selectedBefore)) selectExtension(selectedBefore); + initStateForAll(); updateState(); } @@ -337,7 +339,7 @@ void SqliteExtensionEditor::deleteExtension() void SqliteExtensionEditor::updateState() { bool modified = model->isModified() || currentModified; - bool valid = model->isValid() && validateCurrentExtension(); + bool valid = model->isValid() && (getCurrentExtensionRow() == -1 || validateCurrentExtension()); actionMap[COMMIT]->setEnabled(modified && valid); actionMap[ROLLBACK]->setEnabled(modified); diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp index 86f48bf..b42848a 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp @@ -865,6 +865,9 @@ void TableWindow::changesSuccessfullyCommitted() } } } + + if (ui->tabWidget->currentIndex() == getDataTabIdx()) + ui->dataView->refreshData(); } void TableWindow::changesFailedToCommit(int errorCode, const QString& errorText) @@ -1047,7 +1050,7 @@ bool TableWindow::validate(bool skipWarning) QStringList nonStrictColumns; for (SqliteCreateTable::Column* column : createTable->columns) { - if (DataType::isStrict(column->type->name)) + if (DataType::isStrict(column->type ? column->type->name : QString())) continue; nonStrictColumns << column->name; @@ -1310,7 +1313,7 @@ void TableWindow::importTable() ImportDialog dialog(this); dialog.setDbAndTable(db, table); if (dialog.exec() == QDialog::Accepted && dataLoaded) - ui->dataView->refreshData(); + ui->dataView->refreshData(false); } void TableWindow::populateTable() @@ -1318,7 +1321,7 @@ void TableWindow::populateTable() PopulateDialog dialog(this); dialog.setDbAndTable(db, table); if (dialog.exec() == QDialog::Accepted && dataLoaded) - ui->dataView->refreshData(); + ui->dataView->refreshData(false); } void TableWindow::createSimilarTable() @@ -1342,15 +1345,16 @@ void TableWindow::tabChanged(int newTab) "Do you want to commit the structure, or do you want to go back to the structure tab?"), tr("Go back to structure tab"), tr("Commit modifications and browse data.")); - ui->tabWidget->setCurrentIndex(0); if (res == 1) commitStructure(true); + else + focusStructureTab(); return; } if (!dataLoaded) - ui->dataView->refreshData(); + ui->dataView->refreshData(false); } } @@ -1393,8 +1397,11 @@ void TableWindow::strictChanged() { for (SqliteCreateTable::Column* column : createTable->columns) { - column->type->precision = QVariant(); - column->type->scale = QVariant(); + if (column->type) + { + column->type->precision = QVariant(); + column->type->scale = QVariant(); + } } } @@ -1644,6 +1651,16 @@ void TableWindow::delColumn(const QString& columnName) delColumn(colIdx); } +void TableWindow::focusStructureTab() +{ + ui->tabWidget->setCurrentIndex(getStructureTabIdx()); +} + +void TableWindow::focusDataTab() +{ + ui->tabWidget->setCurrentIndex(getDataTabIdx()); +} + void TableWindow::updateTabsOrder() { tabsMoving = true; diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h index e71162d..dda317a 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h +++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h @@ -255,6 +255,8 @@ class GUI_API_EXPORT TableWindow : public MdiChild void addColumn(); void editColumn(const QString& columnName); void delColumn(const QString& columnName); + void focusStructureTab(); + void focusDataTab(); signals: void modifyStatusChanged(); diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp index 44025e5..73575c4 100644 --- a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp @@ -299,7 +299,7 @@ void ViewWindow::initView() if (existingView) { dataModel->setDb(db); - dataModel->setQuery(originalCreateView->select->detokenize()); + dataModel->setQuery(originalCreateView->equivalentSelectTokens().detokenize()); dataModel->setDatabaseAndView(database, view); ui->dbCombo->setDisabled(true); } @@ -619,7 +619,7 @@ void ViewWindow::tabChanged(int tabIdx) "Do you want to commit the structure, or do you want to go back to the structure tab?"), tr("Go back to structure tab"), tr("Commit modifications and browse data.")); - ui->tabWidget->setCurrentIndex(0); + ui->tabWidget->setCurrentIndex(CFG_UI.General.DataTabAsFirstInViews.get() ? 1 : 0); if (res == 1) commitView(true); @@ -627,7 +627,7 @@ void ViewWindow::tabChanged(int tabIdx) } if (!dataLoaded) - ui->dataView->refreshData(); + ui->dataView->refreshData(false); return; } |
