aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/guiSQLiteStudio/windows
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/windows')
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp155
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h65
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui55
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp389
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h89
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui210
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp287
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h77
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp395
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h70
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp150
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h54
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui126
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp652
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h155
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui137
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp632
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h109
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui346
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp348
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h98
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp482
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h72
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp604
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h89
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp1508
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h241
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui307
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp760
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h146
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui137
31 files changed, 8945 insertions, 0 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp
new file mode 100644
index 0000000..c92f6f4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp
@@ -0,0 +1,155 @@
+#include "bugreporthistorywindow.h"
+#include "ui_bugreporthistorywindow.h"
+#include "common/unused.h"
+#include "services/config.h"
+#include <QDebug>
+#include <QLabel>
+
+CFG_KEYS_DEFINE(BugReportHistoryWindow)
+
+BugReportHistoryWindow::BugReportHistoryWindow(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::BugReportHistoryWindow)
+{
+ init();
+}
+
+BugReportHistoryWindow::~BugReportHistoryWindow()
+{
+ delete ui;
+}
+
+bool BugReportHistoryWindow::restoreSessionNextTime()
+{
+ return false;
+}
+
+QVariant BugReportHistoryWindow::saveSession()
+{
+ return QVariant();
+}
+
+bool BugReportHistoryWindow::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+ return false;
+}
+
+Icon* BugReportHistoryWindow::getIconNameForMdiWindow()
+{
+ return ICONS.BUG_LIST;
+}
+
+QString BugReportHistoryWindow::getTitleForMdiWindow()
+{
+ return tr("Reports history");
+}
+
+void BugReportHistoryWindow::createActions()
+{
+ createAction(CLEAR_HISTORY, ICONS.CLEAR_HISTORY, tr("Clear reports history"), this, SLOT(clearHistory()), ui->toolBar);
+ createAction(DELETE_SELECTED, ICONS.DELETE_ROW, tr("Delete selected entry"), this, SLOT(deleteSelected()), ui->toolBar);
+}
+
+void BugReportHistoryWindow::setupDefShortcuts()
+{
+ setShortcutContext({
+ DELETE_SELECTED
+ },
+ Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(BugReportHistoryWindow, Action);
+}
+
+QToolBar* BugReportHistoryWindow::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolBar;
+}
+
+void BugReportHistoryWindow::init()
+{
+ ui->setupUi(this);
+ initActions();
+
+ reload();
+ connect(ui->reportsList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState()));
+ connect(CFG, SIGNAL(reportsHistoryRefreshNeeded()), this, SLOT(reload()));
+
+ updateState();
+}
+
+void BugReportHistoryWindow::updateState()
+{
+ actionMap[DELETE_SELECTED]->setEnabled(ui->reportsList->selectedItems().size() > 0);
+}
+
+void BugReportHistoryWindow::reload()
+{
+ static_qstring(urlTpl, "<a href=\"%1\">%2</a>");
+ QString invalidUrlTpl = tr("Invalid response from server.");
+
+ QList<Config::ReportHistoryEntryPtr> entries = CFG->getReportHistory();
+ ui->reportsList->clear();
+ ui->reportsList->setRowCount(entries.size());
+
+ QTableWidgetItem* item = nullptr;
+ QLabel* urlLabel = nullptr;
+ int row = 0;
+ for (const Config::ReportHistoryEntryPtr& entry : entries)
+ {
+ item = new QTableWidgetItem((entry->isFeatureRequest ? ICONS.FEATURE_REQUEST : ICONS.BUG), entry->title);
+ item->setData(ENTRY_ID, entry->id);
+ ui->reportsList->setItem(row, 0, item);
+
+ item = new QTableWidgetItem(QDateTime::fromTime_t(entry->timestamp).toString("yyyy-MM-dd HH:mm:ss"));
+ ui->reportsList->setItem(row, 1, item);
+
+ if (entry->url.startsWith("http://"))
+ urlLabel = new QLabel(urlTpl.arg(entry->url, entry->url));
+ else
+ urlLabel = new QLabel(invalidUrlTpl);
+
+ urlLabel->setOpenExternalLinks(true);
+ ui->reportsList->setCellWidget(row, 2, urlLabel);
+
+ row++;
+ }
+
+ ui->reportsList->setHorizontalHeaderLabels({tr("Title"), tr("Reported at"), tr("URL")});
+ ui->reportsList->resizeColumnsToContents();
+}
+
+void BugReportHistoryWindow::clearHistory()
+{
+ CFG->clearReportHistory();
+}
+
+void BugReportHistoryWindow::deleteSelected()
+{
+ QList<QTableWidgetItem*> items = ui->reportsList->selectedItems();
+ if (items.size() == 0)
+ {
+ qDebug() << "Called BugReportHistoryWindow::deleteSelected(), but there's no row selected.";
+ return;
+ }
+
+ int id = items.first()->data(ENTRY_ID).toInt();
+ if (id == 0)
+ {
+ qDebug() << "Called BugReportHistoryWindow::deleteSelected(), but there's no ID in selected row.";
+ return;
+ }
+
+ CFG->deleteReport(id);
+}
+
+bool BugReportHistoryWindow::isUncommited() const
+{
+ return false;
+}
+
+QString BugReportHistoryWindow::getQuitUncommitedConfirmMessage() const
+{
+ return QString();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h
new file mode 100644
index 0000000..e582a48
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h
@@ -0,0 +1,65 @@
+#ifndef BUGREPORTHISTORYWINDOW_H
+#define BUGREPORTHISTORYWINDOW_H
+
+#include "mdichild.h"
+#include <QWidget>
+
+namespace Ui {
+ class BugReportHistoryWindow;
+}
+
+CFG_KEY_LIST(BugReportHistoryWindow, QObject::tr("Reports history window"),
+ CFG_KEY_ENTRY(DELETE_SELECTED, Qt::Key_Delete, QObject::tr("Delete selected entry"))
+)
+
+class GUI_API_EXPORT BugReportHistoryWindow : public MdiChild
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ DELETE_SELECTED,
+ CLEAR_HISTORY
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR
+ };
+
+ explicit BugReportHistoryWindow(QWidget *parent = 0);
+ ~BugReportHistoryWindow();
+
+ bool restoreSessionNextTime();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ QVariant saveSession();
+ bool restoreSession(const QVariant &sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ enum UserRole
+ {
+ ENTRY_ID = Qt::UserRole + 1
+ };
+
+ void init();
+
+ Ui::BugReportHistoryWindow *ui = nullptr;
+
+ private slots:
+ void updateState();
+ void reload();
+ void clearHistory();
+ void deleteSelected();
+};
+
+#endif // BUGREPORTHISTORYWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui
new file mode 100644
index 0000000..3218822
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BugReportHistoryWindow</class>
+ <widget class="QWidget" name="BugReportHistoryWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QToolBar" name="toolBar"/>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="reportsList">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Title</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Reported at</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>URL</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp
new file mode 100644
index 0000000..1d0594d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp
@@ -0,0 +1,389 @@
+#include "collationseditor.h"
+#include "ui_collationseditor.h"
+#include "common/unused.h"
+#include "selectabledbmodel.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "collationseditormodel.h"
+#include "common/utils.h"
+#include "uiutils.h"
+#include "services/pluginmanager.h"
+#include "syntaxhighlighterplugin.h"
+#include "plugins/scriptingplugin.h"
+#include "uiconfig.h"
+#include <QDesktopServices>
+#include <QSyntaxHighlighter>
+
+CollationsEditor::CollationsEditor(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::CollationsEditor)
+{
+ init();
+}
+
+CollationsEditor::~CollationsEditor()
+{
+ delete ui;
+}
+
+bool CollationsEditor::restoreSessionNextTime()
+{
+ return false;
+}
+
+QVariant CollationsEditor::saveSession()
+{
+ return QVariant();
+}
+
+bool CollationsEditor::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+ return true;
+}
+
+Icon* CollationsEditor::getIconNameForMdiWindow()
+{
+ return ICONS.CONSTRAINT_COLLATION;
+}
+
+QString CollationsEditor::getTitleForMdiWindow()
+{
+ return tr("Collations editor");
+}
+
+void CollationsEditor::createActions()
+{
+ createAction(COMMIT, ICONS.COMMIT, tr("Commit all collation changes"), this, SLOT(commit()), ui->toolbar);
+ createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback all collation changes"), this, SLOT(rollback()), ui->toolbar);
+ ui->toolbar->addSeparator();
+ createAction(ADD, ICONS.NEW_COLLATION, tr("Create new collation"), this, SLOT(newCollation()), ui->toolbar);
+ createAction(DELETE, ICONS.DELETE_COLLATION, tr("Delete selected collation"), this, SLOT(deleteCollation()), ui->toolbar);
+ ui->toolbar->addSeparator();
+ createAction(HELP, ICONS.HELP, tr("Editing collations manual"), this, SLOT(help()), ui->toolbar);
+}
+
+void CollationsEditor::setupDefShortcuts()
+{
+
+}
+
+QToolBar* CollationsEditor::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolbar;
+}
+
+void CollationsEditor::init()
+{
+ ui->setupUi(this);
+ initActions();
+
+ setFont(CFG_UI.Fonts.SqlEditor.get());
+
+ model = new CollationsEditorModel(this);
+ collationFilterModel = new QSortFilterProxyModel(this);
+ collationFilterModel->setSourceModel(model);
+ ui->collationList->setModel(collationFilterModel);
+
+ dbListModel = new SelectableDbModel(this);
+ dbListModel->setDisabledVersion(2);
+ dbListModel->setSourceModel(DBTREE->getModel());
+ ui->databaseList->setModel(dbListModel);
+ ui->databaseList->expandAll();
+
+ model->setData(COLLATIONS->getAllCollations());
+
+ connect(ui->collationList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(collationSelected(QItemSelection,QItemSelection)));
+ connect(ui->collationList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState()));
+ connect(ui->codeEdit, SIGNAL(textChanged()), this, SLOT(updateModified()));
+ 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->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
+ foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ ui->langCombo->addItem(plugin->getLanguage());
+
+ // Syntax highlighting plugins
+ foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
+ highlighterPlugins[plugin->getLanguageName()] = plugin;
+
+ updateState();
+}
+
+int CollationsEditor::getCurrentCollationRow() const
+{
+ QModelIndexList idxList = ui->collationList->selectionModel()->selectedIndexes();
+ if (idxList.size() == 0)
+ return -1;
+
+ return idxList.first().row();
+}
+
+void CollationsEditor::collationDeselected(int row)
+{
+ model->setName(row, ui->nameEdit->text());
+ model->setLang(row, ui->langCombo->currentText());
+ model->setAllDatabases(row, ui->allDatabasesRadio->isChecked());
+ model->setCode(row, ui->codeEdit->toPlainText());
+ model->setModified(row, currentModified);
+
+ if (ui->selectedDatabasesRadio->isChecked())
+ model->setDatabases(row, getCurrentDatabases());
+
+ model->validateNames();
+}
+
+void CollationsEditor::collationSelected(int row)
+{
+ updatesForSelection = true;
+ ui->nameEdit->setText(model->getName(row));
+ ui->codeEdit->setPlainText(model->getCode(row));
+ ui->langCombo->setCurrentText(model->getLang(row));
+
+ // Databases
+ dbListModel->setDatabases(model->getDatabases(row));
+ ui->databaseList->expandAll();
+
+ if (model->getAllDatabases(row))
+ ui->allDatabasesRadio->setChecked(true);
+ else
+ ui->selectedDatabasesRadio->setChecked(true);
+
+ updatesForSelection = false;
+ currentModified = false;
+
+ updateCurrentCollationState();
+}
+
+void CollationsEditor::clearEdits()
+{
+ ui->nameEdit->setText(QString::null);
+ ui->codeEdit->setPlainText(QString::null);
+ ui->langCombo->setCurrentText(QString::null);
+ ui->allDatabasesRadio->setChecked(true);
+ ui->langCombo->setCurrentIndex(-1);
+}
+
+void CollationsEditor::selectCollation(int row)
+{
+ if (!model->isValidRowIndex(row))
+ return;
+
+ ui->collationList->selectionModel()->setCurrentIndex(model->index(row), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+QStringList CollationsEditor::getCurrentDatabases() const
+{
+ return dbListModel->getDatabases();
+}
+
+void CollationsEditor::setFont(const QFont& font)
+{
+ ui->codeEdit->setFont(font);
+}
+
+void CollationsEditor::help()
+{
+ static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_collations");
+ QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
+}
+
+void CollationsEditor::commit()
+{
+ int row = getCurrentCollationRow();
+ if (model->isValidRowIndex(row))
+ collationDeselected(row);
+
+ QList<CollationManager::CollationPtr> collations = model->getCollations();
+
+ COLLATIONS->setCollations(collations);
+ model->clearModified();
+ currentModified = false;
+
+ if (model->isValidRowIndex(row))
+ selectCollation(row);
+
+ updateState();
+}
+
+void CollationsEditor::rollback()
+{
+ int selectedBefore = getCurrentCollationRow();
+
+ model->setData(COLLATIONS->getAllCollations());
+ currentModified = false;
+ clearEdits();
+
+ if (model->isValidRowIndex(selectedBefore))
+ selectCollation(selectedBefore);
+
+ updateState();
+}
+
+void CollationsEditor::newCollation()
+{
+ if (ui->langCombo->currentIndex() == -1 && ui->langCombo->count() > 0)
+ ui->langCombo->setCurrentIndex(0);
+
+ CollationManager::CollationPtr coll = CollationManager::CollationPtr::create();
+ coll->name = generateUniqueName("collation", model->getCollationNames());
+
+ if (ui->langCombo->currentIndex() > -1)
+ coll->lang = ui->langCombo->currentText();
+
+ model->addCollation(coll);
+
+ selectCollation(model->rowCount() - 1);
+}
+
+void CollationsEditor::deleteCollation()
+{
+ int row = getCurrentCollationRow();
+ model->deleteCollation(row);
+ clearEdits();
+
+ row = getCurrentCollationRow();
+ if (model->isValidRowIndex(row))
+ collationSelected(row);
+
+ updateState();
+}
+
+void CollationsEditor::updateState()
+{
+ bool modified = model->isModified() || currentModified;
+ bool valid = model->isValid();
+
+ actionMap[COMMIT]->setEnabled(modified && valid);
+ actionMap[ROLLBACK]->setEnabled(modified);
+ actionMap[DELETE]->setEnabled(ui->collationList->selectionModel()->selectedIndexes().size() > 0);
+}
+
+void CollationsEditor::updateCurrentCollationState()
+{
+ int row = getCurrentCollationRow();
+ bool validRow = model->isValidRowIndex(row);
+ ui->rightWidget->setEnabled(validRow);
+ if (!validRow)
+ {
+ setValidState(ui->langCombo, true);
+ setValidState(ui->nameEdit, true);
+ setValidState(ui->codeEdit, true);
+ return;
+ }
+
+ 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 collation."));
+
+ bool langOk = ui->langCombo->currentIndex() >= 0;
+ ui->codeGroup->setEnabled(langOk);
+ ui->databasesGroup->setEnabled(langOk);
+ ui->nameEdit->setEnabled(langOk);
+ ui->nameLabel->setEnabled(langOk);
+ ui->databaseList->setEnabled(ui->selectedDatabasesRadio->isChecked());
+ 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."));
+
+ // Syntax highlighter
+ QString lang = ui->langCombo->currentText();
+ if (lang != currentHighlighterLang)
+ {
+ QSyntaxHighlighter* highlighter = nullptr;
+ if (currentHighlighter)
+ {
+ // A pointers swap with local var - this is necessary, cause deleting highlighter
+ // triggers textChanged on QPlainTextEdit, which then calls this method,
+ // so it becomes an infinite recursion with deleting the same pointer.
+ // We set the pointer to null first, then delete it. That way it's safe.
+ highlighter = currentHighlighter;
+ currentHighlighter = nullptr;
+ delete highlighter;
+ }
+
+ if (langOk && highlighterPlugins.contains(lang))
+ {
+ currentHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->codeEdit);
+ }
+
+ currentHighlighterLang = lang;
+ }
+ model->setValid(row, langOk && codeOk && nameOk);
+ updateState();
+}
+
+void CollationsEditor::collationSelected(const QItemSelection& selected, const QItemSelection& deselected)
+{
+ int deselCnt = deselected.indexes().size();
+ int selCnt = selected.indexes().size();
+
+ if (deselCnt > 0)
+ collationDeselected(deselected.indexes().first().row());
+
+ if (selCnt > 0)
+ collationSelected(selected.indexes().first().row());
+
+ if (deselCnt > 0 && selCnt == 0)
+ {
+ currentModified = false;
+ clearEdits();
+ }
+}
+
+void CollationsEditor::updateModified()
+{
+ if (updatesForSelection)
+ return;
+
+ int row = getCurrentCollationRow();
+ if (model->isValidRowIndex(row))
+ {
+ bool nameDiff = model->getName(row) != ui->nameEdit->text();
+ bool codeDiff = model->getCode(row) != ui->codeEdit->toPlainText();
+ bool langDiff = model->getLang(row) != ui->langCombo->currentText();
+ bool allDatabasesDiff = model->getAllDatabases(row) != ui->allDatabasesRadio->isChecked();
+ bool dbDiff = getCurrentDatabases().toSet() != model->getDatabases(row).toSet(); // QSet to ignore order
+
+ currentModified = (nameDiff || codeDiff || langDiff || allDatabasesDiff || dbDiff);
+ }
+
+ updateCurrentCollationState();
+}
+
+void CollationsEditor::applyFilter(const QString& value)
+{
+ //
+ // See FunctionsEditor::applyFilter() for details why we remember current selection and restore it at the end.
+ //
+
+ int row = getCurrentCollationRow();
+ ui->collationList->selectionModel()->clearSelection();
+
+ collationFilterModel->setFilterFixedString(value);
+
+ selectCollation(row);
+}
+
+void CollationsEditor::changeFont(const QVariant& font)
+{
+ setFont(font.value<QFont>());
+}
+
+
+bool CollationsEditor::isUncommited() const
+{
+ return model->isModified();
+}
+
+QString CollationsEditor::getQuitUncommitedConfirmMessage() const
+{
+ return tr("Collations editor window has uncommited modifications.");
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h
new file mode 100644
index 0000000..62cb281
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h
@@ -0,0 +1,89 @@
+#ifndef COLLATIONSEDITOR_H
+#define COLLATIONSEDITOR_H
+
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include <QItemSelection>
+#include <QModelIndex>
+#include <QWidget>
+
+namespace Ui {
+ class CollationsEditor;
+}
+
+class SyntaxHighlighterPlugin;
+class SelectableDbModel;
+class CollationsEditorModel;
+class QSortFilterProxyModel;
+class QSyntaxHighlighter;
+
+class GUI_API_EXPORT CollationsEditor : public MdiChild
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ COMMIT,
+ ROLLBACK,
+ ADD,
+ DELETE,
+ HELP
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR
+ };
+
+ explicit CollationsEditor(QWidget *parent = 0);
+ ~CollationsEditor();
+
+ bool restoreSessionNextTime();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ QVariant saveSession();
+ bool restoreSession(const QVariant &sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ int getCurrentCollationRow() const;
+ void collationDeselected(int row);
+ void collationSelected(int row);
+ void clearEdits();
+ void selectCollation(int row);
+ QStringList getCurrentDatabases() const;
+ void setFont(const QFont& font);
+
+ Ui::CollationsEditor *ui = nullptr;
+ CollationsEditorModel* model = nullptr;
+ QSortFilterProxyModel* collationFilterModel = nullptr;
+ SelectableDbModel* dbListModel = nullptr;
+ QHash<QString,SyntaxHighlighterPlugin*> highlighterPlugins;
+ QSyntaxHighlighter* currentHighlighter = nullptr;
+ QString currentHighlighterLang;
+ bool currentModified = false;
+ bool updatesForSelection = false;
+
+ private slots:
+ void help();
+ void commit();
+ void rollback();
+ void newCollation();
+ void deleteCollation();
+ void updateState();
+ void updateCurrentCollationState();
+ void collationSelected(const QItemSelection& selected, const QItemSelection& deselected);
+ void updateModified();
+ void applyFilter(const QString& value);
+ void changeFont(const QVariant& font);
+};
+
+#endif // COLLATIONSEDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui
new file mode 100644
index 0000000..635ae59
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CollationsEditor</class>
+ <widget class="QWidget" name="CollationsEditor">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>765</width>
+ <height>529</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="toolbar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="mainWidgt" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="leftWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="lineEdit">
+ <property name="placeholderText">
+ <string>Filter collations</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="collationList"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="rightWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Collation name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="langCombo"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="langLabel">
+ <property name="text">
+ <string>Implementation language:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QWidget" name="rightBottomWidget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QGroupBox" name="databasesGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Databases</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QRadioButton" name="allDatabasesRadio">
+ <property name="text">
+ <string>Register in all databases</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="selectedDatabasesRadio">
+ <property name="text">
+ <string>Register in following databases:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="databaseList">
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QGroupBox" name="codeGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Implementation code:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QPlainTextEdit" name="codeEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp
new file mode 100644
index 0000000..05ca4e1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp
@@ -0,0 +1,287 @@
+#include "collationseditormodel.h"
+#include "common/unused.h"
+#include "common/strhash.h"
+#include "services/pluginmanager.h"
+#include "plugins/scriptingplugin.h"
+#include "icon.h"
+
+#define SETTER(X, Y) \
+ if (!isValidRowIndex(row) || X == Y) \
+ return; \
+ \
+ X = Y; \
+ emitDataChanged(row);
+
+#define GETTER(X, Y) \
+ if (!isValidRowIndex(row)) \
+ return Y; \
+ \
+ return X;
+
+CollationsEditorModel::CollationsEditorModel(QObject *parent) :
+ QAbstractListModel(parent)
+{
+ init();
+}
+
+void CollationsEditorModel::clearModified()
+{
+ beginResetModel();
+ foreach (Collation* coll, collationList)
+ coll->modified = false;
+
+ listModified = false;
+ originalCollationList = collationList;
+
+ endResetModel();
+}
+
+bool CollationsEditorModel::isModified() const
+{
+ if (collationList != originalCollationList)
+ return true;
+
+ foreach (Collation* coll, collationList)
+ {
+ if (coll->modified)
+ return true;
+ }
+ return false;
+}
+
+bool CollationsEditorModel::isModified(int row) const
+{
+ GETTER(collationList[row]->modified, false);
+}
+
+void CollationsEditorModel::setModified(int row, bool modified)
+{
+ SETTER(collationList[row]->modified, modified);
+}
+
+void CollationsEditorModel::setName(int row, const QString& name)
+{
+ SETTER(collationList[row]->data->name, name);
+}
+
+QString CollationsEditorModel::getName(int row) const
+{
+ GETTER(collationList[row]->data->name, QString());
+}
+
+void CollationsEditorModel::setLang(int row, const QString& lang)
+{
+ SETTER(collationList[row]->data->lang, lang);
+}
+
+QString CollationsEditorModel::getLang(int row) const
+{
+ GETTER(collationList[row]->data->lang, QString());
+}
+
+void CollationsEditorModel::setAllDatabases(int row, bool allDatabases)
+{
+ SETTER(collationList[row]->data->allDatabases, allDatabases);
+}
+
+bool CollationsEditorModel::getAllDatabases(int row) const
+{
+ GETTER(collationList[row]->data->allDatabases, true);
+}
+
+void CollationsEditorModel::setCode(int row, const QString& code)
+{
+ SETTER(collationList[row]->data->code, code);
+}
+
+QString CollationsEditorModel::getCode(int row) const
+{
+ GETTER(collationList[row]->data->code, QString());
+}
+
+void CollationsEditorModel::setDatabases(int row, const QStringList& databases)
+{
+ SETTER(collationList[row]->data->databases, databases);
+}
+
+QStringList CollationsEditorModel::getDatabases(int row)
+{
+ GETTER(collationList[row]->data->databases, QStringList());
+}
+
+bool CollationsEditorModel::isValid(int row) const
+{
+ GETTER(collationList[row]->valid, false);
+}
+
+void CollationsEditorModel::setValid(int row, bool valid)
+{
+ SETTER(collationList[row]->valid, valid);
+}
+
+bool CollationsEditorModel::isValid() const
+{
+ foreach (Collation* coll, collationList)
+ {
+ if (!coll->valid)
+ return false;
+ }
+ return true;
+}
+
+void CollationsEditorModel::setData(const QList<CollationManager::CollationPtr>& collations)
+{
+ beginResetModel();
+
+ Collation* collationPtr = nullptr;
+ foreach (collationPtr, collationList)
+ delete collationPtr;
+
+ collationList.clear();
+
+ foreach (const CollationManager::CollationPtr& coll, collations)
+ collationList << new Collation(coll);
+
+ listModified = false;
+ originalCollationList = collationList;
+
+ endResetModel();
+}
+
+void CollationsEditorModel::addCollation(const CollationManager::CollationPtr& collation)
+{
+ int row = collationList.size();
+
+ beginInsertRows(QModelIndex(), row, row);
+
+ collationList << new Collation(collation);
+ listModified = true;
+
+ endInsertRows();
+}
+
+void CollationsEditorModel::deleteCollation(int row)
+{
+ if (!isValidRowIndex(row))
+ return;
+
+ beginRemoveRows(QModelIndex(), row, row);
+
+ delete collationList[row];
+ collationList.removeAt(row);
+
+ listModified = true;
+
+ endRemoveRows();
+}
+
+QList<CollationManager::CollationPtr> CollationsEditorModel::getCollations() const
+{
+ QList<CollationManager::CollationPtr> results;
+
+ foreach (Collation* coll, collationList)
+ results << coll->data;
+
+ return results;
+}
+
+QStringList CollationsEditorModel::getCollationNames() const
+{
+ QStringList names;
+ foreach (Collation* coll, collationList)
+ names << coll->data->name;
+
+ return names;
+}
+
+void CollationsEditorModel::validateNames()
+{
+ StrHash<QList<int>> counter;
+
+ int row = 0;
+ foreach (Collation* coll, collationList)
+ {
+ coll->valid &= true;
+ counter[coll->data->name] << row++;
+ }
+
+ QHashIterator<QString,QList<int>> cntIt = counter.iterator();
+ while (cntIt.hasNext())
+ {
+ cntIt.next();
+ if (cntIt.value().size() > 1)
+ {
+ foreach (int cntRow, cntIt.value())
+ setValid(cntRow, false);
+ }
+ }
+
+ QModelIndex idx;
+ for (int i = 0; i < collationList.size(); i++)
+ {
+ idx = index(i);
+ emit dataChanged(idx, idx);
+ }
+}
+
+bool CollationsEditorModel::isAllowedName(int rowToSkip, const QString& nameToValidate)
+{
+ QStringList names = getCollationNames();
+ names.removeAt(rowToSkip);
+ return !names.contains(nameToValidate, Qt::CaseInsensitive);
+}
+
+bool CollationsEditorModel::isValidRowIndex(int row) const
+{
+ return (row >= 0 && row < collationList.size());
+}
+
+int CollationsEditorModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return collationList.size();
+}
+
+QVariant CollationsEditorModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || !isValidRowIndex(index.row()))
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ return collationList[index.row()]->data->name;
+
+ if (role == Qt::DecorationRole && langToIcon.contains(collationList[index.row()]->data->lang))
+ {
+ QIcon icon = langToIcon[collationList[index.row()]->data->lang];
+ if (!collationList[index.row()]->valid)
+ icon = Icon::merge(icon, Icon::ERROR);
+
+ return icon;
+ }
+
+ return QVariant();
+
+}
+
+void CollationsEditorModel::init()
+{
+ foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ langToIcon[plugin->getLanguage()] = QIcon(plugin->getIconPath());
+}
+
+void CollationsEditorModel::emitDataChanged(int row)
+{
+ QModelIndex idx = index(row);
+ emit dataChanged(idx, idx);
+}
+
+CollationsEditorModel::Collation::Collation()
+{
+ data = CollationManager::CollationPtr::create();
+}
+
+CollationsEditorModel::Collation::Collation(const CollationManager::CollationPtr& other)
+{
+ data = CollationManager::CollationPtr::create(*other);
+ originalName = data->name;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h
new file mode 100644
index 0000000..0c17c5b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h
@@ -0,0 +1,77 @@
+#ifndef COLLATIONSEDITORMODEL_H
+#define COLLATIONSEDITORMODEL_H
+
+#include "services/collationmanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QIcon>
+#include <QHash>
+#include <QAbstractListModel>
+
+class GUI_API_EXPORT CollationsEditorModel : public QAbstractListModel
+{
+ Q_OBJECT
+ public:
+ using QAbstractItemModel::setData;
+
+ explicit CollationsEditorModel(QObject *parent = 0);
+
+ void clearModified();
+ bool isModified() const;
+ bool isModified(int row) const;
+ void setModified(int row, bool modified);
+ void setName(int row, const QString& name);
+ QString getName(int row) const;
+ void setLang(int row, const QString& lang);
+ QString getLang(int row) const;
+ void setAllDatabases(int row, bool allDatabases);
+ bool getAllDatabases(int row) const;
+ void setCode(int row, const QString& code);
+ QString getCode(int row) const;
+ void setDatabases(int row, const QStringList& databases);
+ QStringList getDatabases(int row);
+ bool isValid(int row) const;
+ void setValid(int row, bool valid);
+ bool isValid() const;
+ void setData(const QList<CollationManager::CollationPtr>& collations);
+ void addCollation(const CollationManager::CollationPtr& collation);
+ void deleteCollation(int row);
+ QList<CollationManager::CollationPtr> getCollations() const;
+ QStringList getCollationNames() const;
+ void validateNames();
+ bool isAllowedName(int rowToSkip, const QString& nameToValidate);
+ bool isValidRowIndex(int row) const;
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+
+ private:
+ struct Collation
+ {
+ Collation();
+ Collation(const CollationManager::CollationPtr& other);
+
+ CollationManager::CollationPtr data;
+ bool modified = false;
+ bool valid = true;
+ QString originalName;
+ };
+
+ void init();
+ void emitDataChanged(int row);
+
+ QList<Collation*> collationList;
+
+ /**
+ * @brief List of collation pointers before modifications.
+ *
+ * This list is kept to check for modifications in the overall list of collations.
+ * Pointers on this list may be already deleted, so don't use them!
+ * It's only used to compare list of pointers to collationList, so it can tell you
+ * if the list was modified in regards of adding or deleting collations.
+ */
+ QList<Collation*> originalCollationList;
+ QHash<QString,QIcon> langToIcon;
+ bool listModified = false;
+};
+
+#endif // COLLATIONSEDITORMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp
new file mode 100644
index 0000000..2d8897b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp
@@ -0,0 +1,395 @@
+#include "constrainttabmodel.h"
+#include "common/unused.h"
+#include "iconmanager.h"
+#include "common/utils_sql.h"
+#include <QDebug>
+
+ConstraintTabModel::ConstraintTabModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+}
+
+int ConstraintTabModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (createTable.isNull())
+ return 0;
+
+ int cnt = 0;
+ foreach (SqliteCreateTable::Column* col, createTable->columns)
+ cnt += col->constraints.size();
+
+ cnt += createTable->constraints.size();
+ return cnt;
+}
+
+int ConstraintTabModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return 4;
+}
+
+QVariant ConstraintTabModel::data(const QModelIndex& index, int role) const
+{
+ if (createTable.isNull())
+ return QVariant();
+
+ int constrIdx = index.row();
+ int currIdx = -1;
+ foreach (SqliteCreateTable::Column* column, createTable->columns)
+ {
+ foreach (SqliteCreateTable::Column::Constraint* constr, column->constraints)
+ {
+ currIdx++;
+
+ if (currIdx == constrIdx)
+ return data(constr, index.column(), role);
+ }
+ }
+
+ foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ {
+ currIdx++;
+
+ if (currIdx == constrIdx)
+ return data(constr, index.column(), role);
+ }
+
+ return QVariant();
+}
+
+QVariant ConstraintTabModel::data(SqliteCreateTable::Constraint* constr, int column, int role) const
+{
+ switch (getColumn(column))
+ {
+ case ConstraintTabModel::Columns::SCOPE:
+ {
+ if (role == Qt::DisplayRole)
+ return tr("Table", "table constraints");
+
+ break;
+ }
+ case Columns::TYPE:
+ {
+ if (role == Qt::DisplayRole)
+ return getTypeLabel(constr->type);
+
+ if (role == Qt::DecorationRole)
+ return getTypeIcon(constr->type);
+
+ break;
+ }
+ case Columns::NAME:
+ {
+ if (role == Qt::DisplayRole)
+ return stripObjName(constr->name, createTable->dialect);
+
+ break;
+ }
+ case Columns::DETAILS:
+ {
+ if (role == Qt::DisplayRole)
+ return getDetails(constr);
+
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant ConstraintTabModel::data(SqliteCreateTable::Column::Constraint* constr, int column, int role) const
+{
+ switch (getColumn(column))
+ {
+ case ConstraintTabModel::Columns::SCOPE:
+ {
+ if (role == Qt::DisplayRole)
+ {
+ QString colName = dynamic_cast<SqliteCreateTable::Column*>(constr->parentStatement())->name;
+ return tr("Column (%1)", "table constraints").arg(colName);
+ }
+
+ break;
+ }
+ case Columns::TYPE:
+ {
+ if (role == Qt::DisplayRole)
+ return getTypeLabel(constr->type);
+
+ if (role == Qt::DecorationRole)
+ return getTypeIcon(constr->type);
+
+ break;
+ }
+ case Columns::NAME:
+ {
+ if (role == Qt::DisplayRole)
+ return stripObjName(constr->name, createTable->dialect);
+
+ break;
+ }
+ case Columns::DETAILS:
+ {
+ if (role == Qt::DisplayRole)
+ return getDetails(constr);
+
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant ConstraintTabModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QAbstractTableModel::headerData(section, orientation, role);
+
+ if (orientation == Qt::Vertical)
+ return section + 1;
+
+ switch (getColumn(section))
+ {
+ case ConstraintTabModel::Columns::SCOPE:
+ return tr("Scope", "table constraints");
+ case Columns::TYPE:
+ return tr("Type", "table constraints");
+ case Columns::DETAILS:
+ return tr("Details", "table constraints");
+ case Columns::NAME:
+ return tr("Name", "table constraints");
+ }
+ return QVariant();
+}
+
+void ConstraintTabModel::setCreateTable(const QPointer<SqliteCreateTable>& value)
+{
+ beginResetModel();
+ createTable = value;
+ endResetModel();
+}
+
+ConstraintTabModel::Columns ConstraintTabModel::getColumn(int idx) const
+{
+ return static_cast<Columns>(idx);
+}
+
+QString ConstraintTabModel::getTypeLabel(SqliteCreateTable::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return "PRIMARY KEY";
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return "UNIQUE";
+ case SqliteCreateTable::Constraint::CHECK:
+ return "CHECK";
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return "FOREIGN KEY";
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QString ConstraintTabModel::getTypeLabel(SqliteCreateTable::Column::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return "PRIMARY KEY";
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return "NOT NULL";
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return "UNIQUE";
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return "CHECK";
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return "DEFAULT";
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return "COLLATE";
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return "FOREIGN KEY";
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QString::null;
+}
+
+QIcon ConstraintTabModel::getTypeIcon(SqliteCreateTable::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return ICONS.CONSTRAINT_UNIQUE;
+ case SqliteCreateTable::Constraint::CHECK:
+ return ICONS.CONSTRAINT_CHECK;
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QIcon();
+ }
+ return QIcon();
+}
+
+QIcon ConstraintTabModel::getTypeIcon(SqliteCreateTable::Column::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return ICONS.CONSTRAINT_NOT_NULL;
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return ICONS.CONSTRAINT_UNIQUE;
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return ICONS.CONSTRAINT_CHECK;
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return ICONS.CONSTRAINT_DEFAULT;
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return ICONS.CONSTRAINT_COLLATION;
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QIcon();
+}
+
+QString ConstraintTabModel::getDetails(SqliteCreateTable::Constraint* constr) const
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return getPkDetails(constr);
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return getUniqueDetails(constr);
+ case SqliteCreateTable::Constraint::CHECK:
+ return getCheckDetails(constr);
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return getFkDetails(constr);
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QString ConstraintTabModel::getDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return getPkDetails(constr);
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return getNotNullDetails(constr);
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return getUniqueDetails(constr);
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return getCheckDetails(constr);
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return getDefaultDetails(constr);
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return getCollateDetails(constr);
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return getFkDetails(constr);
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return QString::null;
+}
+
+QString ConstraintTabModel::getPkDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getUniqueDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getCheckDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getFkDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getPkDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getFkDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "REFERENCES", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ConstraintTabModel::getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "NULL", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "COLLATE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "DEFAULT", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString ConstraintTabModel::getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const
+{
+ return getConstrDetails(constr->tokens, tokenOffset);
+}
+
+QString ConstraintTabModel::getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const
+{
+ return getConstrDetails(constr->tokens, tokenOffset);
+}
+
+QString ConstraintTabModel::getConstrDetails(const TokenList& constrTokens, int tokenOffset) const
+{
+ TokenList tokens = constrTokens.mid(tokenOffset);
+ tokens.trimLeft();
+ return tokens.detokenize();
+}
+
+void ConstraintTabModel::updateModel()
+{
+ beginResetModel();
+ endResetModel();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h
new file mode 100644
index 0000000..f93415b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.h
@@ -0,0 +1,70 @@
+#ifndef CONSTRAINTTABMODEL_H
+#define CONSTRAINTTABMODEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractTableModel>
+#include <QPointer>
+
+class GUI_API_EXPORT ConstraintTabModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ public:
+ explicit ConstraintTabModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ QVariant data(SqliteCreateTable::Constraint* constr, int column, int role) const;
+ QVariant data(SqliteCreateTable::Column::Constraint* constr, int column, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+
+ void setCreateTable(const QPointer<SqliteCreateTable>& value);
+
+ private:
+ enum class Columns
+ {
+ SCOPE,
+ TYPE,
+ NAME,
+ DETAILS
+ };
+
+ Columns getColumn(int idx) const;
+
+ QString getTypeLabel(SqliteCreateTable::Constraint::Type type) const;
+ QString getTypeLabel(SqliteCreateTable::Column::Constraint::Type type) const;
+
+ QIcon getTypeIcon(SqliteCreateTable::Constraint::Type type) const;
+ QIcon getTypeIcon(SqliteCreateTable::Column::Constraint::Type type) const;
+
+ QString getDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getDetails(SqliteCreateTable::Column::Constraint* constr) const;
+
+ QString getPkDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getUniqueDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getCheckDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getFkDetails(SqliteCreateTable::Constraint* constr) const;
+
+ QString getPkDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getFkDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const;
+
+ QString getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const;
+ QString getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const;
+ QString getConstrDetails(const TokenList& constrTokens, int tokenOffset) const;
+
+ QPointer<SqliteCreateTable> createTable;
+
+ signals:
+
+ public slots:
+ void updateModel();
+
+};
+
+#endif // CONSTRAINTTABMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp
new file mode 100644
index 0000000..3aeccfb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp
@@ -0,0 +1,150 @@
+#include "ddlhistorywindow.h"
+#include "ui_ddlhistorywindow.h"
+#include "services/config.h"
+#include "common/userinputfilter.h"
+#include "common/extlineedit.h"
+#include "dblistmodel.h"
+#include "ddlhistorymodel.h"
+#include "common/unused.h"
+#include "iconmanager.h"
+#include <QDate>
+#include <QLineEdit>
+#include <QStringListModel>
+
+DdlHistoryWindow::DdlHistoryWindow(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::DdlHistoryWindow)
+{
+ init();
+}
+
+DdlHistoryWindow::~DdlHistoryWindow()
+{
+ delete ui;
+}
+
+void DdlHistoryWindow::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void DdlHistoryWindow::init()
+{
+ ui->setupUi(this);
+
+ dataModel = CFG->getDdlHistoryModel();
+
+ dbListModel = new QStringListModel(this);
+ QStringList dbList = dataModel->getDbNames();
+ dbList.prepend("");
+ dbListModel->setStringList(dbList);
+ ui->comboBox->setModel(dbListModel);
+ ui->comboBox->setCurrentIndex(-1);
+ connect(ui->comboBox, SIGNAL(currentTextChanged(QString)), this, SLOT(applyFilter(QString)));
+ connect(dataModel, SIGNAL(refreshed()), this, SLOT(refreshDbList()));
+
+ ui->tableView->setModel(dataModel);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
+ ui->tableView->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
+
+ connect(ui->tableView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
+ this, SLOT(activated(QModelIndex,QModelIndex)));
+}
+
+void DdlHistoryWindow::activated(const QModelIndex& current, const QModelIndex& previous)
+{
+ UNUSED(previous);
+
+ int row = current.row();
+ QString dbName = dataModel->data(dataModel->index(row, 0)).toString();
+ QString dbFile = dataModel->data(dataModel->index(row, 1)).toString();
+ QString dateString = dataModel->data(dataModel->index(row, 2)).toString();
+ QDate date = QDate::fromString(dateString, "yyyy-MM-dd");
+
+ static const QString templ = tr("-- Queries executed on database %1 (%2)\n"
+ "-- Date and time of execution: %3\n"
+ "%4");
+
+ QStringList contentEntries;
+ QList<Config::DdlHistoryEntryPtr> entries = CFG->getDdlHistoryFor(dbName, dbFile, date);
+ foreach (Config::DdlHistoryEntryPtr entry, entries)
+ {
+ contentEntries << templ.arg(entry->dbName).arg(entry->dbFile)
+ .arg(entry->timestamp.toString("yyyy-MM-dd HH:mm:ss"))
+ .arg(entry->queries);
+ }
+
+ ui->ddlEdit->setPlainText(contentEntries.join("\n\n"));
+}
+
+void DdlHistoryWindow::applyFilter(const QString& filterValue)
+{
+ dataModel->setDbNameForFilter(filterValue);
+}
+
+void DdlHistoryWindow::refreshDbList()
+{
+ QStringList dbList = dataModel->getDbNames();
+ dbList.prepend("");
+ dbListModel->setStringList(dbList);
+}
+
+bool DdlHistoryWindow::restoreSessionNextTime()
+{
+ return false;
+}
+
+QVariant DdlHistoryWindow::saveSession()
+{
+ return QVariant();
+}
+
+bool DdlHistoryWindow::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+ return true;
+}
+
+Icon* DdlHistoryWindow::getIconNameForMdiWindow()
+{
+ return ICONS.DDL_HISTORY;
+}
+
+QString DdlHistoryWindow::getTitleForMdiWindow()
+{
+ return tr("DDL history");
+}
+
+void DdlHistoryWindow::createActions()
+{
+}
+
+void DdlHistoryWindow::setupDefShortcuts()
+{
+}
+
+QToolBar* DdlHistoryWindow::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+
+bool DdlHistoryWindow::isUncommited() const
+{
+ return false;
+}
+
+QString DdlHistoryWindow::getQuitUncommitedConfirmMessage() const
+{
+ return QString();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h
new file mode 100644
index 0000000..16a07ce
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.h
@@ -0,0 +1,54 @@
+#ifndef DDLHISTORYWINDOW_H
+#define DDLHISTORYWINDOW_H
+
+#include "mdichild.h"
+
+namespace Ui {
+ class DdlHistoryWindow;
+}
+
+class QStringListModel;
+class UserInputFilter;
+class DdlHistoryModel;
+
+class GUI_API_EXPORT DdlHistoryWindow : public MdiChild
+{
+ Q_OBJECT
+
+ public:
+ enum ToolBar
+ {
+ };
+
+ explicit DdlHistoryWindow(QWidget *parent = 0);
+ ~DdlHistoryWindow();
+
+ bool restoreSessionNextTime();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ QVariant saveSession();
+ bool restoreSession(const QVariant& sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+
+ Ui::DdlHistoryWindow *ui = nullptr;
+ QStringListModel* dbListModel = nullptr;
+ DdlHistoryModel* dataModel = nullptr;
+ UserInputFilter* filter = nullptr;
+
+ private slots:
+ void activated(const QModelIndex& current, const QModelIndex& previous);
+ void applyFilter(const QString& filterValue);
+ void refreshDbList();
+};
+
+#endif // DDLHISTORYWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui
new file mode 100644
index 0000000..6591198
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.ui
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DdlHistoryWindow</class>
+ <widget class="QWidget" name="DdlHistoryWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>749</width>
+ <height>599</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Filter by database:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBox">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="editable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QTableView" name="tableView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp
new file mode 100644
index 0000000..7856a5e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp
@@ -0,0 +1,652 @@
+#include "editorwindow.h"
+#include "ui_editorwindow.h"
+#include "uiutils.h"
+#include "datagrid/sqlquerymodel.h"
+#include "iconmanager.h"
+#include "dblistmodel.h"
+#include "services/notifymanager.h"
+#include "dbtree/dbtree.h"
+#include "datagrid/sqlqueryitem.h"
+#include "datagrid/sqlqueryview.h"
+#include "mainwindow.h"
+#include "mdiarea.h"
+#include "common/unused.h"
+#include "common/extaction.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include "parser/lexer.h"
+#include "common/utils_sql.h"
+#include "parser/parser.h"
+#include <QComboBox>
+#include <QDebug>
+#include <QStringListModel>
+#include <QActionGroup>
+#include <QMessageBox>
+#include <dbobjectdialogs.h>
+#include <dialogs/exportdialog.h>
+
+CFG_KEYS_DEFINE(EditorWindow)
+EditorWindow::ResultsDisplayMode EditorWindow::resultsDisplayMode;
+QHash<EditorWindow::Action,QAction*> EditorWindow::staticActions;
+QHash<EditorWindow::ActionGroup,QActionGroup*> EditorWindow::staticActionGroups;
+
+EditorWindow::EditorWindow(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::EditorWindow)
+{
+ ui->setupUi(this);
+ init();
+}
+
+EditorWindow::EditorWindow(const EditorWindow& editor) :
+ MdiChild(editor.parentWidget()),
+ ui(new Ui::EditorWindow)
+{
+ ui->setupUi(this);
+ init();
+ ui->sqlEdit->setAutoCompletion(false);
+ ui->sqlEdit->setPlainText(editor.ui->sqlEdit->toPlainText());
+ ui->sqlEdit->setAutoCompletion(true);
+}
+
+EditorWindow::~EditorWindow()
+{
+ delete ui;
+}
+
+void EditorWindow::staticInit()
+{
+ qRegisterMetaType<EditorWindow>("EditorWindow");
+ resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY;
+ loadTabsMode();
+ createStaticActions();
+}
+
+void EditorWindow::insertAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertAction<EditorWindow>(action, toolbar);
+}
+
+void EditorWindow::insertActionBefore(ExtActionPrototype* action, EditorWindow::Action beforeAction, EditorWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionBefore<EditorWindow>(action, beforeAction, toolbar);
+}
+
+void EditorWindow::insertActionAfter(ExtActionPrototype* action, EditorWindow::Action afterAction, EditorWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionAfter<EditorWindow>(action, afterAction, toolbar);
+}
+
+void EditorWindow::removeAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar)
+{
+ ExtActionContainer::removeAction<EditorWindow>(action, toolbar);
+}
+
+void EditorWindow::init()
+{
+ setFocusProxy(ui->sqlEdit);
+ updateResultsDisplayMode();
+
+ resultsModel = new SqlQueryModel(this);
+ ui->dataView->init(resultsModel);
+
+ createDbCombo();
+ initActions();
+ updateShortcutTips();
+
+ Db* currentDb = getCurrentDb();
+ resultsModel->setDb(currentDb);
+ ui->sqlEdit->setDb(currentDb);
+
+ connect(resultsModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
+ connect(resultsModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
+ connect(resultsModel, SIGNAL(totalRowsAndPagesAvailable()), this, SLOT(totalRowsAndPagesAvailable()));
+
+ // SQL history list
+ ui->historyList->setModel(CFG->getSqlHistoryModel());
+ ui->historyList->resizeColumnToContents(1);
+ connect(ui->historyList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
+ this, SLOT(historyEntrySelected(QModelIndex,QModelIndex)));
+ connect(ui->historyList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(historyEntryActivated(QModelIndex)));
+
+ updateState();
+}
+
+void EditorWindow::loadTabsMode()
+{
+ QString tabsString = CFG_UI.General.SqlEditorTabs.get();
+ if (tabsString == "SEPARATE_TAB")
+ resultsDisplayMode = ResultsDisplayMode::SEPARATE_TAB;
+ else if (tabsString == "BELOW_QUERY")
+ resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY;
+}
+
+void EditorWindow::createStaticActions()
+{
+ staticActions[RESULTS_IN_TAB] = new ExtAction(ICONS.RESULTS_IN_TAB, tr("Results in the separate tab"), MainWindow::getInstance());
+ staticActions[RESULTS_BELOW] = new ExtAction(ICONS.RESULTS_BELOW, tr("Results below the query"), MainWindow::getInstance());
+
+ staticActionGroups[ActionGroup::RESULTS_POSITIONING] = new QActionGroup(MainWindow::getInstance());
+ staticActionGroups[ActionGroup::RESULTS_POSITIONING]->addAction(staticActions[RESULTS_IN_TAB]);
+ staticActionGroups[ActionGroup::RESULTS_POSITIONING]->addAction(staticActions[RESULTS_BELOW]);
+
+ connect(staticActions[RESULTS_BELOW], &QAction::triggered, [=]()
+ {
+ resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY;
+ CFG_UI.General.SqlEditorTabs.set("BELOW_QUERY");
+ });
+ connect(staticActions[RESULTS_IN_TAB], &QAction::triggered, [=]()
+ {
+ resultsDisplayMode = ResultsDisplayMode::SEPARATE_TAB;
+ CFG_UI.General.SqlEditorTabs.set("SEPARATE_TAB");
+ });
+
+ staticActions[RESULTS_BELOW]->setCheckable(true);
+ staticActions[RESULTS_IN_TAB]->setCheckable(true);
+ if (resultsDisplayMode == ResultsDisplayMode::BELOW_QUERY)
+ staticActions[RESULTS_BELOW]->setChecked(true);
+ else
+ staticActions[RESULTS_IN_TAB]->setChecked(true);
+}
+
+Icon* EditorWindow::getIconNameForMdiWindow()
+{
+ return ICONS.OPEN_SQL_EDITOR;
+}
+
+QString EditorWindow::getTitleForMdiWindow()
+{
+ QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles();
+ QString title = tr("SQL editor %1").arg(sqlEditorNum++);
+ while (existingNames.contains(title))
+ title = tr("SQL editor %1").arg(sqlEditorNum++);
+
+ return title;
+}
+
+QSize EditorWindow::sizeHint() const
+{
+ return QSize(500, 400);
+}
+
+QAction* EditorWindow::getAction(EditorWindow::Action action)
+{
+ switch (action)
+ {
+ case RESULTS_BELOW:
+ case RESULTS_IN_TAB:
+ {
+ if (!staticActions.contains(action))
+ return nullptr;
+
+ return staticActions.value(action);
+ }
+ default:
+ break;
+ }
+
+ return ExtActionContainer::getAction(action);
+}
+
+QString EditorWindow::getQueryToExecute(bool doSelectCurrentQuery)
+{
+ QString sql;
+ if (ui->sqlEdit->textCursor().hasSelection())
+ {
+ sql = ui->sqlEdit->textCursor().selectedText();
+ fixTextCursorSelectedText(sql);
+ }
+ else if (CFG_UI.General.ExecuteCurrentQueryOnly.get())
+ {
+ ui->sqlEdit->saveSelection();
+ selectCurrentQuery(true);
+ sql = ui->sqlEdit->textCursor().selectedText();
+ fixTextCursorSelectedText(sql);
+ if (!doSelectCurrentQuery)
+ ui->sqlEdit->restoreSelection();
+ }
+ else
+ {
+ sql = ui->sqlEdit->toPlainText();
+ }
+ return sql;
+}
+
+bool EditorWindow::setCurrentDb(Db *db)
+{
+ if (dbCombo->findText(db->getName()) == -1)
+ return false;
+
+ dbCombo->setCurrentText(db->getName());
+ return true;
+}
+
+void EditorWindow::setContents(const QString &sql)
+{
+ ui->sqlEdit->setPlainText(sql);
+}
+
+QString EditorWindow::getContents() const
+{
+ return ui->sqlEdit->toPlainText();
+}
+
+void EditorWindow::execute()
+{
+ execQuery();
+}
+
+QToolBar* EditorWindow::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolBar;
+}
+
+SqlEditor* EditorWindow::getEditor() const
+{
+ return ui->sqlEdit;
+}
+
+QVariant EditorWindow::saveSession()
+{
+ QHash<QString,QVariant> sessionValue;
+ sessionValue["query"] = ui->sqlEdit->toPlainText();
+ sessionValue["curPos"] = ui->sqlEdit->textCursor().position();
+
+ Db* db = getCurrentDb();
+ if (db)
+ sessionValue["db"] = db->getName();
+
+ return sessionValue;
+}
+
+bool EditorWindow::restoreSession(const QVariant& sessionValue)
+{
+ QHash<QString, QVariant> value = sessionValue.toHash();
+ if (value.size() == 0)
+ return true;
+
+ if (value.contains("query"))
+ {
+ ui->sqlEdit->setAutoCompletion(false);
+ ui->sqlEdit->setPlainText(value["query"].toString());
+ ui->sqlEdit->setAutoCompletion(true);
+ }
+
+ if (value.contains("curPos"))
+ {
+ QTextCursor cursor = ui->sqlEdit->textCursor();
+ cursor.setPosition(value["curPos"].toInt());
+ ui->sqlEdit->setTextCursor(cursor);
+ }
+
+ if (value.contains("db"))
+ {
+ dbCombo->setCurrentText(value["db"].toString());
+ if (dbCombo->currentText().isEmpty() && dbCombo->count() > 0)
+ dbCombo->setCurrentIndex(0);
+ }
+ return true;
+}
+
+void EditorWindow::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+Db* EditorWindow::getCurrentDb()
+{
+ return dbComboModel->getDb(dbCombo->currentIndex());
+}
+
+void EditorWindow::updateResultsDisplayMode()
+{
+ switch (resultsDisplayMode)
+ {
+ case EditorWindow::ResultsDisplayMode::SEPARATE_TAB:
+ {
+ // Remove old view
+ ui->resultsContainer->hide();
+ ui->resultsContainer->layout()->removeWidget(ui->resultsFrame);
+
+ // Add new view
+ ui->tabWidget->insertTab(1, ui->results, tr("Results"));
+ ui->resultsFrame->setParent(ui->results);
+ ui->results->layout()->addWidget(ui->resultsFrame);
+ break;
+ }
+ case EditorWindow::ResultsDisplayMode::BELOW_QUERY:
+ {
+ int currIdx = ui->tabWidget->currentIndex();
+
+ // Remove old view
+ ui->tabWidget->removeTab(1);
+ ui->results->layout()->removeWidget(ui->resultsFrame);
+
+ // Add new view
+ ui->resultsContainer->show();
+ ui->resultsFrame->setParent(ui->resultsContainer);
+ ui->resultsContainer->layout()->addWidget(ui->resultsFrame);
+
+ // If results tab was selected before, switch to first tab
+ if (currIdx == 1)
+ {
+ ui->tabWidget->setCurrentIndex(0);
+ ui->dataView->setCurrentIndex(0);
+ ui->dataView->getGridView()->setFocus();
+ }
+ break;
+ }
+ }
+}
+
+void EditorWindow::createActions()
+{
+ // SQL editor toolbar
+ createAction(EXEC_QUERY, ICONS.EXEC_QUERY, tr("Execute query"), this, SLOT(execQuery()), ui->toolBar, ui->sqlEdit);
+ createAction(EXPLAIN_QUERY, ICONS.EXPLAIN_QUERY, tr("Explain query"), this, SLOT(explainQuery()), ui->toolBar, ui->sqlEdit);
+ ui->toolBar->addSeparator();
+ ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::FORMAT_SQL));
+ createAction(CLEAR_HISTORY, ICONS.CLEAR_HISTORY, tr("Clear execution history", "sql editor"), this, SLOT(clearHistory()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ createAction(EXPORT_RESULTS, ICONS.TABLE_EXPORT, tr("Export results", "sql editor"), this, SLOT(exportResults()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ createAction(CREATE_VIEW_FROM_QUERY, ICONS.VIEW_ADD, tr("Create view from query", "sql editor"), this, SLOT(createViewFromQuery()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::SAVE_SQL_FILE));
+ ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::OPEN_SQL_FILE));
+ ui->toolBar->addSeparator();
+ actionMap[CURRENT_DB] = ui->toolBar->addWidget(dbCombo);
+ ui->toolBar->addSeparator();
+ ui->toolBar->addAction(staticActions[RESULTS_IN_TAB]);
+ ui->toolBar->addAction(staticActions[RESULTS_BELOW]);
+ createAction(PREV_DB, tr("Previous database"), this, SLOT(prevDb()), this);
+ createAction(NEXT_DB, tr("Next database"), this, SLOT(nextDb()), this);
+
+ // Other actions
+ createAction(SHOW_NEXT_TAB, tr("Show next tab", "sql editor"), this, SLOT(showNextTab()), this);
+ createAction(SHOW_PREV_TAB, tr("Show previous tab", "sql editor"), this, SLOT(showPrevTab()), this);
+ createAction(FOCUS_RESULTS_BELOW, tr("Focus results below", "sql editor"), this, SLOT(focusResultsBelow()), this);
+ createAction(FOCUS_EDITOR_ABOVE, tr("Focus SQL editor above", "sql editor"), this, SLOT(focusEditorAbove()), this);
+
+ // Static action triggers
+ connect(staticActions[RESULTS_IN_TAB], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode()));
+ connect(staticActions[RESULTS_BELOW], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode()));
+}
+
+void EditorWindow::createDbCombo()
+{
+ dbCombo = new QComboBox(this);
+ dbComboModel = new DbListModel(this);
+ dbComboModel->setCombo(dbCombo);
+ dbCombo->setModel(dbComboModel);
+ dbCombo->setEditable(false);
+ dbCombo->setFixedWidth(100);
+ connect(dbCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(dbChanged()));
+}
+
+void EditorWindow::setupDefShortcuts()
+{
+ // Widget context
+ setShortcutContext({EXEC_QUERY, EXEC_QUERY, SHOW_NEXT_TAB, SHOW_PREV_TAB, FOCUS_RESULTS_BELOW,
+ FOCUS_EDITOR_ABOVE}, Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(EditorWindow, Action);
+}
+
+void EditorWindow::selectCurrentQuery(bool fallBackToPreviousIfNecessary)
+{
+ Dialect dialect = Dialect::Sqlite3;
+ Db* db = getCurrentDb();
+ if (db && db->isValid())
+ dialect = db->getDialect();
+
+ QTextCursor cursor = ui->sqlEdit->textCursor();
+ int pos = cursor.position();
+ int queryStartPos;
+ QString contents = ui->sqlEdit->toPlainText();
+ QString query = getQueryWithPosition(contents, pos, dialect, &queryStartPos);
+ TokenList tokens = Lexer::tokenize(query, dialect);
+ tokens.trim();
+ tokens.trimRight(Token::OPERATOR, ";");
+
+ if (tokens.size() == 0 && fallBackToPreviousIfNecessary)
+ {
+ // Fallback
+ pos = contents.lastIndexOf(";", pos - 1);
+ if (pos > -1)
+ {
+ query = getQueryWithPosition(contents, pos, dialect, &queryStartPos);
+ tokens = Lexer::tokenize(query, dialect);
+ tokens.trim();
+ tokens.trimRight(Token::OPERATOR, ";");
+ }
+ }
+
+ if (tokens.size() == 0)
+ {
+ qWarning() << "No tokens to select in EditorWindow::selectCurrentQuery().";
+ return;
+ }
+
+ cursor.clearSelection();
+ cursor.setPosition(tokens.first()->start + queryStartPos);
+ cursor.setPosition(tokens.last()->end + 1 + queryStartPos, QTextCursor::KeepAnchor);
+ ui->sqlEdit->setTextCursor(cursor);
+}
+
+void EditorWindow::updateShortcutTips()
+{
+ if (actionMap.contains(PREV_DB) && actionMap.contains(NEXT_DB))
+ {
+ QString prevDbKey = actionMap[PREV_DB]->shortcut().toString(QKeySequence::NativeText);
+ QString nextDbKey = actionMap[NEXT_DB]->shortcut().toString(QKeySequence::NativeText);
+ dbCombo->setToolTip(tr("Active database (%1/%2)").arg(prevDbKey).arg(nextDbKey));
+ }
+}
+
+void EditorWindow::execQuery(bool explain)
+{
+ QString sql = getQueryToExecute(true);
+ resultsModel->setDb(getCurrentDb());
+ resultsModel->setExplainMode(explain);
+ resultsModel->setQuery(sql);
+ ui->dataView->refreshData();
+ updateState();
+
+ if (resultsDisplayMode == ResultsDisplayMode::SEPARATE_TAB)
+ {
+ ui->tabWidget->setCurrentIndex(1);
+ ui->dataView->setCurrentIndex(0);
+ ui->dataView->getGridView()->setFocus();
+ }
+}
+
+void EditorWindow::explainQuery()
+{
+ execQuery(true);
+}
+
+void EditorWindow::dbChanged()
+{
+ Db* currentDb = getCurrentDb();
+ ui->sqlEdit->setDb(currentDb);
+}
+
+void EditorWindow::executionSuccessful()
+{
+ double secs = ((double)resultsModel->getExecutionTime()) / 1000;
+ QString time = QString::number(secs, 'f', 3);
+ notifyInfo(tr("Query finished in %2 second(s).").arg(time));
+
+ lastQueryHistoryId = CFG->addSqlHistory(resultsModel->getQuery(), resultsModel->getDb()->getName(), resultsModel->getExecutionTime(), 0);
+
+ // If we added first history entry - resize dates column.
+ if (ui->historyList->model()->rowCount() == 1)
+ ui->historyList->resizeColumnToContents(1);
+
+ Db* currentDb = getCurrentDb();
+ if (currentDb && resultsModel->wasSchemaModified())
+ DBTREE->refreshSchema(currentDb);
+
+ lastSuccessfulQuery = resultsModel->getQuery();
+
+ updateState();
+}
+
+void EditorWindow::executionFailed(const QString &errorText)
+{
+ notifyError(errorText);
+ updateState();
+}
+
+void EditorWindow::totalRowsAndPagesAvailable()
+{
+ qint64 rowsReturned = resultsModel->getTotalRowsReturned();
+ qint64 rowsAffected = resultsModel->getTotalRowsAffected();
+ qint64 rows;
+ if (rowsReturned > 0)
+ rows = rowsReturned;
+ else
+ rows = rowsAffected;
+
+ CFG->updateSqlHistory(lastQueryHistoryId, resultsModel->getQuery(), resultsModel->getDb()->getName(), resultsModel->getExecutionTime(), rows);
+}
+
+void EditorWindow::prevDb()
+{
+ int idx = dbCombo->currentIndex() - 1;
+ if (idx < 0)
+ return;
+
+ dbCombo->setCurrentIndex(idx);
+}
+
+void EditorWindow::nextDb()
+{
+ int idx = dbCombo->currentIndex() + 1;
+ if (idx >= dbCombo->count())
+ return;
+
+ dbCombo->setCurrentIndex(idx);
+}
+
+void EditorWindow::showNextTab()
+{
+ int tabIdx = ui->tabWidget->currentIndex();
+ tabIdx++;
+ ui->tabWidget->setCurrentIndex(tabIdx);
+}
+
+void EditorWindow::showPrevTab()
+{
+ int tabIdx = ui->tabWidget->currentIndex();
+ tabIdx--;
+ ui->tabWidget->setCurrentIndex(tabIdx);
+}
+
+void EditorWindow::focusResultsBelow()
+{
+ if (resultsDisplayMode != ResultsDisplayMode::BELOW_QUERY)
+ return;
+
+ ui->dataView->setCurrentIndex(0);
+ ui->dataView->getGridView()->setFocus();
+}
+
+void EditorWindow::focusEditorAbove()
+{
+ if (resultsDisplayMode != ResultsDisplayMode::BELOW_QUERY)
+ return;
+
+ ui->sqlEdit->setFocus();
+}
+
+void EditorWindow::historyEntrySelected(const QModelIndex& current, const QModelIndex& previous)
+{
+ UNUSED(previous);
+ QString sql = ui->historyList->model()->index(current.row(), 4).data().toString();
+ ui->historyContents->setPlainText(sql);
+}
+
+void EditorWindow::historyEntryActivated(const QModelIndex& current)
+{
+ QString sql = ui->historyList->model()->index(current.row(), 4).data().toString();
+ ui->sqlEdit->setPlainText(sql);
+ ui->tabWidget->setCurrentIndex(0);
+}
+
+void EditorWindow::clearHistory()
+{
+ QMessageBox::StandardButton res = QMessageBox::question(this, tr("Clear execution history"), tr("Are you sure you want to erase the entire SQL execution history? "
+ "This cannot be undone."));
+ if (res != QMessageBox::Yes)
+ return;
+
+ CFG->clearSqlHistory();
+}
+
+void EditorWindow::exportResults()
+{
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ QString query = lastSuccessfulQuery.isEmpty() ? getQueryToExecute() : lastSuccessfulQuery;
+ QStringList queries = splitQueries(query, getCurrentDb()->getDialect(), false);
+ if (queries.size() == 0)
+ {
+ qWarning() << "No queries after split in EditorWindow::exportResults()";
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.setQueryMode(getCurrentDb(), queries.last().trimmed());
+ dialog.exec();
+}
+
+void EditorWindow::createViewFromQuery()
+{
+ if (!getCurrentDb())
+ {
+ notifyError(tr("No database selected in the SQL editor. Cannot create a view for unknown database."));
+ return;
+ }
+
+ QString sql = getQueryToExecute(true);
+ DbObjectDialogs dialogs(getCurrentDb());
+ dialogs.addView(sql);
+}
+
+void EditorWindow::updateState()
+{
+ bool executionInProgress = resultsModel->isExecutionInProgress();
+ actionMap[CURRENT_DB]->setEnabled(!executionInProgress);
+ actionMap[EXEC_QUERY]->setEnabled(!executionInProgress);
+ actionMap[EXPLAIN_QUERY]->setEnabled(!executionInProgress);
+}
+
+int qHash(EditorWindow::ActionGroup actionGroup)
+{
+ return static_cast<int>(actionGroup);
+}
+
+
+bool EditorWindow::isUncommited() const
+{
+ return ui->dataView->isUncommited();
+}
+
+QString EditorWindow::getQuitUncommitedConfirmMessage() const
+{
+ return tr("Editor window \"%1\" has uncommited data.").arg(getMdiWindow()->windowTitle());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h
new file mode 100644
index 0000000..0052a74
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h
@@ -0,0 +1,155 @@
+#ifndef EDITOR_H
+#define EDITOR_H
+
+#include "db/db.h"
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+namespace Ui {
+ class EditorWindow;
+}
+
+class SqlQueryModel;
+class QComboBox;
+class QActionGroup;
+class DbListModel;
+class QLabel;
+class QLineEdit;
+class ExtLineEdit;
+class IntValidator;
+class FormView;
+class SqlQueryItem;
+class SqlEditor;
+
+CFG_KEY_LIST(EditorWindow, QObject::tr("SQL editor window"),
+ CFG_KEY_ENTRY(EXEC_QUERY, Qt::Key_F9, QObject::tr("Execute query"))
+ CFG_KEY_ENTRY(EXPLAIN_QUERY, Qt::Key_F8, QObject::tr("Execute \"EXPLAIN\" query"))
+ CFG_KEY_ENTRY(PREV_DB, Qt::CTRL + Qt::Key_Up, QObject::tr("Switch current working database to previous on the list"))
+ CFG_KEY_ENTRY(NEXT_DB, Qt::CTRL + Qt::Key_Down, QObject::tr("Switch current working database to next on the list"))
+ CFG_KEY_ENTRY(SHOW_NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next editor tab"))
+ CFG_KEY_ENTRY(SHOW_PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous editor tab"))
+ CFG_KEY_ENTRY(FOCUS_RESULTS_BELOW, Qt::ALT + Qt::Key_PageDown, QObject::tr("Move keyboard input focus to the results view below"))
+ CFG_KEY_ENTRY(FOCUS_EDITOR_ABOVE, Qt::ALT + Qt::Key_PageUp, QObject::tr("Move keyboard input focus to the SQL editor above"))
+)
+
+class GUI_API_EXPORT EditorWindow : public MdiChild
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum class ResultsDisplayMode
+ {
+ SEPARATE_TAB = 0,
+ BELOW_QUERY = 1
+ };
+
+ enum Action
+ {
+ EXEC_QUERY,
+ EXPLAIN_QUERY,
+ RESULTS_IN_TAB,
+ RESULTS_BELOW,
+ CURRENT_DB,
+ NEXT_DB,
+ PREV_DB,
+ SHOW_NEXT_TAB,
+ SHOW_PREV_TAB,
+ FOCUS_RESULTS_BELOW,
+ FOCUS_EDITOR_ABOVE,
+ CLEAR_HISTORY,
+ EXPORT_RESULTS,
+ CREATE_VIEW_FROM_QUERY
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR_MAIN
+ };
+
+ enum class ActionGroup
+ {
+ RESULTS_POSITIONING
+ };
+
+ explicit EditorWindow(QWidget *parent = 0);
+ EditorWindow(const EditorWindow& editor);
+ ~EditorWindow();
+
+ static void staticInit();
+ static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_MAIN);
+ static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_MAIN);
+ static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_MAIN);
+ static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_MAIN);
+
+ QSize sizeHint() const;
+ QAction* getAction(Action action);
+ QString getQueryToExecute(bool doSelectCurrentQuery = false);
+ bool setCurrentDb(Db* db);
+ void setContents(const QString& sql);
+ QString getContents() const;
+ void execute();
+ QToolBar* getToolBar(int toolbar) const;
+ SqlEditor* getEditor() const;
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ QVariant saveSession();
+ bool restoreSession(const QVariant& sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ Db* getCurrentDb();
+
+ private:
+ static void createStaticActions();
+ static void loadTabsMode();
+
+ void init();
+ void createActions();
+ void createDbCombo();
+ void setupDefShortcuts();
+ void selectCurrentQuery(bool fallBackToPreviousIfNecessary = false);
+ void updateShortcutTips();
+
+ static ResultsDisplayMode resultsDisplayMode;
+ static QHash<Action,QAction*> staticActions;
+ static QHash<ActionGroup,QActionGroup*> staticActionGroups;
+
+ Ui::EditorWindow *ui = nullptr;
+ SqlQueryModel* resultsModel = nullptr;
+ QHash<ActionGroup,QActionGroup*> actionGroups;
+ QComboBox* dbCombo = nullptr;
+ DbListModel* dbComboModel = nullptr;
+ int sqlEditorNum = 1;
+ qint64 lastQueryHistoryId = 0;
+ QString lastSuccessfulQuery;
+
+ private slots:
+ void execQuery(bool explain = false);
+ void explainQuery();
+ void dbChanged();
+ void executionSuccessful();
+ void executionFailed(const QString& errorText);
+ void totalRowsAndPagesAvailable();
+ void updateResultsDisplayMode();
+ void prevDb();
+ void nextDb();
+ void showNextTab();
+ void showPrevTab();
+ void focusResultsBelow();
+ void focusEditorAbove();
+ void historyEntrySelected(const QModelIndex& current, const QModelIndex& previous);
+ void historyEntryActivated(const QModelIndex& current);
+ void clearHistory();
+ void exportResults();
+ void createViewFromQuery();
+ void updateState();
+};
+
+GUI_API_EXPORT int qHash(EditorWindow::ActionGroup action);
+
+#endif // EDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui
new file mode 100644
index 0000000..b0d598b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EditorWindow</class>
+ <widget class="QWidget" name="EditorWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>502</width>
+ <height>325</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>SQL editor</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="toolBar"/>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="query">
+ <attribute name="title">
+ <string>Query</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="SqlEditor" name="sqlEdit">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ </widget>
+ <widget class="QWidget" name="resultsContainer" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="results">
+ <attribute name="title">
+ <string notr="true">Results</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QWidget" name="resultsFrame" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="DataView" name="dataView">
+ <property name="tabPosition">
+ <enum>QTabWidget::South</enum>
+ </property>
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="history">
+ <attribute name="title">
+ <string>History</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QTableView" name="historyList">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ <widget class="SqlView" name="historyContents">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>DataView</class>
+ <extends>QTabWidget</extends>
+ <header>dataview.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp
new file mode 100644
index 0000000..29163e3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp
@@ -0,0 +1,632 @@
+#include "functionseditor.h"
+#include "ui_functionseditor.h"
+#include "common/unused.h"
+#include "common/utils.h"
+#include "uiutils.h"
+#include "functionseditormodel.h"
+#include "services/pluginmanager.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "dbtree/dbtreeitem.h"
+#include "iconmanager.h"
+#include "syntaxhighlighterplugin.h"
+#include "sqlitesyntaxhighlighter.h"
+#include "plugins/scriptingplugin.h"
+#include "common/userinputfilter.h"
+#include "selectabledbmodel.h"
+#include "uiconfig.h"
+#include <QDebug>
+#include <QDesktopServices>
+#include <QStyleFactory>
+
+// TODO handle plugin loading/unloading to update editor state
+
+FunctionsEditor::FunctionsEditor(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::FunctionsEditor)
+{
+ init();
+}
+
+FunctionsEditor::~FunctionsEditor()
+{
+ delete ui;
+}
+
+bool FunctionsEditor::restoreSessionNextTime()
+{
+ return false;
+}
+
+bool FunctionsEditor::restoreSession(const QVariant &sessionValue)
+{
+ UNUSED(sessionValue);
+ return true;
+}
+
+Icon* FunctionsEditor::getIconNameForMdiWindow()
+{
+ return ICONS.FUNCTION;
+}
+
+QString FunctionsEditor::getTitleForMdiWindow()
+{
+ return tr("SQL function editor");
+}
+
+void FunctionsEditor::createActions()
+{
+ createAction(COMMIT, ICONS.COMMIT, tr("Commit all function changes"), this, SLOT(commit()), ui->toolBar);
+ createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback all function changes"), this, SLOT(rollback()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ createAction(ADD, ICONS.NEW_FUNCTION, tr("Create new function"), this, SLOT(newFunction()), ui->toolBar);
+ createAction(DELETE, ICONS.DELETE_FUNCTION, tr("Delete selected function"), this, SLOT(deleteFunction()), ui->toolBar);
+ ui->toolBar->addSeparator();
+ createAction(HELP, ICONS.HELP, tr("Custom SQL functions manual"), this, SLOT(help()), ui->toolBar);
+
+ // Args toolbar
+ createAction(ARG_ADD, ICONS.INSERT_FN_ARG, tr("Add function argument"), this, SLOT(addFunctionArg()), ui->argsToolBar);
+ createAction(ARG_EDIT, ICONS.RENAME_FN_ARG, tr("Rename function argument"), this, SLOT(editFunctionArg()), ui->argsToolBar);
+ createAction(ARG_DEL, ICONS.DELETE_FN_ARG, tr("Delete function argument"), this, SLOT(delFunctionArg()), ui->argsToolBar);
+ ui->argsToolBar->addSeparator();
+ createAction(ARG_MOVE_UP, ICONS.MOVE_UP, tr("Move function argument up"), this, SLOT(moveFunctionArgUp()), ui->argsToolBar);
+ createAction(ARG_MOVE_DOWN, ICONS.MOVE_DOWN, tr("Move function argument down"), this, SLOT(moveFunctionArgDown()), ui->argsToolBar);
+
+#ifdef Q_OS_MACX
+ QStyle *fusion = QStyleFactory::create("Fusion");
+ ui->toolBar->setStyle(fusion);
+ ui->argsToolBar->setStyle(fusion);
+#endif
+}
+
+void FunctionsEditor::setupDefShortcuts()
+{
+}
+
+QToolBar* FunctionsEditor::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolBar;
+}
+
+void FunctionsEditor::init()
+{
+ ui->setupUi(this);
+ clearEdits();
+ ui->initCodeGroup->setVisible(false);
+ ui->finalCodeGroup->setVisible(false);
+
+ setFont(CFG_UI.Fonts.SqlEditor.get());
+
+ model = new FunctionsEditorModel(this);
+ functionFilterModel = new QSortFilterProxyModel(this);
+ functionFilterModel->setSourceModel(model);
+ ui->list->setModel(functionFilterModel);
+
+ dbListModel = new SelectableDbModel(this);
+ dbListModel->setSourceModel(DBTREE->getModel());
+ ui->databasesList->setModel(dbListModel);
+ ui->databasesList->expandAll();
+
+ ui->typeCombo->addItem(tr("Scalar"), FunctionManager::ScriptFunction::SCALAR);
+ ui->typeCombo->addItem(tr("Aggregate"), FunctionManager::ScriptFunction::AGGREGATE);
+
+ new UserInputFilter(ui->functionFilterEdit, this, SLOT(applyFilter(QString)));
+ functionFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+
+ initActions();
+
+ connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(functionSelected(QItemSelection,QItemSelection)));
+ connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState()));
+ connect(ui->initCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified()));
+ connect(ui->mainCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified()));
+ connect(ui->finalCodeEdit, SIGNAL(textChanged()), this, SLOT(updateModified()));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateModified()));
+ connect(ui->undefArgsCheck, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->allDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->selDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->langCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified()));
+ connect(ui->typeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateModified()));
+
+ connect(ui->argsList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateArgsState()));
+ connect(ui->argsList->model(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateModified()));
+ connect(ui->argsList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified()));
+ connect(ui->argsList->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateModified()));
+ connect(ui->argsList->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateModified()));
+
+ connect(dbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified()));
+ connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant)));
+
+ model->setData(FUNCTIONS->getAllScriptFunctions());
+
+ // Language plugins
+ foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ scriptingPlugins[plugin->getLanguage()] = plugin;
+
+ ui->langCombo->addItems(scriptingPlugins.keys());
+
+ // Syntax highlighting plugins
+ foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
+ highlighterPlugins[plugin->getLanguageName()] = plugin;
+
+ updateState();
+}
+
+int FunctionsEditor::getCurrentFunctionRow() const
+{
+ QModelIndexList idxList = ui->list->selectionModel()->selectedIndexes();
+ if (idxList.size() == 0)
+ return -1;
+
+ return idxList.first().row();
+}
+
+void FunctionsEditor::functionDeselected(int row)
+{
+ model->setName(row, ui->nameEdit->text());
+ 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->setModified(row, currentModified);
+
+ if (model->isAggregate(row))
+ {
+ model->setInitCode(row, ui->initCodeEdit->toPlainText());
+ model->setFinalCode(row, ui->finalCodeEdit->toPlainText());
+ }
+ else
+ {
+ model->setInitCode(row, QString::null);
+ model->setFinalCode(row, QString::null);
+ }
+
+ if (!ui->undefArgsCheck->isChecked())
+ model->setArguments(row, getCurrentArgList());
+
+ if (ui->selDatabasesRadio->isChecked())
+ model->setDatabases(row, getCurrentDatabases());
+
+ model->validateNames();
+}
+
+void FunctionsEditor::functionSelected(int row)
+{
+ updatesForSelection = true;
+ ui->nameEdit->setText(model->getName(row));
+ ui->initCodeEdit->setPlainText(model->getInitCode(row));
+ ui->mainCodeEdit->setPlainText(model->getCode(row));
+ ui->finalCodeEdit->setPlainText(model->getFinalCode(row));
+ ui->undefArgsCheck->setChecked(model->getUndefinedArgs(row));
+ ui->langCombo->setCurrentText(model->getLang(row));
+
+ // Arguments
+ ui->argsList->clear();
+ QListWidgetItem* item = nullptr;
+ foreach (const QString& arg, model->getArguments(row))
+ {
+ item = new QListWidgetItem(arg);
+ item->setFlags(item->flags() | Qt::ItemIsEditable);
+ ui->argsList->addItem(item);
+ }
+
+ // Databases
+ dbListModel->setDatabases(model->getDatabases(row));
+ ui->databasesList->expandAll();
+
+ if (model->getAllDatabases(row))
+ ui->allDatabasesRadio->setChecked(true);
+ else
+ ui->selDatabasesRadio->setChecked(true);
+
+ // Type
+ FunctionManager::ScriptFunction::Type type = model->getType(row);
+ for (int i = 0; i < ui->typeCombo->count(); i++)
+ {
+ if (ui->typeCombo->itemData(i).toInt() == type)
+ {
+ ui->typeCombo->setCurrentIndex(i);
+ break;
+ }
+ }
+
+ updatesForSelection = false;
+ currentModified = false;
+
+ updateCurrentFunctionState();
+}
+
+void FunctionsEditor::clearEdits()
+{
+ ui->nameEdit->setText(QString::null);
+ ui->mainCodeEdit->setPlainText(QString::null);
+ ui->langCombo->setCurrentText(QString::null);
+ ui->undefArgsCheck->setChecked(true);
+ ui->argsList->clear();
+ ui->allDatabasesRadio->setChecked(true);
+ ui->typeCombo->setCurrentIndex(0);
+ ui->langCombo->setCurrentIndex(-1);
+}
+
+void FunctionsEditor::selectFunction(int row)
+{
+ if (!model->isValidRowIndex(row))
+ return;
+
+ ui->list->selectionModel()->setCurrentIndex(model->index(row), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+void FunctionsEditor::setFont(const QFont& font)
+{
+ ui->initCodeEdit->setFont(font);
+ ui->mainCodeEdit->setFont(font);
+ ui->finalCodeEdit->setFont(font);
+}
+
+QModelIndex FunctionsEditor::getSelectedArg() const
+{
+ QModelIndexList indexes = ui->argsList->selectionModel()->selectedIndexes();
+ if (indexes.size() == 0 || !indexes.first().isValid())
+ return QModelIndex();
+
+ return indexes.first();
+
+}
+
+QStringList FunctionsEditor::getCurrentArgList() const
+{
+ QStringList currArgList;
+ for (int row = 0; row < ui->argsList->model()->rowCount(); row++)
+ currArgList << ui->argsList->item(row)->text();
+
+ return currArgList;
+}
+
+QStringList FunctionsEditor::getCurrentDatabases() const
+{
+ return dbListModel->getDatabases();
+}
+
+FunctionManager::ScriptFunction::Type FunctionsEditor::getCurrentFunctionType() const
+{
+ int intValue = ui->typeCombo->itemData(ui->typeCombo->currentIndex()).toInt();
+ return static_cast<FunctionManager::ScriptFunction::Type>(intValue);
+}
+
+void FunctionsEditor::commit()
+{
+ int row = getCurrentFunctionRow();
+ if (model->isValidRowIndex(row))
+ functionDeselected(row);
+
+ QList<FunctionManager::ScriptFunction*> functions = model->generateFunctions();
+
+ FUNCTIONS->setScriptFunctions(functions);
+ model->clearModified();
+ currentModified = false;
+
+ if (model->isValidRowIndex(row))
+ selectFunction(row);
+
+ updateState();
+}
+
+void FunctionsEditor::rollback()
+{
+ int selectedBefore = getCurrentFunctionRow();
+
+ model->setData(FUNCTIONS->getAllScriptFunctions());
+ currentModified = false;
+ clearEdits();
+
+ if (model->isValidRowIndex(selectedBefore))
+ selectFunction(selectedBefore);
+
+ updateState();
+}
+
+void FunctionsEditor::newFunction()
+{
+ if (ui->langCombo->currentIndex() == -1 && ui->langCombo->count() > 0)
+ ui->langCombo->setCurrentIndex(0);
+
+ FunctionManager::ScriptFunction* func = new FunctionManager::ScriptFunction();
+ func->name = generateUniqueName("function", model->getFunctionNames());
+
+ if (ui->langCombo->currentIndex() > -1)
+ func->lang = ui->langCombo->currentText();
+
+ model->addFunction(func);
+
+ selectFunction(model->rowCount() - 1);
+}
+
+void FunctionsEditor::deleteFunction()
+{
+ int row = getCurrentFunctionRow();
+ model->deleteFunction(row);
+ clearEdits();
+
+ row = getCurrentFunctionRow();
+ if (model->isValidRowIndex(row))
+ functionSelected(row);
+
+ updateState();
+}
+
+void FunctionsEditor::updateModified()
+{
+ if (updatesForSelection)
+ return;
+
+ int row = getCurrentFunctionRow();
+ if (model->isValidRowIndex(row))
+ {
+ bool nameDiff = model->getName(row) != ui->nameEdit->text();
+ bool codeDiff = model->getCode(row) != ui->mainCodeEdit->toPlainText();
+ bool initCodeDiff = model->getInitCode(row) != ui->initCodeEdit->toPlainText();
+ bool finalCodeDiff = model->getFinalCode(row) != ui->finalCodeEdit->toPlainText();
+ bool langDiff = model->getLang(row) != ui->langCombo->currentText();
+ bool undefArgsDiff = model->getUndefinedArgs(row) != ui->undefArgsCheck->isChecked();
+ bool allDatabasesDiff = model->getAllDatabases(row) != ui->allDatabasesRadio->isChecked();
+ bool argDiff = getCurrentArgList() != model->getArguments(row);
+ bool dbDiff = getCurrentDatabases().toSet() != model->getDatabases(row).toSet(); // QSet to ignore order
+ bool typeDiff = model->getType(row) != getCurrentFunctionType();
+
+ currentModified = (nameDiff || codeDiff || typeDiff || langDiff || undefArgsDiff || allDatabasesDiff || argDiff || dbDiff ||
+ initCodeDiff || finalCodeDiff);
+ }
+
+ updateCurrentFunctionState();
+}
+
+void FunctionsEditor::updateState()
+{
+ bool modified = model->isModified() || currentModified;
+ bool valid = model->isValid();
+
+ actionMap[COMMIT]->setEnabled(modified && valid);
+ actionMap[ROLLBACK]->setEnabled(modified);
+ actionMap[DELETE]->setEnabled(ui->list->selectionModel()->selectedIndexes().size() > 0);
+}
+
+void FunctionsEditor::updateCurrentFunctionState()
+{
+ int row = getCurrentFunctionRow();
+ bool validRow = model->isValidRowIndex(row);
+ ui->rightWidget->setEnabled(validRow);
+ if (!validRow)
+ {
+ setValidState(ui->langCombo, true);
+ setValidState(ui->nameEdit, true);
+ setValidState(ui->mainCodeEdit, true);
+ setValidState(ui->finalCodeEdit, true);
+ return;
+ }
+
+ 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."));
+
+ bool langOk = ui->langCombo->currentIndex() >= 0;
+ ui->initCodeGroup->setEnabled(langOk);
+ ui->mainCodeGroup->setEnabled(langOk);
+ ui->finalCodeGroup->setEnabled(langOk);
+ ui->argsGroup->setEnabled(langOk);
+ ui->databasesGroup->setEnabled(langOk);
+ ui->nameEdit->setEnabled(langOk);
+ ui->nameLabel->setEnabled(langOk);
+ ui->typeCombo->setEnabled(langOk);
+ ui->typeLabel->setEnabled(langOk);
+ setValidState(ui->langCombo, langOk, tr("Pick the implementation language."));
+
+ bool aggregate = getCurrentFunctionType() == FunctionManager::ScriptFunction::AGGREGATE;
+ ui->initCodeGroup->setVisible(aggregate);
+ ui->mainCodeGroup->setTitle(aggregate ? tr("Per step code:") : tr("Function implementation code:"));
+ ui->finalCodeGroup->setVisible(aggregate);
+
+ ui->databasesList->setEnabled(ui->selDatabasesRadio->isChecked());
+
+ bool codeOk = !ui->mainCodeEdit->toPlainText().trimmed().isEmpty();
+ setValidState(ui->mainCodeEdit, codeOk, tr("Enter a non-empty implementation code."));
+
+ bool finalCodeOk = true;
+ if (aggregate)
+ finalCodeOk = !ui->finalCodeEdit->toPlainText().trimmed().isEmpty();
+
+ setValidState(ui->finalCodeEdit, finalCodeOk);
+
+ // Syntax highlighter
+ QString lang = ui->langCombo->currentText();
+ if (lang != currentHighlighterLang)
+ {
+ QSyntaxHighlighter* highlighter = nullptr;
+ if (currentMainHighlighter)
+ {
+ // A pointers swap with local var - this is necessary, cause deleting highlighter
+ // triggers textChanged on QPlainTextEdit, which then calls this method,
+ // so it becomes an infinite recursion with deleting the same pointer.
+ // We set the pointer to null first, then delete it. That way it's safe.
+ highlighter = currentMainHighlighter;
+ currentMainHighlighter = nullptr;
+ delete highlighter;
+ }
+
+ if (currentFinalHighlighter)
+ {
+ highlighter = currentFinalHighlighter;
+ currentFinalHighlighter = nullptr;
+ delete highlighter;
+ }
+
+ if (currentInitHighlighter)
+ {
+ highlighter = currentInitHighlighter;
+ currentInitHighlighter = nullptr;
+ delete highlighter;
+ }
+
+ if (langOk && highlighterPlugins.contains(lang))
+ {
+ currentInitHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->initCodeEdit);
+ currentMainHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->mainCodeEdit);
+ currentFinalHighlighter = highlighterPlugins[lang]->createSyntaxHighlighter(ui->finalCodeEdit);
+ }
+
+ currentHighlighterLang = lang;
+ }
+
+ updateArgsState();
+ model->setValid(row, langOk && codeOk && finalCodeOk && nameOk);
+ updateState();
+}
+
+void FunctionsEditor::functionSelected(const QItemSelection& selected, const QItemSelection& deselected)
+{
+ int deselCnt = deselected.indexes().size();
+ int selCnt = selected.indexes().size();
+
+ if (deselCnt > 0)
+ functionDeselected(deselected.indexes().first().row());
+
+ if (selCnt > 0)
+ functionSelected(selected.indexes().first().row());
+
+ if (deselCnt > 0 && selCnt == 0)
+ {
+ currentModified = false;
+ clearEdits();
+ }
+}
+
+void FunctionsEditor::addFunctionArg()
+{
+ QListWidgetItem* item = new QListWidgetItem(tr("argument", "new function argument name in function editor window"));
+ item->setFlags(item->flags () | Qt::ItemIsEditable);
+ ui->argsList->addItem(item);
+
+ QModelIndex idx = ui->argsList->model()->index(ui->argsList->model()->rowCount() - 1, 0);
+ ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+
+ ui->argsList->editItem(item);
+}
+
+void FunctionsEditor::editFunctionArg()
+{
+ QModelIndex selected = getSelectedArg();
+ if (!selected.isValid())
+ return;
+
+ int row = selected.row();
+ QListWidgetItem* item = ui->argsList->item(row);
+ ui->argsList->editItem(item);
+}
+
+void FunctionsEditor::delFunctionArg()
+{
+ QModelIndex selected = getSelectedArg();
+ if (!selected.isValid())
+ return;
+
+ int row = selected.row();
+ delete ui->argsList->takeItem(row);
+}
+
+void FunctionsEditor::moveFunctionArgUp()
+{
+ QModelIndex selected = getSelectedArg();
+ if (!selected.isValid())
+ return;
+
+ int row = selected.row();
+ if (row <= 0)
+ return;
+
+ ui->argsList->insertItem(row - 1, ui->argsList->takeItem(row));
+
+ QModelIndex idx = ui->argsList->model()->index(row - 1, 0);
+ ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+void FunctionsEditor::moveFunctionArgDown()
+{
+ QModelIndex selected = getSelectedArg();
+ if (!selected.isValid())
+ return;
+
+ int row = selected.row();
+ if (row >= ui->argsList->model()->rowCount() - 1)
+ return;
+
+ ui->argsList->insertItem(row + 1, ui->argsList->takeItem(row));
+
+ QModelIndex idx = ui->argsList->model()->index(row + 1, 0);
+ ui->argsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+void FunctionsEditor::updateArgsState()
+{
+ bool argsEnabled = !ui->undefArgsCheck->isChecked();
+ QModelIndexList indexes = ui->argsList->selectionModel()->selectedIndexes();
+ bool argSelected = indexes.size() > 0;
+
+ bool canMoveUp = false;
+ bool canMoveDown = false;
+ if (argSelected)
+ {
+ canMoveUp = indexes.first().row() > 0;
+ canMoveDown = (indexes.first().row() + 1) < ui->argsList->count();
+ }
+
+ actionMap[ARG_ADD]->setEnabled(argsEnabled);
+ actionMap[ARG_EDIT]->setEnabled(argsEnabled && argSelected);
+ actionMap[ARG_DEL]->setEnabled(argsEnabled && argSelected);
+ actionMap[ARG_MOVE_UP]->setEnabled(argsEnabled && canMoveUp);
+ actionMap[ARG_MOVE_DOWN]->setEnabled(argsEnabled && canMoveDown);
+ ui->argsList->setEnabled(argsEnabled);
+}
+
+void FunctionsEditor::applyFilter(const QString& value)
+{
+ // Remembering old selection, clearing it and restoring afterwards is a workaround for a problem,
+ // which causees application to crash, when the item was selected, but after applying filter string,
+ // item was about to disappear.
+ // This must have something to do with the underlying model (FunctionsEditorModel) implementation,
+ // but for now I don't really know what is that.
+ // I have tested simple Qt application with the same routine, but the underlying model was QStandardItemModel
+ // and everything worked fine.
+ int row = getCurrentFunctionRow();
+ ui->list->selectionModel()->clearSelection();
+
+ functionFilterModel->setFilterFixedString(value);
+
+ selectFunction(row);
+}
+
+void FunctionsEditor::help()
+{
+ static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions");
+ QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
+}
+
+void FunctionsEditor::changeFont(const QVariant& font)
+{
+ setFont(font.value<QFont>());
+}
+
+QVariant FunctionsEditor::saveSession()
+{
+ return QVariant();
+}
+
+
+bool FunctionsEditor::isUncommited() const
+{
+ return model->isModified();
+}
+
+QString FunctionsEditor::getQuitUncommitedConfirmMessage() const
+{
+ return tr("Functions editor window has uncommited modifications.");
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h
new file mode 100644
index 0000000..0fa496e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.h
@@ -0,0 +1,109 @@
+#ifndef FUNCTIONSEDITOR_H
+#define FUNCTIONSEDITOR_H
+
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include "services/config.h"
+#include "services/functionmanager.h"
+#include <QItemSelection>
+#include <QSortFilterProxyModel>
+
+namespace Ui {
+class FunctionsEditor;
+}
+
+class FunctionsEditorModel;
+class ScriptingPlugin;
+class SyntaxHighlighterPlugin;
+class DbTreeItem;
+class QTreeWidgetItem;
+class QSyntaxHighlighter;
+class SelectableDbModel;
+
+class GUI_API_EXPORT FunctionsEditor : public MdiChild
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ COMMIT,
+ ROLLBACK,
+ ADD,
+ DELETE,
+ ARG_ADD,
+ ARG_EDIT,
+ ARG_DEL,
+ ARG_MOVE_UP,
+ ARG_MOVE_DOWN,
+ HELP
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR
+ };
+
+ explicit FunctionsEditor(QWidget *parent = 0);
+ ~FunctionsEditor();
+
+ bool restoreSessionNextTime();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+
+ protected:
+ QVariant saveSession();
+ bool restoreSession(const QVariant &sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ int getCurrentFunctionRow() const;
+ void functionDeselected(int row);
+ void functionSelected(int row);
+ void clearEdits();
+ void selectFunction(int row);
+ void setFont(const QFont& font);
+ QModelIndex getSelectedArg() const;
+ QStringList getCurrentArgList() const;
+ QStringList getCurrentDatabases() const;
+ FunctionManager::ScriptFunction::Type getCurrentFunctionType() const;
+
+ Ui::FunctionsEditor *ui = nullptr;
+ FunctionsEditorModel* model = nullptr;
+ QSortFilterProxyModel* functionFilterModel = nullptr;
+ bool currentModified = false;
+ QHash<QString,ScriptingPlugin*> scriptingPlugins;
+ QHash<QString,SyntaxHighlighterPlugin*> highlighterPlugins;
+ SelectableDbModel* dbListModel = nullptr;
+ QString currentHighlighterLang;
+ QSyntaxHighlighter* currentMainHighlighter = nullptr;
+ QSyntaxHighlighter* currentFinalHighlighter = nullptr;
+ QSyntaxHighlighter* currentInitHighlighter = nullptr;
+ bool updatesForSelection = false;
+
+ private slots:
+ void commit();
+ void rollback();
+ void newFunction();
+ void deleteFunction();
+ void updateModified();
+ void updateState();
+ void updateCurrentFunctionState();
+ void functionSelected(const QItemSelection& selected, const QItemSelection& deselected);
+ void addFunctionArg();
+ void editFunctionArg();
+ void delFunctionArg();
+ void moveFunctionArgUp();
+ void moveFunctionArgDown();
+ void updateArgsState();
+ void applyFilter(const QString& value);
+ void help();
+ void changeFont(const QVariant& font);
+};
+
+#endif // FUNCTIONSEDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui
new file mode 100644
index 0000000..d5d5015
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui
@@ -0,0 +1,346 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FunctionsEditor</class>
+ <widget class="QWidget" name="FunctionsEditor">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>816</width>
+ <height>621</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="toolBar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="mainWidget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="leftWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="functionFilterEdit">
+ <property name="minimumSize">
+ <size>
+ <width>150</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="placeholderText">
+ <string>Filter funtions</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="list">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="rightWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QWidget" name="topWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Function name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QComboBox" name="typeCombo"/>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="langLabel">
+ <property name="text">
+ <string>Implementation language:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="typeLabel">
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QComboBox" name="langCombo"/>
+ </item>
+ <item row="4" column="0">
+ <widget class="QGroupBox" name="argsGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Input arguments</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="undefArgsCheck">
+ <property name="text">
+ <string>Undefined</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" rowspan="5" colspan="3">
+ <widget class="QListWidget" name="argsList">
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QToolBar" name="argsToolBar"/>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="4" column="1" colspan="3">
+ <widget class="QGroupBox" name="databasesGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Databases</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QRadioButton" name="allDatabasesRadio">
+ <property name="text">
+ <string>Register in all databases</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="selDatabasesRadio">
+ <property name="text">
+ <string>Register in following databases:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="databasesList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="headerDefaultSectionSize">
+ <number>0</number>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QGroupBox" name="initCodeGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Initialization code:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QPlainTextEdit" name="initCodeEdit"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QGroupBox" name="mainCodeGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Function implementation code:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QPlainTextEdit" name="mainCodeEdit"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QGroupBox" name="finalCodeGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Final step implementation code:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QPlainTextEdit" name="finalCodeEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>functionFilterEdit</tabstop>
+ <tabstop>list</tabstop>
+ <tabstop>nameEdit</tabstop>
+ <tabstop>typeCombo</tabstop>
+ <tabstop>langCombo</tabstop>
+ <tabstop>undefArgsCheck</tabstop>
+ <tabstop>argsList</tabstop>
+ <tabstop>allDatabasesRadio</tabstop>
+ <tabstop>selDatabasesRadio</tabstop>
+ <tabstop>databasesList</tabstop>
+ <tabstop>initCodeEdit</tabstop>
+ <tabstop>mainCodeEdit</tabstop>
+ <tabstop>finalCodeEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp
new file mode 100644
index 0000000..cf7efdf
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp
@@ -0,0 +1,348 @@
+#include "functionseditormodel.h"
+#include "common/strhash.h"
+#include "common/unused.h"
+#include "services/pluginmanager.h"
+#include "plugins/scriptingplugin.h"
+#include "icon.h"
+#include <QDebug>
+
+#define SETTER(X, Y) \
+ if (!isValidRowIndex(row) || X == Y) \
+ return; \
+ \
+ X = Y; \
+ emitDataChanged(row);
+
+#define GETTER(X, Y) \
+ if (!isValidRowIndex(row)) \
+ return Y; \
+ \
+ return X;
+
+FunctionsEditorModel::FunctionsEditorModel(QObject *parent) :
+ QAbstractListModel(parent)
+{
+ init();
+}
+
+void FunctionsEditorModel::clearModified()
+{
+ beginResetModel();
+ foreach (Function* func, functionList)
+ func->modified = false;
+
+ listModified = false;
+ originalFunctionList = functionList;
+
+ endResetModel();
+}
+
+bool FunctionsEditorModel::isModified() const
+{
+ if (functionList != originalFunctionList)
+ return true;
+
+ foreach (Function* func, functionList)
+ {
+ if (func->modified)
+ return true;
+ }
+ return false;
+}
+
+bool FunctionsEditorModel::isModified(int row) const
+{
+ GETTER(functionList[row]->modified, false);
+}
+
+void FunctionsEditorModel::setModified(int row, bool modified)
+{
+ SETTER(functionList[row]->modified, modified);
+}
+
+bool FunctionsEditorModel::isValid() const
+{
+ foreach (Function* func, functionList)
+ {
+ if (!func->valid)
+ return false;
+ }
+ return true;
+}
+
+bool FunctionsEditorModel::isValid(int row) const
+{
+ GETTER(functionList[row]->valid, false);
+}
+
+void FunctionsEditorModel::setValid(int row, bool valid)
+{
+ SETTER(functionList[row]->valid, valid);
+}
+
+void FunctionsEditorModel::setCode(int row, const QString& code)
+{
+ SETTER(functionList[row]->data.code, code);
+}
+
+QString FunctionsEditorModel::getCode(int row) const
+{
+ GETTER(functionList[row]->data.code, QString::null);
+}
+
+void FunctionsEditorModel::setFinalCode(int row, const QString& code)
+{
+ SETTER(functionList[row]->data.finalCode, code);
+}
+
+QString FunctionsEditorModel::getFinalCode(int row) const
+{
+ GETTER(functionList[row]->data.finalCode, QString::null);
+}
+
+void FunctionsEditorModel::setInitCode(int row, const QString& code)
+{
+ SETTER(functionList[row]->data.initCode, code);
+}
+
+QString FunctionsEditorModel::getInitCode(int row) const
+{
+ GETTER(functionList[row]->data.initCode, QString::null);
+}
+
+void FunctionsEditorModel::setName(int row, const QString& newName)
+{
+ SETTER(functionList[row]->data.name, newName);
+}
+
+QString FunctionsEditorModel::getName(int row) const
+{
+ GETTER(functionList[row]->data.name, QString::null);
+}
+
+void FunctionsEditorModel::setLang(int row, const QString& lang)
+{
+ SETTER(functionList[row]->data.lang, lang);
+}
+
+QString FunctionsEditorModel::getLang(int row) const
+{
+ GETTER(functionList[row]->data.lang, QString::null);
+}
+
+bool FunctionsEditorModel::getUndefinedArgs(int row) const
+{
+ GETTER(functionList[row]->data.undefinedArgs, true);
+}
+
+void FunctionsEditorModel::setUndefinedArgs(int row, bool value)
+{
+ SETTER(functionList[row]->data.undefinedArgs, value);
+}
+
+bool FunctionsEditorModel::getAllDatabases(int row) const
+{
+ GETTER(functionList[row]->data.allDatabases, true);
+}
+
+void FunctionsEditorModel::setAllDatabases(int row, bool value)
+{
+ SETTER(functionList[row]->data.allDatabases, value);
+}
+
+FunctionManager::ScriptFunction::Type FunctionsEditorModel::getType(int row) const
+{
+ GETTER(functionList[row]->data.type, FunctionManager::ScriptFunction::SCALAR);
+}
+
+void FunctionsEditorModel::setType(int row, FunctionManager::ScriptFunction::Type type)
+{
+ SETTER(functionList[row]->data.type, type);
+}
+
+bool FunctionsEditorModel::isAggregate(int row) const
+{
+ GETTER(functionList[row]->data.type == FunctionManager::ScriptFunction::AGGREGATE, false);
+}
+
+bool FunctionsEditorModel::isScalar(int row) const
+{
+ GETTER(functionList[row]->data.type == FunctionManager::ScriptFunction::SCALAR, false);
+}
+
+QStringList FunctionsEditorModel::getArguments(int row) const
+{
+ GETTER(functionList[row]->data.arguments, QStringList());
+}
+
+void FunctionsEditorModel::setArguments(int row, const QStringList& value)
+{
+ SETTER(functionList[row]->data.arguments, value);
+}
+
+QStringList FunctionsEditorModel::getDatabases(int row) const
+{
+ GETTER(functionList[row]->data.databases, QStringList());
+}
+
+void FunctionsEditorModel::setDatabases(int row, const QStringList& value)
+{
+ SETTER(functionList[row]->data.databases, value);
+}
+
+void FunctionsEditorModel::setData(const QList<FunctionManager::ScriptFunction*>& functions)
+{
+ beginResetModel();
+
+ for (Function* functionPtr : functionList)
+ delete functionPtr;
+
+ functionList.clear();
+
+ foreach (FunctionManager::ScriptFunction* func, functions)
+ functionList << new Function(func);
+
+ listModified = false;
+ originalFunctionList = functionList;
+
+ endResetModel();
+}
+
+void FunctionsEditorModel::addFunction(FunctionManager::ScriptFunction* function)
+{
+ int row = functionList.size();
+
+ beginInsertRows(QModelIndex(), row, row);
+
+ functionList << new Function(function);
+ listModified = true;
+
+ endInsertRows();
+}
+
+void FunctionsEditorModel::deleteFunction(int row)
+{
+ if (!isValidRowIndex(row))
+ return;
+
+ beginRemoveRows(QModelIndex(), row, row);
+
+ delete functionList[row];
+ functionList.removeAt(row);
+
+ listModified = true;
+
+ endRemoveRows();
+}
+
+QList<FunctionManager::ScriptFunction*> FunctionsEditorModel::generateFunctions() const
+{
+ QList<FunctionManager::ScriptFunction*> results;
+
+ foreach (Function* func, functionList)
+ results << new FunctionManager::ScriptFunction(func->data);
+
+ return results;
+}
+
+QStringList FunctionsEditorModel::getFunctionNames() const
+{
+ QStringList names;
+ foreach (Function* func, functionList)
+ names << func->data.name;
+
+ return names;
+}
+
+void FunctionsEditorModel::validateNames()
+{
+ StrHash<QList<int>> counter;
+
+ int row = 0;
+ foreach (Function* func, functionList)
+ {
+ func->valid &= true;
+ counter[func->data.name] << row++;
+ }
+
+ QHashIterator<QString,QList<int>> cntIt = counter.iterator();
+ while (cntIt.hasNext())
+ {
+ cntIt.next();
+ if (cntIt.value().size() > 1)
+ {
+ foreach (int cntRow, cntIt.value())
+ setValid(cntRow, false);
+ }
+ }
+
+ QModelIndex idx;
+ for (int i = 0; i < functionList.size(); i++)
+ {
+ idx = index(i);
+ emit dataChanged(idx, idx);
+ }
+}
+
+bool FunctionsEditorModel::isAllowedName(int rowToSkip, const QString& nameToValidate)
+{
+ QStringList names = getFunctionNames();
+ names.removeAt(rowToSkip);
+ return !names.contains(nameToValidate, Qt::CaseInsensitive);
+}
+
+int FunctionsEditorModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return functionList.size();
+}
+
+QVariant FunctionsEditorModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || !isValidRowIndex(index.row()))
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ {
+ Function* fn = functionList[index.row()];
+ return fn->data.toString();
+ }
+
+ if (role == Qt::DecorationRole && langToIcon.contains(functionList[index.row()]->data.lang))
+ {
+ QIcon icon = langToIcon[functionList[index.row()]->data.lang];
+ if (!functionList[index.row()]->valid)
+ icon = Icon::merge(icon, Icon::ERROR);
+
+ return icon;
+ }
+
+ return QVariant();
+}
+
+void FunctionsEditorModel::init()
+{
+ foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ langToIcon[plugin->getLanguage()] = QIcon(plugin->getIconPath());
+}
+
+bool FunctionsEditorModel::isValidRowIndex(int row) const
+{
+ return (row >= 0 && row < functionList.size());
+}
+
+void FunctionsEditorModel::emitDataChanged(int row)
+{
+ QModelIndex idx = index(row);
+ emit dataChanged(idx, idx);
+}
+
+FunctionsEditorModel::Function::Function()
+{
+}
+
+FunctionsEditorModel::Function::Function(FunctionManager::ScriptFunction* other)
+{
+ data = FunctionManager::ScriptFunction(*other);
+ originalName = data.name;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h
new file mode 100644
index 0000000..79f073f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.h
@@ -0,0 +1,98 @@
+#ifndef FUNCTIONSEDITORMODEL_H
+#define FUNCTIONSEDITORMODEL_H
+
+#include "services/config.h"
+#include "services/functionmanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QIcon>
+#include <QAbstractListModel>
+
+class GUI_API_EXPORT FunctionsEditorModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ public:
+ using QAbstractItemModel::setData;
+
+ enum Role
+ {
+ CODE = 1000,
+ MODIFIED = 1001,
+ VALID = 1002,
+ TYPE = 1003
+ };
+
+ explicit FunctionsEditorModel(QObject *parent = 0);
+
+ void clearModified();
+ bool isModified() const;
+ bool isModified(int row) const;
+ void setModified(int row, bool modified);
+ bool isValid() const;
+ bool isValid(int row) const;
+ void setValid(int row, bool valid);
+ void setCode(int row, const QString& code);
+ QString getCode(int row) const;
+ void setFinalCode(int row, const QString& code);
+ QString getFinalCode(int row) const;
+ void setInitCode(int row, const QString& code);
+ QString getInitCode(int row) const;
+ void setName(int row, const QString& newName);
+ QString getName(int row) const;
+ void setLang(int row, const QString& lang);
+ QString getLang(int row) const;
+ QStringList getDatabases(int row) const;
+ void setDatabases(int row, const QStringList& value);
+ QStringList getArguments(int row) const;
+ void setArguments(int row, const QStringList& value);
+ FunctionManager::ScriptFunction::Type getType(int row) const;
+ void setType(int row, FunctionManager::ScriptFunction::Type type);
+ bool isAggregate(int row) const;
+ bool isScalar(int row) const;
+ bool getUndefinedArgs(int row) const;
+ void setUndefinedArgs(int row, bool value);
+ bool getAllDatabases(int row) const;
+ void setAllDatabases(int row, bool value);
+ void setData(const QList<FunctionManager::ScriptFunction*>& functions);
+ void addFunction(FunctionManager::ScriptFunction* function);
+ void deleteFunction(int row);
+ QList<FunctionManager::ScriptFunction*> generateFunctions() const;
+ QStringList getFunctionNames() const;
+ void validateNames();
+ bool isAllowedName(int rowToSkip, const QString& nameToValidate);
+ bool isValidRowIndex(int row) const;
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+
+ private:
+ struct Function
+ {
+ Function();
+ Function(FunctionManager::ScriptFunction* other);
+
+ FunctionManager::ScriptFunction data;
+ bool modified = false;
+ bool valid = true;
+ QString originalName;
+ };
+
+ void init();
+ void emitDataChanged(int row);
+
+ QList<Function*> functionList;
+
+ /**
+ * @brief List of function pointers before modifications.
+ *
+ * This list is kept to check for modifications in the overall list of functions.
+ * Pointers on this list may be already deleted, so don't use them!
+ * It's only used to compare list of pointers to functionList, so it can tell you
+ * if the list was modified in regards of adding or deleting functions.
+ */
+ QList<Function*> originalFunctionList;
+ QHash<QString,QIcon> langToIcon;
+ bool listModified = false;
+};
+
+#endif // FUNCTIONSEDITORMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp
new file mode 100644
index 0000000..850d8a7
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp
@@ -0,0 +1,482 @@
+#include "tableconstraintsmodel.h"
+#include "iconmanager.h"
+#include "common/utils_sql.h"
+#include "common/unused.h"
+#include <QDebug>
+#include <QMimeData>
+
+TableConstraintsModel::TableConstraintsModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+}
+
+int TableConstraintsModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (createTable.isNull())
+ return 0;
+
+ return createTable->constraints.size();
+}
+
+int TableConstraintsModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return 3;
+}
+
+QVariant TableConstraintsModel::data(const QModelIndex& index, int role) const
+{
+ if (createTable.isNull())
+ return QVariant();
+
+ SqliteCreateTable::Constraint* constr = createTable->constraints[index.row()];
+ switch (getColumn(index.column()))
+ {
+ case Columns::TYPE:
+ {
+ if (role == Qt::DisplayRole)
+ return getTypeLabel(constr->type);
+
+ if (role == Qt::DecorationRole)
+ return getTypeIcon(constr->type);
+
+ break;
+ }
+ case Columns::NAME:
+ {
+ if (role == Qt::DisplayRole)
+ return stripObjName(constr->name, createTable->dialect);
+
+ break;
+ }
+ case Columns::DETAILS:
+ {
+ if (role == Qt::DisplayRole)
+ return getDetails(constr);
+
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant TableConstraintsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QAbstractTableModel::headerData(section, orientation, role);
+
+ if (orientation == Qt::Vertical)
+ return section + 1;
+
+ switch (getColumn(section))
+ {
+ case TableConstraintsModel::Columns::TYPE:
+ return tr("Type", "table constraints");
+ case TableConstraintsModel::Columns::DETAILS:
+ return tr("Details", "table constraints");
+ case TableConstraintsModel::Columns::NAME:
+ return tr("Name", "table constraints");
+ }
+ return QVariant();
+}
+
+Qt::DropActions TableConstraintsModel::supportedDropActions() const
+{
+ return Qt::MoveAction;
+}
+
+Qt::DropActions TableConstraintsModel::supportedDragActions() const
+{
+ return Qt::CopyAction|Qt::MoveAction;
+}
+
+QStringList TableConstraintsModel::mimeTypes() const
+{
+ return {mimeType};
+}
+
+QMimeData* TableConstraintsModel::mimeData(const QModelIndexList& indexes) const
+{
+ if (indexes.size() < 1)
+ return nullptr;
+
+ QModelIndex idx = indexes.first();
+
+ QMimeData *data = new QMimeData();
+
+ QByteArray output;
+ QDataStream stream(&output, QIODevice::WriteOnly);
+
+ stream << idx.row();
+ data->setData(mimeType, output);
+
+ return data;
+}
+
+bool TableConstraintsModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
+{
+ UNUSED(action);
+ UNUSED(row);
+ UNUSED(column);
+ UNUSED(parent);
+
+ if (!data)
+ return false;
+
+ if (!data->hasFormat(mimeType))
+ return false;
+
+ return true;
+}
+
+bool TableConstraintsModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+{
+ UNUSED(column);
+
+ if (action == Qt::IgnoreAction)
+ return true;
+
+ if (!data)
+ return false;
+
+ if (!data->hasFormat(mimeType))
+ return false;
+
+ if (action != Qt::MoveAction)
+ return false;
+
+ if (row < 0)
+ {
+ if (!parent.isValid() && !createTable.isNull())
+ row = createTable->constraints.size();
+ else
+ row = parent.row();
+ }
+
+ if (row < 0)
+ return false;
+
+ QByteArray byteData = data->data(mimeType);
+ QDataStream stream(&byteData, QIODevice::ReadOnly);
+ int oldRow;
+ stream >> oldRow;
+
+ moveConstraintColumnTo(oldRow, row);
+ return true;
+}
+
+Qt::ItemFlags TableConstraintsModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags defFlags = QAbstractItemModel::flags(index);
+ if (!index.isValid())
+ return defFlags|Qt::ItemIsDropEnabled;
+
+ return defFlags|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled;
+}
+
+bool TableConstraintsModel::isModified() const
+{
+ return modified;
+}
+
+void TableConstraintsModel::setCreateTable(SqliteCreateTable* value)
+{
+ beginResetModel();
+ createTable = value;
+ endResetModel();
+ modified = false;
+ emit modifiyStateChanged();
+}
+
+SqliteCreateTable::Constraint* TableConstraintsModel::getConstraint(int constrIdx) const
+{
+ if (createTable.isNull())
+ return nullptr;
+
+ return createTable->constraints[constrIdx];
+}
+
+void TableConstraintsModel::replaceConstraint(int constrIdx, SqliteCreateTable::Constraint* constr)
+{
+ if (createTable.isNull())
+ return;
+
+ delete createTable->constraints[constrIdx];
+ createTable->constraints[constrIdx] = constr;
+ constr->setParent(createTable);
+ modified = true;
+
+ emit modifiyStateChanged();
+ emit dataChanged(createIndex(constrIdx, 0), createIndex(constrIdx, columnCount()-1));
+}
+
+void TableConstraintsModel::constraintModified(int constrIdx)
+{
+ modified = true;
+
+ emit modifiyStateChanged();
+ emit dataChanged(createIndex(constrIdx, 0), createIndex(constrIdx, columnCount()-1));
+}
+
+void TableConstraintsModel::insertConstraint(int constrIdx, SqliteCreateTable::Constraint* constr)
+{
+ if (createTable.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), constrIdx, constrIdx);
+ createTable->constraints.insert(constrIdx, constr);
+ constr->setParent(createTable);
+ endInsertRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableConstraintsModel::appendConstraint(SqliteCreateTable::Constraint* constr)
+{
+ if (createTable.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ createTable->constraints.append(constr);
+ constr->setParent(createTable);
+ endInsertRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableConstraintsModel::delConstraint(int constrIdx)
+{
+ if (createTable.isNull())
+ return;
+
+ beginRemoveRows(QModelIndex(), constrIdx, constrIdx);
+ delete createTable->constraints[constrIdx];
+ createTable->constraints.removeAt(constrIdx);
+ endRemoveRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableConstraintsModel::moveConstraintUp(int constrIdx)
+{
+ moveConstraintColumnTo(constrIdx, constrIdx-1);
+}
+
+void TableConstraintsModel::moveConstraintDown(int constrIdx)
+{
+ moveConstraintColumnTo(constrIdx, constrIdx+1);
+}
+
+void TableConstraintsModel::moveConstraintColumnTo(int constrIdx, int newIdx)
+{
+ if (createTable.isNull())
+ return;
+
+ if (newIdx == constrIdx)
+ return;
+
+ if (newIdx == constrIdx+1)
+ {
+ // See TableStructureModel::moveColumnTo() for details above code below.
+ int tmpIdx = newIdx;
+ newIdx = constrIdx;
+ constrIdx = tmpIdx;
+ }
+
+ beginMoveRows(QModelIndex(), constrIdx, constrIdx, QModelIndex(), newIdx);
+ if (newIdx >= createTable->constraints.size())
+ {
+ SqliteCreateTable::Constraint* constr = createTable->constraints.takeAt(constrIdx);
+ createTable->constraints.append(constr);
+ }
+ else
+ createTable->constraints.move(constrIdx, newIdx);
+
+ endMoveRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+ emit constraintOrderChanged();
+}
+
+TableConstraintsModel::Columns TableConstraintsModel::getColumn(int idx) const
+{
+ return static_cast<Columns>(idx);
+}
+
+QString TableConstraintsModel::getTypeLabel(SqliteCreateTable::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return "PRIMARY KEY";
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return "UNIQUE";
+ case SqliteCreateTable::Constraint::CHECK:
+ return "CHECK";
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return "FOREIGN KEY";
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QIcon TableConstraintsModel::getTypeIcon(SqliteCreateTable::Constraint::Type type) const
+{
+ switch (type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return ICONS.CONSTRAINT_UNIQUE;
+ case SqliteCreateTable::Constraint::CHECK:
+ return ICONS.CONSTRAINT_CHECK;
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QIcon();
+ }
+ return QIcon();
+}
+
+QString TableConstraintsModel::getDetails(SqliteCreateTable::Constraint* constr) const
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return getPkDetails(constr);
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return getUniqueDetails(constr);
+ case SqliteCreateTable::Constraint::CHECK:
+ return getCheckDetails(constr);
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return getFkDetails(constr);
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ return QString::null;
+ }
+ return QString::null;
+}
+
+QString TableConstraintsModel::getPkDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString TableConstraintsModel::getUniqueDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString TableConstraintsModel::getCheckDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString TableConstraintsModel::getFkDetails(SqliteCreateTable::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx + 1);
+}
+
+QString TableConstraintsModel::getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const
+{
+ TokenList tokens = constr->tokens.mid(tokenOffset);
+ tokens.trimLeft();
+ return tokens.detokenize();
+}
+
+void TableConstraintsModel::columnRenamed(SqliteCreateTable::Constraint* constr, const QString& oldColumn, const QString& newColumn)
+{
+ foreach (SqliteIndexedColumn* idxCol, constr->indexedColumns)
+ {
+ if (idxCol->name.compare(oldColumn, Qt::CaseInsensitive) == 0)
+ {
+ idxCol->name = newColumn;
+ modified = true;
+ }
+ }
+
+ emit modifiyStateChanged();
+}
+
+bool TableConstraintsModel::handleColumnDeleted(SqliteCreateTable::Constraint* constr, const QString& column)
+{
+ switch (constr->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ case SqliteCreateTable::Constraint::UNIQUE:
+ {
+ QMutableListIterator<SqliteIndexedColumn*> it(constr->indexedColumns);
+ SqliteIndexedColumn* idxCol = nullptr;
+ while (it.hasNext())
+ {
+ idxCol = it.next();
+ if (idxCol->name.compare(column, Qt::CaseInsensitive) == 0)
+ {
+ it.remove();
+ delete idxCol;
+ modified = true;
+ }
+ }
+
+ emit modifiyStateChanged();
+ return constr->indexedColumns.count() > 0;
+ }
+ case SqliteCreateTable::Constraint::CHECK:
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ break;
+ }
+ return true;
+}
+
+void TableConstraintsModel::columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn)
+{
+ if (newColumn->name == oldColumn)
+ return;
+
+ int idx = 0;
+ foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ {
+ if (constr->doesAffectColumn(oldColumn))
+ {
+ columnRenamed(constr, oldColumn, newColumn->name);
+ constr->rebuildTokens();
+ emit dataChanged(createIndex(idx, 0), createIndex(idx, columnCount()-1));
+ }
+
+ idx++;
+ }
+}
+
+void TableConstraintsModel::columnDeleted(const QString& column)
+{
+ QList<int> toDelete;
+ int idx = 0;
+ foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ {
+ if (constr->doesAffectColumn(column))
+ {
+ if (handleColumnDeleted(constr, column))
+ {
+ constr->rebuildTokens();
+ emit dataChanged(createIndex(idx, 0), createIndex(idx, columnCount()-1));
+ }
+ else
+ toDelete << idx;
+ }
+
+ idx++;
+ }
+
+ foreach (int idx, toDelete)
+ delConstraint(idx);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h
new file mode 100644
index 0000000..e3ee9e3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.h
@@ -0,0 +1,72 @@
+#ifndef TABLECONSTRAINTSMODEL_H
+#define TABLECONSTRAINTSMODEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractTableModel>
+#include <QPointer>
+
+class GUI_API_EXPORT TableConstraintsModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ public:
+ explicit TableConstraintsModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ Qt::DropActions supportedDropActions() const;
+ Qt::DropActions supportedDragActions() const;
+ QStringList mimeTypes() const;
+ QMimeData* mimeData(const QModelIndexList& indexes) const;
+ bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
+ Qt::ItemFlags flags(const QModelIndex& index) const;
+ bool isModified() const;
+ void setCreateTable(SqliteCreateTable* value);
+ SqliteCreateTable::Constraint* getConstraint(int constrIdx) const;
+ void replaceConstraint(int constrIdx, SqliteCreateTable::Constraint* constr);
+ void constraintModified(int constrIdx);
+ void insertConstraint(int constrIdx, SqliteCreateTable::Constraint* constr);
+ void appendConstraint(SqliteCreateTable::Constraint* constr);
+ void delConstraint(int constrIdx);
+ void moveConstraintUp(int constrIdx);
+ void moveConstraintDown(int constrIdx);
+ void moveConstraintColumnTo(int constrIdx, int newIdx);
+
+ private:
+ enum class Columns
+ {
+ TYPE,
+ NAME,
+ DETAILS
+ };
+
+ Columns getColumn(int idx) const;
+ QString getTypeLabel(SqliteCreateTable::Constraint::Type type) const;
+ QIcon getTypeIcon(SqliteCreateTable::Constraint::Type type) const;
+ QString getDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getPkDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getUniqueDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getCheckDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getFkDetails(SqliteCreateTable::Constraint* constr) const;
+ QString getConstrDetails(SqliteCreateTable::Constraint* constr, int tokenOffset) const;
+ void columnRenamed(SqliteCreateTable::Constraint* constr, const QString& oldColumn, const QString& newColumn);
+ bool handleColumnDeleted(SqliteCreateTable::Constraint* constr, const QString& column);
+
+ static const constexpr char* mimeType = "application/x-sqlitestudio-tablestructureconstraintmodel-row-index";
+
+ QPointer<SqliteCreateTable> createTable;
+ bool modified = false;
+
+ public slots:
+ void columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn);
+ void columnDeleted(const QString& column);
+
+ signals:
+ void modifiyStateChanged();
+ void constraintOrderChanged();
+};
+
+#endif // TABLECONSTRAINTSMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp
new file mode 100644
index 0000000..80c4567
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp
@@ -0,0 +1,604 @@
+#include "tablestructuremodel.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include "uiconfig.h"
+#include <QFont>
+#include <QDebug>
+#include <QMimeData>
+
+TableStructureModel::TableStructureModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+}
+
+int TableStructureModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (createTable.isNull())
+ return 0;
+
+ return createTable->columns.size();
+}
+
+int TableStructureModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (createTable.isNull())
+ return 0;
+
+ switch (createTable->dialect)
+ {
+ case Dialect::Sqlite3:
+ return 9;
+ case Dialect::Sqlite2:
+ return 7;
+ }
+ return 0;
+}
+
+QVariant TableStructureModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (createTable.isNull())
+ return QVariant();
+
+ if (!isValidColumnIdx(index.column()))
+ return QVariant();
+
+ int row = index.row();
+ if (createTable->columns.size() <= row)
+ return QVariant();
+
+ switch (getHeaderColumn(index.column()))
+ {
+ case TableStructureModel::Columns::NAME:
+ {
+ if (role != Qt::DisplayRole)
+ break;
+
+ return getColumnName(row);
+ }
+ case TableStructureModel::Columns::TYPE:
+ {
+ if (role != Qt::DisplayRole)
+ break;
+
+ return getColumnType(row);
+ }
+ case TableStructureModel::Columns::PK:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnPk(row);
+ }
+ case TableStructureModel::Columns::FK:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnFk(row);
+ }
+ case TableStructureModel::Columns::UNIQUE:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnUnique(row);
+ }
+ case TableStructureModel::Columns::CHECK:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnCheck(row);
+ }
+ case TableStructureModel::Columns::NOTNULL:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnNotNull(row);
+ }
+ case TableStructureModel::Columns::COLLATE:
+ {
+ if (role != Qt::DecorationRole)
+ break;
+
+ return getColumnCollate(row);
+ }
+ case TableStructureModel::Columns::DEFAULT:
+ {
+ if (role == Qt::FontRole)
+ return getColumnDefaultFont(row);
+
+ if (role == Qt::ForegroundRole)
+ return getColumnDefaultColor(row);
+
+ if (role == Qt::DisplayRole)
+ return getColumnDefaultValue(row);
+
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant TableStructureModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QAbstractTableModel::headerData(section, orientation, role);
+
+ if (orientation == Qt::Vertical)
+ return section + 1;
+
+ // Now it's horizontal orientation with DisplayRole
+ return columnLabel(section);
+}
+
+TableStructureModel::Columns TableStructureModel::getHeaderColumn(int colIdx) const
+{
+ if (!createTable.isNull() && createTable->dialect == Dialect::Sqlite2)
+ {
+ if (colIdx >= 3)
+ colIdx++; // skip FK
+
+ if (colIdx >= 7)
+ colIdx++; // skip COLLATE
+ }
+ return static_cast<Columns>(colIdx);
+}
+
+bool TableStructureModel::isValidColumnIdx(int colIdx) const
+{
+ if (!createTable.isNull() && createTable->dialect == Dialect::Sqlite2)
+ return colIdx >= 0 && colIdx < 7;
+
+ return colIdx >= 0 && colIdx < 9;
+}
+
+SqliteCreateTable::Column* TableStructureModel::getColumn(int colIdx) const
+{
+ if (createTable.isNull())
+ return nullptr;
+
+ return createTable->columns[colIdx];
+}
+
+void TableStructureModel::replaceColumn(int colIdx, SqliteCreateTable::Column* column)
+{
+ if (createTable.isNull())
+ return;
+
+ SqliteCreateTable::Column* oldColumn = createTable->columns[colIdx];
+ QString oldColumnName = oldColumn->name;
+
+ delete oldColumn;
+ createTable->columns[colIdx] = column;
+ column->setParent(createTable);
+ modified = true;
+
+ emit modifiyStateChanged();
+ emit dataChanged(createIndex(colIdx, 0), createIndex(colIdx, columnCount()-1));
+ emit columnModified(oldColumnName, column);
+}
+
+void TableStructureModel::insertColumn(int colIdx, SqliteCreateTable::Column* column)
+{
+ if (createTable.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), colIdx, colIdx);
+ createTable->columns.insert(colIdx, column);
+ column->setParent(createTable);
+ endInsertRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableStructureModel::appendColumn(SqliteCreateTable::Column* column)
+{
+ if (createTable.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ createTable->columns.append(column);
+ column->setParent(createTable);
+ endInsertRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+}
+
+void TableStructureModel::delColumn(int colIdx)
+{
+ if (createTable.isNull())
+ return;
+
+ QString name = createTable->columns[colIdx]->name;
+
+ beginRemoveRows(QModelIndex(), colIdx, colIdx);
+ delete createTable->columns[colIdx];
+ createTable->columns.removeAt(colIdx);
+ endRemoveRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+ emit columnDeleted(name);
+}
+
+void TableStructureModel::moveColumnUp(int colIdx)
+{
+ moveColumnTo(colIdx, colIdx-1);
+}
+
+void TableStructureModel::moveColumnDown(int colIdx)
+{
+ moveColumnTo(colIdx, colIdx+1);
+}
+
+void TableStructureModel::moveColumnTo(int colIdx, int newIdx)
+{
+ if (createTable.isNull())
+ return;
+
+ if (newIdx == colIdx)
+ return;
+
+ int totalCols = createTable->columns.size();
+ if (colIdx + 1 == totalCols && newIdx == totalCols) // Moving last column out of range? Nothing to do.
+ return;
+
+ if (newIdx == colIdx+1)
+ {
+ // From Qt docs: "you must ensure that the destinationChild is not within the range of sourceFirst and sourceLast + 1".
+ // So in this case - which is easy to handle - we will invert operation. We will move target index one level up,
+ // instead of moving source index down.
+ int tmpIdx = newIdx;
+ newIdx = colIdx;
+ colIdx = tmpIdx;
+ }
+
+ beginMoveRows(QModelIndex(), colIdx, colIdx, QModelIndex(), newIdx);
+ if (newIdx >= totalCols)
+ {
+ SqliteCreateTable::Column* col = createTable->columns.takeAt(colIdx);
+ createTable->columns.append(col);
+ }
+ else
+ createTable->columns.move(colIdx, newIdx);
+
+ endMoveRows();
+
+ modified = true;
+ emit modifiyStateChanged();
+ emit columnsOrderChanged();
+}
+
+QModelIndex TableStructureModel::findColumn(const QString& columnName, Qt::CaseSensitivity cs) const
+{
+ int row = 0;
+ foreach (SqliteCreateTable::Column* col, createTable->columns)
+ {
+ if (col->name.compare(columnName, cs) == 0)
+ return createIndex(row, 0);
+
+ row++;
+ }
+ return QModelIndex();
+}
+
+QString TableStructureModel::columnLabel(int column) const
+{
+ switch (getHeaderColumn(column))
+ {
+ case Columns::NAME:
+ return tr("Name", "table structure columns");
+ case Columns::TYPE:
+ return tr("Data type", "table structure columns");
+ case Columns::PK:
+ return "P";
+ case Columns::FK:
+ return "F";
+ case Columns::UNIQUE:
+ return "U";
+ case Columns::CHECK:
+ return "H";
+ case Columns::NOTNULL:
+ return "N";
+ case Columns::COLLATE:
+ return "C";
+ case Columns::DEFAULT:
+ return tr("Default value", "table structure columns");
+ }
+ return QString::null;
+}
+
+QVariant TableStructureModel::getColumnName(int row) const
+{
+ return getColumn(row)->name;
+}
+
+QVariant TableStructureModel::getColumnType(int row) const
+{
+ SqliteColumnType* type = getColumn(row)->type;
+ return type ? type->detokenize() : "";
+}
+
+QVariant TableStructureModel::getColumnPk(int row) const
+{
+ if (isColumnPk(getColumn(row)))
+ return ICONS.CONSTRAINT_PRIMARY_KEY;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnFk(int row) const
+{
+ if (isColumnFk(getColumn(row)))
+ return ICONS.CONSTRAINT_FOREIGN_KEY;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnUnique(int row) const
+{
+ if (isColumnUnique(getColumn(row)))
+ return ICONS.CONSTRAINT_UNIQUE;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnCheck(int row) const
+{
+ if (isColumnCheck(getColumn(row)))
+ return ICONS.CONSTRAINT_CHECK;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnNotNull(int row) const
+{
+ if (isColumnNotNull(getColumn(row)))
+ return ICONS.CONSTRAINT_NOT_NULL;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnCollate(int row) const
+{
+ if (isColumnCollate(getColumn(row)))
+ return ICONS.CONSTRAINT_COLLATION;
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnDefaultValue(int row) const
+{
+ QVariant value = getColumnDefault(row);
+ if (value.isNull())
+ return "NULL";
+
+ return value;
+}
+
+QVariant TableStructureModel::getColumnDefaultFont(int row) const
+{
+ QVariant value = getColumnDefault(row);
+ if (value.isNull())
+ {
+ QFont font;
+ font.setItalic(true);
+ return font;
+ }
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnDefaultColor(int row) const
+{
+ QVariant value = getColumnDefault(row);
+ if (value.isNull())
+ return QColor(CFG_UI.Colors.DataNullFg);
+
+ return QVariant();
+}
+
+QVariant TableStructureModel::getColumnDefault(int row) const
+{
+ SqliteCreateTable::Column::Constraint* constr = getColumn(row)->getConstraint(SqliteCreateTable::Column::Constraint::DEFAULT);
+ if (!constr)
+ return QVariant();
+
+ if (!constr->id.isNull())
+ return constr->id;
+ else if (!constr->literalValue.isNull())
+ return constr->literalValue;
+ else if (!constr->ctime.isNull())
+ return constr->ctime;
+ else if (constr->expr)
+ return constr->expr->detokenize();
+ else
+ return QVariant();
+}
+
+bool TableStructureModel::isColumnPk(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY))
+ return true;
+
+ QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY);
+ foreach (SqliteCreateTable::Constraint* constr, constraints)
+ if (constr->doesAffectColumn(column->name))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnFk(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::FOREIGN_KEY))
+ return true;
+
+ QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::FOREIGN_KEY);
+ foreach (SqliteCreateTable::Constraint* constr, constraints)
+ if (constr->doesAffectColumn(column->name))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnUnique(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::UNIQUE))
+ return true;
+
+ QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::UNIQUE);
+ foreach (SqliteCreateTable::Constraint* constr, constraints)
+ if (constr->doesAffectColumn(column->name))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnCheck(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::CHECK))
+ return true;
+
+ QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::CHECK);
+ foreach (SqliteCreateTable::Constraint* constr, constraints)
+ if (constr->expr->getContextColumns(false).contains(column->name, Qt::CaseInsensitive))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnNotNull(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::NOT_NULL))
+ return true;
+
+ return false;
+}
+
+bool TableStructureModel::isColumnCollate(SqliteCreateTable::Column* column) const
+{
+ if (column->hasConstraint(SqliteCreateTable::Column::Constraint::COLLATE))
+ return true;
+
+ return false;
+}
+
+void TableStructureModel::setCreateTable(SqliteCreateTable* value)
+{
+ beginResetModel();
+ createTable = value;
+ endResetModel();
+
+ modified = false;
+ emit modifiyStateChanged();
+}
+
+bool TableStructureModel::isModified() const
+{
+ return modified;
+}
+
+Qt::DropActions TableStructureModel::supportedDropActions() const
+{
+ return Qt::MoveAction;
+}
+
+Qt::DropActions TableStructureModel::supportedDragActions() const
+{
+ return Qt::CopyAction|Qt::MoveAction;
+}
+
+
+QStringList TableStructureModel::mimeTypes() const
+{
+ return {mimeType};
+}
+
+QMimeData* TableStructureModel::mimeData(const QModelIndexList& indexes) const
+{
+ if (indexes.size() < 1)
+ return nullptr;
+
+ QModelIndex idx = indexes.first();
+
+ QMimeData *data = new QMimeData();
+
+ QByteArray output;
+ QDataStream stream(&output, QIODevice::WriteOnly);
+
+ stream << idx.row();
+ data->setData(mimeType, output);
+
+ return data;
+}
+
+
+bool TableStructureModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
+{
+ UNUSED(action);
+ UNUSED(row);
+ UNUSED(column);
+ UNUSED(parent);
+
+ if (!data)
+ return false;
+
+ if (!data->hasFormat(mimeType))
+ return false;
+
+ return true;
+}
+
+bool TableStructureModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+{
+ UNUSED(column);
+
+ if (action == Qt::IgnoreAction)
+ return true;
+
+ if (!data)
+ return false;
+
+ if (!data->hasFormat(mimeType))
+ return false;
+
+ if (action != Qt::MoveAction)
+ return false;
+
+ if (row < 0)
+ {
+ if (!parent.isValid() && !createTable.isNull())
+ row = createTable->columns.size();
+ else
+ row = parent.row();
+ }
+
+ if (row < 0)
+ return false;
+
+ QByteArray byteData = data->data(mimeType);
+ QDataStream stream(&byteData, QIODevice::ReadOnly);
+ int oldRow;
+ stream >> oldRow;
+
+ moveColumnTo(oldRow, row);
+ return true;
+}
+
+Qt::ItemFlags TableStructureModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags defFlags = QAbstractItemModel::flags(index);
+ if (!index.isValid())
+ return defFlags|Qt::ItemIsDropEnabled;
+
+ return defFlags|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h
new file mode 100644
index 0000000..e1cfa4e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.h
@@ -0,0 +1,89 @@
+#ifndef TABLESTRUCTUREMODEL_H
+#define TABLESTRUCTUREMODEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractTableModel>
+#include <QPointer>
+
+class GUI_API_EXPORT TableStructureModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+ public:
+ explicit TableStructureModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ Qt::DropActions supportedDropActions() const;
+ Qt::DropActions supportedDragActions() const;
+ QStringList mimeTypes() const;
+ QMimeData* mimeData(const QModelIndexList& indexes) const;
+ bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
+ Qt::ItemFlags flags(const QModelIndex& index) const;
+ bool isModified() const;
+ void setCreateTable(SqliteCreateTable* value);
+ SqliteCreateTable::Column* getColumn(int colIdx) const;
+ void replaceColumn(int colIdx, SqliteCreateTable::Column* column);
+ void insertColumn(int colIdx, SqliteCreateTable::Column* column);
+ void appendColumn(SqliteCreateTable::Column* column);
+ void delColumn(int colIdx);
+ void moveColumnUp(int colIdx);
+ void moveColumnDown(int colIdx);
+ void moveColumnTo(int colIdx, int newIdx);
+ QModelIndex findColumn(const QString& columnName, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;
+
+ private:
+ enum class Columns
+ {
+ NAME,
+ TYPE,
+ PK,
+ FK,
+ UNIQUE,
+ CHECK,
+ NOTNULL,
+ COLLATE,
+ DEFAULT
+ };
+
+ Columns getHeaderColumn(int colIdx) const;
+ bool isValidColumnIdx(int colIdx) const;
+ bool doesColumnHasConstraint(SqliteCreateTable::Column* column, SqliteCreateTable::Column::Constraint::Type type);
+ QString columnLabel(int column) const;
+ QString columnLabelForSqlite2(int column) const;
+ QVariant getColumnName(int row) const;
+ QVariant getColumnType(int row) const;
+ QVariant getColumnPk(int row) const;
+ QVariant getColumnFk(int row) const;
+ QVariant getColumnUnique(int row) const;
+ QVariant getColumnCheck(int row) const;
+ QVariant getColumnNotNull(int row) const;
+ QVariant getColumnCollate(int row) const;
+ QVariant getColumnDefaultValue(int row) const;
+ QVariant getColumnDefaultFont(int row) const;
+ QVariant getColumnDefaultColor(int row) const;
+ QVariant getColumnDefault(int row) const;
+ bool isColumnPk(SqliteCreateTable::Column* column) const;
+ bool isColumnFk(SqliteCreateTable::Column* column) const;
+ bool isColumnUnique(SqliteCreateTable::Column* column) const;
+ bool isColumnCheck(SqliteCreateTable::Column* column) const;
+ bool isColumnNotNull(SqliteCreateTable::Column* column) const;
+ bool isColumnCollate(SqliteCreateTable::Column* column) const;
+
+ static const constexpr char* mimeType = "application/x-sqlitestudio-tablestructuremodel-row-index";
+
+ QPointer<SqliteCreateTable> createTable;
+ bool modified = false;
+
+ signals:
+ void columnModified(const QString& oldColumn, SqliteCreateTable::Column* newColumn);
+ void columnDeleted(const QString& column);
+ void modifiyStateChanged();
+ void columnsOrderChanged();
+};
+
+#endif // TABLESTRUCTUREMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp
new file mode 100644
index 0000000..56accd0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp
@@ -0,0 +1,1508 @@
+#include "tablewindow.h"
+#include "ui_tablewindow.h"
+#include "services/dbmanager.h"
+#include "services/notifymanager.h"
+#include "sqlitestudio.h"
+#include "common/unused.h"
+#include "schemaresolver.h"
+#include "iconmanager.h"
+#include "common/intvalidator.h"
+#include "common/extlineedit.h"
+#include "datagrid/sqltablemodel.h"
+#include "common/extaction.h"
+#include "mainwindow.h"
+#include "tablestructuremodel.h"
+#include "tableconstraintsmodel.h"
+#include "dialogs/columndialog.h"
+#include "dialogs/constraintdialog.h"
+#include "mdiarea.h"
+#include "sqlitesyntaxhighlighter.h"
+#include "dialogs/newconstraintdialog.h"
+#include "db/chainexecutor.h"
+#include "common/widgetcover.h"
+#include "mdiwindow.h"
+#include "dbtree/dbtree.h"
+#include "constrainttabmodel.h"
+#include "parser/ast/sqlitecreateindex.h"
+#include "parser/ast/sqlitecreatetrigger.h"
+#include "dialogs/messagelistdialog.h"
+#include "services/codeformatter.h"
+#include "uiconfig.h"
+#include "dialogs/ddlpreviewdialog.h"
+#include "services/config.h"
+#include "services/importmanager.h"
+#include "dbobjectdialogs.h"
+#include "dialogs/exportdialog.h"
+#include <QMenu>
+#include <QToolButton>
+#include <QLabel>
+#include <QDebug>
+#include <QMessageBox>
+#include <tablemodifier.h>
+#include <QProgressBar>
+#include <QPushButton>
+#include <QDebug>
+#include <QStyleFactory>
+#include <dialogs/importdialog.h>
+#include <dialogs/populatedialog.h>
+
+// TODO extend QTableView for columns and constraints, so they show full-row-width drop indicator,
+// instead of single column drop indicator.
+
+CFG_KEYS_DEFINE(TableWindow)
+
+TableWindow::TableWindow(QWidget* parent) :
+ MdiChild(parent),
+ ui(new Ui::TableWindow)
+{
+ init();
+ applyInitialTab();
+}
+
+TableWindow::TableWindow(Db* db, QWidget* parent) :
+ MdiChild(parent),
+ db(db),
+ ui(new Ui::TableWindow)
+{
+ newTable();
+ init();
+ initDbAndTable();
+ applyInitialTab();
+}
+
+TableWindow::TableWindow(const TableWindow& win) :
+ MdiChild(win.parentWidget()),
+ db(win.db),
+ database(win.database),
+ table(win.table),
+ ui(new Ui::TableWindow)
+{
+ init();
+ initDbAndTable();
+ applyInitialTab();
+}
+
+TableWindow::TableWindow(QWidget *parent, Db* db, const QString& database, const QString& table) :
+ MdiChild(parent),
+ db(db),
+ database(database),
+ table(table),
+ ui(new Ui::TableWindow)
+{
+ init();
+ initDbAndTable();
+ applyInitialTab();
+}
+
+TableWindow::~TableWindow()
+{
+ delete ui;
+
+ if (tableModifier)
+ {
+ delete tableModifier;
+ tableModifier = nullptr;
+ }
+}
+
+void TableWindow::staticInit()
+{
+ qRegisterMetaType<TableWindow>("TableWindow");
+}
+
+void TableWindow::insertAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertAction<TableWindow>(action, toolbar);
+}
+
+void TableWindow::insertActionBefore(ExtActionPrototype* action, TableWindow::Action beforeAction, TableWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionBefore<TableWindow>(action, beforeAction, toolbar);
+}
+
+void TableWindow::insertActionAfter(ExtActionPrototype* action, TableWindow::Action afterAction, TableWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionAfter<TableWindow>(action, afterAction, toolbar);
+}
+
+void TableWindow::removeAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar)
+{
+ ExtActionContainer::removeAction<TableWindow>(action, toolbar);
+}
+
+void TableWindow::newTable()
+{
+ existingTable = false;
+ table = "";
+}
+
+void TableWindow::init()
+{
+ ui->setupUi(this);
+ ui->structureSplitter->setStretchFactor(0, 2);
+
+#ifdef Q_OS_MACX
+ QStyle *fusion = QStyleFactory::create("Fusion");
+ ui->structureToolBar->setStyle(fusion);
+ ui->structureTab->layout()->setSpacing(0);
+ ui->tableConstraintsToolbar->setStyle(fusion);
+ ui->constraintsWidget->layout()->setSpacing(0);
+ ui->indexToolBar->setStyle(fusion);
+ ui->indexesTab->layout()->setSpacing(0);
+ ui->triggerToolBar->setStyle(fusion);
+ ui->triggersTab->layout()->setSpacing(0);
+#endif
+
+ dataModel = new SqlTableModel(this);
+ ui->dataView->init(dataModel);
+
+ initActions();
+
+ connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
+ connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
+ connect(this, SIGNAL(modifyStatusChanged()), this, SLOT(updateStructureCommitState()));
+ connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(modifyStatusChanged()));
+ connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameChanged()));
+ connect(ui->indexList, SIGNAL(itemSelectionChanged()), this, SLOT(updateIndexesState()));
+ connect(ui->triggerList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState()));
+
+ structureExecutor = new ChainExecutor(this);
+ connect(structureExecutor, SIGNAL(success()), this, SLOT(changesSuccessfullyCommited()));
+ connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString)));
+
+ setupCoverWidget();
+ updateAfterInit();
+}
+
+void TableWindow::createActions()
+{
+ createAction(EXPORT, ICONS.TABLE_EXPORT, tr("Export table", "table window"), this, SLOT(exportTable()), this);
+ createAction(IMPORT, ICONS.TABLE_IMPORT, tr("Import data to table", "table window"), this, SLOT(importTable()), this);
+ createAction(POPULATE, ICONS.TABLE_POPULATE, tr("Populate table", "table window"), this, SLOT(populateTable()), this);
+
+ createStructureActions();
+ createDataGridActions();
+ createDataFormActions();
+ createIndexActions();
+ createTriggerActions();
+
+ createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this);
+ createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this);
+}
+
+void TableWindow::createStructureActions()
+{
+ createAction(REFRESH_STRUCTURE, ICONS.RELOAD, tr("Refresh structure", "table window"), this, SLOT(refreshStructure()), ui->structureToolBar);
+ ui->structureToolBar->addSeparator();
+ createAction(COMMIT_STRUCTURE, ICONS.COMMIT, tr("Commit structure changes", "table window"), this, SLOT(commitStructure()), ui->structureToolBar);
+ createAction(ROLLBACK_STRUCTURE, ICONS.ROLLBACK, tr("Rollback structure changes", "table window"), this, SLOT(rollbackStructure()), ui->structureToolBar);
+ createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add column", "table window"), this, SLOT(addColumn()), ui->structureToolBar, ui->structureView);
+ createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit column", "table window"), this, SLOT(editColumn()), ui->structureToolBar, ui->structureView);
+ createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete column", "table window"), this, SLOT(delColumn()), ui->structureToolBar, ui->structureView);
+ createAction(MOVE_COLUMN_UP, ICONS.MOVE_UP, tr("Move column up", "table window"), this, SLOT(moveColumnUp()), ui->structureToolBar, ui->structureView);
+ createAction(MOVE_COLUMN_DOWN, ICONS.MOVE_DOWN, tr("Move column down", "table window"), this, SLOT(moveColumnDown()), ui->structureToolBar, ui->structureView);
+ ui->structureToolBar->addSeparator();
+ ui->structureToolBar->addAction(actionMap[IMPORT]);
+ ui->structureToolBar->addAction(actionMap[EXPORT]);
+ ui->structureToolBar->addAction(actionMap[POPULATE]);
+ ui->structureToolBar->addSeparator();
+ createAction(CREATE_SIMILAR, ICONS.TABLE_CREATE_SIMILAR, tr("Create similar table", "table window"), this, SLOT(createSimilarTable()), ui->structureToolBar);
+
+ // Table constraints
+ createAction(ADD_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_ADD, tr("Add table constraint", "table window"), this, SLOT(addConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(EDIT_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_EDIT, tr("Edit table constraint", "table window"), this, SLOT(editConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(DEL_TABLE_CONSTRAINT, ICONS.TABLE_COLUMN_DELETE, tr("Delete table constraint", "table window"), this, SLOT(delConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(MOVE_CONSTRAINT_UP, ICONS.MOVE_UP, tr("Move table constraint up", "table window"), this, SLOT(moveConstraintUp()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(MOVE_CONSTRAINT_DOWN, ICONS.MOVE_DOWN, tr("Move table constraint down", "table window"), this, SLOT(moveConstraintDown()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ ui->tableConstraintsToolbar->addSeparator();
+ createAction(ADD_TABLE_PK, ICONS.CONSTRAINT_PRIMARY_KEY_ADD, tr("Add table primary key", "table window"), this, SLOT(addPk()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(ADD_TABLE_FK, ICONS.CONSTRAINT_FOREIGN_KEY_ADD, tr("Add table foreign key", "table window"), this, SLOT(addFk()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(ADD_TABLE_UNIQUE, ICONS.CONSTRAINT_UNIQUE_ADD, tr("Add table unique constraint", "table window"), this, SLOT(addUnique()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+ createAction(ADD_TABLE_CHECK, ICONS.CONSTRAINT_CHECK_ADD, tr("Add table check constraint", "table window"), this, SLOT(addCheck()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
+}
+
+void TableWindow::createDataGridActions()
+{
+ QAction* before = ui->dataView->getAction(DataView::FILTER_VALUE);
+ ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[IMPORT]);
+ ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[EXPORT]);
+ ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[POPULATE]);
+ ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertSeparator(before);
+}
+
+void TableWindow::createDataFormActions()
+{
+}
+
+void TableWindow::createIndexActions()
+{
+ createAction(REFRESH_INDEXES, ICONS.RELOAD, tr("Refresh index list", "table window"), this, SLOT(updateIndexes()), ui->indexToolBar, ui->indexList);
+ ui->indexToolBar->addSeparator();
+ createAction(ADD_INDEX, ICONS.INDEX_ADD, tr("Create index", "table window"), this, SLOT(addIndex()), ui->indexToolBar, ui->indexList);
+ createAction(EDIT_INDEX, ICONS.INDEX_EDIT, tr("Edit index", "table window"), this, SLOT(editIndex()), ui->indexToolBar, ui->indexList);
+ createAction(DEL_INDEX, ICONS.INDEX_DEL, tr("Delete index", "table window"), this, SLOT(delIndex()), ui->indexToolBar, ui->indexList);
+ connect(ui->indexList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editIndex()));
+}
+
+void TableWindow::createTriggerActions()
+{
+ createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "table window"), this, SLOT(updateTriggers()), ui->triggerToolBar, ui->triggerList);
+ ui->triggerToolBar->addSeparator();
+ createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create trigger", "table window"), this, SLOT(addTrigger()), ui->triggerToolBar, ui->triggerList);
+ createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit trigger", "table window"), this, SLOT(editTrigger()), ui->triggerToolBar, ui->triggerList);
+ createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete trigger", "table window"), this, SLOT(delTrigger()), ui->triggerToolBar, ui->triggerList);
+ connect(ui->triggerList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editTrigger()));
+}
+
+void TableWindow::editColumn(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ {
+ qWarning() << "Called TableWindow::editColumn() with invalid index.";
+ return;
+ }
+
+ SqliteCreateTable::Column* column = structureModel->getColumn(idx.row());
+ ColumnDialog columnDialog(db, this);
+ columnDialog.setColumn(column);
+ if (columnDialog.exec() != QDialog::Accepted)
+ return;
+
+ SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn();
+ structureModel->replaceColumn(idx.row(), modifiedColumn);
+ ui->structureView->resizeColumnToContents(0);
+}
+
+void TableWindow::delColumn(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ {
+ qWarning() << "Called TableWindow::delColumn() with invalid index.";
+ return;
+ }
+
+ SqliteCreateTable::Column* column = structureModel->getColumn(idx.row());
+
+ QString msg = tr("Are you sure you want to delete column '%1'?", "table window").arg(column->name);
+ int btn = QMessageBox::question(this, tr("Delete column", "table window"), msg);
+ if (btn != QMessageBox::Yes)
+ return;
+
+ structureModel->delColumn(idx.row());
+ ui->structureView->resizeColumnToContents(0);
+}
+
+void TableWindow::executeStructureChanges()
+{
+ QStringList sqls;
+
+ createTable->rebuildTokens();
+ if (!existingTable)
+ {
+ sqls << createTable->detokenize();
+ }
+ else
+ {
+ if (tableModifier)
+ delete tableModifier;
+
+ tableModifier = new TableModifier(db, database, table);
+ tableModifier->alterTable(createTable);
+
+ if (tableModifier->hasMessages())
+ {
+ MessageListDialog dialog(tr("Following problems will take place while modifying the table.\n"
+ "Would you like to proceed?", "table window"));
+ dialog.setWindowTitle(tr("Table modification", "table window"));
+ foreach (const QString& error, tableModifier->getErrors())
+ dialog.addError(error);
+
+ foreach (const QString& warn, tableModifier->getWarnings())
+ dialog.addWarning(warn);
+
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ sqls = tableModifier->generateSqls();
+ }
+
+ if (!CFG_UI.General.DontShowDdlPreview.get())
+ {
+ DdlPreviewDialog dialog(db, this);
+ dialog.setDdl(sqls);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ modifyingThisTable = true;
+ structureExecutor->setDb(db);
+ structureExecutor->setQueries(sqls);
+ widgetCover->show();
+ structureExecutor->exec();
+}
+
+void TableWindow::updateAfterInit()
+{
+ updateStructureCommitState();
+ updateStructureToolbarState();
+ updateTableConstraintsToolbarState();
+ updateNewTableState();
+ updateIndexesState();
+ updateTriggersState();
+}
+
+QModelIndex TableWindow::structureCurrentIndex() const
+{
+ return ui->structureView->selectionModel()->currentIndex();
+}
+
+void TableWindow::updateStructureToolbarState()
+{
+ QItemSelectionModel *selModel = ui->structureView->selectionModel();
+ bool validIdx = false;
+ bool isFirst = false;
+ bool isLast = false;
+ if (selModel)
+ {
+ QModelIndex currIdx = selModel->currentIndex();
+ if (currIdx.isValid())
+ {
+ validIdx = true;
+ if (currIdx.row() == 0)
+ isFirst = true;
+
+ if (currIdx.row() == (structureModel->rowCount() - 1))
+ isLast = true;
+ }
+ }
+
+ actionMap[EDIT_COLUMN]->setEnabled(validIdx);
+ actionMap[DEL_COLUMN]->setEnabled(validIdx);
+ actionMap[MOVE_COLUMN_UP]->setEnabled(validIdx && !isFirst);
+ actionMap[MOVE_COLUMN_DOWN]->setEnabled(validIdx && !isLast);
+}
+
+void TableWindow::updateStructureCommitState()
+{
+ bool modified = isModified();
+ actionMap[COMMIT_STRUCTURE]->setEnabled(modified);
+ actionMap[ROLLBACK_STRUCTURE]->setEnabled(modified && existingTable);
+}
+
+void TableWindow::updateTableConstraintsToolbarState()
+{
+ QItemSelectionModel *selModel = ui->tableConstraintsView->selectionModel();
+ bool anyColumn = structureModel && structureModel->rowCount() > 0;
+ bool validIdx = false;
+ bool isFirst = false;
+ bool isLast = false;
+ if (selModel)
+ {
+ QModelIndex currIdx = selModel->currentIndex();
+ if (currIdx.isValid())
+ {
+ validIdx = true;
+ if (currIdx.row() == 0)
+ isFirst = true;
+
+ if (currIdx.row() == (structureConstraintsModel->rowCount() - 1))
+ isLast = true;
+ }
+ }
+
+ actionMap[EDIT_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx);
+ actionMap[DEL_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx);
+ actionMap[MOVE_CONSTRAINT_UP]->setEnabled(anyColumn && validIdx && !isFirst);
+ actionMap[MOVE_CONSTRAINT_DOWN]->setEnabled(anyColumn && validIdx && !isLast);
+}
+
+void TableWindow::setupDefShortcuts()
+{
+ // Widget context
+ setShortcutContext({
+ REFRESH_STRUCTURE,
+ REFRESH_INDEXES,
+ REFRESH_TRIGGERS,
+ ADD_COLUMN,
+ EDIT_COLUMN,
+ DEL_COLUMN,
+ ADD_TABLE_CONSTRAINT,
+ EDIT_TABLE_CONSTRAINT,
+ DEL_TABLE_CONSTRAINT,
+ ADD_INDEX,
+ EDIT_INDEX,
+ DEL_INDEX,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ },
+ Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(TableWindow, Action);
+}
+
+void TableWindow::executionSuccessful()
+{
+ modifyingThisTable = false;
+ dataLoaded = true;
+}
+
+void TableWindow::executionFailed(const QString& errorText)
+{
+ modifyingThisTable = false;
+ notifyError(tr("Could not load data for table %1. Error details: %2").arg(table).arg(errorText));
+}
+
+void TableWindow::initDbAndTable()
+{
+ if (db->getVersion() == 2)
+ {
+ ui->withoutRowIdCheck->setVisible(false);
+ }
+
+ if (existingTable)
+ {
+ dataModel->setDb(db);
+ dataModel->setDatabaseAndTable(database, table);
+ }
+
+ ui->tableNameEdit->setText(table); // TODO no attached/temp db name support here
+
+ if (structureModel)
+ {
+ delete structureModel;
+ structureModel = nullptr;
+ }
+
+ if (structureConstraintsModel)
+ {
+ delete structureConstraintsModel;
+ structureConstraintsModel = nullptr;
+ }
+
+ if (constraintTabModel)
+ {
+ delete constraintTabModel;
+ constraintTabModel = nullptr;
+ }
+
+ structureModel = new TableStructureModel(this);
+ structureConstraintsModel = new TableConstraintsModel(this);
+ constraintTabModel = new ConstraintTabModel(this);
+
+ // Columns model signals
+ connect(structureModel, SIGNAL(columnModified(QString,SqliteCreateTable::Column*)),
+ structureConstraintsModel, SLOT(columnModified(QString,SqliteCreateTable::Column*)));
+ connect(structureModel, SIGNAL(columnDeleted(QString)),
+ structureConstraintsModel, SLOT(columnDeleted(QString)));
+ connect(structureModel, SIGNAL(columnsOrderChanged()), this, SLOT(updateStructureToolbarState()));
+
+ connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
+ connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab()));
+ connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
+ connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab()));
+ connect(structureModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged()));
+
+ ui->structureView->setModel(structureModel);
+ ui->structureView->verticalHeader()->setDefaultSectionSize(ui->structureView->fontMetrics().height() + 8);
+
+ // Constraints model signals
+ connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
+ connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab()));
+ connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
+ connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab()));
+
+ connect(structureConstraintsModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged()));
+ connect(structureConstraintsModel, SIGNAL(constraintOrderChanged()), this, SLOT(updateTableConstraintsToolbarState()));
+
+ ui->tableConstraintsView->setModel(structureConstraintsModel);
+ ui->tableConstraintsView->verticalHeader()->setDefaultSectionSize(ui->tableConstraintsView->fontMetrics().height() + 8);
+
+ // Constraint tab model signals
+ connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel()));
+ connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
+ connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel()));
+
+ ui->constraintsView->setModel(constraintTabModel);
+
+ connect(ui->withoutRowIdCheck, SIGNAL(clicked()), this, SLOT(withOutRowIdChanged()));
+
+ ui->ddlEdit->setSqliteVersion(db->getVersion());
+ parseDdl();
+ updateIndexes();
+ updateTriggers();
+
+ // (Re)connect to DB signals
+ connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfTableDeleted(QString,QString,DbObjectType)));
+
+ // Selection model is recreated when setModel() is called on the view
+ connect(ui->structureView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+ this, SLOT(updateStructureToolbarState()));
+ connect(ui->tableConstraintsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
+ this, SLOT(updateTableConstraintsToolbarState()));
+}
+
+void TableWindow::setupCoverWidget()
+{
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer();
+ widgetCover->hide();
+ connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt()));
+}
+
+void TableWindow::parseDdl()
+{
+ if (existingTable)
+ {
+ SchemaResolver resolver(db);
+ SqliteQueryPtr parsedObject = resolver.getParsedObject(database, table, SchemaResolver::TABLE);
+ if (!parsedObject.dynamicCast<SqliteCreateTable>())
+ {
+ notifyError(tr("Could not process the %1 table correctly. Unable to open a table window.").arg(table));
+ invalid = true;
+ return;
+ }
+
+ createTable = parsedObject.dynamicCast<SqliteCreateTable>();
+ }
+ else
+ {
+ createTable = SqliteCreateTablePtr::create();
+ createTable->table = table;
+ createTable->dialect = db->getDialect();
+ }
+ originalCreateTable = SqliteCreateTablePtr::create(*createTable);
+ structureModel->setCreateTable(createTable.data());
+ structureConstraintsModel->setCreateTable(createTable.data());
+ constraintTabModel->setCreateTable(createTable.data());
+ ui->withoutRowIdCheck->setChecked(!createTable->withOutRowId.isNull());
+ ui->tableConstraintsView->resizeColumnsToContents();
+ ui->structureView->resizeColumnsToContents();
+ ui->constraintsView->resizeColumnsToContents();
+
+ updateStructureToolbarState();
+ updateTableConstraintsToolbarState();
+ updateDdlTab();
+}
+
+void TableWindow::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+QVariant TableWindow::saveSession()
+{
+ if (!db || DBLIST->isTemporary(db))
+ return QVariant();
+
+ QHash<QString,QVariant> sessionValue;
+ sessionValue["table"] = table;
+ sessionValue["db"] = db->getName();
+ return sessionValue;
+}
+
+bool TableWindow::restoreSession(const QVariant& sessionValue)
+{
+ QHash<QString, QVariant> value = sessionValue.toHash();
+ if (value.size() == 0)
+ {
+ notifyWarn("Could not restore window, because no database or table was stored in session for this window.");
+ return false;
+ }
+
+ if (!value.contains("db") || !value.contains("table"))
+ {
+ notifyWarn("Could not restore window, because no database or table was stored in session for this window.");
+ return false;
+ }
+
+ db = DBLIST->getByName(value["db"].toString());
+ if (!db || !db->isValid() || (!db->isOpen() && !db->open()))
+ {
+ notifyWarn(tr("Could not restore window, because database %1 could not be resolved.").arg(value["db"].toString()));
+ return false;
+ }
+
+ table = value["table"].toString();
+ database = value["database"].toString();
+ SchemaResolver resolver(db);
+ if (!resolver.getTables(database).contains(table, Qt::CaseInsensitive))
+ {
+ notifyWarn(tr("Could not restore window, because the table %1 doesn't exist in the database %2.").arg(table).arg(db->getName()));
+ return false;
+ }
+
+ initDbAndTable();
+ applyInitialTab();
+ return true;
+}
+
+Icon* TableWindow::getIconNameForMdiWindow()
+{
+ return ICONS.TABLE;
+}
+
+QString TableWindow::getTitleForMdiWindow()
+{
+ QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")"));
+ if (existingTable)
+ return table + dbSuffix;
+
+ QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles();
+ if (existingNames.contains(windowTitle()))
+ return windowTitle();
+
+ // Generate new name
+ QString title = tr("New table %1").arg(newTableWindowNum++);
+ while (existingNames.contains(title))
+ title = tr("New table %1").arg(newTableWindowNum++);
+
+ title += dbSuffix;
+ return title;
+}
+
+Db* TableWindow::getDb() const
+{
+ return db;
+}
+
+QString TableWindow::getTable() const
+{
+ return table;
+}
+
+void TableWindow::dbClosedFinalCleanup()
+{
+ db = nullptr;
+ dataModel->setDb(nullptr);
+ structureExecutor->setDb(nullptr);
+}
+
+void TableWindow::checkIfTableDeleted(const QString& database, const QString& object, DbObjectType type)
+{
+ UNUSED(database);
+
+ // TODO uncomment below when dbnames are supported
+// if (this->database != database)
+// return;
+
+ switch (type)
+ {
+ case DbObjectType::TABLE:
+ break;
+ case DbObjectType::INDEX:
+ checkIfIndexDeleted(object);
+ return;
+ case DbObjectType::TRIGGER:
+ checkIfTriggerDeleted(object);
+ return;
+ case DbObjectType::VIEW:
+ return;
+ }
+
+ if (modifyingThisTable)
+ return;
+
+ if (object.compare(table, Qt::CaseInsensitive) == 0)
+ {
+ dbClosedFinalCleanup();
+ getMdiWindow()->close();
+ }
+}
+
+void TableWindow::checkIfIndexDeleted(const QString& object)
+{
+ for (int i = 0, total = ui->indexList->rowCount(); i < total; ++i)
+ {
+ if (ui->indexList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
+ {
+ ui->indexList->removeRow(i);
+ return;
+ }
+ }
+}
+
+void TableWindow::checkIfTriggerDeleted(const QString& object)
+{
+ for (int i = 0, total = ui->triggerList->rowCount(); i < total; ++i)
+ {
+ if (ui->triggerList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
+ {
+ ui->triggerList->removeRow(i);
+ return;
+ }
+ }
+}
+
+void TableWindow::refreshStructure()
+{
+ parseDdl();
+ updateIndexes();
+ updateTriggers();
+}
+
+void TableWindow::commitStructure(bool skipWarning)
+{
+ if (!isModified())
+ {
+ qWarning() << "Called TableWindow::commitStructure(), but isModified() returned false.";
+ updateStructureCommitState();
+ return;
+ }
+
+ if (!validate(skipWarning))
+ return;
+
+ executeStructureChanges();
+}
+
+void TableWindow::changesSuccessfullyCommited()
+{
+ QStringList sqls = structureExecutor->getQueries();
+ CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
+
+ widgetCover->hide();
+
+ originalCreateTable = createTable;
+ structureModel->setCreateTable(createTable.data());
+ structureConstraintsModel->setCreateTable(createTable.data());
+ dataLoaded = false;
+
+ QString oldTable = table;
+ database = createTable->database;
+ table = createTable->table;
+ existingTable = true;
+ initDbAndTable();
+ updateStructureCommitState();
+ updateNewTableState();
+ updateWindowTitle();
+
+ DBTREE->refreshSchema(db);
+
+ if (tableModifier)
+ {
+ QList<QStringList> modifiedObjects = {
+ tableModifier->getModifiedTables(),
+ tableModifier->getModifiedIndexes(),
+ tableModifier->getModifiedTriggers(),
+ tableModifier->getModifiedViews()
+ };
+ NotifyManager* notifyManager = NotifyManager::getInstance();
+ foreach (const QStringList& objList, modifiedObjects)
+ {
+ foreach (const QString& obj, objList)
+ {
+ if (obj.compare(oldTable, Qt::CaseInsensitive) == 0)
+ continue;
+
+ notifyManager->modified(db, database, obj);
+ }
+ }
+ }
+}
+
+void TableWindow::changesFailedToCommit(int errorCode, const QString& errorText)
+{
+ qDebug() << "TableWindow::changesFailedToCommit:" << errorCode << errorText;
+
+ widgetCover->hide();
+ notifyError(tr("Could not commit table structure. Error message: %1", "table window").arg(errorText));
+}
+
+void TableWindow::rollbackStructure()
+{
+ createTable = SqliteCreateTablePtr::create(*originalCreateTable.data());
+ structureModel->setCreateTable(createTable.data());
+ structureConstraintsModel->setCreateTable(createTable.data());
+ constraintTabModel->setCreateTable(createTable.data());
+ ui->tableNameEdit->setText(createTable->table);
+
+ updateStructureCommitState();
+ updateStructureToolbarState();
+ updateTableConstraintsToolbarState();
+}
+
+void TableWindow::addColumn()
+{
+ SqliteCreateTable::Column column;
+ column.setParent(createTable.data());
+
+ ColumnDialog columnDialog(db, this);
+ columnDialog.setColumn(&column);
+ if (columnDialog.exec() != QDialog::Accepted)
+ return;
+
+ SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn();
+ structureModel->appendColumn(modifiedColumn);
+ ui->structureView->resizeColumnToContents(0);
+
+ ui->structureView->setCurrentIndex(structureModel->index(structureModel->rowCount()-1, 0));
+}
+
+void TableWindow::editColumn()
+{
+ editColumn(structureCurrentIndex());
+}
+
+void TableWindow::delColumn()
+{
+ QModelIndex idx = structureCurrentIndex();
+ delColumn(idx);
+}
+
+void TableWindow::moveColumnUp()
+{
+ QModelIndex idx = structureCurrentIndex();
+ if (!idx.isValid())
+ {
+ qWarning() << "Called TableWindow::moveColumnUp() with invalid index.";
+ return;
+ }
+
+ structureModel->moveColumnUp(idx.row());
+}
+
+void TableWindow::moveColumnDown()
+{
+ QModelIndex idx = structureCurrentIndex();
+ if (!idx.isValid())
+ {
+ qWarning() << "Called TableWindow::moveColumnDown() with invalid index.";
+ return;
+ }
+
+ structureModel->moveColumnDown(idx.row());
+}
+
+
+void TableWindow::addConstraint(ConstraintDialog::Constraint mode)
+{
+ NewConstraintDialog dialog(mode, createTable.data(), db, this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ SqliteStatement* constrStmt = dialog.getConstraint();
+ SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(constrStmt);
+ if (!tableConstr)
+ {
+ qCritical() << "Constraint returned from ConstraintDialog was not of table type, while we're trying to add table constraint.";
+ return;
+ }
+
+ structureConstraintsModel->appendConstraint(tableConstr);
+ ui->tableConstraintsView->resizeColumnToContents(0);
+ ui->tableConstraintsView->resizeColumnToContents(1);
+}
+
+bool TableWindow::validate(bool skipWarning)
+{
+ if (!existingTable && !skipWarning && ui->tableNameEdit->text().isEmpty())
+ {
+ int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the table is allowed in SQLite, but it is not recommended.\n"
+ "Are you sure you want to create a table with blank name?"), QMessageBox::Yes, QMessageBox::No);
+
+ if (res != QMessageBox::Yes)
+ return false;
+ }
+
+ if (structureModel->rowCount() == 0)
+ {
+ notifyError(tr("Cannot create a table without at least one column."));
+ return false;
+ }
+
+ if (ui->withoutRowIdCheck->isChecked())
+ {
+ bool hasPk = false;
+ bool isPkAutoIncr = false;
+
+ if (createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY).size() > 0)
+ hasPk = true;
+
+ SqliteCreateTable::Column::Constraint* colConstraint = nullptr;
+ foreach (SqliteCreateTable::Column* column, createTable->columns)
+ {
+ colConstraint = column->getConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY);
+ if (colConstraint)
+ {
+ hasPk = true;
+ if (colConstraint->autoincrKw)
+ isPkAutoIncr = true;
+ }
+ }
+
+ if (!hasPk)
+ {
+ notifyError(tr("Cannot create table without ROWID, if it has no PRIMARY KEY defined."
+ " Either uncheck the WITHOUT ROWID, or define a PRIMARY KEY."));
+ return false;
+ }
+
+ if (isPkAutoIncr)
+ {
+ notifyError(tr("Cannot use AUTOINCREMENT for PRIMARY KEY when WITHOUT ROWID clause is used."
+ " Either uncheck the WITHOUT ROWID, or the AUTOINCREMENT in a PRIMARY KEY."));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TableWindow::isModified() const
+{
+ return (structureModel && structureModel->isModified()) ||
+ (structureConstraintsModel && structureConstraintsModel->isModified()) ||
+ (originalCreateTable &&
+ (originalCreateTable->table != ui->tableNameEdit->text() ||
+ originalCreateTable->withOutRowId != createTable->withOutRowId)
+ ) ||
+ !existingTable;
+}
+
+TokenList TableWindow::indexColumnTokens(SqliteCreateIndexPtr index)
+{
+ if (index->indexedColumns.size() == 0)
+ return TokenList();
+
+ SqliteIndexedColumn* firstCol = index->indexedColumns.first();
+ SqliteIndexedColumn* lastCol = index->indexedColumns.last();
+ if (firstCol->tokens.size() == 0)
+ return TokenList();
+
+ if (lastCol->tokens.size() == 0)
+ return TokenList();
+
+ int firstIdx = index->tokens.indexOf(firstCol->tokens.first());
+ int lastIdx = index->tokens.indexOf(lastCol->tokens.last());
+
+ return index->tokens.mid(firstIdx, lastIdx-firstIdx+1);
+}
+
+QString TableWindow::getCurrentIndex() const
+{
+ int row = ui->indexList->currentRow();
+ QTableWidgetItem* item = ui->indexList->item(row, 0);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QString TableWindow::getCurrentTrigger() const
+{
+ int row = ui->triggerList->currentRow();
+ QTableWidgetItem* item = ui->triggerList->item(row, 0);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+void TableWindow::applyInitialTab()
+{
+ if (existingTable && !table.isNull() && CFG_UI.General.OpenTablesOnData.get())
+ ui->tabWidget->setCurrentIndex(1);
+ else
+ ui->tabWidget->setCurrentIndex(0);
+}
+
+void TableWindow::updateDdlTab()
+{
+ CodeFormatter* formatter = SQLITESTUDIO->getCodeFormatter();
+ createTable->rebuildTokens();
+ ui->ddlEdit->setPlainText(formatter->format("sql", createTable->detokenize(), db));
+}
+
+void TableWindow::updateNewTableState()
+{
+ for (int i = 1; i < 5; i++)
+ ui->tabWidget->setTabEnabled(i, existingTable);
+
+ actionMap[EXPORT]->setEnabled(existingTable);
+ actionMap[IMPORT]->setEnabled(existingTable);
+ actionMap[POPULATE]->setEnabled(existingTable);
+ actionMap[CREATE_SIMILAR]->setEnabled(existingTable);
+ actionMap[REFRESH_STRUCTURE]->setEnabled(existingTable);
+}
+
+void TableWindow::addConstraint()
+{
+ addConstraint(ConstraintDialog::UNKNOWN);
+}
+
+void TableWindow::editConstraint()
+{
+ QModelIndex idx = ui->tableConstraintsView->currentIndex();
+ editConstraint(idx);
+}
+
+void TableWindow::delConstraint()
+{
+ QModelIndex idx = ui->tableConstraintsView->currentIndex();
+ delConstraint(idx);
+}
+
+void TableWindow::editConstraint(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return;
+
+ SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row());
+ ConstraintDialog dialog(ConstraintDialog::EDIT, constr, createTable.data(), db, this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ structureConstraintsModel->constraintModified(idx.row());
+ ui->tableConstraintsView->resizeColumnToContents(0);
+ ui->tableConstraintsView->resizeColumnToContents(1);
+}
+
+void TableWindow::delConstraint(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return;
+
+ SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row());
+
+ QString arg = constr->name.isNull() ? constr->typeString() : constr->name;
+ QString msg = tr("Are you sure you want to delete table constraint '%1'?", "table window").arg(arg);
+ int btn = QMessageBox::question(this, tr("Delete constraint", "table window"), msg);
+ if (btn != QMessageBox::Yes)
+ return;
+
+ structureConstraintsModel->delConstraint(idx.row());
+ ui->structureView->resizeColumnToContents(0);
+}
+
+void TableWindow::moveConstraintUp()
+{
+ QModelIndex idx = ui->tableConstraintsView->currentIndex();
+ if (!idx.isValid())
+ return;
+
+ structureConstraintsModel->moveConstraintUp(idx.row());
+ updateTableConstraintsToolbarState();
+ updateStructureCommitState();
+}
+
+void TableWindow::moveConstraintDown()
+{
+ QModelIndex idx = ui->tableConstraintsView->currentIndex();
+ if (!idx.isValid())
+ return;
+
+ structureConstraintsModel->moveConstraintDown(idx.row());
+ updateTableConstraintsToolbarState();
+ updateStructureCommitState();
+}
+
+void TableWindow::addPk()
+{
+ addConstraint(ConstraintDialog::PK);
+}
+
+void TableWindow::addFk()
+{
+ addConstraint(ConstraintDialog::FK);
+}
+
+void TableWindow::addUnique()
+{
+ addConstraint(ConstraintDialog::UNIQUE);
+}
+
+void TableWindow::addCheck()
+{
+ addConstraint(ConstraintDialog::CHECK);
+}
+
+void TableWindow::exportTable()
+{
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.setTableMode(db, table);
+ dialog.exec();
+}
+
+void TableWindow::importTable()
+{
+ if (!ImportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot import, because no import plugin is loaded."));
+ return;
+ }
+
+ ImportDialog dialog(this);
+ dialog.setDbAndTable(db, table);
+ if (dialog.exec() == QDialog::Accepted && dataLoaded)
+ ui->dataView->refreshData();
+}
+
+void TableWindow::populateTable()
+{
+ PopulateDialog dialog(this);
+ dialog.setDbAndTable(db, table);
+ if (dialog.exec() == QDialog::Accepted && dataLoaded)
+ ui->dataView->refreshData();
+}
+
+void TableWindow::createSimilarTable()
+{
+ DbObjectDialogs dialog(db);
+ dialog.addTableSimilarTo(QString(), table);
+}
+
+void TableWindow::tabChanged(int newTab)
+{
+ switch (newTab)
+ {
+ case 1:
+ {
+ if (isModified())
+ {
+ int res = QMessageBox::question(this, tr("Uncommited changes"),
+ tr("There are uncommited structure modifications. You cannot browse or edit data until you have "
+ "table structure settled.\n"
+ "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);
+
+ break;
+ }
+
+ if (!dataLoaded)
+ ui->dataView->refreshData();
+
+ break;
+ }
+ }
+}
+
+void TableWindow::on_structureView_doubleClicked(const QModelIndex &index)
+{
+ editColumn(index);
+}
+
+void TableWindow::on_tableConstraintsView_doubleClicked(const QModelIndex &index)
+{
+ editConstraint(index);
+}
+
+void TableWindow::nameChanged()
+{
+ if (!createTable)
+ return;
+
+ createTable->table = ui->tableNameEdit->text();
+ updateDdlTab();
+}
+
+void TableWindow::withOutRowIdChanged()
+{
+ if (!createTable)
+ return;
+
+ createTable->withOutRowId = ui->withoutRowIdCheck->isChecked() ? QStringLiteral("ROWID") : QString::null;
+ updateDdlTab();
+ emit modifyStatusChanged();
+}
+
+void TableWindow::addIndex()
+{
+ DbObjectDialogs dialogs(db, this);
+ dialogs.addIndex(table);
+ updateIndexes();
+}
+
+void TableWindow::editIndex()
+{
+ QString index = getCurrentIndex();
+ if (index.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.editIndex(index);
+ updateIndexes();
+}
+
+void TableWindow::delIndex()
+{
+ QString index = getCurrentIndex();
+ if (index.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.dropObject(index);
+ updateIndexes();
+}
+
+void TableWindow::addTrigger()
+{
+ DbObjectDialogs dialogs(db, this);
+ dialogs.addTriggerOnTable(table);
+ updateTriggers();
+}
+
+void TableWindow::editTrigger()
+{
+ QString trigger = getCurrentTrigger();
+ if (trigger.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.editTrigger(trigger);
+ updateTriggers();
+}
+
+void TableWindow::delTrigger()
+{
+ QString trigger = getCurrentTrigger();
+ if (trigger.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.dropObject(trigger);
+ updateTriggers();
+}
+
+void TableWindow::updateIndexesState()
+{
+ bool editDel = ui->indexList->currentItem() != nullptr;
+ actionMap[REFRESH_INDEXES]->setEnabled(existingTable);
+ actionMap[ADD_INDEX]->setEnabled(existingTable);
+ actionMap[EDIT_INDEX]->setEnabled(editDel);
+ actionMap[DEL_INDEX]->setEnabled(editDel);
+}
+
+void TableWindow::updateTriggersState()
+{
+ bool editDel = ui->triggerList->currentItem() != nullptr;
+ actionMap[REFRESH_TRIGGERS]->setEnabled(existingTable);
+ actionMap[ADD_TRIGGER]->setEnabled(existingTable);
+ actionMap[EDIT_TRIGGER]->setEnabled(editDel);
+ actionMap[DEL_TRIGGER]->setEnabled(editDel);
+}
+
+void TableWindow::nextTab()
+{
+ int idx = ui->tabWidget->currentIndex();
+ idx++;
+ ui->tabWidget->setCurrentIndex(idx);
+}
+
+void TableWindow::prevTab()
+{
+ int idx = ui->tabWidget->currentIndex();
+ idx--;
+ ui->tabWidget->setCurrentIndex(idx);
+}
+
+void TableWindow::updateIndexes()
+{
+ ui->indexList->clear();
+
+ if (!db || !db->isValid())
+ return;
+
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(true);
+ QList<SqliteCreateIndexPtr> indexes = resolver.getParsedIndexesForTable(database, table);
+
+ ui->indexList->setColumnCount(4);
+ ui->indexList->setRowCount(indexes.size());
+ ui->indexList->setHorizontalHeaderLabels({
+ tr("Name", "table window indexes"),
+ tr("Unique", "table window indexes"),
+ tr("Columns", "table window indexes"),
+ tr("Partial index condition", "table window indexes"),
+ });
+
+ Dialect dialect= db->getDialect();
+ if (dialect == Dialect::Sqlite2)
+ ui->indexList->setColumnCount(3);
+
+ ui->indexList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
+
+ QTableWidgetItem* item = nullptr;
+ int row = 0;
+ foreach (SqliteCreateIndexPtr index, indexes)
+ {
+ item = new QTableWidgetItem(index->index);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->indexList->setItem(row, 0, item);
+
+ // TODO a delegate to make the checkbox in the center, or use setCellWidget()
+ item = new QTableWidgetItem();
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ item->setCheckState(index->uniqueKw ? Qt::Checked : Qt::Unchecked);
+ ui->indexList->setItem(row, 1, item);
+
+ item = new QTableWidgetItem(indexColumnTokens(index).detokenize());
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->indexList->setItem(row, 2, item);
+
+ if (dialect == Dialect::Sqlite3)
+ {
+ item = new QTableWidgetItem(index->where ? index->where->detokenize() : "");
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->indexList->setItem(row, 3, item);
+ }
+
+ row++;
+ }
+
+ ui->indexList->resizeColumnsToContents();
+ updateIndexesState();
+}
+
+void TableWindow::updateTriggers()
+{
+ if (!db || !db->isValid())
+ return;
+
+ SchemaResolver resolver(db);
+ QList<SqliteCreateTriggerPtr> triggers = resolver.getParsedTriggersForTable(database, table);
+
+ ui->triggerList->setColumnCount(4);
+ ui->triggerList->setRowCount(triggers.size());
+ ui->triggerList->horizontalHeader()->setMaximumSectionSize(200);
+ ui->triggerList->setHorizontalHeaderLabels({
+ tr("Name", "table window triggers"),
+ tr("Event", "table window triggers"),
+ tr("Condition", "table window triggers"),
+ tr("Details", "table window triggers")
+ });
+
+ QTableWidgetItem* item = nullptr;
+ QString timeAndEvent;
+ int row = 0;
+ foreach (SqliteCreateTriggerPtr trig, triggers)
+ {
+ item = new QTableWidgetItem(trig->trigger);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggerList->setItem(row, 0, item);
+
+ timeAndEvent = trig->tokensMap["trigger_time"].detokenize() + trig->tokensMap["trigger_event"].detokenize();
+ item = new QTableWidgetItem(timeAndEvent);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggerList->setItem(row, 1, item);
+
+ item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : "");
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggerList->setItem(row, 2, item);
+
+ item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed());
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggerList->setItem(row, 3, item);
+
+ row++;
+ }
+
+ ui->triggerList->resizeColumnsToContents();
+ updateTriggersState();
+}
+
+void TableWindow::editColumn(const QString& columnName)
+{
+ QModelIndex colIdx = structureModel->findColumn(columnName);
+ if (!colIdx.isValid())
+ return;
+
+ editColumn(colIdx);
+}
+
+void TableWindow::delColumn(const QString& columnName)
+{
+ QModelIndex colIdx = structureModel->findColumn(columnName);
+ if (!colIdx.isValid())
+ return;
+
+ delColumn(colIdx);
+}
+
+bool TableWindow::restoreSessionNextTime()
+{
+ return existingTable && db && !DBLIST->isTemporary(db);
+}
+
+QToolBar* TableWindow::getToolBar(int toolbar) const
+{
+ switch (static_cast<ToolBar>(toolbar))
+ {
+ case TOOLBAR_STRUCTURE:
+ return ui->structureToolBar;
+ case TOOLBAR_INDEXES:
+ return ui->indexToolBar;
+ case TOOLBAR_TRIGGERS:
+ return ui->triggerToolBar;
+ }
+ return nullptr;
+}
+
+bool TableWindow::handleInitialFocus()
+{
+ if (!existingTable)
+ {
+ ui->tableNameEdit->setFocus();
+ return true;
+ }
+ return false;
+}
+
+bool TableWindow::isUncommited() const
+{
+ return ui->dataView->isUncommited() || isModified();
+}
+
+QString TableWindow::getQuitUncommitedConfirmMessage() const
+{
+ QString title = getMdiWindow()->windowTitle();
+ if (ui->dataView->isUncommited() && isModified())
+ return tr("Table window \"%1\" has uncommited structure modifications and data.").arg(title);
+ else if (ui->dataView->isUncommited())
+ return tr("Table window \"%1\" has uncommited data.").arg(title);
+ else if (isModified())
+ return tr("Table window \"%1\" has uncommited structure modifications.").arg(title);
+ else
+ {
+ qCritical() << "Unhandled message case in TableWindow::getQuitUncommitedConfirmMessage().";
+ return QString();
+ }
+}
+
+void TableWindow::useCurrentTableAsBaseForNew()
+{
+ newTable();
+ ui->tableNameEdit->clear();
+ updateWindowTitle();
+ ui->tableNameEdit->setFocus();
+ updateAfterInit();
+}
+
+Db* TableWindow::getAssociatedDb() const
+{
+ return db;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h
new file mode 100644
index 0000000..b0ee1e3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.h
@@ -0,0 +1,241 @@
+#ifndef TABLEWINDOW_H
+#define TABLEWINDOW_H
+
+#include "db/db.h"
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "parser/ast/sqlitecreateindex.h"
+#include "dialogs/constraintdialog.h"
+#include "db/chainexecutor.h"
+#include "guiSQLiteStudio_global.h"
+#include <QPointer>
+
+class TableModifier;
+class SqlTableModel;
+class ExtLineEdit;
+class IntValidator;
+class QLabel;
+class TableStructureModel;
+class TableConstraintsModel;
+class QProgressBar;
+class WidgetCover;
+class SqliteSyntaxHighlighter;
+class ConstraintTabModel;
+
+namespace Ui {
+ class TableWindow;
+}
+
+CFG_KEY_LIST(TableWindow, QObject::tr("Table window"),
+ CFG_KEY_ENTRY(REFRESH_STRUCTURE, Qt::Key_F5, QObject::tr("Refresh table structure"))
+ CFG_KEY_ENTRY(ADD_COLUMN, Qt::Key_Insert, QObject::tr("Add new column"))
+ CFG_KEY_ENTRY(EDIT_COLUMN, Qt::Key_Return, QObject::tr("Edit selected column"))
+ CFG_KEY_ENTRY(DEL_COLUMN, Qt::Key_Delete, QObject::tr("Delete selected column"))
+ CFG_KEY_ENTRY(EXPORT, Qt::CTRL + Qt::Key_E, QObject::tr("Export table data"))
+ CFG_KEY_ENTRY(IMPORT, Qt::CTRL + Qt::Key_I, QObject::tr("Import data to the table"))
+ CFG_KEY_ENTRY(ADD_TABLE_CONSTRAINT, Qt::Key_Insert, QObject::tr("Add new table constraint"))
+ CFG_KEY_ENTRY(EDIT_TABLE_CONSTRAINT, Qt::Key_Return, QObject::tr("Edit selected table constraint"))
+ CFG_KEY_ENTRY(DEL_TABLE_CONSTRAINT, Qt::Key_Delete, QObject::tr("Delete selected table constraint"))
+ CFG_KEY_ENTRY(REFRESH_INDEXES, Qt::Key_F5, QObject::tr("Refresh table index list"))
+ CFG_KEY_ENTRY(ADD_INDEX, Qt::Key_Insert, QObject::tr("Add new index"))
+ CFG_KEY_ENTRY(EDIT_INDEX, Qt::Key_Return, QObject::tr("Edit selected index"))
+ CFG_KEY_ENTRY(DEL_INDEX, Qt::Key_Delete, QObject::tr("Delete selected index"))
+ CFG_KEY_ENTRY(REFRESH_TRIGGERS, Qt::Key_F5, QObject::tr("Refresh table trigger list"))
+ CFG_KEY_ENTRY(ADD_TRIGGER, Qt::Key_Insert, QObject::tr("Add new trigger"))
+ CFG_KEY_ENTRY(EDIT_TRIGGER, Qt::Key_Return, QObject::tr("Edit selected trigger"))
+ CFG_KEY_ENTRY(DEL_TRIGGER, Qt::Key_Delete, QObject::tr("Delete selected trigger"))
+ CFG_KEY_ENTRY(NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next tab"))
+ CFG_KEY_ENTRY(PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous tab"))
+)
+
+class GUI_API_EXPORT TableWindow : public MdiChild
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ // Structure tab
+ REFRESH_STRUCTURE,
+ COMMIT_STRUCTURE,
+ ROLLBACK_STRUCTURE,
+ ADD_COLUMN,
+ EDIT_COLUMN,
+ DEL_COLUMN,
+ MOVE_COLUMN_UP,
+ MOVE_COLUMN_DOWN,
+ ADD_TABLE_CONSTRAINT,
+ EDIT_TABLE_CONSTRAINT,
+ DEL_TABLE_CONSTRAINT,
+ ADD_TABLE_PK,
+ ADD_TABLE_FK,
+ ADD_TABLE_UNIQUE,
+ ADD_TABLE_CHECK,
+ MOVE_CONSTRAINT_UP,
+ MOVE_CONSTRAINT_DOWN,
+ EXPORT,
+ IMPORT,
+ POPULATE,
+ CREATE_SIMILAR,
+ // Indexes tab
+ REFRESH_INDEXES,
+ ADD_INDEX,
+ EDIT_INDEX,
+ DEL_INDEX,
+ // Triggers tab
+ REFRESH_TRIGGERS,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ // All tabs
+ NEXT_TAB,
+ PREV_TAB
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR_STRUCTURE,
+ TOOLBAR_INDEXES,
+ TOOLBAR_TRIGGERS
+ };
+
+ explicit TableWindow(QWidget *parent = 0);
+ TableWindow(Db* db, QWidget *parent = 0);
+ TableWindow(const TableWindow& win);
+ TableWindow(QWidget *parent, Db* db, const QString& database, const QString& table);
+ ~TableWindow();
+
+ static void staticInit();
+ static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_STRUCTURE);
+ static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_STRUCTURE);
+ static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_STRUCTURE);
+ static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_STRUCTURE);
+
+ QString getTable() const;
+ Db* getDb() const;
+ bool handleInitialFocus();
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+ void useCurrentTableAsBaseForNew();
+ Db* getAssociatedDb() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ QVariant saveSession();
+ bool restoreSession(const QVariant& sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ bool restoreSessionNextTime();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ void newTable();
+ void parseDdl();
+ void initDbAndTable();
+ void setupCoverWidget();
+ void createStructureActions();
+ void createDataGridActions();
+ void createDataFormActions();
+ void createIndexActions();
+ void createTriggerActions();
+ void editColumn(const QModelIndex& idx);
+ void delColumn(const QModelIndex& idx);
+ void editConstraint(const QModelIndex& idx);
+ void delConstraint(const QModelIndex& idx);
+ void executeStructureChanges();
+ void updateAfterInit();
+ QModelIndex structureCurrentIndex() const;
+ void addConstraint(ConstraintDialog::Constraint mode);
+ bool validate(bool skipWarning = false);
+ bool isModified() const;
+ TokenList indexColumnTokens(SqliteCreateIndexPtr index);
+ QString getCurrentIndex() const;
+ QString getCurrentTrigger() const;
+ void applyInitialTab();
+
+ int newTableWindowNum = 1;
+
+ Db* db = nullptr;
+ QString database;
+ QString table;
+ Ui::TableWindow *ui = nullptr;
+ SqlTableModel* dataModel = nullptr;
+ bool dataLoaded = false;
+ bool existingTable = true;
+ SqliteCreateTablePtr createTable;
+ SqliteCreateTablePtr originalCreateTable;
+ TableStructureModel* structureModel = nullptr;
+ TableConstraintsModel* structureConstraintsModel = nullptr;
+ ConstraintTabModel* constraintTabModel = nullptr;
+ WidgetCover* widgetCover = nullptr;
+ ChainExecutor* structureExecutor = nullptr;
+ TableModifier* tableModifier = nullptr;
+ bool modifyingThisTable = false;
+
+ private slots:
+ void executionSuccessful();
+ void executionFailed(const QString& errorText);
+ void dbClosedFinalCleanup();
+ void checkIfTableDeleted(const QString& database, const QString& object, DbObjectType type);
+ void checkIfIndexDeleted(const QString& object);
+ void checkIfTriggerDeleted(const QString& object);
+ void refreshStructure();
+ void commitStructure(bool skipWarning = false);
+ void changesSuccessfullyCommited();
+ void changesFailedToCommit(int errorCode, const QString& errorText);
+ void rollbackStructure();
+ void editColumn();
+ void delColumn();
+ void moveColumnUp();
+ void moveColumnDown();
+ void addConstraint();
+ void editConstraint();
+ void delConstraint();
+ void moveConstraintUp();
+ void moveConstraintDown();
+ void addPk();
+ void addFk();
+ void addUnique();
+ void addCheck();
+ void exportTable();
+ void importTable();
+ void populateTable();
+ void createSimilarTable();
+ void tabChanged(int newTab);
+ void updateStructureToolbarState();
+ void updateStructureCommitState();
+ void updateTableConstraintsToolbarState();
+ void updateDdlTab();
+ void updateNewTableState();
+ void on_structureView_doubleClicked(const QModelIndex &index);
+ void on_tableConstraintsView_doubleClicked(const QModelIndex &index);
+ void nameChanged();
+ void withOutRowIdChanged();
+ void addIndex();
+ void editIndex();
+ void delIndex();
+ void addTrigger();
+ void editTrigger();
+ void delTrigger();
+ void updateIndexesState();
+ void updateTriggersState();
+ void nextTab();
+ void prevTab();
+
+ public slots:
+ void updateIndexes();
+ void updateTriggers();
+ void addColumn();
+ void editColumn(const QString& columnName);
+ void delColumn(const QString& columnName);
+
+ signals:
+ void modifyStatusChanged();
+};
+
+#endif // TABLEWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui
new file mode 100644
index 0000000..8c46443
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.ui
@@ -0,0 +1,307 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TableWindow</class>
+ <widget class="QWidget" name="TableWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>609</width>
+ <height>415</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="structureTab">
+ <attribute name="title">
+ <string>Structure</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="structureTabLayout">
+ <item>
+ <widget class="QToolBar" name="structureToolBar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="structureTopBar" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="tableNameLabel">
+ <property name="text">
+ <string>Table name:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="tableNameEdit">
+ <property name="maximumSize">
+ <size>
+ <width>200</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="withoutRowIdCheck">
+ <property name="text">
+ <string>WITHOUT ROWID</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSplitter" name="structureSplitter">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="childrenCollapsible">
+ <bool>false</bool>
+ </property>
+ <widget class="QTableView" name="structureView">
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropOverwriteMode">
+ <bool>false</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="constraintsWidget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolBar" name="tableConstraintsToolbar"/>
+ </item>
+ <item>
+ <widget class="QTableView" name="tableConstraintsView">
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropOverwriteMode">
+ <bool>false</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataTab">
+ <attribute name="title">
+ <string>Data</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="DataView" name="dataView">
+ <property name="tabPosition">
+ <enum>QTabWidget::South</enum>
+ </property>
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="constraintsTab">
+ <attribute name="title">
+ <string>Constraints</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QTableView" name="constraintsView">
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="indexesTab">
+ <attribute name="title">
+ <string>Indexes</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QToolBar" name="indexToolBar"/>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="indexList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="triggersTab">
+ <attribute name="title">
+ <string>Triggers</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QToolBar" name="triggerToolBar"/>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="triggerList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="ddlTab">
+ <attribute name="title">
+ <string>DDL</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ <customwidget>
+ <class>DataView</class>
+ <extends>QTabWidget</extends>
+ <header>dataview.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp
new file mode 100644
index 0000000..d54a359
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp
@@ -0,0 +1,760 @@
+#include "viewwindow.h"
+#include "ui_viewwindow.h"
+#include "common/unused.h"
+#include "schemaresolver.h"
+#include "services/notifymanager.h"
+#include "services/dbmanager.h"
+#include "mainwindow.h"
+#include "mdiarea.h"
+#include "sqlitesyntaxhighlighter.h"
+#include "datagrid/sqlquerymodel.h"
+#include "common/utils_sql.h"
+#include "viewmodifier.h"
+#include "common/widgetcover.h"
+#include "db/chainexecutor.h"
+#include "dbtree/dbtree.h"
+#include "parser/ast/sqlitecreatetrigger.h"
+#include "dialogs/messagelistdialog.h"
+#include "dbobjectdialogs.h"
+#include "dialogs/ddlpreviewdialog.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include <QPushButton>
+#include <QProgressBar>
+#include <QDebug>
+#include <QMessageBox>
+
+CFG_KEYS_DEFINE(ViewWindow)
+
+ViewWindow::ViewWindow(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::ViewWindow)
+{
+ init();
+ applyInitialTab();
+}
+
+ViewWindow::ViewWindow(Db* db, QWidget* parent) :
+ MdiChild(parent),
+ db(db),
+ ui(new Ui::ViewWindow)
+{
+ newView();
+ init();
+ applyInitialTab();
+}
+
+ViewWindow::ViewWindow(const ViewWindow& win) :
+ MdiChild(win.parentWidget()),
+ db(win.db),
+ database(win.database),
+ view(win.view),
+ ui(new Ui::ViewWindow)
+{
+ init();
+ initView();
+ applyInitialTab();
+}
+
+ViewWindow::ViewWindow(QWidget* parent, Db* db, const QString& database, const QString& view) :
+ MdiChild(parent),
+ db(db),
+ database(database),
+ view(view),
+ ui(new Ui::ViewWindow)
+{
+ init();
+ initView();
+ applyInitialTab();
+}
+
+ViewWindow::~ViewWindow()
+{
+ delete ui;
+}
+
+void ViewWindow::changeEvent(QEvent *e)
+{
+ QWidget::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+QVariant ViewWindow::saveSession()
+{
+ QHash<QString,QVariant> sessionValue;
+ sessionValue["view"] = view;
+ sessionValue["db"] = db->getName();
+ return sessionValue;
+}
+
+bool ViewWindow::restoreSession(const QVariant& sessionValue)
+{
+ QHash<QString, QVariant> value = sessionValue.toHash();
+ if (value.size() == 0)
+ {
+ notifyWarn("Could not restore window, because no database or view was stored in session for this window.");
+ return false;
+ }
+
+ if (!value.contains("db") || !value.contains("view"))
+ {
+ notifyWarn("Could not restore window, because no database or view was stored in session for this window.");
+ return false;
+ }
+
+ db = DBLIST->getByName(value["db"].toString());
+ if (!db)
+ {
+ notifyWarn(tr("Could not restore window, because database %1 could not be resolved.").arg(value["db"].toString()));
+ return false;
+ }
+
+ if (!db->isOpen() && !db->open())
+ {
+ notifyWarn(tr("Could not restore window, because database %1 could not be open.").arg(value["db"].toString()));
+ return false;
+ }
+
+ view = value["view"].toString();
+ database = value["database"].toString();
+ SchemaResolver resolver(db);
+ if (!resolver.getViews(database).contains(view, Qt::CaseInsensitive))
+ {
+ notifyWarn(tr("Could not restore window, because the view %1 doesn't exist in the database %2.").arg(view).arg(db->getName()));
+ return false;
+ }
+
+ initView();
+ applyInitialTab();
+ return true;
+}
+
+Icon* ViewWindow::getIconNameForMdiWindow()
+{
+ return ICONS.VIEW;
+}
+
+QString ViewWindow::getTitleForMdiWindow()
+{
+ QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")"));
+ if (existingView)
+ return view + dbSuffix;
+
+ QStringList existingNames = MDIAREA->getWindowTitles();
+ if (existingNames.contains(windowTitle()))
+ return windowTitle();
+
+ // Generate new name
+ QString title = tr("New view %1").arg(newViewWindowNum++);
+ while (existingNames.contains(title))
+ title = tr("New view %1").arg(newViewWindowNum++);
+
+ title += dbSuffix;
+ return title;
+}
+
+void ViewWindow::createActions()
+{
+ createQueryTabActions();
+ createTriggersTabActions();
+
+ createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this);
+ createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this);
+}
+
+void ViewWindow::setupDefShortcuts()
+{
+ // Widget context
+ setShortcutContext({
+ REFRESH_TRIGGERS,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ },
+ Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(ViewWindow, Action);
+}
+
+bool ViewWindow::restoreSessionNextTime()
+{
+ return existingView;
+}
+
+QToolBar* ViewWindow::getToolBar(int toolbar) const
+{
+ switch (static_cast<ToolBar>(toolbar))
+ {
+ case TOOLBAR_QUERY:
+ return ui->queryToolbar;
+ case TOOLBAR_TRIGGERS:
+ return ui->triggersToolbar;
+ }
+ return nullptr;
+}
+
+void ViewWindow::init()
+{
+ ui->setupUi(this);
+
+ dataModel = new SqlQueryModel(this);
+ ui->dataView->init(dataModel);
+
+ ui->queryEdit->setVirtualSqlExpression("CREATE VIEW name AS %1");
+ ui->queryEdit->setDb(db);
+
+ connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
+ connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateQueryToolbarStatus()));
+ connect(ui->queryEdit, SIGNAL(textChanged()), this, SLOT(updateQueryToolbarStatus()));
+ connect(ui->queryEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateQueryToolbarStatus()));
+ connect(ui->triggersList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState()));
+
+ structureExecutor = new ChainExecutor(this);
+ connect(structureExecutor, SIGNAL(success()), this, SLOT(changesSuccessfullyCommited()));
+ connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString)));
+
+ setupCoverWidget();
+
+ initActions();
+
+ refreshTriggers();
+ updateQueryToolbarStatus();
+ updateTriggersState();
+}
+
+void ViewWindow::newView()
+{
+ existingView = false;
+ view = "";
+}
+
+void ViewWindow::initView()
+{
+ ui->nameEdit->setText(view);
+
+ parseDdl();
+
+ if (!createView)
+ return; // error occured while parsing ddl, window will be closed
+
+ if (existingView)
+ {
+ dataModel->setDb(db);
+ dataModel->setQuery(originalCreateView->select->detokenize());
+ }
+
+ ui->queryEdit->setDb(db);
+ ui->queryEdit->setPlainText(createView->select->detokenize());
+ updateDdlTab();
+
+ ui->ddlEdit->setSqliteVersion(db->getVersion());
+
+ refreshTriggers();
+
+ connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfViewDeleted(QString,QString,DbObjectType)));
+}
+
+void ViewWindow::setupCoverWidget()
+{
+ widgetCover = new WidgetCover(this);
+ widgetCover->hide();
+ connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt()));
+}
+
+void ViewWindow::createQueryTabActions()
+{
+ createAction(REFRESH_QUERY, ICONS.RELOAD, tr("Refresh the view", "view window"), this, SLOT(refreshView()), ui->queryToolbar);
+ ui->queryToolbar->addSeparator();
+ createAction(COMMIT_QUERY, ICONS.COMMIT, tr("Commit the view changes", "view window"), this, SLOT(commitView()), ui->queryToolbar);
+ createAction(ROLLBACK_QUERY, ICONS.ROLLBACK, tr("Rollback the view changes", "view window"), this, SLOT(rollbackView()), ui->queryToolbar);
+ ui->queryToolbar->addSeparator();
+ ui->queryToolbar->addAction(ui->queryEdit->getAction(SqlEditor::FORMAT_SQL));
+}
+
+void ViewWindow::createTriggersTabActions()
+{
+ createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "view window"), this, SLOT(refreshTriggers()), ui->triggersToolbar, ui->triggersList);
+ ui->triggersToolbar->addSeparator();
+ createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create new triger", "view window"), this, SLOT(addTrigger()), ui->triggersToolbar, ui->triggersList);
+ createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit selected triger", "view window"), this, SLOT(editTrigger()), ui->triggersToolbar, ui->triggersList);
+ createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete selected triger", "view window"), this, SLOT(deleteTrigger()), ui->triggersToolbar, ui->triggersList);
+}
+QString ViewWindow::getView() const
+{
+ return view;
+}
+
+void ViewWindow::setSelect(const QString &selectSql)
+{
+ ui->queryEdit->setPlainText(selectSql);
+}
+
+bool ViewWindow::isUncommited() const
+{
+ return ui->dataView->isUncommited() || isModified();
+}
+
+QString ViewWindow::getQuitUncommitedConfirmMessage() const
+{
+ QString title = getMdiWindow()->windowTitle();
+ if (ui->dataView->isUncommited() && isModified())
+ return tr("View window \"%1\" has uncommited structure modifications and data.").arg(title);
+ else if (ui->dataView->isUncommited())
+ return tr("View window \"%1\" has uncommited data.").arg(title);
+ else if (isModified())
+ return tr("View window \"%1\" has uncommited structure modifications.").arg(title);
+ else
+ {
+ qCritical() << "Unhandled message case in ViewWindow::getQuitUncommitedConfirmMessage().";
+ return QString();
+ }
+}
+
+Db* ViewWindow::getAssociatedDb() const
+{
+ return db;
+}
+
+void ViewWindow::staticInit()
+{
+ qRegisterMetaType<ViewWindow>("ViewWindow");
+}
+
+void ViewWindow::insertAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertAction<ViewWindow>(action, toolbar);
+}
+
+void ViewWindow::insertActionBefore(ExtActionPrototype* action, ViewWindow::Action beforeAction, ViewWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionBefore<ViewWindow>(action, beforeAction, toolbar);
+}
+
+void ViewWindow::insertActionAfter(ExtActionPrototype* action, ViewWindow::Action afterAction, ViewWindow::ToolBar toolbar)
+{
+ return ExtActionContainer::insertActionAfter<ViewWindow>(action, afterAction, toolbar);
+}
+
+void ViewWindow::removeAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar)
+{
+ ExtActionContainer::removeAction<ViewWindow>(action, toolbar);
+}
+
+QString ViewWindow::getDatabase() const
+{
+ return database;
+}
+
+Db* ViewWindow::getDb() const
+{
+ return db;
+}
+
+void ViewWindow::refreshView()
+{
+ initView();
+ updateTriggersState();
+}
+
+void ViewWindow::commitView(bool skipWarnings)
+{
+ if (!isModified())
+ {
+ qWarning() << "Called ViewWindow::commitView(), but isModified() returned false.";
+ updateQueryToolbarStatus();
+ return;
+ }
+
+ if (!validate(skipWarnings))
+ return;
+
+ executeStructureChanges();
+}
+
+void ViewWindow::rollbackView()
+{
+ createView = SqliteCreateViewPtr::create(*originalCreateView.data());
+ ui->nameEdit->setText(createView->view);
+ ui->queryEdit->setPlainText(createView->select->detokenize());
+
+ updateQueryToolbarStatus();
+ updateDdlTab();
+}
+
+QString ViewWindow::getCurrentTrigger() const
+{
+ int row = ui->triggersList->currentRow();
+ QTableWidgetItem* item = ui->triggersList->item(row, 0);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+void ViewWindow::applyInitialTab()
+{
+ if (existingView && !view.isNull() && CFG_UI.General.OpenViewsOnData.get())
+ ui->tabWidget->setCurrentIndex(1);
+ else
+ ui->tabWidget->setCurrentIndex(0);
+}
+
+void ViewWindow::addTrigger()
+{
+ DbObjectDialogs dialogs(db, this);
+ dialogs.addTriggerOnView(view);
+ refreshTriggers();
+}
+
+void ViewWindow::editTrigger()
+{
+ QString trigger = getCurrentTrigger();
+ if (trigger.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.editTrigger(trigger);
+ refreshTriggers();
+}
+
+void ViewWindow::deleteTrigger()
+{
+ QString trigger = getCurrentTrigger();
+ if (trigger.isNull())
+ return;
+
+ DbObjectDialogs dialogs(db, this);
+ dialogs.dropObject(trigger);
+ refreshTriggers();
+}
+
+void ViewWindow::executionSuccessful()
+{
+ modifyingThisView = false;
+ dataLoaded = true;
+}
+
+void ViewWindow::executionFailed(const QString& errorMessage)
+{
+ modifyingThisView = false;
+ notifyError(tr("Could not load data for view %1. Error details: %2").arg(view).arg(errorMessage));
+}
+
+void ViewWindow::tabChanged(int tabIdx)
+{
+ switch (tabIdx)
+ {
+ case 1:
+ {
+ if (isModified())
+ {
+ int res = QMessageBox::question(this, tr("Uncommited changes"),
+ tr("There are uncommited structure modifications. You cannot browse or edit data until you have "
+ "the view structure settled.\n"
+ "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)
+ commitView(true);
+
+ break;
+ }
+
+ if (!dataLoaded)
+ ui->dataView->refreshData();
+
+ break;
+ }
+ case 3:
+ {
+ updateDdlTab();
+ break;
+ }
+ }
+}
+
+void ViewWindow::updateQueryToolbarStatus()
+{
+ bool modified = isModified();
+ bool queryOk = ui->queryEdit->isSyntaxChecked() && !ui->queryEdit->haveErrors();
+ actionMap[COMMIT_QUERY]->setEnabled(modified && queryOk);
+ actionMap[ROLLBACK_QUERY]->setEnabled(modified && existingView);
+ actionMap[REFRESH_QUERY]->setEnabled(existingView);
+}
+
+void ViewWindow::changesSuccessfullyCommited()
+{
+ QStringList sqls = structureExecutor->getQueries();
+ CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
+
+ widgetCover->hide();
+
+ originalCreateView = createView;
+ dataLoaded = false;
+
+ //QString oldView = view; // uncomment when implementing notify manager call
+ database = createView->database;
+ view = createView->view;
+ existingView = true;
+ initView();
+ updateQueryToolbarStatus();
+ updateWindowTitle();
+
+ DBTREE->refreshSchema(db);
+}
+
+void ViewWindow::changesFailedToCommit(int errorCode, const QString& errorText)
+{
+ qDebug() << "ViewWindow::changesFailedToCommit:" << errorCode << errorText;
+
+ widgetCover->hide();
+
+ NotifyManager::getInstance()->error(tr("Could not commit view changes. Error message: %1", "view window").arg(errorText));
+}
+
+void ViewWindow::updateTriggersState()
+{
+ bool editDel = ui->triggersList->currentItem() != nullptr;
+ actionMap[REFRESH_TRIGGERS]->setEnabled(existingView);
+ actionMap[ADD_TRIGGER]->setEnabled(existingView);
+ actionMap[EDIT_TRIGGER]->setEnabled(editDel);
+ actionMap[DEL_TRIGGER]->setEnabled(editDel);
+}
+
+void ViewWindow::nextTab()
+{
+ int idx = ui->tabWidget->currentIndex();
+ idx++;
+ ui->tabWidget->setCurrentIndex(idx);
+}
+
+void ViewWindow::prevTab()
+{
+ int idx = ui->tabWidget->currentIndex();
+ idx--;
+ ui->tabWidget->setCurrentIndex(idx);
+}
+
+void ViewWindow::dbClosedFinalCleanup()
+{
+ dataModel->setDb(nullptr);
+ ui->queryEdit->setDb(nullptr);
+ structureExecutor->setDb(nullptr);
+}
+
+void ViewWindow::checkIfViewDeleted(const QString& database, const QString& object, DbObjectType type)
+{
+ UNUSED(database);
+
+ if (type == DbObjectType::TRIGGER)
+ {
+ for (int i = 0, total = ui->triggersList->rowCount(); i < total; ++i)
+ {
+ if (ui->triggersList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
+ {
+ ui->triggersList->removeRow(i);
+ return;
+ }
+ }
+ }
+
+ if (type != DbObjectType::VIEW)
+ return;
+
+ if (modifyingThisView)
+ return;
+
+ // TODO uncomment below when dbnames are supported
+// if (this->database != database)
+// return;
+
+ if (object.compare(view, Qt::CaseInsensitive) == 0)
+ {
+ dbClosedFinalCleanup();
+ getMdiWindow()->close();
+ }
+}
+
+void ViewWindow::refreshTriggers()
+{
+ if (!db || !db->isValid())
+ return;
+
+ SchemaResolver resolver(db);
+ QList<SqliteCreateTriggerPtr> triggers = resolver.getParsedTriggersForView(database, view);
+
+ ui->triggersList->setColumnCount(4);
+ ui->triggersList->setRowCount(triggers.size());
+ ui->triggersList->horizontalHeader()->setMaximumSectionSize(200);
+ ui->triggersList->setHorizontalHeaderLabels({
+ tr("Name", "view window triggers"),
+ tr("Instead of", "view window triggers"),
+ tr("Condition", "view window triggers"),
+ tr("Details", "table window triggers")
+ });
+
+ QTableWidgetItem* item = nullptr;
+ QString event;
+ int row = 0;
+ foreach (SqliteCreateTriggerPtr trig, triggers)
+ {
+ item = new QTableWidgetItem(trig->trigger);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggersList->setItem(row, 0, item);
+
+ event = trig->tokensMap["trigger_event"].detokenize();
+ item = new QTableWidgetItem(event);
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggersList->setItem(row, 1, item);
+
+ item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : "");
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggersList->setItem(row, 2, item);
+
+ item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed());
+ item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+ ui->triggersList->setItem(row, 3, item);
+
+ row++;
+ }
+
+ ui->triggersList->resizeColumnsToContents();
+ updateTriggersState();
+}
+
+void ViewWindow::parseDdl()
+{
+ if (existingView)
+ {
+ SchemaResolver resolver(db);
+ SqliteQueryPtr parsedObject = resolver.getParsedObject(database, view, SchemaResolver::VIEW);
+ if (!parsedObject.dynamicCast<SqliteCreateView>())
+ {
+ notifyError(tr("Could not process the %1 view correctly. Unable to open a view window.").arg(view));
+ invalid = true;
+ return;
+ }
+
+ createView = parsedObject.dynamicCast<SqliteCreateView>();
+ }
+ else
+ {
+ createView = SqliteCreateViewPtr::create();
+ createView->view = view;
+ createView->dialect = db->getDialect();
+ }
+ originalCreateView = SqliteCreateViewPtr::create(*createView);
+ originalQuery = originalCreateView->select->detokenize();
+}
+
+void ViewWindow::updateDdlTab()
+{
+ QString ddl = "CREATE VIEW %1 AS %2";
+ ui->ddlEdit->setPlainText(ddl.arg(wrapObjIfNeeded(ui->nameEdit->text(), db->getDialect())).arg(ui->queryEdit->toPlainText()));
+}
+
+bool ViewWindow::isModified() const
+{
+ return (originalCreateView && originalCreateView->view != ui->nameEdit->text()) ||
+ ui->queryEdit->toPlainText() != originalQuery ||
+ !existingView;
+}
+
+bool ViewWindow::validate(bool skipWarnings)
+{
+ if (!existingView && !skipWarnings && ui->nameEdit->text().isEmpty())
+ {
+ int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the view is allowed in SQLite, but it is not recommended.\n"
+ "Are you sure you want to create a view with blank name?"), QMessageBox::Yes, QMessageBox::No);
+
+ if (res != QMessageBox::Yes)
+ return false;
+ }
+
+ // Rebuilding createView statement and validating it on the fly.
+ QString ddl = "CREATE VIEW %1 AS %2";
+ QString viewName = wrapObjIfNeeded(ui->nameEdit->text(), db->getDialect());
+ QString select = ui->queryEdit->toPlainText();
+
+ Parser parser(db->getDialect());
+ if (!parser.parse(ddl.arg(viewName).arg(select)) || parser.getQueries().size() < 1)
+ {
+ notifyError(tr("The SELECT statement could not be parsed. Please correct the query and retry.\nDetails: %1").arg(parser.getErrorString()));
+ return false;
+ }
+
+ SqliteQueryPtr query = parser.getQueries().first();
+ SqliteCreateViewPtr viewStmt = query.dynamicCast<SqliteCreateView>();
+ if (!viewStmt)
+ {
+ notifyError(tr("The view could not be modified due to internal SQLiteStudio error. Please report this!"));
+ qCritical() << "Could not parse new view, because parsed object is of different type. The type is"
+ << sqliteQueryTypeToString(query->queryType) << "for following query:" << ddl;
+ return false;
+ }
+
+ createView = viewStmt;
+ return true;
+}
+
+void ViewWindow::executeStructureChanges()
+{
+ QStringList sqls;
+ QList<bool> sqlMandatoryFlags;
+
+ createView->rebuildTokens();
+ if (!existingView)
+ {
+ sqls << createView->detokenize();
+ }
+ else
+ {
+ if (viewModifier)
+ delete viewModifier;
+
+ viewModifier = new ViewModifier(db, database, view);
+ viewModifier->alterView(createView);
+
+ if (viewModifier->hasMessages())
+ {
+ MessageListDialog dialog(tr("Following problems will take place while modifying the view.\n"
+ "Would you like to proceed?", "view window"));
+ dialog.setWindowTitle(tr("View modification", "view window"));
+ foreach (const QString& error, viewModifier->getErrors())
+ dialog.addError(error);
+
+ foreach (const QString& warn, viewModifier->getWarnings())
+ dialog.addWarning(warn);
+
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ sqls = viewModifier->generateSqls();
+ sqlMandatoryFlags = viewModifier->getMandatoryFlags();
+ }
+
+ if (!CFG_UI.General.DontShowDdlPreview.get())
+ {
+ DdlPreviewDialog dialog(db, this);
+ dialog.setDdl(sqls);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ modifyingThisView = true;
+ structureExecutor->setDb(db);
+ structureExecutor->setQueries(sqls);
+ structureExecutor->setMandatoryQueries(sqlMandatoryFlags);
+ structureExecutor->exec();
+ widgetCover->show();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h
new file mode 100644
index 0000000..6b50135
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h
@@ -0,0 +1,146 @@
+#ifndef VIEWWINDOW_H
+#define VIEWWINDOW_H
+
+#include "mdichild.h"
+#include "common/extactioncontainer.h"
+#include "db/db.h"
+#include "parser/ast/sqlitecreateview.h"
+#include "guiSQLiteStudio_global.h"
+#include <QWidget>
+
+namespace Ui {
+ class ViewWindow;
+}
+
+class SqliteSyntaxHighlighter;
+class SqlQueryModel;
+class WidgetCover;
+class QPushButton;
+class QProgressBar;
+class ChainExecutor;
+class ViewModifier;
+
+CFG_KEY_LIST(ViewWindow, QObject::tr("A view window"),
+ CFG_KEY_ENTRY(REFRESH_TRIGGERS, Qt::Key_F5, QObject::tr("Refresh view trigger list"))
+ CFG_KEY_ENTRY(ADD_TRIGGER, Qt::Key_Insert, QObject::tr("Add new trigger"))
+ CFG_KEY_ENTRY(EDIT_TRIGGER, Qt::Key_Return, QObject::tr("Edit selected trigger"))
+ CFG_KEY_ENTRY(DEL_TRIGGER, Qt::Key_Delete, QObject::tr("Delete selected trigger"))
+ CFG_KEY_ENTRY(NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next tab"))
+ CFG_KEY_ENTRY(PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous tab"))
+)
+
+class GUI_API_EXPORT ViewWindow : public MdiChild
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ enum Action
+ {
+ // Structure tab
+ REFRESH_QUERY,
+ COMMIT_QUERY,
+ ROLLBACK_QUERY,
+ // Triggers tab
+ REFRESH_TRIGGERS,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ // All tabs
+ NEXT_TAB,
+ PREV_TAB
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR_QUERY,
+ TOOLBAR_TRIGGERS
+ };
+
+ explicit ViewWindow(QWidget *parent = 0);
+ ViewWindow(Db* db, QWidget *parent = 0);
+ ViewWindow(const ViewWindow& win);
+ ViewWindow(QWidget *parent, Db* db, const QString& database, const QString& view);
+ ~ViewWindow();
+
+ Db* getDb() const;
+ QString getDatabase() const;
+ QString getView() const;
+ void setSelect(const QString& selectSql);
+ bool isUncommited() const;
+ QString getQuitUncommitedConfirmMessage() const;
+ Db* getAssociatedDb() const;
+
+ static void staticInit();
+ static void insertAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_QUERY);
+ static void insertActionBefore(ExtActionPrototype* action, Action beforeAction, ToolBar toolbar = TOOLBAR_QUERY);
+ static void insertActionAfter(ExtActionPrototype* action, Action afterAction, ToolBar toolbar = TOOLBAR_QUERY);
+ static void removeAction(ExtActionPrototype* action, ToolBar toolbar = TOOLBAR_QUERY);
+
+ protected:
+ void changeEvent(QEvent *e);
+ QVariant saveSession();
+ bool restoreSession(const QVariant& sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ bool restoreSessionNextTime();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ void newView();
+ void initView();
+ void setupCoverWidget();
+ void createQueryTabActions();
+ void createTriggersTabActions();
+ void parseDdl();
+ void updateDdlTab();
+ bool isModified() const;
+ bool validate(bool skipWarnings = false);
+ void executeStructureChanges();
+ QString getCurrentTrigger() const;
+ void applyInitialTab();
+
+ Db* db = nullptr;
+ QString database;
+ QString view;
+ bool existingView = true;
+ bool dataLoaded = false;
+ int newViewWindowNum = 1;
+ bool modified = false;
+ SqliteCreateViewPtr originalCreateView;
+ SqliteCreateViewPtr createView;
+ SqlQueryModel* dataModel = nullptr;
+ QString originalQuery;
+ WidgetCover* widgetCover = nullptr;
+ ChainExecutor* structureExecutor = nullptr;
+ ViewModifier* viewModifier = nullptr;
+ Ui::ViewWindow *ui = nullptr;
+ bool modifyingThisView = false;
+
+ private slots:
+ void refreshView();
+ void commitView(bool skipWarnings = false);
+ void rollbackView();
+ void addTrigger();
+ void editTrigger();
+ void deleteTrigger();
+ void executionSuccessful();
+ void executionFailed(const QString& errorMessage);
+ void tabChanged(int tabIdx);
+ void updateQueryToolbarStatus();
+ void changesSuccessfullyCommited();
+ void changesFailedToCommit(int errorCode, const QString& errorText);
+ void updateTriggersState();
+ void nextTab();
+ void prevTab();
+ void dbClosedFinalCleanup();
+ void checkIfViewDeleted(const QString& database, const QString& object, DbObjectType type);
+
+ public slots:
+ void refreshTriggers();
+};
+
+#endif // VIEWWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui
new file mode 100644
index 0000000..734a265
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ViewWindow</class>
+ <widget class="QWidget" name="ViewWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>621</width>
+ <height>468</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="queryTab">
+ <attribute name="title">
+ <string>Query</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QToolBar" name="queryToolbar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="nameWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>View name:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="SqlEditor" name="queryEdit"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataTab">
+ <attribute name="title">
+ <string>Data</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="DataView" name="dataView"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="triggersTab">
+ <attribute name="title">
+ <string>Triggers</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QToolBar" name="triggersToolbar"/>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="triggersList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="ddl">
+ <attribute name="title">
+ <string>DDL</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>DataView</class>
+ <extends>QTabWidget</extends>
+ <header>dataview.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>