aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/guiSQLiteStudio/dialogs
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/dialogs')
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp94
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h37
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui109
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp220
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h42
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui208
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp94
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h40
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui132
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp616
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h113
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui348
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp335
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h58
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp1529
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h141
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui1923
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp213
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h79
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui113
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp219
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h52
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui144
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp590
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h89
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui236
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp58
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h35
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui106
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp47
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h28
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui85
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp737
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h105
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui438
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp376
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h69
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui230
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp468
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h72
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui195
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp97
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h37
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui84
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp275
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h65
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui75
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp68
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h34
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui144
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp119
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h47
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui99
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp332
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h76
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui158
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp42
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h28
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui83
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp75
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h39
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui153
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp248
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h60
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui112
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp52
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h33
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui117
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp413
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h62
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui215
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp31
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h28
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui91
74 files changed, 14415 insertions, 0 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp
new file mode 100644
index 0000000..df790de
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.cpp
@@ -0,0 +1,94 @@
+#include "aboutdialog.h"
+#include "ui_aboutdialog.h"
+#include "common/utils.h"
+#include "sqlitestudio.h"
+#include "iconmanager.h"
+#include "services/extralicensemanager.h"
+#include <QDebug>
+#include <QFile>
+
+AboutDialog::AboutDialog(InitialMode initialMode, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::AboutDialog)
+{
+ init(initialMode);
+}
+
+AboutDialog::~AboutDialog()
+{
+ delete ui;
+}
+
+void AboutDialog::init(InitialMode initialMode)
+{
+ ui->setupUi(this);
+ ui->leftIcon->setPixmap(ICONS.SQLITESTUDIO_APP.toQIcon().pixmap(200, 200));
+
+ ui->tabWidget->setCurrentWidget(initialMode == ABOUT ? ui->about : ui->license);
+
+ QString distName;
+ switch (getDistributionType())
+ {
+ case DistributionType::PORTABLE:
+ distName = tr("Portable distribution.");
+ break;
+ case DistributionType::OSX_BOUNDLE:
+ distName = tr("MacOS X application boundle distribution.");
+ break;
+ case DistributionType::OS_MANAGED:
+ distName = tr("Operating system managed distribution.");
+ break;
+ }
+
+ QString newLabelValue = ui->aboutLabel->text().arg(SQLITESTUDIO->getVersionString(), distName);
+ ui->aboutLabel->setText(newLabelValue);
+
+ licenseContents = "";
+ int row = 1;
+
+ QHash<QString,QString> licenses = SQLITESTUDIO->getExtraLicenseManager()->getLicenses();
+ QHashIterator<QString,QString> it(licenses);
+ while (it.hasNext())
+ {
+ it.next();
+ readLicense(row++, it.key(), it.value());
+ }
+
+ buildIndex();
+
+ ui->licenseEdit->setHtml(licenseContents);
+ indexContents.clear();
+ licenseContents.clear();
+}
+
+void AboutDialog::buildIndex()
+{
+ static const QString entryTpl = QStringLiteral("<li>%1</li>");
+ QStringList entries;
+ for (const QString& idx : indexContents)
+ entries += entryTpl.arg(idx);
+
+ licenseContents.prepend("<h3>Table of contents:</h3><ol>" + entries.join("") + "</ol>");
+}
+
+void AboutDialog::readLicense(int row, const QString& title, const QString& path)
+{
+ QString rowNum = QString::number(row);
+ QString contents = readFile(path);
+ licenseContents += "<h3>" + rowNum + ". " + title + "</h3>";
+ licenseContents += "<pre>" + contents + "</pre>";
+ indexContents += title;
+}
+
+QString AboutDialog::readFile(const QString& path)
+{
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ qCritical() << "Error opening" << file.fileName();
+ return QString::null;
+ }
+ QString contents = QString::fromLatin1(file.readAll()).toHtmlEscaped();
+ file.close();
+ return contents;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h
new file mode 100644
index 0000000..3c828c0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.h
@@ -0,0 +1,37 @@
+#ifndef ABOUTDIALOG_H
+#define ABOUTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QStringList>
+
+namespace Ui {
+ class AboutDialog;
+}
+
+class GUI_API_EXPORT AboutDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ enum InitialMode
+ {
+ ABOUT,
+ LICENSES
+ };
+
+ AboutDialog(InitialMode initialMode, QWidget *parent = 0);
+ ~AboutDialog();
+
+ private:
+ void init(InitialMode initialMode);
+ void buildIndex();
+ void readLicense(int row, const QString& title, const QString& path);
+ QString readFile(const QString& path);
+
+ Ui::AboutDialog *ui = nullptr;
+ QStringList indexContents;
+ QString licenseContents;
+};
+
+#endif // ABOUTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui
new file mode 100644
index 0000000..67fa632
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/aboutdialog.ui
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AboutDialog</class>
+ <widget class="QDialog" name="AboutDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>741</width>
+ <height>447</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>About SQLiteStudio and licenses</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="about">
+ <attribute name="title">
+ <string>About</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="aboutLabel">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;SQLiteStudio v%1&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;Free, open-source, cross-platform SQLite database manager.&lt;br/&gt;&lt;a href=&quot;http://sqlitestudio.pl&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://sqlitestudio.pl&lt;/span&gt;&lt;/a&gt;&lt;br/&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;%2&lt;br/&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;Author and active maintainer:&lt;br/&gt;SalSoft (&lt;a href=&quot;http://salsoft.com.pl&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://salsoft.com.pl&lt;/span&gt;&lt;/a&gt;)&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="license">
+ <attribute name="title">
+ <string>Licenses</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QTextEdit" name="licenseEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="leftIcon">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp
new file mode 100644
index 0000000..8f5d433
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.cpp
@@ -0,0 +1,220 @@
+#include "bugdialog.h"
+#include "ui_bugdialog.h"
+#include "iconmanager.h"
+#include "uiutils.h"
+#include "common/utils.h"
+#include "sqlitestudio.h"
+#include "mainwindow.h"
+#include "bugreportlogindialog.h"
+#include "services/pluginmanager.h"
+#include "services/bugreporter.h"
+#include "services/notifymanager.h"
+#include <QPushButton>
+#include <QDebug>
+#include <QDesktopServices>
+
+BugDialog::BugDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::BugDialog)
+{
+ init();
+}
+
+BugDialog::~BugDialog()
+{
+ delete ui;
+}
+
+void BugDialog::setFeatureRequestMode(bool feature)
+{
+ bugMode = !feature;
+ updateState();
+}
+
+void BugDialog::init()
+{
+ ui->setupUi(this);
+ resize(width(), height() - 50);
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Send"));
+
+ connect(ui->moreDetailsGroup, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ connect(ui->shortDescriptionEdit, SIGNAL(textChanged(QString)), this, SLOT(validate()));
+ connect(ui->longDescriptionEdit, SIGNAL(textChanged()), this, SLOT(validate()));
+ connect(ui->emailEdit, SIGNAL(textChanged(QString)), this, SLOT(validate()));
+ connect(ui->helpButton, SIGNAL(clicked()), this, SLOT(help()));
+ connect(ui->loginButton, SIGNAL(clicked()), this, SLOT(logIn()));
+
+ ui->versionEdit->setText(SQLITESTUDIO->getVersionString());
+ ui->osEdit->setText(getOsString());
+ ui->pluginsEdit->setText(PLUGINS->getLoadedPluginNames().join(", "));
+
+ user = CFG_CORE.Internal.BugReportUser.get();
+
+ if (CFG_CORE.Internal.BugReportRecentError.get())
+ {
+ ui->shortDescriptionEdit->setText(CFG_CORE.Internal.BugReportRecentTitle.get());
+ ui->longDescriptionEdit->setPlainText(CFG_CORE.Internal.BugReportRecentContents.get());
+ }
+
+ updateState();
+ validate();
+}
+
+QString BugDialog::getMessageAboutReportHistory()
+{
+ return tr("You can see all your reported bugs and ideas by selecting menu '%1' and then '%2'.").arg(MAINWINDOW->getSQLiteStudioMenu()->title())
+ .arg(MAINWINDOW->getAction(MainWindow::BUG_REPORT_HISTORY)->text());
+ return "";
+}
+
+void BugDialog::finishedBugReport(bool success, const QString& errorMsg)
+{
+ if (success)
+ {
+ notifyInfo(tr("A bug report sent successfully.") + " " + getMessageAboutReportHistory());
+ }
+ else
+ {
+ CFG_CORE.Internal.BugReportRecentError.set(true);
+ notifyError(tr("An error occurred while sending a bug report: %1\n%2").arg(errorMsg,
+ tr("You can retry sending. The contents will be restored when you open a report dialog after an error like this.")));
+ }
+}
+
+void BugDialog::finishedFeatureRequest(bool success, const QString& errorMsg)
+{
+ if (success)
+ {
+ notifyInfo(tr("An idea proposal sent successfully.") + " " + getMessageAboutReportHistory());
+ }
+ else
+ {
+ CFG_CORE.Internal.BugReportRecentError.set(true);
+ notifyError(tr("An error occurred while sending an idea proposal: %1\n%2").arg(errorMsg,
+ tr("You can retry sending. The contents will be restored when you open a report dialog after an error like this.")));
+ }
+}
+
+void BugDialog::updateState()
+{
+ ui->scrollArea->setVisible(ui->moreDetailsGroup->isChecked());
+
+ ui->moreDetailsGroup->setVisible(bugMode);
+ if (bugMode)
+ {
+ setWindowTitle(tr("A bug report"));
+ ui->shortDescriptionEdit->setPlaceholderText(tr("Describe problem in few words"));
+ ui->longDescriptionEdit->setPlaceholderText(tr("Describe problem and how to reproduce it"));
+ }
+ else
+ {
+ setWindowTitle(tr("A new feature idea"));
+ ui->shortDescriptionEdit->setPlaceholderText(tr("A title for your idea"));
+ ui->longDescriptionEdit->setPlaceholderText(tr("Describe your idea in more details"));
+ }
+
+ if (user.isNull())
+ {
+ ui->currentLoginLabel->setToolTip(tr("Reporting as an unregistered user, using e-mail address."));
+ ui->currentLoginLabel->setPixmap(ICONS.USER_UNKNOWN);
+ ui->emailEdit->setEnabled(true);
+ ui->emailEdit->clear();
+ ui->loginButton->setText(tr("Log in"));
+ ui->loginButton->setIcon(ICONS.USER);
+ }
+ else
+ {
+ ui->currentLoginLabel->setToolTip(tr("Reporting as a registered user."));
+ ui->currentLoginLabel->setPixmap(ICONS.USER);
+ ui->emailEdit->setText(user);
+ ui->emailEdit->setEnabled(false);
+ ui->loginButton->setText(tr("Log out"));
+ ui->loginButton->setIcon(ICONS.USER_UNKNOWN);
+ }
+}
+
+void BugDialog::validate()
+{
+ bool emailOk = !user.isNull() || validateEmail(ui->emailEdit->text());
+ int shortSize = ui->shortDescriptionEdit->text().trimmed().size();
+ int longSize = ui->longDescriptionEdit->toPlainText().trimmed().size();
+ bool shortOk = shortSize >= 10 && shortSize <= 100;
+ bool longOk = longSize >= 30;
+
+ setValidStateWihtTooltip(ui->emailEdit, tr("Providing true email address will make it possible to contact you regarding your report. "
+ "To learn more, press 'help' button on the right side."),
+ emailOk, tr("Enter vaild e-mail address, or log in."));
+
+ setValidState(ui->shortDescriptionEdit, shortOk, tr("Short description requires at least 10 characters, but not more than 100. "
+ "Longer description can be entered in the field below."));
+
+ setValidState(ui->longDescriptionEdit, longOk, tr("Long description requires at least 30 characters."));
+
+ bool valid = shortOk && longOk && emailOk;
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
+}
+
+void BugDialog::help()
+{
+ if (user.isNull())
+ QDesktopServices::openUrl(QUrl(BUGS->getReporterEmailHelpUrl()));
+ else
+ QDesktopServices::openUrl(QUrl(BUGS->getReporterUserAndPasswordHelpUrl()));
+}
+
+void BugDialog::logIn()
+{
+ if (!user.isNull())
+ {
+ // Log out
+ user = QString();
+ updateState();
+ BUGS->clearBugReportCredentials();
+ return;
+ }
+
+ BugReportLoginDialog dialog(this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ if (!dialog.isValid())
+ return;
+
+ BUGS->useBugReportCredentials(dialog.getLogin(), dialog.getPassword());
+ user = dialog.getLogin();
+ updateState();
+}
+
+void BugDialog::accept()
+{
+ CFG_CORE.Internal.BugReportRecentError.set(false);
+ CFG_CORE.Internal.BugReportRecentTitle.set(ui->shortDescriptionEdit->text());
+ CFG_CORE.Internal.BugReportRecentContents.set(ui->longDescriptionEdit->toPlainText());
+
+ if (bugMode)
+ {
+ if (user.isNull())
+ {
+ BUGS->reportBug(ui->emailEdit->text(), ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), ui->versionEdit->text(),
+ ui->osEdit->text(), ui->pluginsEdit->text(), BugDialog::finishedBugReport);
+ }
+ else
+ {
+ BUGS->reportBug(ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), ui->versionEdit->text(), ui->osEdit->text(), ui->pluginsEdit->text(),
+ BugDialog::finishedFeatureRequest);
+ }
+ }
+ else
+ {
+ if (user.isNull())
+ {
+ BUGS->requestFeature(ui->emailEdit->text(), ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), BugDialog::finishedFeatureRequest);
+ }
+ else
+ {
+ BUGS->requestFeature(ui->shortDescriptionEdit->text(), ui->longDescriptionEdit->toPlainText(), BugDialog::finishedFeatureRequest);
+ }
+ }
+ QDialog::accept();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h
new file mode 100644
index 0000000..bf60104
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.h
@@ -0,0 +1,42 @@
+#ifndef BUGDIALOG_H
+#define BUGDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class BugDialog;
+}
+
+class GUI_API_EXPORT BugDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit BugDialog(QWidget *parent = 0);
+ ~BugDialog();
+
+ void setFeatureRequestMode(bool feature);
+
+ private:
+ void init();
+
+ static QString getMessageAboutReportHistory();
+ static void finishedBugReport(bool success, const QString& errorMsg);
+ static void finishedFeatureRequest(bool success, const QString& errorMsg);
+
+ Ui::BugDialog *ui = nullptr;
+ bool bugMode = true;
+ QString user;
+
+ private slots:
+ void updateState();
+ void validate();
+ void help();
+ void logIn();
+
+ public slots:
+ void accept();
+};
+
+#endif // BUGDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui
new file mode 100644
index 0000000..f2dbcf3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugdialog.ui
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BugDialog</class>
+ <widget class="QDialog" name="BugDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>516</width>
+ <height>421</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="reporterGroup">
+ <property name="title">
+ <string>Reporter</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="currentLoginLabel">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../icons.qrc">:/icons/img/user_unknown.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="emailEdit">
+ <property name="placeholderText">
+ <string>E-mail address</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="loginButton">
+ <property name="text">
+ <string>Log in</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/user.png</normaloff>:/icons/img/user.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="helpButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/help.png</normaloff>:/icons/img/help.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="shortDescriptionGroup">
+ <property name="title">
+ <string>Short description</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLineEdit" name="shortDescriptionEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="longDescriptionGroup">
+ <property name="title">
+ <string>Detailed description</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QPlainTextEdit" name="longDescriptionEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="moreDetailsGroup">
+ <property name="title">
+ <string>Show more details</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>462</width>
+ <height>209</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <widget class="QGroupBox" name="versionGroup">
+ <property name="title">
+ <string>SQLiteStudio version</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QLineEdit" name="versionEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="osGroup">
+ <property name="title">
+ <string>Operating system</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QLineEdit" name="osEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="pluginsGroup">
+ <property name="title">
+ <string>Loaded plugins</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QLineEdit" name="pluginsEdit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>BugDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>BugDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp
new file mode 100644
index 0000000..19727fe
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.cpp
@@ -0,0 +1,94 @@
+#include "bugreportlogindialog.h"
+#include "ui_bugreportlogindialog.h"
+#include "uiutils.h"
+#include "services/bugreporter.h"
+#include "iconmanager.h"
+#include "common/widgetcover.h"
+#include <QPushButton>
+
+BugReportLoginDialog::BugReportLoginDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::BugReportLoginDialog)
+{
+ init();
+}
+
+BugReportLoginDialog::~BugReportLoginDialog()
+{
+ delete ui;
+}
+
+bool BugReportLoginDialog::isValid() const
+{
+ return validCredentials;
+}
+
+QString BugReportLoginDialog::getLogin() const
+{
+ return ui->loginEdit->text();
+}
+
+QString BugReportLoginDialog::getPassword() const
+{
+ return ui->passwordEdit->text();
+}
+
+void BugReportLoginDialog::init()
+{
+ ui->setupUi(this);
+ connect(ui->loginEdit, SIGNAL(textChanged(QString)), this, SLOT(credentialsChanged()));
+ connect(ui->passwordEdit, SIGNAL(textChanged(QString)), this, SLOT(credentialsChanged()));
+ connect(ui->validationButton, SIGNAL(clicked()), this, SLOT(remoteValidation()));
+ connect(BUGS, SIGNAL(credentialsValidationResult(bool,QString)), this, SLOT(remoteValidationResult(bool,QString)));
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer(tr("Abort"));
+ connect(widgetCover, SIGNAL(cancelClicked()), this, SLOT(abortRemoteValidation()));
+
+ validate();
+}
+
+void BugReportLoginDialog::credentialsChanged()
+{
+ validCredentials = false;
+ validate();
+}
+
+void BugReportLoginDialog::validate()
+{
+ QString login = ui->loginEdit->text();
+ QString pass = ui->passwordEdit->text();
+
+ bool loginOk = login.size() >= 2;
+ bool passOk = pass.size() >= 5;
+
+ setValidState(ui->loginEdit, loginOk, tr("A login must be at least 2 characters long."));
+ setValidState(ui->passwordEdit, passOk, tr("A password must be at least 5 characters long."));
+
+ bool credentialsOk = loginOk && passOk;
+ ui->validationButton->setEnabled(credentialsOk);
+ ui->validationLabel->setEnabled(credentialsOk);
+
+ bool valid = credentialsOk && validCredentials;
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
+}
+
+void BugReportLoginDialog::abortRemoteValidation()
+{
+ BUGS->abortCredentialsValidation();
+}
+
+void BugReportLoginDialog::remoteValidation()
+{
+ widgetCover->show();
+ BUGS->validateBugReportCredentials(ui->loginEdit->text(), ui->passwordEdit->text());
+}
+
+void BugReportLoginDialog::remoteValidationResult(bool success, const QString& errorMessage)
+{
+ validCredentials = success;
+ ui->validationButton->setIcon(success ? ICONS.TEST_CONN_OK : ICONS.TEST_CONN_ERROR);
+ ui->validationLabel->setText(success ? tr("Valid") : errorMessage);
+ validate();
+ widgetCover->hide();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h
new file mode 100644
index 0000000..131ba3d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.h
@@ -0,0 +1,40 @@
+#ifndef BUGREPORTLOGINDIALOG_H
+#define BUGREPORTLOGINDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class BugReportLoginDialog;
+}
+
+class WidgetCover;
+
+class GUI_API_EXPORT BugReportLoginDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit BugReportLoginDialog(QWidget *parent = 0);
+ ~BugReportLoginDialog();
+
+ bool isValid() const;
+ QString getLogin() const;
+ QString getPassword() const;
+
+ private:
+ void init();
+
+ Ui::BugReportLoginDialog *ui = nullptr;
+ bool validCredentials = false;
+ WidgetCover* widgetCover = nullptr;
+
+ private slots:
+ void credentialsChanged();
+ void validate();
+ void abortRemoteValidation();
+ void remoteValidation();
+ void remoteValidationResult(bool success, const QString& errorMessage);
+};
+
+#endif // BUGREPORTLOGINDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui
new file mode 100644
index 0000000..f6597bc
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/bugreportlogindialog.ui
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BugReportLoginDialog</class>
+ <widget class="QDialog" name="BugReportLoginDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>343</width>
+ <height>197</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Log in</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="credentialsGroup">
+ <property name="title">
+ <string>Credentials</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="loginLabel">
+ <property name="text">
+ <string>Login:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="loginEdit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="passwordLabel">
+ <property name="text">
+ <string>Password</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="passwordEdit">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="validationGroup">
+ <property name="title">
+ <string>Validation</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="validationButton">
+ <property name="text">
+ <string>Validate</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/test_conn_error.png</normaloff>:/icons/img/test_conn_error.png</iconset>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="validationLabel">
+ <property name="text">
+ <string>Validation result message</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>BugReportLoginDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>BugReportLoginDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp
new file mode 100644
index 0000000..f4e48fe
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.cpp
@@ -0,0 +1,616 @@
+#include "columndialog.h"
+#include "common/unused.h"
+#include "ui_columndialog.h"
+#include "columndialogconstraintsmodel.h"
+#include "iconmanager.h"
+#include "newconstraintdialog.h"
+#include "dialogs/constraintdialog.h"
+#include "constraints/constraintpanel.h"
+#include "datatype.h"
+#include "uiutils.h"
+#include <QDebug>
+#include <QCheckBox>
+#include <QMessageBox>
+#include <QDebug>
+#include <QPushButton>
+
+ColumnDialog::ColumnDialog(Db* db, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ColumnDialog),
+ db(db)
+{
+ init();
+}
+
+ColumnDialog::~ColumnDialog()
+{
+ delete ui;
+}
+
+void ColumnDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+ setWindowIcon(ICONS.COLUMN);
+
+ ui->scale->setStrict(true);
+ ui->precision->setStrict(true);
+
+ ui->typeCombo->addItem("");
+ foreach (DataType::Enum type, DataType::getAllTypes())
+ ui->typeCombo->addItem(DataType::toString(type));
+
+ connect(ui->typeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateDataType()));
+
+ constraintsModel = new ColumnDialogConstraintsModel();
+ ui->constraintsView->setModel(constraintsModel);
+ initActions();
+
+ setupConstraintCheckBoxes();
+
+ connect(ui->advancedCheck, SIGNAL(toggled(bool)), this, SLOT(switchMode(bool)));
+
+ connect(ui->constraintsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateConstraintsToolbarState()));
+ connect(ui->constraintsView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editConstraint(QModelIndex)));
+ connect(constraintsModel, SIGNAL(constraintsChanged()), this, SLOT(updateConstraints()));
+ connect(constraintsModel, SIGNAL(constraintsChanged()), this, SLOT(updateState()));
+
+ connect(ui->pkButton, SIGNAL(clicked()), this, SLOT(configurePk()));
+ connect(ui->fkButton, SIGNAL(clicked()), this, SLOT(configureFk()));
+ connect(ui->checkButton, SIGNAL(clicked()), this, SLOT(configureCheck()));
+ connect(ui->defaultButton, SIGNAL(clicked()), this, SLOT(configureDefault()));
+ connect(ui->notNullButton, SIGNAL(clicked()), this, SLOT(configureNotNull()));
+ connect(ui->collateButton, SIGNAL(clicked()), this, SLOT(configureCollate()));
+ connect(ui->uniqueButton, SIGNAL(clicked()), this, SLOT(configureUnique()));
+
+ updateState();
+}
+
+void ColumnDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void ColumnDialog::createActions()
+{
+ createAction(ADD_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_ADD, tr("Add constraint", "column dialog"), this, SLOT(addConstraint()), ui->constraintsToolbar);
+ createAction(EDIT_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_EDIT, tr("Edit constraint", "column dialog"), this, SLOT(editConstraint()), ui->constraintsToolbar);
+ createAction(DEL_CONSTRAINT, ICONS.COLUMN_CONSTRAINT_DEL, tr("Delete constraint", "column dialog"), this, SLOT(delConstraint()), ui->constraintsToolbar);
+ createAction(MOVE_CONSTRAINT_UP, ICONS.MOVE_UP, tr("Move constraint up", "column dialog"), this, SLOT(moveConstraintUp()), ui->constraintsToolbar);
+ createAction(MOVE_CONSTRAINT_DOWN, ICONS.MOVE_DOWN, tr("Move constraint down", "column dialog"), this, SLOT(moveConstraintDown()), ui->constraintsToolbar);
+ ui->constraintsToolbar->addSeparator();
+ createAction(ADD_PK, ICONS.CONSTRAINT_PRIMARY_KEY_ADD, tr("Add a primary key", "column dialog"), this, SLOT(addPk()), ui->constraintsToolbar);
+ createAction(ADD_FK, ICONS.CONSTRAINT_FOREIGN_KEY_ADD, tr("Add a foreign key", "column dialog"), this, SLOT(addFk()), ui->constraintsToolbar);
+ createAction(ADD_UNIQUE, ICONS.CONSTRAINT_UNIQUE_ADD, tr("Add an unique constraint", "column dialog"), this, SLOT(addUnique()), ui->constraintsToolbar);
+ createAction(ADD_CHECK, ICONS.CONSTRAINT_CHECK_ADD, tr("Add a check constraint", "column dialog"), this, SLOT(addCheck()), ui->constraintsToolbar);
+ createAction(ADD_NOT_NULL, ICONS.CONSTRAINT_NOT_NULL_ADD, tr("Add a not null constraint", "column dialog"), this, SLOT(addNotNull()), ui->constraintsToolbar);
+ createAction(ADD_COLLATE, ICONS.CONSTRAINT_COLLATION_ADD, tr("Add a collate constraint", "column dialog"), this, SLOT(addCollate()), ui->constraintsToolbar);
+ createAction(ADD_DEFAULT, ICONS.CONSTRAINT_DEFAULT_ADD, tr("Add a default constraint", "column dialog"), this, SLOT(addDefault()), ui->constraintsToolbar);
+}
+
+void ColumnDialog::setupDefShortcuts()
+{
+}
+
+void ColumnDialog::updateConstraintsToolbarState()
+{
+ QModelIndex idx = ui->constraintsView->selectionModel()->currentIndex();
+ bool hasSelected = idx.isValid();
+ bool isFirst = false;
+ bool isLast = false;
+ if (constraintsModel->rowCount() > 0)
+ {
+ isFirst = (idx.row() == 0);
+ isLast = (idx.row() == (constraintsModel->rowCount() - 1));
+ }
+
+ actionMap[EDIT_CONSTRAINT]->setEnabled(hasSelected);
+ actionMap[DEL_CONSTRAINT]->setEnabled(hasSelected);
+ actionMap[MOVE_CONSTRAINT_UP]->setEnabled(hasSelected && !isFirst);
+ actionMap[MOVE_CONSTRAINT_DOWN]->setEnabled(hasSelected && !isLast);
+}
+
+void ColumnDialog::updateState()
+{
+ ui->pkButton->setEnabled(ui->pkCheck->isChecked());
+ ui->fkButton->setEnabled(ui->fkCheck->isChecked());
+ ui->uniqueButton->setEnabled(ui->uniqueCheck->isChecked());
+ ui->notNullButton->setEnabled(ui->notNullCheck->isChecked());
+ ui->checkButton->setEnabled(ui->checkCheck->isChecked());
+ ui->collateButton->setEnabled(ui->collateCheck->isChecked());
+ ui->defaultButton->setEnabled(ui->defaultCheck->isChecked());
+ updateConstraintsToolbarState();
+}
+
+void ColumnDialog::addConstraint(ConstraintDialog::Constraint mode)
+{
+ NewConstraintDialog dialog(mode, column.data(), db, this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ SqliteStatement* constrStmt = dialog.getConstraint();
+ SqliteCreateTable::Column::Constraint* constr = dynamic_cast<SqliteCreateTable::Column::Constraint*>(constrStmt);
+ if (!constr)
+ {
+ qCritical() << "Constraint returned from ConstraintDialog was not of column type, while we're trying to add column constraint.";
+ return;
+ }
+
+ constraintsModel->appendConstraint(constr);
+ ui->constraintsView->resizeColumnToContents(0);
+ ui->constraintsView->resizeColumnToContents(1);
+}
+
+void ColumnDialog::setupConstraintCheckBoxes()
+{
+ ui->pkCheck->setIcon(ICONS.CONSTRAINT_PRIMARY_KEY);
+ ui->fkCheck->setIcon(ICONS.CONSTRAINT_FOREIGN_KEY);
+ ui->uniqueCheck->setIcon(ICONS.CONSTRAINT_UNIQUE);
+ ui->notNullCheck->setIcon(ICONS.CONSTRAINT_NOT_NULL);
+ ui->checkCheck->setIcon(ICONS.CONSTRAINT_CHECK);
+ ui->collateCheck->setIcon(ICONS.CONSTRAINT_COLLATION);
+ ui->defaultCheck->setIcon(ICONS.CONSTRAINT_DEFAULT);
+
+ connect(ui->pkCheck, SIGNAL(clicked(bool)), this, SLOT(pkToggled(bool)));
+ connect(ui->fkCheck, SIGNAL(clicked(bool)), this, SLOT(fkToggled(bool)));
+ connect(ui->uniqueCheck, SIGNAL(clicked(bool)), this, SLOT(uniqueToggled(bool)));
+ connect(ui->notNullCheck, SIGNAL(clicked(bool)), this, SLOT(notNullToggled(bool)));
+ connect(ui->checkCheck, SIGNAL(clicked(bool)), this, SLOT(checkToggled(bool)));
+ connect(ui->collateCheck, SIGNAL(clicked(bool)), this, SLOT(collateToggled(bool)));
+ connect(ui->defaultCheck, SIGNAL(clicked(bool)), this, SLOT(defaultToggled(bool)));
+
+ for (QCheckBox* cb : {
+ ui->pkCheck,
+ ui->fkCheck,
+ ui->uniqueCheck,
+ ui->notNullCheck,
+ ui->checkCheck,
+ ui->collateCheck,
+ ui->defaultCheck
+ })
+ {
+ connect(cb, SIGNAL(toggled(bool)), this, SLOT(updateState()));
+ }
+}
+
+void ColumnDialog::addConstraint()
+{
+ addConstraint(ConstraintDialog::UNKNOWN);
+}
+
+void ColumnDialog::editConstraint()
+{
+ QModelIndex idx = ui->constraintsView->currentIndex();
+ editConstraint(idx);
+}
+
+void ColumnDialog::delConstraint()
+{
+ QModelIndex idx = ui->constraintsView->currentIndex();
+ delConstraint(idx);
+}
+
+void ColumnDialog::editConstraint(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return;
+
+ SqliteCreateTable::Column::Constraint* constr = constraintsModel->getConstraint(idx.row());
+ editConstraint(constr);
+}
+
+void ColumnDialog::editConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ ConstraintDialog dialog(ConstraintDialog::EDIT, constraint, column.data(), db, this);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ ui->constraintsView->resizeColumnToContents(0);
+ ui->constraintsView->resizeColumnToContents(1);
+ updateConstraints();
+}
+
+void ColumnDialog::delConstraint(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return;
+
+ SqliteCreateTable::Column::Constraint* constr = constraintsModel->getConstraint(idx.row());
+
+ QString arg = constr->name.isNull() ? constr->typeString() : constr->name;
+ QString msg = tr("Are you sure you want to delete constraint '%1'?", "column dialog").arg(arg);
+ int btn = QMessageBox::question(this, tr("Delete constraint", "column dialog"), msg);
+ if (btn != QMessageBox::Yes)
+ return;
+
+ constraintsModel->delConstraint(idx.row());
+}
+
+void ColumnDialog::configureConstraint(SqliteCreateTable::Column::Constraint::Type type)
+{
+ SqliteCreateTable::Column::Constraint* constraint = column->getConstraint(type);
+ if (!constraint)
+ {
+ qCritical() << "Called ColumnDialog::configureConstraint(), but there's no specified type constraint in the column!";
+ return;
+ }
+ editConstraint(constraint);
+}
+
+void ColumnDialog::addEmptyConstraint(SqliteCreateTable::Column::Constraint::Type type)
+{
+ SqliteCreateTable::Column::Constraint* constr = new SqliteCreateTable::Column::Constraint();
+ constr->type = type;
+ constraintsModel->appendConstraint(constr);
+ constr->rebuildTokens();
+}
+
+void ColumnDialog::delAllConstraint(SqliteCreateTable::Column::Constraint::Type type)
+{
+ SqliteCreateTable::Column::Constraint* constr = nullptr;
+ while ((constr = column->getConstraint(type)) != nullptr)
+ constraintsModel->delConstraint(constr);
+}
+
+void ColumnDialog::constraintToggled(SqliteCreateTable::Column::Constraint::Type type, bool enabled)
+{
+ if (enabled)
+ addEmptyConstraint(type);
+ else
+ delAllConstraint(type);
+}
+
+void ColumnDialog::updateConstraintState(SqliteCreateTable::Column::Constraint* constraint)
+{
+ QToolButton* toolButton = getToolButtonForConstraint(constraint);
+ if (!toolButton)
+ return;
+
+ bool result = true;
+ ConstraintPanel* panel = ConstraintPanel::produce(constraint);
+ if (!panel)
+ {
+ qCritical() << "Could not produce ConstraintPanel for constraint validation in ColumnDialog::updateConstraintState().";
+ }
+ else
+ {
+ panel->setDb(db);
+ panel->setConstraint(constraint);
+ result = panel->validateOnly();
+ delete panel;
+ }
+
+ QString errMsg = tr("Correct the constraint's configuration.");
+ if (db->getDialect() == Dialect::Sqlite2 && isUnofficialSqlite2Constraint(constraint))
+ {
+ QString tooltip = tr("This constraint is not officially supported by SQLite 2,\nbut it's okay to use it.");
+ setValidStateWihtTooltip(toolButton, tooltip, result, errMsg);
+ }
+ else
+ {
+ setValidState(toolButton, result, errMsg);
+ }
+
+ if (!result)
+ {
+ QPushButton* btn = ui->buttonBox->button(QDialogButtonBox::Ok);
+ btn->setEnabled(false);
+ }
+}
+
+QCheckBox* ColumnDialog::getCheckBoxForConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return ui->pkCheck;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return ui->notNullCheck;
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return ui->uniqueCheck;
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return ui->checkCheck;
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return ui->defaultCheck;
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return ui->collateCheck;
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return ui->fkCheck;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return nullptr;
+}
+
+QToolButton* ColumnDialog::getToolButtonForConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return ui->pkButton;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return ui->notNullButton;
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ return ui->uniqueButton;
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ return ui->checkButton;
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ return ui->defaultButton;
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return ui->collateButton;
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ return ui->fkButton;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return nullptr;
+}
+
+bool ColumnDialog::isUnofficialSqlite2Constraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::FOREIGN_KEY:
+ case SqliteCreateTable::Column::Constraint::COLLATE:
+ return true;
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ case SqliteCreateTable::Column::Constraint::UNIQUE:
+ case SqliteCreateTable::Column::Constraint::CHECK:
+ case SqliteCreateTable::Column::Constraint::DEFAULT:
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return false;
+}
+
+void ColumnDialog::moveConstraintUp()
+{
+ QModelIndex idx = ui->constraintsView->currentIndex();
+ if (!idx.isValid())
+ return;
+
+ constraintsModel->moveConstraintUp(idx.row());
+}
+
+void ColumnDialog::moveConstraintDown()
+{
+ QModelIndex idx = ui->constraintsView->currentIndex();
+ if (!idx.isValid())
+ return;
+
+ constraintsModel->moveConstraintDown(idx.row());
+}
+
+void ColumnDialog::addPk()
+{
+ addConstraint(ConstraintDialog::PK);
+}
+
+void ColumnDialog::addFk()
+{
+ addConstraint(ConstraintDialog::FK);
+}
+
+void ColumnDialog::addUnique()
+{
+ addConstraint(ConstraintDialog::UNIQUE);
+}
+
+void ColumnDialog::addCheck()
+{
+ addConstraint(ConstraintDialog::CHECK);
+}
+
+void ColumnDialog::addCollate()
+{
+ addConstraint(ConstraintDialog::COLLATE);
+}
+
+void ColumnDialog::addNotNull()
+{
+ addConstraint(ConstraintDialog::NOTNULL);
+}
+
+void ColumnDialog::addDefault()
+{
+ addConstraint(ConstraintDialog::DEFAULT);
+}
+
+void ColumnDialog::configurePk()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY);
+}
+
+void ColumnDialog::configureFk()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::FOREIGN_KEY);
+}
+
+void ColumnDialog::configureUnique()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::UNIQUE);
+}
+
+void ColumnDialog::configureCheck()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::CHECK);
+}
+
+void ColumnDialog::configureCollate()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::COLLATE);
+}
+
+void ColumnDialog::configureNotNull()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::NOT_NULL);
+}
+
+void ColumnDialog::configureDefault()
+{
+ configureConstraint(SqliteCreateTable::Column::Constraint::DEFAULT);
+}
+
+void ColumnDialog::pkToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::PRIMARY_KEY, enabled);
+}
+
+void ColumnDialog::fkToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::FOREIGN_KEY, enabled);
+}
+
+void ColumnDialog::uniqueToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::UNIQUE, enabled);
+}
+
+void ColumnDialog::checkToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::CHECK, enabled);
+}
+
+void ColumnDialog::collateToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::COLLATE, enabled);
+}
+
+void ColumnDialog::notNullToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::NOT_NULL, enabled);
+}
+
+void ColumnDialog::defaultToggled(bool enabled)
+{
+ constraintToggled(SqliteCreateTable::Column::Constraint::DEFAULT, enabled);
+}
+
+void ColumnDialog::switchMode(bool advanced)
+{
+ ui->constraintModesWidget->setCurrentWidget(advanced ? ui->advancedPage : ui->simplePage);
+}
+
+void ColumnDialog::updateConstraints()
+{
+ QPushButton* btn = ui->buttonBox->button(QDialogButtonBox::Ok);
+ btn->setEnabled(true);
+
+ for (QCheckBox* cb : {
+ ui->pkCheck,
+ ui->fkCheck,
+ ui->uniqueCheck,
+ ui->notNullCheck,
+ ui->checkCheck,
+ ui->collateCheck,
+ ui->defaultCheck
+ })
+ {
+ cb->setChecked(false);
+ }
+
+ for (QToolButton* tb : {
+ ui->pkButton,
+ ui->fkButton,
+ ui->uniqueButton,
+ ui->notNullButton,
+ ui->checkButton,
+ ui->collateButton,
+ ui->defaultButton
+ })
+ {
+ setValidState(tb, true);
+ }
+
+ foreach (SqliteCreateTable::Column::Constraint* constr, column->constraints)
+ updateConstraint(constr);
+}
+
+void ColumnDialog::updateConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ QCheckBox* checkBox = getCheckBoxForConstraint(constraint);
+ if (checkBox)
+ {
+ checkBox->setChecked(true);
+ updateConstraintState(constraint);
+ }
+}
+
+void ColumnDialog::setColumn(SqliteCreateTable::Column* value)
+{
+ column = SqliteCreateTable::ColumnPtr::create(*value);
+ column->setParent(value->parent());
+ constraintsModel->setColumn(column.data());
+
+ ui->name->setText(value->name);
+ if (value->type)
+ {
+ ui->typeCombo->setEditText(value->type->name);
+ ui->scale->setValue(value->type->scale, false);
+ ui->precision->setValue(value->type->precision, false);
+ }
+
+ updateConstraints();
+}
+
+SqliteCreateTable::Column* ColumnDialog::getModifiedColumn()
+{
+ column->name = ui->name->text();
+ updateDataType();
+ column->rebuildTokens();
+
+ return new SqliteCreateTable::Column(*column);
+}
+
+QToolBar* ColumnDialog::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+void ColumnDialog::updateDataType()
+{
+ if (!column)
+ return;
+
+ QString typeTxt = ui->typeCombo->currentText();
+ QString scaleTxt = ui->scale->getValue().toString();
+ QString precisionTxt = ui->precision->getValue().toString();
+ if (!typeTxt.isEmpty())
+ {
+ if (!column->type)
+ {
+ column->type = new SqliteColumnType();
+ column->type->setParent(column.data());
+ }
+
+ column->type->name = typeTxt;
+
+ if (!scaleTxt.isEmpty())
+ column->type->scale = ui->scale->getValue();
+
+ if (!precisionTxt.isEmpty())
+ column->type->precision = ui->precision->getValue();
+
+ column->type->rebuildTokens();
+ }
+ else if (column->type) // there was a type, but there's not now
+ {
+ delete column->type;
+ column->type = nullptr;
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h
new file mode 100644
index 0000000..c567e1a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.h
@@ -0,0 +1,113 @@
+#ifndef COLUMNDIALOG_H
+#define COLUMNDIALOG_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "common/extactioncontainer.h"
+#include "constraintdialog.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QPointer>
+
+class ColumnDialogConstraintsModel;
+class QCheckBox;
+class QToolButton;
+
+namespace Ui {
+ class ColumnDialog;
+}
+
+class GUI_API_EXPORT ColumnDialog : public QDialog, public ExtActionContainer
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ ADD_CONSTRAINT,
+ EDIT_CONSTRAINT,
+ DEL_CONSTRAINT,
+ MOVE_CONSTRAINT_UP,
+ MOVE_CONSTRAINT_DOWN,
+ ADD_PK,
+ ADD_FK,
+ ADD_UNIQUE,
+ ADD_CHECK,
+ ADD_DEFAULT,
+ ADD_NOT_NULL,
+ ADD_COLLATE
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit ColumnDialog(Db* db, QWidget *parent = 0);
+ ~ColumnDialog();
+
+ void init();
+ void setColumn(SqliteCreateTable::Column* value);
+ SqliteCreateTable::Column* getModifiedColumn();
+ QToolBar* getToolBar(int toolbar) const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ void createActions();
+ void setupDefShortcuts();
+
+ private:
+ void addConstraint(ConstraintDialog::Constraint mode);
+ void setupConstraintCheckBoxes();
+ void editConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ void delConstraint(const QModelIndex& idx);
+ void configureConstraint(SqliteCreateTable::Column::Constraint::Type type);
+ void addEmptyConstraint(SqliteCreateTable::Column::Constraint::Type type);
+ void delAllConstraint(SqliteCreateTable::Column::Constraint::Type type);
+ void constraintToggled(SqliteCreateTable::Column::Constraint::Type type, bool enabled);
+ void updateConstraintState(SqliteCreateTable::Column::Constraint* constraint);
+ QCheckBox* getCheckBoxForConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ QToolButton* getToolButtonForConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ bool isUnofficialSqlite2Constraint(SqliteCreateTable::Column::Constraint* constraint);
+
+ Ui::ColumnDialog *ui = nullptr;
+ SqliteCreateTable::ColumnPtr column;
+ ColumnDialogConstraintsModel* constraintsModel = nullptr;
+ QCheckBox* modeCheckBox = nullptr;
+ Db* db = nullptr;
+
+ private slots:
+ void updateConstraintsToolbarState();
+ void updateState();
+ void addConstraint();
+ void editConstraint();
+ void editConstraint(const QModelIndex& idx);
+ void delConstraint();
+ void moveConstraintUp();
+ void moveConstraintDown();
+ void addPk();
+ void addFk();
+ void addUnique();
+ void addCheck();
+ void addCollate();
+ void addNotNull();
+ void addDefault();
+ void configurePk();
+ void configureFk();
+ void configureUnique();
+ void configureCheck();
+ void configureCollate();
+ void configureNotNull();
+ void configureDefault();
+ void pkToggled(bool enabled);
+ void fkToggled(bool enabled);
+ void uniqueToggled(bool enabled);
+ void checkToggled(bool enabled);
+ void collateToggled(bool enabled);
+ void notNullToggled(bool enabled);
+ void defaultToggled(bool enabled);
+ void switchMode(bool advanced);
+ void updateConstraints();
+ void updateConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ void updateDataType();
+};
+
+#endif // COLUMNDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui
new file mode 100644
index 0000000..ac7e5ae
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialog.ui
@@ -0,0 +1,348 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ColumnDialog</class>
+ <widget class="QDialog" name="ColumnDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>424</width>
+ <height>360</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Column</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="columnGroup">
+ <property name="title">
+ <string>Name and type</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="2">
+ <widget class="NumericSpinBox" name="scale">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>,</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="NumericSpinBox" name="precision">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="typeLabel">
+ <property name="text">
+ <string>Data type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Column name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="name"/>
+ </item>
+ <item row="0" column="2" colspan="3">
+ <widget class="QLabel" name="sizeLabel">
+ <property name="text">
+ <string>Size:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="typeCombo">
+ <property name="minimumSize">
+ <size>
+ <width>120</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="constraintsGroup">
+ <property name="title">
+ <string>Constraints</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QStackedWidget" name="constraintModesWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="simplePage">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="uniqueCheck">
+ <property name="text">
+ <string>Unique</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QToolButton" name="pkButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="fkCheck">
+ <property name="text">
+ <string>Foreign Key</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QToolButton" name="fkButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="collateCheck">
+ <property name="text">
+ <string>Collate</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="notNullCheck">
+ <property name="text">
+ <string>Not NULL</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="checkCheck">
+ <property name="text">
+ <string>Check condition</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="pkCheck">
+ <property name="text">
+ <string>Primary Key</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QCheckBox" name="defaultCheck">
+ <property name="text">
+ <string>Default</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QToolButton" name="uniqueButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QToolButton" name="checkButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QToolButton" name="notNullButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QToolButton" name="collateButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QToolButton" name="defaultButton">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="advancedPage">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <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="constraintsToolbar"/>
+ </item>
+ <item>
+ <widget class="QTableView" name="constraintsView">
+ <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>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomWidget" 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="QCheckBox" name="advancedCheck">
+ <property name="text">
+ <string>Advanced mode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>NumericSpinBox</class>
+ <extends>QSpinBox</extends>
+ <header>common/numericspinbox.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>name</tabstop>
+ <tabstop>typeCombo</tabstop>
+ <tabstop>scale</tabstop>
+ <tabstop>precision</tabstop>
+ <tabstop>pkCheck</tabstop>
+ <tabstop>pkButton</tabstop>
+ <tabstop>fkCheck</tabstop>
+ <tabstop>fkButton</tabstop>
+ <tabstop>uniqueCheck</tabstop>
+ <tabstop>uniqueButton</tabstop>
+ <tabstop>checkCheck</tabstop>
+ <tabstop>checkButton</tabstop>
+ <tabstop>notNullCheck</tabstop>
+ <tabstop>notNullButton</tabstop>
+ <tabstop>collateCheck</tabstop>
+ <tabstop>collateButton</tabstop>
+ <tabstop>defaultCheck</tabstop>
+ <tabstop>defaultButton</tabstop>
+ <tabstop>advancedCheck</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>constraintsView</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ColumnDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ColumnDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp
new file mode 100644
index 0000000..853b680
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.cpp
@@ -0,0 +1,335 @@
+#include "columndialogconstraintsmodel.h"
+#include "common/unused.h"
+#include "iconmanager.h"
+
+ColumnDialogConstraintsModel::ColumnDialogConstraintsModel(QObject *parent) :
+ QAbstractTableModel(parent)
+{
+}
+
+void ColumnDialogConstraintsModel::setColumn(SqliteCreateTable::Column* value)
+{
+ beginResetModel();
+ column = value;
+ endResetModel();
+}
+
+SqliteCreateTable::Column::Constraint* ColumnDialogConstraintsModel::getConstraint(int constrIdx) const
+{
+ if (column.isNull())
+ return nullptr;
+
+ return column->constraints[constrIdx];
+}
+
+void ColumnDialogConstraintsModel::replaceConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr)
+{
+ if (column.isNull())
+ return;
+
+ delete column->constraints[constrIdx];
+ column->constraints[constrIdx] = constr;
+ constr->setParent(column);
+
+ emit constraintsChanged();
+}
+
+void ColumnDialogConstraintsModel::insertConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr)
+{
+ if (column.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), constrIdx, constrIdx);
+ column->constraints.insert(constrIdx, constr);
+ constr->setParent(column);
+ endInsertRows();
+
+ emit constraintsChanged();
+}
+
+void ColumnDialogConstraintsModel::appendConstraint(SqliteCreateTable::Column::Constraint* constr)
+{
+ if (column.isNull())
+ return;
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ column->constraints.append(constr);
+ constr->setParent(column);
+ endInsertRows();
+
+ emit constraintsChanged();
+}
+
+void ColumnDialogConstraintsModel::delConstraint(int constrIdx)
+{
+ if (column.isNull())
+ return;
+
+ beginRemoveRows(QModelIndex(), constrIdx, constrIdx);
+ delete column->constraints[constrIdx];
+ column->constraints.removeAt(constrIdx);
+ endRemoveRows();
+
+ emit constraintsChanged();
+}
+
+void ColumnDialogConstraintsModel::delConstraint(SqliteCreateTable::Column::Constraint* constr)
+{
+ if (column.isNull())
+ return;
+
+ int constrIdx = column->constraints.indexOf(constr);
+ if (constrIdx < -1)
+ return;
+
+ delConstraint(constrIdx);
+}
+
+void ColumnDialogConstraintsModel::moveConstraintUp(int constrIdx)
+{
+ moveConstraintColumnTo(constrIdx, constrIdx-1);
+}
+
+void ColumnDialogConstraintsModel::moveConstraintDown(int constrIdx)
+{
+ moveConstraintColumnTo(constrIdx, constrIdx+1);
+}
+
+void ColumnDialogConstraintsModel::moveConstraintColumnTo(int constrIdx, int newIdx)
+{
+ if (column.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 >= column->constraints.size())
+ {
+ SqliteCreateTable::Column::Constraint* constr = column->constraints.takeAt(constrIdx);
+ column->constraints.append(constr);
+ }
+ else
+ column->constraints.move(constrIdx, newIdx);
+
+ endMoveRows();
+
+ emit constraintsChanged();
+}
+
+ColumnDialogConstraintsModel::Column ColumnDialogConstraintsModel::getColumn(int colIdx) const
+{
+ return static_cast<Column>(colIdx);
+}
+
+QIcon ColumnDialogConstraintsModel::getIcon(int rowIdx) const
+{
+ SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx];
+ switch (constr->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 ColumnDialogConstraintsModel::getName(int rowIdx) const
+{
+ SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx];
+ return constr->name;
+}
+
+QString ColumnDialogConstraintsModel::getType(int rowIdx) const
+{
+ SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx];
+ switch (constr->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;
+}
+
+QString ColumnDialogConstraintsModel::getDetails(int rowIdx) const
+{
+ SqliteCreateTable::Column::Constraint* constr = column->constraints[rowIdx];
+ 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 ColumnDialogConstraintsModel::getPkDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "NULL", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "UNIQUE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "CHECK", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "DEFAULT", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "COLLATE", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getFkDetails(SqliteCreateTable::Column::Constraint* constr) const
+{
+ int idx = constr->tokens.indexOf(Token::KEYWORD, "KEY", Qt::CaseInsensitive);
+ return getConstrDetails(constr, idx);
+}
+
+QString ColumnDialogConstraintsModel::getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const
+{
+ TokenList tokens = constr->tokens.mid(tokenOffset + 1);
+ tokens.trimLeft();
+ return tokens.detokenize();
+}
+
+int ColumnDialogConstraintsModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ if (column.isNull())
+ return 0;
+
+ return column->constraints.size();
+}
+
+int ColumnDialogConstraintsModel::columnCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return 3;
+}
+
+QVariant ColumnDialogConstraintsModel::data(const QModelIndex& index, int role) const
+{
+ if (column.isNull())
+ return QVariant();
+
+ switch (getColumn(index.column()))
+ {
+ case Column::TYPE:
+ {
+ if (role == Qt::DecorationRole)
+ return getIcon(index.row());
+
+ if (role == Qt::DisplayRole)
+ return getType(index.row());
+
+ break;
+ }
+ case Column::NAME:
+ {
+ if (role == Qt::DisplayRole)
+ return getName(index.row());
+
+ break;
+ }
+ case Column::DETAILS:
+ if (role == Qt::DisplayRole)
+ return getDetails(index.row());
+
+ break;
+ }
+ return QVariant();
+}
+
+QVariant ColumnDialogConstraintsModel::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 Column::TYPE:
+ return tr("Type", "column dialog constraints");
+ case Column::NAME:
+ return tr("Name", "column dialog constraints");
+ case Column::DETAILS:
+ return tr("Details", "column dialog constraints");
+ }
+ return QVariant();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h
new file mode 100644
index 0000000..f37933a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/columndialogconstraintsmodel.h
@@ -0,0 +1,58 @@
+#ifndef COLUMNDIALOGCONSTRAINTSMODEL_H
+#define COLUMNDIALOGCONSTRAINTSMODEL_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "guiSQLiteStudio_global.h"
+#include <QAbstractTableModel>
+#include <QPointer>
+
+class GUI_API_EXPORT ColumnDialogConstraintsModel : public QAbstractTableModel
+{
+ Q_OBJECT
+ public:
+ explicit ColumnDialogConstraintsModel(QObject *parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ int columnCount(const QModelIndex& parent) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+ void setColumn(SqliteCreateTable::Column* value);
+ SqliteCreateTable::Column::Constraint* getConstraint(int constrIdx) const;
+ void replaceConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr);
+ void insertConstraint(int constrIdx, SqliteCreateTable::Column::Constraint* constr);
+ void appendConstraint(SqliteCreateTable::Column::Constraint* constr);
+ void delConstraint(int constrIdx);
+ void delConstraint(SqliteCreateTable::Column::Constraint* constr);
+ void moveConstraintUp(int constrIdx);
+ void moveConstraintDown(int constrIdx);
+ void moveConstraintColumnTo(int constrIdx, int newIdx);
+
+ private:
+ enum class Column
+ {
+ TYPE,
+ NAME,
+ DETAILS
+ };
+
+ Column getColumn(int colIdx) const;
+ QIcon getIcon(int rowIdx) const;
+ QString getName(int rowIdx) const;
+ QString getType(int rowIdx) const;
+ QString getDetails(int rowIdx) const;
+ QString getPkDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getNotNullDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getUniqueDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getCheckDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getDefaultDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getCollateDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getFkDetails(SqliteCreateTable::Column::Constraint* constr) const;
+ QString getConstrDetails(SqliteCreateTable::Column::Constraint* constr, int tokenOffset) const;
+
+ QPointer<SqliteCreateTable::Column> column;
+
+ signals:
+ void constraintsChanged();
+};
+
+#endif // COLUMNDIALOGCONSTRAINTSMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp
new file mode 100644
index 0000000..932036e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.cpp
@@ -0,0 +1,1529 @@
+#include "configdialog.h"
+#include "ui_configdialog.h"
+#include "services/config.h"
+#include "uiconfig.h"
+#include "customconfigwidgetplugin.h"
+#include "services/pluginmanager.h"
+#include "formmanager.h"
+#include "services/codeformatter.h"
+#include "plugins/codeformatterplugin.h"
+#include "configwidgets/styleconfigwidget.h"
+#include "configwidgets/combodatawidget.h"
+#include "configwidgets/listtostringlisthash.h"
+#include "iconmanager.h"
+#include "common/userinputfilter.h"
+#include "multieditor/multieditorwidget.h"
+#include "multieditor/multieditorwidgetplugin.h"
+#include "plugins/confignotifiableplugin.h"
+#include "mainwindow.h"
+#include "common/unused.h"
+#include "sqlitestudio.h"
+#include "configmapper.h"
+#include "datatype.h"
+#include "uiutils.h"
+#include <QSignalMapper>
+#include <QLineEdit>
+#include <QSpinBox>
+#include <QDebug>
+#include <QComboBox>
+#include <QStyleFactory>
+#include <QFile>
+#include <QDir>
+#include <QMessageBox>
+#include <QPlainTextEdit>
+#include <QListWidget>
+#include <QTableWidget>
+#include <QDesktopServices>
+#include <QtUiTools/QUiLoader>
+#include <QKeySequenceEdit>
+#include <plugins/uiconfiguredplugin.h>
+
+#define GET_FILTER_STRING(Widget, WidgetType, Method) \
+ if (qobject_cast<WidgetType*>(Widget))\
+ return qobject_cast<WidgetType*>(Widget)->Method() + " " + Widget->toolTip();\
+
+#define GET_FILTER_STRING2(Widget, WidgetType) \
+ WidgetType* w##WidgetType = qobject_cast<WidgetType*>(widget);\
+ if (w##WidgetType)\
+ return getFilterString(w##WidgetType) + " " + Widget->toolTip();
+
+ConfigDialog::ConfigDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ConfigDialog)
+{
+ init();
+}
+
+ConfigDialog::~ConfigDialog()
+{
+ // Cancel transaction on CfgMain objects from plugins
+ rollbackPluginConfigs();
+
+ // Notify plugins about dialog being closed
+ UiConfiguredPlugin* cfgPlugin = nullptr;
+ foreach (Plugin* plugin, PLUGINS->getLoadedPlugins())
+ {
+ cfgPlugin = dynamic_cast<UiConfiguredPlugin*>(plugin);
+ if (!cfgPlugin)
+ continue;
+
+ cfgPlugin->configDialogClosed();
+ }
+
+ // Delete UI and other resources
+ delete ui;
+ safe_delete(configMapper);
+
+ for (ConfigMapper* mapper : pluginConfigMappers.values())
+ delete mapper;
+
+ pluginConfigMappers.clear();
+
+}
+
+void ConfigDialog::configureDataEditors(const QString& dataTypeString)
+{
+ ui->categoriesWidget->setVisible(false);
+ ui->stackedWidget->setCurrentWidget(ui->dataEditorsPage);
+
+ for (int i = 0; i < ui->dataEditorsTypesList->count(); i++)
+ {
+ if (ui->dataEditorsTypesList->item(i)->text() == dataTypeString.toUpper())
+ {
+ ui->dataEditorsTypesList->setCurrentRow(i);
+ return;
+ }
+ }
+
+ addDataType(dataTypeString.toUpper());
+}
+
+QString ConfigDialog::getFilterString(QWidget *widget)
+{
+ // Common code for widgets with single method call
+ GET_FILTER_STRING(widget, QLabel, text);
+ GET_FILTER_STRING(widget, QAbstractButton, text);
+ GET_FILTER_STRING(widget, QLineEdit, text);
+ GET_FILTER_STRING(widget, QTextEdit, toPlainText);
+ GET_FILTER_STRING(widget, QPlainTextEdit, toPlainText);
+ GET_FILTER_STRING(widget, QGroupBox, title);
+ GET_FILTER_STRING(widget, QKeySequenceEdit, keySequence().toString);
+
+ // Widgets needs a little more than single method call
+ GET_FILTER_STRING2(widget, QComboBox);
+ GET_FILTER_STRING2(widget, QTreeWidget);
+ GET_FILTER_STRING2(widget, QListWidget);
+ GET_FILTER_STRING2(widget, QTableWidget);
+
+ return QString::null;
+}
+
+QString ConfigDialog::getFilterString(QComboBox *widget)
+{
+ QStringList items;
+ for (int i = 0; i < widget->count(); i++)
+ items << widget->itemText(i);
+
+ return items.join(" ");
+}
+
+QString ConfigDialog::getFilterString(QTreeWidget *widget)
+{
+ QList<QTreeWidgetItem*> items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive);
+ QStringList strList;
+ foreach (QTreeWidgetItem* item, items)
+ for (int i = 0; i < widget->columnCount(); i++)
+ strList << item->text(i) + " " + item->toolTip(0);
+
+ return strList.join(" ");
+}
+
+QString ConfigDialog::getFilterString(QListWidget *widget)
+{
+ QList<QListWidgetItem*> items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive);
+ QStringList strList;
+ foreach (QListWidgetItem* item, items)
+ strList << item->text() + " " + item->toolTip();
+
+ return strList.join(" ");
+}
+
+QString ConfigDialog::getFilterString(QTableWidget *widget)
+{
+ QList<QTableWidgetItem*> items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive);
+ QStringList strList;
+ foreach (QTableWidgetItem* item, items)
+ strList << item->text() + " " + item->toolTip();
+
+ return strList.join(" ");
+}
+
+void ConfigDialog::init()
+{
+ ui->setupUi(this);
+ setWindowIcon(ICONS.CONFIGURE);
+
+ ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(0));
+
+ configMapper = new ConfigMapper(CfgMain::getPersistableInstances());
+ connect(configMapper, SIGNAL(modified()), this, SLOT(markModified()));
+ connect(configMapper, &ConfigMapper::notifyEnabledWidgetModified, [=](QWidget* widget, CfgEntry* key, const QVariant& value)
+ {
+ UNUSED(widget);
+ for (ConfigNotifiablePlugin* plugin : notifiablePlugins)
+ plugin->configModified(key, value);
+ });
+
+ ui->categoriesFilter->setClearButtonEnabled(true);
+ UserInputFilter* filter = new UserInputFilter(ui->categoriesFilter, this, SLOT(applyFilter(QString)));
+ filter->setDelay(500);
+
+ ui->stackedWidget->setCurrentWidget(ui->generalPage);
+ initPageMap();
+ initInternalCustomConfigWidgets();
+ initPlugins();
+ initPluginsPage();
+ initFormatterPlugins();
+ initDataEditors();
+ initShortcuts();
+
+ connect(ui->categoriesTree, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(switchPage(QTreeWidgetItem*)));
+ connect(ui->previewTabs, SIGNAL(currentChanged(int)), this, SLOT(updateStylePreview()));
+ connect(ui->activeStyleCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateStylePreview()));
+ connect(ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply()));
+ connect(ui->hideBuiltInPluginsCheck, SIGNAL(toggled(bool)), this, SLOT(updateBuiltInPluginsVisibility()));
+
+ ui->activeStyleCombo->addItems(QStyleFactory::keys());
+
+ connect(ui->stackedWidget, SIGNAL(currentChanged(int)), this, SLOT(pageSwitched()));
+
+ ui->hideBuiltInPluginsCheck->setChecked(true);
+
+#ifdef NO_AUTO_UPDATES
+ ui->updatesGroup->setVisible(false);
+#endif
+
+ load();
+ updateStylePreview();
+}
+
+void ConfigDialog::load()
+{
+ updatingDataEditorItem = true;
+ configMapper->loadToWidget(ui->stackedWidget);
+ updatingDataEditorItem = false;
+ setModified(false);
+}
+
+void ConfigDialog::save()
+{
+ MainWindow::getInstance()->setStyle(ui->activeStyleCombo->currentText());
+
+ QString loadedPlugins = collectLoadedPlugins();
+ storeSelectedFormatters();
+ CFG->beginMassSave();
+ CFG_CORE.General.LoadedPlugins.set(loadedPlugins);
+ configMapper->saveFromWidget(ui->stackedWidget, true);
+ commitPluginConfigs();
+ CFG->commitMassSave();
+}
+
+void ConfigDialog::storeSelectedFormatters()
+{
+ CodeFormatterPlugin* plugin = nullptr;
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ QString lang;
+ QString pluginName;
+ for (int i = 0, total = ui->formatterPluginsTree->topLevelItemCount(); i < total; ++i)
+ {
+ item = ui->formatterPluginsTree->topLevelItem(i);
+ lang = item->text(0);
+
+ combo = formatterLangToPluginComboMap[lang];
+ if (!combo)
+ {
+ qCritical() << "Could not find combo for lang " << lang << " in storeSelectedFormatters()";
+ continue;
+ }
+
+ pluginName = combo->currentData().toString();
+ plugin = dynamic_cast<CodeFormatterPlugin*>(PLUGINS->getLoadedPlugin(pluginName));
+ if (!plugin)
+ {
+ qCritical() << "Could not find plugin for lang " << lang << " in storeSelectedFormatters()";
+ continue;
+ }
+
+ FORMATTER->setFormatter(lang, plugin);
+ }
+
+ FORMATTER->storeCurrentSettings();
+}
+
+void ConfigDialog::markModified()
+{
+ setModified(true);
+}
+
+void ConfigDialog::setModified(bool modified)
+{
+ modifiedFlag = modified;
+ updateModified();
+}
+
+void ConfigDialog::updateModified()
+{
+ ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(modifiedFlag);
+}
+
+void ConfigDialog::applyFilter(const QString &filter)
+{
+ QColor normalColor = ui->categoriesTree->palette().color(QPalette::Active, QPalette::WindowText);
+ QColor disabledColor = ui->categoriesTree->palette().color(QPalette::Disabled, QPalette::WindowText);
+ if (filter.isEmpty())
+ {
+ foreach (QTreeWidgetItem* item, getAllCategoryItems())
+ item->setForeground(0, normalColor);
+
+ return;
+ }
+
+ QList<QWidget*> widgets = ui->stackedWidget->findChildren<QWidget*>();
+ QList<QWidget*> matchedWidgets;
+ foreach (QWidget* widget, widgets)
+ {
+ if (getFilterString(widget).contains(filter, Qt::CaseInsensitive))
+ matchedWidgets << widget;
+ }
+
+ QHash<QWidget*, QTreeWidgetItem*> pageToCategoryItem = buildPageToCategoryItemMap();
+ QSet<QTreeWidgetItem*> matchedCategories;
+ foreach (QWidget* page, pageToCategoryItem.keys())
+ {
+ foreach (QWidget* matched, matchedWidgets)
+ {
+ if (page->isAncestorOf(matched))
+ {
+ if (!pageToCategoryItem.contains(page))
+ {
+ qCritical() << "Page" << page << "not on page-to-category-item mapping.";
+ continue;
+ }
+
+ matchedCategories << pageToCategoryItem[page];
+ break;
+ }
+ }
+ }
+
+ foreach (QTreeWidgetItem* item, getAllCategoryItems())
+ item->setForeground(0, disabledColor);
+
+ foreach (QTreeWidgetItem* item, matchedCategories)
+ {
+ item->setForeground(0, normalColor);
+ while ((item = item->parent()) != nullptr)
+ item->setForeground(0, normalColor);
+ }
+}
+
+QHash<QWidget*, QTreeWidgetItem*> ConfigDialog::buildPageToCategoryItemMap() const
+{
+ QHash<QString,QTreeWidgetItem*> pageNameToCategoryItem;
+ foreach (QTreeWidgetItem* item, getAllCategoryItems())
+ pageNameToCategoryItem[item->statusTip(0)] = item;
+
+ QWidget* page = nullptr;
+ QHash<QWidget*,QTreeWidgetItem*> pageToCategoryItem;
+ for (int i = 0; i < ui->stackedWidget->count(); i++)
+ {
+ page = ui->stackedWidget->widget(i);
+ pageToCategoryItem[page] = pageNameToCategoryItem[page->objectName()];
+ }
+ return pageToCategoryItem;
+}
+
+QList<QTreeWidgetItem *> ConfigDialog::getAllCategoryItems() const
+{
+ return ui->categoriesTree->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive);
+}
+
+QList<MultiEditorWidgetPlugin*> ConfigDialog::getDefaultEditorsForType(DataType::Enum dataType)
+{
+ QList<MultiEditorWidgetPlugin*> plugins = PLUGINS->getLoadedPlugins<MultiEditorWidgetPlugin>();
+ DataType modelDataType;
+ modelDataType.setType(dataType);
+
+ typedef QPair<int,MultiEditorWidgetPlugin*> PluginWithPriority;
+ QList<PluginWithPriority> sortedPlugins;
+ PluginWithPriority editorWithPrio;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ {
+ if (!plugin->validFor(modelDataType))
+ continue;
+
+ editorWithPrio.first = plugin->getPriority(modelDataType);
+ editorWithPrio.second = plugin;
+ sortedPlugins << editorWithPrio;
+ }
+
+ qSort(sortedPlugins.begin(), sortedPlugins.end(), [=](const PluginWithPriority& p1, const PluginWithPriority& p2) -> bool
+ {
+ return p1.first < p2.first;
+ });
+
+ QList<MultiEditorWidgetPlugin*> results;
+ for (const PluginWithPriority& p: sortedPlugins)
+ results << p.second;
+
+ return results;
+}
+
+void ConfigDialog::pageSwitched()
+{
+ if (ui->stackedWidget->currentWidget() == ui->dataEditorsPage)
+ {
+ updateDataTypeEditors();
+ return;
+ }
+}
+
+void ConfigDialog::updateDataTypeEditors()
+{
+ QString typeName = ui->dataEditorsTypesList->currentItem()->text();
+ DataType::Enum typeEnum = DataType::fromString(typeName);
+ bool usingCustomOrder = false;
+ QStringList editorsOrder = getPluginNamesFromDataTypeItem(ui->dataEditorsTypesList->currentItem(), &usingCustomOrder);
+ QList<MultiEditorWidgetPlugin*> sortedPlugins;
+
+ while (ui->dataEditorsSelectedTabs->count() > 0)
+ delete ui->dataEditorsSelectedTabs->widget(0);
+
+ ui->dataEditorsAvailableList->clear();
+ if (usingCustomOrder)
+ sortedPlugins = updateCustomDataTypeEditors(editorsOrder);
+ else
+ sortedPlugins = updateDefaultDataTypeEditors(typeEnum);
+
+ ui->dataEditorsAvailableList->sortItems();
+
+ for (MultiEditorWidgetPlugin* plugin : sortedPlugins)
+ addDataTypeEditor(plugin);
+}
+
+QList<MultiEditorWidgetPlugin*> ConfigDialog::updateCustomDataTypeEditors(const QStringList& editorsOrder)
+{
+ // Building plugins list
+ QList<MultiEditorWidgetPlugin*> plugins = PLUGINS->getLoadedPlugins<MultiEditorWidgetPlugin>();
+ QList<MultiEditorWidgetPlugin*> enabledPlugins;
+ QListWidgetItem* item = nullptr;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ {
+ item = new QListWidgetItem(plugin->getTitle());
+ item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
+ item->setCheckState(editorsOrder.contains(plugin->getName()) ? Qt::Checked : Qt::Unchecked);
+ item->setData(QListWidgetItem::UserType, plugin->getName());
+ if (item->checkState() == Qt::Checked)
+ enabledPlugins << plugin;
+
+ ui->dataEditorsAvailableList->addItem(item);
+ }
+
+ qSort(enabledPlugins.begin(), enabledPlugins.end(), [=](MultiEditorWidgetPlugin* p1, MultiEditorWidgetPlugin* p2) -> bool
+ {
+ return editorsOrder.indexOf(p1->getName()) < editorsOrder.indexOf(p2->getName());
+ });
+
+ return enabledPlugins;
+}
+
+QList<MultiEditorWidgetPlugin*> ConfigDialog::updateDefaultDataTypeEditors(DataType::Enum typeEnum)
+{
+ // Building plugins list
+ QList<MultiEditorWidgetPlugin*> plugins = PLUGINS->getLoadedPlugins<MultiEditorWidgetPlugin>();
+ QList<MultiEditorWidgetPlugin*> enabledPlugins = getDefaultEditorsForType(typeEnum);
+ QListWidgetItem* item = nullptr;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ {
+ item = new QListWidgetItem(plugin->getTitle());
+ item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
+ item->setCheckState(enabledPlugins.contains(plugin) ? Qt::Checked : Qt::Unchecked);
+ item->setData(QListWidgetItem::UserType, plugin->getName());
+ ui->dataEditorsAvailableList->addItem(item);
+ }
+ return enabledPlugins;
+}
+
+void ConfigDialog::addDataTypeEditor(const QString& pluginName)
+{
+ MultiEditorWidgetPlugin* plugin = dynamic_cast<MultiEditorWidgetPlugin*>(PLUGINS->getLoadedPlugin(pluginName));
+ if (!plugin)
+ {
+ qCritical() << "Could not find plugin" << pluginName << " in ConfigDialog::addDataTypeEditor()";
+ return;
+ }
+
+ addDataTypeEditor(plugin);
+}
+
+void ConfigDialog::addDataTypeEditor(MultiEditorWidgetPlugin* plugin)
+{
+ MultiEditorWidget* editor = plugin->getInstance();
+ ui->dataEditorsSelectedTabs->addTab(editor, editor->getTabLabel().replace("&", "&&"));
+}
+
+void ConfigDialog::removeDataTypeEditor(QListWidgetItem* item, const QString& pluginName)
+{
+ QStringList orderedPlugins = getPluginNamesFromDataTypeItem(item);
+ int idx = orderedPlugins.indexOf(pluginName);
+ removeDataTypeEditor(idx);
+}
+
+void ConfigDialog::removeDataTypeEditor(int idx)
+{
+ if (idx < 0 || idx > (ui->dataEditorsSelectedTabs->count() - 1))
+ {
+ qCritical() << "Index out of range in ConfigDialog::removeDataTypeEditor():" << idx << "(tabs:" << ui->dataEditorsSelectedTabs->count() << ")";
+ return;
+ }
+
+ delete ui->dataEditorsSelectedTabs->widget(idx);
+}
+
+void ConfigDialog::transformDataTypeEditorsToCustomList(QListWidgetItem* typeItem)
+{
+ DataType::Enum dataType = DataType::fromString(typeItem->text());
+ QList<MultiEditorWidgetPlugin*> plugins = getDefaultEditorsForType(dataType);
+
+ QStringList pluginNames;
+ for (MultiEditorWidgetPlugin* plugin : plugins)
+ pluginNames << plugin->getName();
+
+ setPluginNamesForDataTypeItem(typeItem, pluginNames);
+}
+
+QStringList ConfigDialog::getPluginNamesFromDataTypeItem(QListWidgetItem* typeItem, bool* exists)
+{
+ QVariant data = typeItem->data(QListWidgetItem::UserType);
+ if (exists)
+ *exists = data.isValid();
+
+ return data.toStringList();
+}
+
+void ConfigDialog::setPluginNamesForDataTypeItem(QListWidgetItem* typeItem, const QStringList& pluginNames)
+{
+ updatingDataEditorItem = true;
+ typeItem->setData(QListWidgetItem::UserType, pluginNames);
+ updatingDataEditorItem = false;
+}
+
+void ConfigDialog::addDataType(const QString& typeStr)
+{
+ QListWidgetItem* item = new QListWidgetItem(typeStr);
+ item->setFlags(item->flags()|Qt::ItemIsEditable);
+ ui->dataEditorsTypesList->addItem(item);
+ ui->dataEditorsTypesList->setCurrentRow(ui->dataEditorsTypesList->count() - 1, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+ markModified();
+}
+
+void ConfigDialog::rollbackPluginConfigs()
+{
+ CfgMain* mainCfg = nullptr;
+ for (UiConfiguredPlugin* plugin : pluginConfigMappers.keys())
+ {
+ mainCfg = plugin->getMainUiConfig();
+ if (mainCfg)
+ mainCfg->rollback();
+ }
+}
+
+void ConfigDialog::commitPluginConfigs()
+{
+ CfgMain* mainCfg = nullptr;
+ for (UiConfiguredPlugin* plugin : pluginConfigMappers.keys())
+ {
+ mainCfg = plugin->getMainUiConfig();
+ if (mainCfg)
+ {
+ mainCfg->commit();
+ mainCfg->begin(); // be prepared for further changes after "Apply"
+ }
+ }
+}
+
+void ConfigDialog::updateDataTypeListState()
+{
+ bool listEditingEnabled = ui->dataEditorsTypesList->selectedItems().size() > 0 && ui->dataEditorsTypesList->currentItem()->flags().testFlag(Qt::ItemIsEditable);
+ dataEditRenameAction->setEnabled(listEditingEnabled);
+ dataEditDeleteAction->setEnabled(listEditingEnabled);
+
+ bool orderEditingEnabled = ui->dataEditorsTypesList->selectedItems().size() > 0;
+ ui->dataEditorsAvailableList->setEnabled(orderEditingEnabled);
+ ui->dataEditorsSelectedTabs->setEnabled(orderEditingEnabled);
+}
+
+void ConfigDialog::dataEditorItemEdited(QListWidgetItem* item)
+{
+ if (updatingDataEditorItem)
+ return;
+
+ updatingDataEditorItem = true;
+ QString txt = item->text().toUpper();
+ if (DataType::getAllNames().contains(txt))
+ txt += "_";
+
+ while (ui->dataEditorsTypesList->findItems(txt, Qt::MatchExactly).size() > 1)
+ txt += "_";
+
+ item->setText(txt);
+ updatingDataEditorItem = false;
+}
+
+void ConfigDialog::dataEditorAvailableChanged(QListWidgetItem* item)
+{
+ QListWidgetItem* typeItem = ui->dataEditorsTypesList->currentItem();
+ if (!typeItem)
+ return;
+
+ bool exists = false;
+ QStringList pluginNames = getPluginNamesFromDataTypeItem(typeItem, &exists);
+ if (!exists)
+ {
+ transformDataTypeEditorsToCustomList(typeItem);
+ pluginNames = getPluginNamesFromDataTypeItem(typeItem);
+ }
+
+ QString pluginName = item->data(QListWidgetItem::UserType).toString();
+ Qt::CheckState state = item->checkState();
+ if (pluginNames.contains(pluginName) && state == Qt::Unchecked)
+ {
+ removeDataTypeEditor(typeItem, pluginName);
+ pluginNames.removeOne(pluginName);
+
+ }
+ else if (!pluginNames.contains(pluginName) && state == Qt::Checked)
+ {
+ addDataTypeEditor(pluginName);
+ pluginNames << pluginName;
+ }
+
+ setPluginNamesForDataTypeItem(typeItem, pluginNames);
+}
+
+void ConfigDialog::dataEditorTabsOrderChanged(int from, int to)
+{
+ QListWidgetItem* typeItem = ui->dataEditorsTypesList->currentItem();
+ if (!typeItem)
+ return;
+
+ bool exists = false;
+ QStringList pluginNames = getPluginNamesFromDataTypeItem(typeItem, &exists);
+ if (!exists)
+ {
+ transformDataTypeEditorsToCustomList(typeItem);
+ pluginNames = getPluginNamesFromDataTypeItem(typeItem);
+ }
+
+ int pluginSize = pluginNames.size();
+ if (from >= pluginSize || to >= pluginSize)
+ {
+ qCritical() << "Tabse moved out of range. in ConfigDialog::dataEditorTabsOrderChanged(). Range was: " << pluginSize << "and indexes were:" << from << to;
+ return;
+ }
+
+ QString pluginName = pluginNames[from];
+ pluginNames.removeAt(from);
+ pluginNames.insert(to, pluginName);
+
+ setPluginNamesForDataTypeItem(typeItem, pluginNames);
+}
+
+void ConfigDialog::addDataType()
+{
+ addDataType("");
+ renameDataType();
+}
+
+void ConfigDialog::renameDataType()
+{
+ QListWidgetItem* item = ui->dataEditorsTypesList->currentItem();
+ if (!item)
+ return;
+
+ ui->dataEditorsTypesList->editItem(item);
+}
+
+void ConfigDialog::delDataType()
+{
+ QListWidgetItem* item = ui->dataEditorsTypesList->currentItem();
+ if (!item)
+ return;
+
+ int row = ui->dataEditorsTypesList->currentRow();
+ delete ui->dataEditorsTypesList->takeItem(row);
+
+ if (ui->dataEditorsTypesList->count() > 0)
+ {
+ if (ui->dataEditorsTypesList->count() <= row)
+ {
+ row--;
+ if (row < 0)
+ row = 0;
+ }
+
+ ui->dataEditorsTypesList->setCurrentRow(row, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+ }
+
+ updateDataTypeListState();
+ markModified();
+}
+
+void ConfigDialog::dataTypesHelp()
+{
+ static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Customizing_data_type_editors");
+ QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
+}
+
+void ConfigDialog::updateActiveFormatterState()
+{
+ CodeFormatterPlugin* plugin = nullptr;
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ QToolButton* button = nullptr;
+ QString lang;
+ QString pluginName;
+ for (int i = 0, total = ui->formatterPluginsTree->topLevelItemCount(); i < total; ++i)
+ {
+ item = ui->formatterPluginsTree->topLevelItem(i);
+ lang = item->text(0);
+
+ combo = formatterLangToPluginComboMap[lang];
+ button = formatterLangToConfigButtonMap[lang];
+ if (!button)
+ {
+ qCritical() << "Could not find button for lang " << lang << " in updateActiveFormatterState()";
+ continue;
+ }
+
+ if (!combo)
+ {
+ qCritical() << "Could not find combo for lang " << lang << " in updateActiveFormatterState()";
+ button->setEnabled(false);
+ continue;
+ }
+
+ pluginName = combo->currentData().toString();
+ plugin = dynamic_cast<CodeFormatterPlugin*>(PLUGINS->getLoadedPlugin(pluginName));
+ if (!plugin)
+ {
+ qCritical() << "Could not find plugin for lang " << lang << " in updateActiveFormatterState()";
+ button->setEnabled(false);
+ continue;
+ }
+
+ button->setEnabled(dynamic_cast<UiConfiguredPlugin*>(plugin));
+ }
+}
+
+void ConfigDialog::configureFormatter(const QString& pluginTitle)
+{
+ QTreeWidgetItem* item = getItemByTitle(pluginTitle);
+ if (!item)
+ return;
+
+ ui->categoriesTree->setCurrentItem(item);
+}
+
+void ConfigDialog::activeFormatterChanged()
+{
+ markModified();
+ updateActiveFormatterState();
+}
+
+void ConfigDialog::detailsClicked(const QString& pluginName)
+{
+ static const QString details = QStringLiteral(
+ "<table>"
+ "<thead>"
+ "<tr><td colspan=2 align=\"center\"><b>%1</b></td></tr>"
+ "<tr><td colspan=2></td></tr>"
+ "</thead>"
+ "<tbody>%2</tbody>"
+ "</table>");
+ static const QString row = QStringLiteral("<tr><td>%1</td><td align=\"right\">%2</td></tr>");
+ static const QString hline = QStringLiteral("<tr><td colspan=\"2\"><hr/></td></tr>");
+
+ PluginType* type = PLUGINS->getPluginType(pluginName);
+ Q_ASSERT(type != nullptr);
+
+ // Rows
+ QStringList rows;
+ rows << row.arg(tr("Description:", "plugin details")).arg(PLUGINS->getDescription(pluginName));
+ rows << row.arg(tr("Category:", "plugin details")).arg(type->getTitle());
+ rows << row.arg(tr("Version:", "plugin details")).arg(PLUGINS->getPrintableVersion(pluginName));
+ rows << row.arg(tr("Author:", "plugin details")).arg(PLUGINS->getAuthor(pluginName));
+ rows << hline;
+ rows << row.arg(tr("Internal name:", "plugin details")).arg(pluginName);
+ rows << row.arg(tr("Dependencies:", "plugin details")).arg(PLUGINS->getDependencies(pluginName).join(", "));
+ rows << row.arg(tr("Conflicts:", "plugin details")).arg(PLUGINS->getConflicts(pluginName).join(", "));
+
+ // Message
+ QString pluginDetails = details.arg(PLUGINS->getTitle(pluginName)).arg(rows.join(""));
+ QMessageBox::information(this, tr("Plugin details"), pluginDetails);
+}
+
+void ConfigDialog::failedToLoadPlugin(const QString& pluginName)
+{
+ QTreeWidgetItem* theItem = itemToPluginNameMap.valueByRight(pluginName);
+ if (!theItem)
+ {
+ qWarning() << "Plugin" << pluginName << "failed to load, but it could not be found on the plugins list in ConfigDialog.";
+ return;
+ }
+
+ theItem->setCheckState(0, Qt::Unchecked);
+}
+
+void ConfigDialog::codeFormatterUnloaded()
+{
+ refreshFormattersPage();
+}
+
+void ConfigDialog::codeFormatterLoaded()
+{
+ refreshFormattersPage();
+}
+
+void ConfigDialog::loadUnloadPlugin(QTreeWidgetItem* item, int column)
+{
+ if (column != 0)
+ return;
+
+ QString pluginName = itemToPluginNameMap.valueByLeft(item);
+ if (PLUGINS->isBuiltIn(pluginName))
+ return;
+
+ bool wasLoaded = PLUGINS->isLoaded(pluginName);
+
+ if (wasLoaded == (item->checkState(0) == Qt::Checked))
+ return;
+
+ if (wasLoaded)
+ PLUGINS->unload(pluginName);
+ else
+ PLUGINS->load(pluginName);
+
+ markModified();
+}
+
+void ConfigDialog::pluginAboutToUnload(Plugin* plugin, PluginType* type)
+{
+ // Deinit tree item
+ QTreeWidgetItem* typeItem = getPluginsCategoryItem(type);
+ QTreeWidgetItem* pluginItem = getPluginItem(plugin);
+ if (pluginItem)
+ {
+ typeItem->removeChild(pluginItem);
+ pluginToItemMap.remove(plugin);
+ }
+
+ // Notifiable plugin
+ ConfigNotifiablePlugin* notifiablePlugin = dynamic_cast<ConfigNotifiablePlugin*>(plugin);
+ if (notifiablePlugin && notifiablePlugins.contains(notifiablePlugin))
+ notifiablePlugins.removeOne(notifiablePlugin);
+
+ // Deinit page
+ deinitPluginPage(plugin);
+
+ // Update tree categories
+ updatePluginCategoriesVisibility();
+}
+
+void ConfigDialog::pluginLoaded(Plugin* plugin, PluginType* type, bool skipConfigLoading)
+{
+ // Update formatters page
+ if (type->isForPluginType<CodeFormatterPlugin>())
+ codeFormatterLoaded();
+
+ // Init page
+ if (!initPluginPage(plugin, skipConfigLoading))
+ return;
+
+ // Init tree item
+ QTreeWidgetItem* typeItem = getPluginsCategoryItem(type);
+ QTreeWidgetItem* pluginItem = new QTreeWidgetItem({plugin->getTitle()});
+ pluginItem->setStatusTip(0, plugin->getName());
+ typeItem->addChild(pluginItem);
+ pluginToItemMap[plugin] = pluginItem;
+
+ // Update tree categories
+ updatePluginCategoriesVisibility();
+
+ // Notifiable plugin
+ ConfigNotifiablePlugin* notifiablePlugin = dynamic_cast<ConfigNotifiablePlugin*>(plugin);
+ if (notifiablePlugin)
+ notifiablePlugins << notifiablePlugin;
+}
+
+void ConfigDialog::pluginUnloaded(const QString& pluginName, PluginType* type)
+{
+ UNUSED(pluginName);
+
+ // Update formatters page
+ if (type->isForPluginType<CodeFormatterPlugin>())
+ codeFormatterUnloaded();
+}
+
+void ConfigDialog::updatePluginCategoriesVisibility()
+{
+ QTreeWidgetItem* categories = getPluginsCategoryItem();
+ for (int i = 0; i < categories->childCount(); i++)
+ updatePluginCategoriesVisibility(categories->child(i));
+}
+
+void ConfigDialog::updateBuiltInPluginsVisibility()
+{
+ bool hideBuiltIn = ui->hideBuiltInPluginsCheck->isChecked();
+ QHashIterator<QTreeWidgetItem*,QString> it = itemToPluginNameMap.iterator();
+ while (it.hasNext())
+ {
+ it.next();
+ if (PLUGINS->isBuiltIn(it.value()))
+ ui->pluginsList->setItemHidden(it.key(), hideBuiltIn);
+ else
+ ui->pluginsList->setItemHidden(it.key(), false);
+ }
+}
+
+void ConfigDialog::applyShortcutsFilter(const QString &filter)
+{
+ QTreeWidgetItem* categoryItem = nullptr;
+ QTreeWidgetItem* item = nullptr;
+ QKeySequenceEdit* seqEdit = nullptr;
+ bool empty = filter.isEmpty();
+ bool visible = true;
+ int foundInCategory = 0;
+ for (int i = 0, total_i = ui->shortcutsTable->topLevelItemCount(); i < total_i; ++i)
+ {
+ foundInCategory = 0;
+ categoryItem = ui->shortcutsTable->topLevelItem(i);
+ for (int j = 0 , total_j = categoryItem->childCount(); j < total_j; ++j)
+ {
+ item = categoryItem->child(j);
+ seqEdit = dynamic_cast<QKeySequenceEdit*>(ui->shortcutsTable->itemWidget(item, 1));
+ visible = empty || item->text(0).contains(filter, Qt::CaseInsensitive) ||
+ seqEdit->keySequence().toString().contains(filter, Qt::CaseInsensitive);
+
+ item->setHidden(!visible);
+ if (visible)
+ foundInCategory++;
+ }
+
+ categoryItem->setHidden(foundInCategory == 0);
+ }
+}
+
+void ConfigDialog::updatePluginCategoriesVisibility(QTreeWidgetItem* categoryItem)
+{
+ categoryItem->setHidden(categoryItem->childCount() == 0);
+}
+
+QString ConfigDialog::collectLoadedPlugins() const
+{
+ QStringList loaded;
+ QHashIterator<QTreeWidgetItem*,QString> it = itemToPluginNameMap.iterator();
+ while (it.hasNext())
+ {
+ it.next();
+ loaded << (it.value() + "=" + ((it.key()->checkState(0) == Qt::Checked) ? "1" : "0"));
+ }
+
+ return loaded.join(",");
+}
+
+void ConfigDialog::initPageMap()
+{
+ int pages = ui->stackedWidget->count();
+ QWidget* widget = nullptr;
+ for (int i = 0; i < pages; i++)
+ {
+ widget = ui->stackedWidget->widget(i);
+ nameToPage[widget->objectName()] = widget;
+ }
+}
+
+void ConfigDialog::initInternalCustomConfigWidgets()
+{
+ QList<CustomConfigWidgetPlugin*> customWidgets;
+ customWidgets << new StyleConfigWidget();
+ customWidgets << new ListToStringListHash(&CFG_UI.General.DataEditorsOrder);
+ configMapper->setInternalCustomConfigWidgets(customWidgets);
+}
+
+void ConfigDialog::initFormatterPlugins()
+{
+ ui->formatterPluginsTree->header()->setSectionsMovable(false);
+ ui->formatterPluginsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+ ui->formatterPluginsTree->resizeColumnToContents(1);
+ ui->formatterPluginsTree->resizeColumnToContents(2);
+
+ refreshFormattersPage();
+}
+
+void ConfigDialog::refreshFormattersPage()
+{
+ ui->formatterPluginsTree->clear();
+
+ QHash<QString,QVariant> activeFormatters = CFG_CORE.General.ActiveCodeFormatter.get();
+
+ QList<CodeFormatterPlugin*> plugins = PLUGINS->getLoadedPlugins<CodeFormatterPlugin>();
+ QHash<QString,QList<CodeFormatterPlugin*>> groupedPlugins;
+ for (CodeFormatterPlugin* plugin : plugins)
+ groupedPlugins[plugin->getLanguage()] << plugin;
+
+ formatterLangToPluginComboMap.clear();
+ formatterLangToConfigButtonMap.clear();
+ int row = 0;
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ QToolButton* configButton = nullptr;
+ QStringList pluginTitles;
+ QStringList pluginNames;
+ QStringList sortedPluginNames;
+ QString selectedPluginName;
+ QModelIndex index;
+ QString groupName;
+ QHashIterator<QString,QList<CodeFormatterPlugin*>> it(groupedPlugins);
+ while (it.hasNext())
+ {
+ it.next();
+ groupName = it.key();
+
+ item = new QTreeWidgetItem({groupName});
+ ui->formatterPluginsTree->addTopLevelItem(item);
+
+ pluginNames.clear();
+ pluginTitles.clear();
+ for (CodeFormatterPlugin* plugin : it.value())
+ {
+ pluginNames << plugin->getName();
+ pluginTitles << plugin->getTitle();
+ }
+ sortedPluginNames = pluginNames;
+ qSort(sortedPluginNames);
+
+ combo = new QComboBox(ui->formatterPluginsTree);
+ for (int i = 0, total = pluginNames.size(); i < total; ++i)
+ combo->addItem(pluginTitles[i], pluginNames[i]);
+
+ connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(activeFormatterChanged()));
+ index = ui->formatterPluginsTree->model()->index(row, 1);
+ ui->formatterPluginsTree->setIndexWidget(index, combo);
+ formatterLangToPluginComboMap[groupName] = combo;
+
+ if (activeFormatters.contains(groupName) && pluginNames.contains(activeFormatters[groupName].toString()))
+ {
+ selectedPluginName = activeFormatters[groupName].toString();
+ }
+ else
+ {
+ // Pick first from sorted list and put it to combobox
+ selectedPluginName = sortedPluginNames.first();
+ }
+
+ configButton = new QToolButton(ui->formatterPluginsTree);
+ configButton->setIcon(ICONS.CONFIGURE);
+ index = ui->formatterPluginsTree->model()->index(row, 2);
+ ui->formatterPluginsTree->setIndexWidget(index, configButton);
+ connect(configButton, &QToolButton::clicked, [this, combo]() {configureFormatter(combo->currentText());});
+ formatterLangToConfigButtonMap[groupName] = configButton;
+
+ combo->setCurrentIndex(pluginNames.indexOf(selectedPluginName));
+
+ row++;
+ }
+
+ updateActiveFormatterState();
+}
+
+void ConfigDialog::applyStyle(QWidget *widget, QStyle *style)
+{
+ widget->setStyle(style);
+ foreach (QObject* child, widget->children())
+ {
+ if (!qobject_cast<QWidget*>(child))
+ continue;
+
+ applyStyle(qobject_cast<QWidget*>(child), style);
+ }
+}
+
+QTreeWidgetItem* ConfigDialog::getPluginsCategoryItem() const
+{
+ QTreeWidgetItem* item = nullptr;
+ for (int i = 0; i < ui->categoriesTree->topLevelItemCount(); i++)
+ {
+ item = ui->categoriesTree->topLevelItem(i);
+ if (item->statusTip(0) == ui->pluginsPage->objectName())
+ return item;
+ }
+ Q_ASSERT_X(true, "ConfigDialog", "No Plugins toplevel item in config categories tree!");
+ return nullptr;
+}
+
+QTreeWidgetItem* ConfigDialog::getPluginsCategoryItem(PluginType* type) const
+{
+ if (!pluginTypeToItemMap.contains(type))
+ return nullptr;
+
+ return pluginTypeToItemMap[type];
+}
+
+QTreeWidgetItem* ConfigDialog::getPluginItem(Plugin* plugin) const
+{
+ if (!pluginToItemMap.contains(plugin))
+ return nullptr;
+
+ return pluginToItemMap[plugin];
+}
+
+QTreeWidgetItem* ConfigDialog::createPluginsTypeItem(const QString& widgetName, const QString& title) const
+{
+ if (FORMS->hasWidget(widgetName))
+ return new QTreeWidgetItem({title});
+
+ QTreeWidgetItem* pluginsCategoryItem = getPluginsCategoryItem();
+ QTreeWidgetItem* item = nullptr;
+ for (int i = 0; i < pluginsCategoryItem->childCount(); i++)
+ {
+ item = pluginsCategoryItem->child(i);
+ if (item->statusTip(0) == widgetName)
+ return item;
+ }
+ return nullptr;
+
+}
+
+QTreeWidgetItem* ConfigDialog::getItemByTitle(const QString& title) const
+{
+ QList<QTreeWidgetItem*> items = ui->categoriesTree->findItems(title, Qt::MatchExactly|Qt::MatchRecursive);
+ if (items.size() == 0)
+ return nullptr;
+
+ return items.first();
+}
+
+void ConfigDialog::switchPage(QTreeWidgetItem *item)
+{
+ if (isPluginCategoryItem((item)))
+ {
+ switchPageToPlugin(item);
+ return;
+ }
+
+ QString name = item->statusTip(0);
+ if (!nameToPage.contains(name))
+ {
+ qWarning() << "Switched page to item" << name << "but there's no such named page defined in ConfigDialog.";
+ return;
+ }
+
+ ui->stackedWidget->setCurrentWidget(nameToPage[name]);
+}
+
+void ConfigDialog::switchPageToPlugin(QTreeWidgetItem *item)
+{
+ QString pluginName = item->statusTip(0);
+ if (!nameToPage.contains(pluginName))
+ {
+ qCritical() << "No plugin page available for plugin:" << pluginName;
+ return;
+ }
+ ui->stackedWidget->setCurrentWidget(nameToPage[pluginName]);
+}
+
+void ConfigDialog::initPlugins()
+{
+ QTreeWidgetItem *item = getPluginsCategoryItem();
+
+ // Recreate
+ QTreeWidgetItem *typeItem = nullptr;
+ foreach (PluginType* pluginType, PLUGINS->getPluginTypes())
+ {
+ typeItem = createPluginsTypeItem(pluginType->getConfigUiForm(), pluginType->getTitle());
+ if (!typeItem)
+ continue;
+
+ item->addChild(typeItem);
+ pluginTypeToItemMap[pluginType] = typeItem;
+
+ foreach (Plugin* plugin, pluginType->getLoadedPlugins())
+ pluginLoaded(plugin, pluginType, true);
+ }
+
+ updatePluginCategoriesVisibility();
+
+ connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(pluginLoaded(Plugin*,PluginType*)));
+ connect(PLUGINS, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(pluginAboutToUnload(Plugin*,PluginType*)));
+}
+
+void ConfigDialog::initPluginsPage()
+{
+ setValidStateTooltip(ui->pluginsList, tr("Plugins are loaded/unloaded immediately when checked/unchecked, "
+ "but modified list of plugins to load at startup is not saved until "
+ "you commit the whole configuration dialog."));
+
+ QTreeWidgetItem* category = nullptr;
+ QTreeWidgetItem* item = nullptr;
+ QFont font;
+ QModelIndex categoryIndex;
+ QModelIndex itemIndex;
+ int itemRow;
+ int categoryRow;
+ bool builtIn;
+ QLabel* detailsLabel = nullptr;
+ QString title;
+ QSize itemSize;
+ QStringList pluginNames;
+
+ // Font and metrics
+ item = new QTreeWidgetItem({""});
+ font = item->font(0);
+
+ QFontMetrics fm(font);
+ itemSize = QSize(-1, (fm.ascent() + fm.descent() + 4));
+
+ delete item;
+
+ // Creating...
+ ui->pluginsList->header()->setSectionsMovable(false);
+ ui->pluginsList->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+ QBrush categoryBg = ui->pluginsList->palette().button();
+ QBrush categoryFg = ui->pluginsList->palette().buttonText();
+
+ connect(ui->pluginsList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(loadUnloadPlugin(QTreeWidgetItem*,int)));
+ connect(PLUGINS, SIGNAL(failedToLoad(QString)), this, SLOT(failedToLoadPlugin(QString)));
+
+ categoryRow = 0;
+ QList<PluginType*> pluginTypes = PLUGINS->getPluginTypes();
+ qSort(pluginTypes.begin(), pluginTypes.end(), PluginType::nameLessThan);
+ foreach (PluginType* pluginType, pluginTypes)
+ {
+ category = new QTreeWidgetItem({pluginType->getTitle()});
+ font.setItalic(false);
+ font.setBold(true);
+ category->setFont(0, font);
+ for (int i = 0; i < 2; i++)
+ {
+ category->setBackground(i, categoryBg);
+ category->setForeground(i, categoryFg);
+ }
+ category->setSizeHint(0, itemSize);
+ ui->pluginsList->addTopLevelItem(category);
+
+ categoryIndex = ui->pluginsList->model()->index(categoryRow, 0);
+ categoryRow++;
+
+ itemRow = 0;
+ pluginNames = pluginType->getAllPluginNames();
+ qSort(pluginNames);
+ foreach (const QString& pluginName, pluginNames)
+ {
+ builtIn = PLUGINS->isBuiltIn(pluginName);
+ title = PLUGINS->getTitle(pluginName);
+ if (builtIn)
+ title += tr(" (built-in)", "plugins manager in configuration dialog");
+
+ item = new QTreeWidgetItem({title});
+ item->setCheckState(0, PLUGINS->isLoaded(pluginName) ? Qt::Checked : Qt::Unchecked);
+ item->setSizeHint(0, itemSize);
+ if (builtIn)
+ item->setDisabled(true);
+
+ category->addChild(item);
+
+ itemToPluginNameMap.insert(item, pluginName);
+
+ // Details button
+ detailsLabel = new QLabel(QString("<a href='%1'>%2</a> ").arg(pluginName).arg(tr("Details")), ui->pluginsList);
+ detailsLabel->setAlignment(Qt::AlignRight);
+ itemIndex = ui->pluginsList->model()->index(itemRow, 1, categoryIndex);
+ ui->pluginsList->setIndexWidget(itemIndex, detailsLabel);
+
+ connect(detailsLabel, SIGNAL(linkActivated(QString)), this, SLOT(detailsClicked(QString)));
+
+ itemRow++;
+ }
+
+ if (itemRow == 0)
+ {
+ item = new QTreeWidgetItem({tr("No plugins in this category.")});
+ item->setDisabled(true);
+ item->setSizeHint(0, itemSize);
+
+ font.setItalic(true);
+ font.setBold(false);
+ item->setFont(0, font);
+
+ category->addChild(item);
+ }
+
+ category->setExpanded(true);
+ }
+}
+
+bool ConfigDialog::initPluginPage(Plugin* plugin, bool skipConfigLoading)
+{
+ if (!dynamic_cast<UiConfiguredPlugin*>(plugin))
+ return false;
+
+ UiConfiguredPlugin* cfgPlugin = dynamic_cast<UiConfiguredPlugin*>(plugin);
+ QString pluginName = plugin->getName();
+ QString formName = cfgPlugin->getConfigUiForm();
+ QWidget* widget = FORMS->createWidget(formName);
+ if (!widget)
+ {
+ qWarning() << "Could not load plugin UI file" << formName << "for plugin:" << pluginName;
+ return false;
+ }
+
+ nameToPage[pluginName] = widget;
+ ui->stackedWidget->addWidget(widget);
+ CfgMain* mainConfig = cfgPlugin->getMainUiConfig();
+ if (mainConfig)
+ {
+ pluginConfigMappers[cfgPlugin] = new ConfigMapper(mainConfig);
+ pluginConfigMappers[cfgPlugin]->bindToConfig(widget);
+ mainConfig->begin();
+ }
+ else if (!skipConfigLoading)
+ {
+ configMapper->loadToWidget(widget);
+ }
+
+ cfgPlugin->configDialogOpen();
+ return true;
+}
+
+void ConfigDialog::deinitPluginPage(Plugin* plugin)
+{
+ QString pluginName = plugin->getName();
+ if (!nameToPage.contains(pluginName))
+ return;
+
+ if (!dynamic_cast<UiConfiguredPlugin*>(plugin))
+ {
+ UiConfiguredPlugin* cfgPlugin = dynamic_cast<UiConfiguredPlugin*>(plugin);
+ CfgMain* mainCfg = cfgPlugin->getMainUiConfig();
+ if (mainCfg)
+ mainCfg->rollback();
+
+ cfgPlugin->configDialogClosed();
+
+ if (pluginConfigMappers.contains(cfgPlugin))
+ {
+ delete pluginConfigMappers[cfgPlugin];
+ pluginConfigMappers.remove(cfgPlugin);
+ }
+ }
+
+ QWidget* widget = nameToPage[pluginName];
+ nameToPage.remove(pluginName);
+ ui->stackedWidget->removeWidget(widget);
+ delete widget;
+}
+
+void ConfigDialog::initDataEditors()
+{
+ ui->dataEditorsAvailableList->setSpacing(1);
+
+ QHash<QString,QVariant> editorsOrder = CFG_UI.General.DataEditorsOrder.get();
+ QSet<QString> dataTypeSet = editorsOrder.keys().toSet();
+ dataTypeSet += DataType::getAllNames().toSet();
+ QStringList dataTypeList = dataTypeSet.toList();
+ qSort(dataTypeList);
+
+ QListWidgetItem* item = nullptr;
+ for (const QString& type : dataTypeList)
+ {
+ item = new QListWidgetItem(type);
+ if (!DataType::getAllNames().contains(type))
+ item->setFlags(item->flags()|Qt::ItemIsEditable);
+
+ ui->dataEditorsTypesList->addItem(item);
+ }
+
+ QAction* act = new QAction(ICONS.INSERT_DATATYPE, tr("Add new data type"), ui->dataEditorsTypesToolbar);
+ connect(act, SIGNAL(triggered()), this, SLOT(addDataType()));
+ ui->dataEditorsTypesToolbar->addAction(act);
+
+ dataEditRenameAction = new QAction(ICONS.RENAME_DATATYPE, tr("Rename selected data type"), ui->dataEditorsTypesToolbar);
+ connect(dataEditRenameAction, SIGNAL(triggered()), this, SLOT(renameDataType()));
+ ui->dataEditorsTypesToolbar->addAction(dataEditRenameAction);
+
+ dataEditDeleteAction = new QAction(ICONS.DELETE_DATATYPE, tr("Delete selected data type"), ui->dataEditorsTypesToolbar);
+ connect(dataEditDeleteAction, SIGNAL(triggered()), this, SLOT(delDataType()));
+ ui->dataEditorsTypesToolbar->addAction(dataEditDeleteAction);
+
+ act = new QAction(ICONS.HELP, tr("Help for configuring data type editors"), ui->dataEditorsTypesToolbar);
+ connect(act, SIGNAL(triggered()), this, SLOT(dataTypesHelp()));
+ ui->dataEditorsTypesToolbar->addAction(act);
+
+ connect(ui->dataEditorsTypesList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateDataTypeEditors()));
+ connect(ui->dataEditorsTypesList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateDataTypeListState()));
+ connect(ui->dataEditorsTypesList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(dataEditorItemEdited(QListWidgetItem*)));
+ connect(ui->dataEditorsAvailableList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(dataEditorAvailableChanged(QListWidgetItem*)));
+ connect(ui->dataEditorsSelectedTabs->tabBar(), SIGNAL(tabMoved(int,int)), this, SLOT(dataEditorTabsOrderChanged(int,int)));
+
+ ui->dataEditorsTypesList->setCurrentRow(0, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+ updateDataTypeListState();
+}
+
+void ConfigDialog::initShortcuts()
+{
+ ui->shortcutsTable->header()->setSectionsMovable(false);
+ ui->shortcutsTable->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+ ui->shortcutsTable->header()->setSectionResizeMode(1, QHeaderView::Fixed);
+ ui->shortcutsTable->header()->setSectionResizeMode(2, QHeaderView::Fixed);
+ ui->shortcutsTable->header()->resizeSection(1, 150);
+ ui->shortcutsTable->header()->resizeSection(2, 26);
+
+ ui->shortcutsFilterEdit->setClearButtonEnabled(true);
+ new UserInputFilter(ui->shortcutsFilterEdit, this, SLOT(applyShortcutsFilter(QString)));
+
+ static const QString metaName = CFG_SHORTCUTS_METANAME;
+ QList<CfgCategory*> categories;
+ for (CfgMain* cfgMain : CfgMain::getInstances())
+ {
+ if (cfgMain->getMetaName() != metaName)
+ continue;
+
+ for (CfgCategory* cat : cfgMain->getCategories().values())
+ categories << cat;
+ }
+
+ qSort(categories.begin(), categories.end(), [](CfgCategory* cat1, CfgCategory* cat2) -> bool
+ {
+ return cat1->getTitle().compare(cat2->getTitle()) < 0;
+ });
+
+ for (CfgCategory* cat : categories)
+ initShortcuts(cat);
+}
+
+void ConfigDialog::initShortcuts(CfgCategory *cfgCategory)
+{
+ QTreeWidgetItem* item = nullptr;
+ QFont font;
+ QModelIndex categoryIndex;
+ QModelIndex itemIndex;
+ QKeySequenceEdit *sequenceEdit = nullptr;
+ QToolButton* clearButton = nullptr;
+ QString title;
+ QSize itemSize;
+
+ // Font and metrics
+ item = new QTreeWidgetItem({""});
+ font = item->font(0);
+
+ QFontMetrics fm(font);
+ itemSize = QSize(-1, (fm.ascent() + fm.descent() + 4));
+
+ delete item;
+
+ // Creating...
+ QBrush categoryBg = ui->shortcutsTable->palette().button();
+ QBrush categoryFg = ui->shortcutsTable->palette().buttonText();
+
+ QTreeWidgetItem* category = new QTreeWidgetItem({cfgCategory->getTitle()});
+ font.setItalic(false);
+ font.setBold(true);
+ category->setFont(0, font);
+ for (int i = 0; i < 3; i++)
+ {
+ category->setBackground(i, categoryBg);
+ category->setForeground(i, categoryFg);
+ }
+ category->setSizeHint(0, itemSize);
+ category->setFlags(category->flags() ^ Qt::ItemIsSelectable);
+ ui->shortcutsTable->addTopLevelItem(category);
+
+ int categoryRow = ui->shortcutsTable->topLevelItemCount() - 1;
+ categoryIndex = ui->shortcutsTable->model()->index(categoryRow, 0);
+
+ int itemRow = 0;
+ QStringList entryNames = cfgCategory->getEntries().keys();
+ qSort(entryNames);
+ foreach (const QString& entryName, entryNames)
+ {
+ // Title
+ title = cfgCategory->getEntries()[entryName]->getTitle();
+ item = new QTreeWidgetItem(category, {title});
+
+ // Key edit
+ sequenceEdit = new QKeySequenceEdit(ui->shortcutsTable);
+ sequenceEdit->setFixedWidth(150);
+ sequenceEdit->setProperty("cfg", cfgCategory->getEntries()[entryName]->getFullKey());
+ itemIndex = ui->shortcutsTable->model()->index(itemRow, 1, categoryIndex);
+ ui->shortcutsTable->setIndexWidget(itemIndex, sequenceEdit);
+ configMapper->addExtraWidget(sequenceEdit);
+
+ // Clear button
+ clearButton = new QToolButton(ui->shortcutsTable);
+ clearButton->setIcon(ICONS.CLEAR_LINEEDIT);
+ connect(clearButton, &QToolButton::clicked, [this, sequenceEdit]()
+ {
+ sequenceEdit->clear();
+ this->markModified();
+
+ });
+ itemIndex = ui->shortcutsTable->model()->index(itemRow, 2, categoryIndex);
+ ui->shortcutsTable->setIndexWidget(itemIndex, clearButton);
+
+ itemRow++;
+ }
+
+ category->setExpanded(true);
+}
+
+bool ConfigDialog::isPluginCategoryItem(QTreeWidgetItem *item) const
+{
+ return item->parent() && item->parent()->parent() && item->parent()->parent() == getPluginsCategoryItem();
+}
+
+void ConfigDialog::updateStylePreview()
+{
+ ui->previewWidget->parentWidget()->layout()->removeWidget(ui->previewWidget);
+ ui->previewTabs->currentWidget()->layout()->addWidget(ui->previewWidget);
+ ui->previewWidget->setEnabled(ui->previewTabs->currentIndex() == 0);
+
+ QStyle* previousStyle = previewStyle;
+ previewStyle = QStyleFactory::create(ui->activeStyleCombo->currentText());
+ if (!previewStyle)
+ {
+ qWarning() << "Could not create style:" << ui->activeStyleCombo->currentText();
+ return;
+ }
+
+ applyStyle(ui->activeStylePreviewGroup, previewStyle);
+
+ if (previousStyle)
+ delete previousStyle;
+}
+
+void ConfigDialog::apply()
+{
+ if (modifiedFlag)
+ save();
+
+ setModified(false);
+}
+
+void ConfigDialog::accept()
+{
+ apply();
+ QDialog::accept();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h
new file mode 100644
index 0000000..95e9f1a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.h
@@ -0,0 +1,141 @@
+#ifndef CONFIGDIALOG_H
+#define CONFIGDIALOG_H
+
+#include "config_builder.h"
+#include "datatype.h"
+#include "common/bihash.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class ConfigDialog;
+}
+
+class QListWidgetItem;
+class QTreeWidgetItem;
+class CustomConfigWidgetPlugin;
+class QSignalMapper;
+class Plugin;
+class PluginType;
+class QComboBox;
+class QToolButton;
+class QTreeWidget;
+class QListWidget;
+class QTableWidget;
+class ConfigMapper;
+class MultiEditorWidgetPlugin;
+class ConfigNotifiablePlugin;
+class UiConfiguredPlugin;
+
+class GUI_API_EXPORT ConfigDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit ConfigDialog(QWidget *parent = 0);
+ ~ConfigDialog();
+
+ void configureDataEditors(const QString& dataTypeString);
+
+ static QString getFilterString(QWidget* widget);
+ static QString getFilterString(QComboBox* widget);
+ static QString getFilterString(QTreeWidget* widget);
+ static QString getFilterString(QListWidget* widget);
+ static QString getFilterString(QTableWidget* widget);
+
+ private:
+ void init();
+ void load();
+ void initPageMap();
+ void initInternalCustomConfigWidgets();
+ void initFormatterPlugins();
+ void initPlugins();
+ void initPluginsPage();
+ bool initPluginPage(Plugin* plugin, bool skipConfigLoading);
+ void deinitPluginPage(Plugin* pluginName);
+ void initDataEditors();
+ void initShortcuts();
+ void initShortcuts(CfgCategory* cfgCategory);
+ void applyStyle(QWidget* widget, QStyle* style);
+ QTreeWidgetItem* getPluginsCategoryItem() const;
+ QTreeWidgetItem* getPluginsCategoryItem(PluginType* type) const;
+ QTreeWidgetItem* getPluginItem(Plugin* plugin) const;
+ QTreeWidgetItem* createPluginsTypeItem(const QString& widgetName, const QString& title) const;
+ QTreeWidgetItem* getItemByTitle(const QString& title) const;
+ void switchPageToPlugin(QTreeWidgetItem* item);
+ bool isPluginCategoryItem(QTreeWidgetItem *item) const;
+ void codeFormatterUnloaded();
+ void codeFormatterLoaded();
+ void updatePluginCategoriesVisibility(QTreeWidgetItem* categoryItem);
+ QString collectLoadedPlugins() const;
+ QHash<QWidget*,QTreeWidgetItem*> buildPageToCategoryItemMap() const;
+ QList<QTreeWidgetItem*> getAllCategoryItems() const;
+ QList<MultiEditorWidgetPlugin*> getDefaultEditorsForType(DataType::Enum dataType);
+ QList<MultiEditorWidgetPlugin*> updateCustomDataTypeEditors(const QStringList& editorsOrder);
+ QList<MultiEditorWidgetPlugin*> updateDefaultDataTypeEditors(DataType::Enum typeEnum);
+ void addDataTypeEditor(const QString& pluginName);
+ void addDataTypeEditor(MultiEditorWidgetPlugin* plugin);
+ void removeDataTypeEditor(QListWidgetItem* item, const QString& pluginName);
+ void removeDataTypeEditor(int idx);
+ void transformDataTypeEditorsToCustomList(QListWidgetItem* typeItem);
+ QStringList getPluginNamesFromDataTypeItem(QListWidgetItem* typeItem, bool* exists = nullptr);
+ void setPluginNamesForDataTypeItem(QListWidgetItem* typeItem, const QStringList& pluginNames);
+ void addDataType(const QString& typeStr);
+ void rollbackPluginConfigs();
+ void commitPluginConfigs();
+
+ Ui::ConfigDialog *ui = nullptr;
+ QStyle* previewStyle = nullptr;
+ QHash<QString,QWidget*> nameToPage;
+ BiHash<QTreeWidgetItem*,QString> itemToPluginNameMap;
+ QHash<PluginType*,QTreeWidgetItem*> pluginTypeToItemMap;
+ QHash<Plugin*,QTreeWidgetItem*> pluginToItemMap;
+ QHash<QString,QComboBox*> formatterLangToPluginComboMap;
+ QHash<QString,QToolButton*> formatterLangToConfigButtonMap;
+ ConfigMapper* configMapper = nullptr;
+ QHash<UiConfiguredPlugin*,ConfigMapper*> pluginConfigMappers;
+ QAction* dataEditRenameAction = nullptr;
+ QAction* dataEditDeleteAction = nullptr;
+ bool updatingDataEditorItem = false;
+ bool modifiedFlag = false;
+ QList<ConfigNotifiablePlugin*> notifiablePlugins;
+
+ private slots:
+ void refreshFormattersPage();
+ void pageSwitched();
+ void updateDataTypeEditors();
+ void updateDataTypeListState();
+ void dataEditorItemEdited(QListWidgetItem* item);
+ void dataEditorAvailableChanged(QListWidgetItem* item);
+ void dataEditorTabsOrderChanged(int from, int to);
+ void addDataType();
+ void renameDataType();
+ void delDataType();
+ void dataTypesHelp();
+ void switchPage(QTreeWidgetItem* item);
+ void updateStylePreview();
+ void apply();
+ void save();
+ void storeSelectedFormatters();
+ void markModified();
+ void setModified(bool modified);
+ void updateModified();
+ void applyFilter(const QString& filter);
+ void updateActiveFormatterState();
+ void configureFormatter(const QString& pluginTitle);
+ void activeFormatterChanged();
+ void detailsClicked(const QString& pluginName);
+ void failedToLoadPlugin(const QString& pluginName);
+ void loadUnloadPlugin(QTreeWidgetItem* item, int column);
+ void pluginAboutToUnload(Plugin* plugin, PluginType* type);
+ void pluginLoaded(Plugin* plugin, PluginType* type, bool skipConfigLoading = false);
+ void pluginUnloaded(const QString& pluginName, PluginType* type);
+ void updatePluginCategoriesVisibility();
+ void updateBuiltInPluginsVisibility();
+ void applyShortcutsFilter(const QString& filter);
+
+ public slots:
+ void accept();
+};
+
+#endif // CONFIGDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui
new file mode 100644
index 0000000..82cc286
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/configdialog.ui
@@ -0,0 +1,1923 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigDialog</class>
+ <widget class="QDialog" name="ConfigDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>770</width>
+ <height>539</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configuration</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="1" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QWidget" name="mainWidget" native="true">
+ <property name="contextMenuPolicy">
+ <enum>Qt::PreventContextMenu</enum>
+ </property>
+ <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="categoriesWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </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="QLineEdit" name="categoriesFilter">
+ <property name="placeholderText">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="categoriesTree">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>150</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </column>
+ <item>
+ <property name="text">
+ <string>General</string>
+ </property>
+ <property name="statusTip">
+ <string>generalPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_general.png</normaloff>:/icons/img/config_general.png</iconset>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Keyboard shortcuts</string>
+ </property>
+ <property name="statusTip">
+ <string>shortcutsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/keyboard.png</normaloff>:/icons/img/keyboard.png</iconset>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Look &amp; feel</string>
+ </property>
+ <property name="statusTip">
+ <string>lookAndFeelPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_look_and_feel.png</normaloff>:/icons/img/config_look_and_feel.png</iconset>
+ </property>
+ <item>
+ <property name="text">
+ <string>Style</string>
+ </property>
+ <property name="statusTip">
+ <string>stylePage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_style.png</normaloff>:/icons/img/config_style.png</iconset>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Fonts</string>
+ </property>
+ <property name="statusTip">
+ <string>fontsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_font.png</normaloff>:/icons/img/config_font.png</iconset>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Colors</string>
+ </property>
+ <property name="statusTip">
+ <string>colorsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_colors.png</normaloff>:/icons/img/config_colors.png</iconset>
+ </property>
+ </item>
+ </item>
+ <item>
+ <property name="text">
+ <string>Plugins</string>
+ </property>
+ <property name="statusTip">
+ <string>pluginsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/plugin.png</normaloff>:/icons/img/plugin.png</iconset>
+ </property>
+ <item>
+ <property name="text">
+ <string>Code formatters</string>
+ </property>
+ <property name="statusTip">
+ <string>formatterPluginsPage</string>
+ </property>
+ </item>
+ </item>
+ <item>
+ <property name="text">
+ <string>Data browsing</string>
+ </property>
+ <property name="statusTip">
+ <string>dataBrowsingPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/table.png</normaloff>:/icons/img/table.png</iconset>
+ </property>
+ <item>
+ <property name="text">
+ <string>Data editors</string>
+ </property>
+ <property name="statusTip">
+ <string>dataEditorsPage</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/config_data_editors.png</normaloff>:/icons/img/config_data_editors.png</iconset>
+ </property>
+ </item>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>5</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>2</number>
+ </property>
+ <widget class="QWidget" name="dataBrowsingPage">
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <item>
+ <widget class="QGroupBox" name="dataBrowsingGroup">
+ <property name="title">
+ <string>Data browsing and editing</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="rowsPerPageLabel">
+ <property name="text">
+ <string>Number of data rows per page:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="rowsPerPageSpin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>99999</number>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.NumberOfRowsPerPage</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataEditorsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_24">
+ <item>
+ <widget class="QSplitter" name="splitter_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QGroupBox" name="dataEditorsTypesGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Data types</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_22">
+ <item>
+ <widget class="QToolBar" name="dataEditorsTypesToolbar"/>
+ </item>
+ <item>
+ <widget class="QListWidget" name="dataEditorsTypesList">
+ <property name="cfg" stdset="0">
+ <string>General.DataEditorsOrder</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="dataEditorsRightWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>3</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_23">
+ <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="QGroupBox" name="dataEditorsAvailableGroup">
+ <property name="title">
+ <string>Available editors:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_26">
+ <item>
+ <widget class="QListWidget" name="dataEditorsAvailableList">
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dataEditorsSelectedGroup">
+ <property name="title">
+ <string>Editors selected for this data type:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_27">
+ <item>
+ <widget class="QTabWidget" name="dataEditorsSelectedTabs">
+ <property name="movable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="generalPage">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <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="QGroupBox" name="schemaEditingGroup">
+ <property name="title">
+ <string>Schema editing</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_12">
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="ddlHistorySizeSpin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Number of DDL changes kept in history.</string>
+ </property>
+ <property name="maximum">
+ <number>9999999</number>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.DdlHistorySize</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="ddlHistorySizeLabel">
+ <property name="text">
+ <string>DDL history size:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QCheckBox" name="dontShowDdlPreview">
+ <property name="text">
+ <string>Don't show DDL preview dialog when commiting schema changes</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.DontShowDdlPreview</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="sqlQueriesGroup">
+ <property name="title">
+ <string>SQL queries</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="queryHistorySizeSpin">
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Number of queries kept in the history.</string>
+ </property>
+ <property name="maximum">
+ <number>999999</number>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.SqlHistorySize</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="queryHistorySizeLabel">
+ <property name="toolTip">
+ <string>Number of queries kept in the history.</string>
+ </property>
+ <property name="text">
+ <string>History size:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="checkBox">
+ <property name="toolTip">
+ <string>&lt;p&gt;If there is more than one query in the SQL editor window, then (if this option is enabled) only a single query will be executed - the one under the keyboard insertion cursor. Otherwise all queries will be executed. You can always limit queries to be executed by selecting those queries before calling to execute.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Execute only the query under the cursor</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ExecuteCurrentQueryOnly</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="updatesGroup">
+ <property name="title">
+ <string>Updates</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_29">
+ <item>
+ <widget class="QCheckBox" name="checkForUpdatesCheck">
+ <property name="text">
+ <string>Automatically check for updates at startup</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.CheckUpdatesOnStartup</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="shortcutsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_28">
+ <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="shortcutsFilterEdit">
+ <property name="placeholderText">
+ <string>Filter shortcuts by name or key combination</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="shortcutsTable">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="indentation">
+ <number>0</number>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="expandsOnDoubleClick">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerDefaultSectionSize">
+ <number>150</number>
+ </attribute>
+ <attribute name="headerMinimumSectionSize">
+ <number>16</number>
+ </attribute>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Action</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Key combination</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string/>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="lookAndFeelPage">
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <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="QGroupBox" name="dbListGroup">
+ <property name="title">
+ <string>Database list</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="sortColumns">
+ <property name="toolTip">
+ <string>If switched off, then columns will be sorted in the order they are typed in CREATE TABLE statement.</string>
+ </property>
+ <property name="text">
+ <string>Sort table columns alphabetically</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.SortColumns</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="expandTablesCheck">
+ <property name="text">
+ <string>Expand tables node when connected to a database</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ExpandTables</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QGroupBox" name="addLabelsGroup">
+ <property name="toolTip">
+ <string>&lt;p&gt;Additional labels are those displayed next to the names on the databases list (they are blue, unless configured otherwise). Enabling this option will result in labels for databases, invalid databases and aggregated nodes (column group, index group, trigger group). For more labels see options below.&lt;p&gt;</string>
+ </property>
+ <property name="title">
+ <string>Display additional labels on the list</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ShowDbTreeLabels</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_20">
+ <item>
+ <widget class="QCheckBox" name="regularTableLabelsCheck">
+ <property name="toolTip">
+ <string>For regular tables labels will show number of columns, indexes and triggers for each of tables.</string>
+ </property>
+ <property name="text">
+ <string>Display labels for regular tables</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ShowRegularTableLabels</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="virtTableLabelsCheck">
+ <property name="toolTip">
+ <string>Virtual tables will be marked with a 'virtual' label.</string>
+ </property>
+ <property name="text">
+ <string>Display labels for virtual tables</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ShowVirtualTableLabels</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="expandViewsCheck">
+ <property name="text">
+ <string>Expand views node when connected to a database</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ExpandViews</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="sortObjects">
+ <property name="toolTip">
+ <string>If this option is switched off, then objects will be sorted in order they appear in sqlite_master table (which is in order they were created)</string>
+ </property>
+ <property name="text">
+ <string>Sort objects (tables, indexes, triggers and views) alphabetically</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.SortObjects</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="dispSysTableCheck">
+ <property name="text">
+ <string>Display system tables and indexes on the list</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.ShowSystemObjects</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="tableWinGroup">
+ <property name="title">
+ <string>Table windows</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_18">
+ <item>
+ <widget class="QCheckBox" name="openTablesOnDataCheck">
+ <property name="toolTip">
+ <string>When enabled, Table Windows will show up with the data tab, instead of the structure tab.</string>
+ </property>
+ <property name="text">
+ <string>Open Table Windows with the data tab for start</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.OpenTablesOnData</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="viewWinGroup">
+ <property name="title">
+ <string>View windows</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_19">
+ <item>
+ <widget class="QCheckBox" name="openViewsOnDataCheck">
+ <property name="toolTip">
+ <string>When enabled, View Windows will show up with the data tab, instead of the structure tab.</string>
+ </property>
+ <property name="text">
+ <string>Open View Windows with the data tab for start</string>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>General.OpenViewsOnData</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pluginsPage">
+ <layout class="QGridLayout" name="gridLayout_18">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="1" column="0" colspan="2">
+ <widget class="QTreeWidget" name="pluginsList">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="indentation">
+ <number>0</number>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="expandsOnDoubleClick">
+ <bool>false</bool>
+ </property>
+ <property name="columnCount">
+ <number>2</number>
+ </property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string notr="true">2</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QWidget" name="hideBuiltInPluginsWidget" 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>
+ <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>
+ <item>
+ <widget class="QCheckBox" name="hideBuiltInPluginsCheck">
+ <property name="text">
+ <string>Hide built-in plugins</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="stylePage">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <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="QWidget" name="activeStyleWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="activeStyleLabel">
+ <property name="text">
+ <string>Current style:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="activeStyleCombo">
+ <property name="cfg" stdset="0">
+ <string>General.Style</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="activeStylePreviewGroup">
+ <property name="title">
+ <string>Preview</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <widget class="QTabWidget" name="previewTabs">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="previewTab1">
+ <attribute name="title">
+ <string>Enabled</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QWidget" name="previewWidget" native="true">
+ <layout class="QGridLayout" name="gridLayout_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 row="2" column="0">
+ <widget class="QProgressBar" name="previewProgressBar">
+ <property name="value">
+ <number>24</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QCheckBox" name="previewCheckBox">
+ <property name="text">
+ <string>CheckBox</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" rowspan="4">
+ <widget class="QSlider" name="previewVerticalSlider">
+ <property name="value">
+ <number>50</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2" rowspan="4">
+ <widget class="QScrollBar" name="previewVerticalScrollBar">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" colspan="2">
+ <widget class="QPushButton" name="previewPushButton">
+ <property name="text">
+ <string>PushButton</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="3">
+ <widget class="QSpinBox" name="previewSpinBox"/>
+ </item>
+ <item row="2" column="3" colspan="5">
+ <widget class="QProgressBar" name="progressBar">
+ <property name="maximum">
+ <number>0</number>
+ </property>
+ <property name="value">
+ <number>0</number>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QTreeWidget" name="previewTreeWidget">
+ <column>
+ <property name="text">
+ <string>Column</string>
+ </property>
+ </column>
+ <item>
+ <property name="text">
+ <string>123</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>11111</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>22222</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>33333</string>
+ </property>
+ </item>
+ </item>
+ <item>
+ <property name="text">
+ <string>456</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>44444</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>55555</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>66666</string>
+ </property>
+ </item>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QToolButton" name="previewToolButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="previewLineEdit"/>
+ </item>
+ <item row="0" column="6">
+ <widget class="QRadioButton" name="previewRadioButton">
+ <property name="text">
+ <string>RadioButton</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QComboBox" name="previewComboBox">
+ <item>
+ <property name="text">
+ <string>ABC</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>XYZ</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="3" colspan="5">
+ <widget class="QTextEdit" name="previewTextEdit">
+ <property name="html">
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;Abcdefgh&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="previewTab2">
+ <attribute name="title">
+ <string>Disabled</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_7"/>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="formatterPluginsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_17">
+ <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="QTreeWidget" name="formatterPluginsTree">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Language</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Active formatter plugin</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Configuration</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="fontsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QScrollArea" name="fontsScrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="fontsWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>258</width>
+ <height>286</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <item>
+ <widget class="QGroupBox" name="sqlEditorFontGroup">
+ <property name="title">
+ <string>SQL editor font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
+ <item>
+ <widget class="FontEdit" name="sqlEditorFont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.SqlEditor</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dbTreeFontGroup">
+ <property name="title">
+ <string>Database list font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <item>
+ <widget class="FontEdit" name="dbTreeFont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.DbTree</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dbTreeLabelGroup">
+ <property name="title">
+ <string>Database list additional label font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <item>
+ <widget class="FontEdit" name="dbTreeLabelFont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.DbTreeLabel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dataViewFontGroup">
+ <property name="title">
+ <string>Data view font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <item>
+ <widget class="FontEdit" name="dataViewFont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.DataView</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="statusFieldFontGroup">
+ <property name="title">
+ <string>Status field font</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <item>
+ <widget class="FontEdit" name="statusFieldfont" native="true">
+ <property name="cfg" stdset="0">
+ <string>Fonts.StatusField</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="colorsPage">
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="colorsWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>307</width>
+ <height>666</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QGroupBox" name="sqlEditorColorsGroup">
+ <property name="title">
+ <string>SQL editor colors</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="14" column="0">
+ <widget class="QLabel" name="sqlEditorCurrLineBgLabel">
+ <property name="text">
+ <string>Current line background</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="sqlEditorStringFgLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;SQL strings are enclosed with single quote characters.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>String foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="ColorButton" name="sqlEditorKeywordFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorKeywordFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="ColorButton" name="sqlEditorStringFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorStringFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="1">
+ <widget class="ColorButton" name="sqlEditorCommentFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorCommentFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="ColorButton" name="sqlEditorRegularFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorForeground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="14" column="1">
+ <widget class="ColorButton" name="sqlEditorCurrLineBgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorCurrentLineBg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="16" column="1">
+ <widget class="ColorButton" name="sqlEditorLineNumAreaBgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorLineNumAreaBg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="12" column="0">
+ <widget class="QLabel" name="sqlEditorBindParamFgLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;Bind parameters are placeholders for values yet to be provided by the user. They have one of the forms:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;:param_name&lt;/li&gt;&lt;li&gt;$param_name&lt;/li&gt;&lt;li&gt;@param_name&lt;/li&gt;&lt;li&gt;?&lt;/li&gt;&lt;/ul&gt;</string>
+ </property>
+ <property name="text">
+ <string>Bind parameter foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="15" column="1">
+ <widget class="ColorButton" name="sqlEditorParBgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorParenthesisBg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="15" column="0">
+ <widget class="QLabel" name="sqlEditorParBgLabel">
+ <property name="text">
+ <string>Highlighted parenthesis background</string>
+ </property>
+ </widget>
+ </item>
+ <item row="11" column="0">
+ <widget class="QLabel" name="sqlEditorBlobFgLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;BLOB values are binary values represented as hexadecimal numbers, like:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;X'12B4'&lt;/li&gt;&lt;li&gt;x'46A2F4'&lt;/li&gt;&lt;/ul&gt;</string>
+ </property>
+ <property name="text">
+ <string>BLOB value foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="sqlEditorRegularFgLabel">
+ <property name="text">
+ <string>Regular foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="16" column="0">
+ <widget class="QLabel" name="sqlEditorLineNumAreaBgLabel">
+ <property name="text">
+ <string>Line numbers area background</string>
+ </property>
+ </widget>
+ </item>
+ <item row="11" column="1">
+ <widget class="ColorButton" name="sqlEditorBlobFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorBlobFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="sqlEditorKeywordFgLabel">
+ <property name="text">
+ <string>Keyword foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="10" column="0">
+ <widget class="QLabel" name="sqlEditorNumberFgLabel">
+ <property name="text">
+ <string>Number foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="0">
+ <widget class="QLabel" name="sqlEditorCommentFgLabel">
+ <property name="text">
+ <string>Comment foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="10" column="1">
+ <widget class="ColorButton" name="sqlEditorNumberFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorNumberFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="12" column="1">
+ <widget class="ColorButton" name="sqlEditorBindParamFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorBindParamFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="13" column="0">
+ <widget class="QLabel" name="sqlEditorValidObjectsLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;Valid objects are name of tables, indexes, triggers, or views that exist in the SQLite database.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Valid objects foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="13" column="1">
+ <widget class="ColorButton" name="sqlEditorValidObjectsButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.SqlEditorValidObject</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dataViewGroup">
+ <property name="title">
+ <string>Data view colors</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <item row="0" column="0">
+ <widget class="QLabel" name="dataViewUncommitedLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;Any data changes will be outlined with this color, until they're commited to the database.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Uncommited data outline color</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="ColorButton" name="dataViewUncommitedButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DataUncommited</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="dataViewErrorLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;In case of error while commiting data changes, the problematic cell will be outlined with this color.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Commit error outline color</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="ColorButton" name="dataViewErrorButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DataUncommitedError</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="dataViewNullFgLabel">
+ <property name="text">
+ <string>NULL value foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="ColorButton" name="dataViewNullFgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DataNullFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="dataViewDeletedRowBgLabel">
+ <property name="text">
+ <string>Deleted row background</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="ColorButton" name="dataViewDeletedRowBgButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DataDeletedBg</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dbTreeGroup">
+ <property name="title">
+ <string>Database list colors</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_9">
+ <item row="0" column="0">
+ <widget class="QLabel" name="dbTreeLabelsLabel">
+ <property name="toolTip">
+ <string>&lt;p&gt;Additional labels are those which tell you SQLite version, number of objects deeper in the tree, etc.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Additional labels foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="ColorButton" name="dbTreeLabelsButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.DbTreeLabelsFg</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="statusFieldGroup">
+ <property name="title">
+ <string>Status field colors</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_10">
+ <item row="0" column="0">
+ <widget class="QLabel" name="statusFieldInfoLabel">
+ <property name="text">
+ <string>Information message foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="ColorButton" name="statusFieldInfoButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.StatusFieldInfoFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="statusFieldWarnLabel">
+ <property name="text">
+ <string>Warning message foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="ColorButton" name="statusFieldWarnButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.StatusFieldWarnFg</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="statusFieldErrorLabel">
+ <property name="text">
+ <string>Error message foreground</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="ColorButton" name="statusFieldErrorButton">
+ <property name="maximumSize">
+ <size>
+ <width>50</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="cfg" stdset="0">
+ <string>Colors.StatusFieldErrorFg</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ <widget class="QWidget" name="layoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>100</width>
+ <height>30</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_25"/>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ColorButton</class>
+ <extends>QPushButton</extends>
+ <header>common/colorbutton.h</header>
+ </customwidget>
+ <customwidget>
+ <class>FontEdit</class>
+ <extends>QWidget</extends>
+ <header>common/fontedit.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>categoriesFilter</tabstop>
+ <tabstop>categoriesTree</tabstop>
+ <tabstop>pluginsList</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>expandViewsCheck</tabstop>
+ <tabstop>sortObjects</tabstop>
+ <tabstop>sortColumns</tabstop>
+ <tabstop>ddlHistorySizeSpin</tabstop>
+ <tabstop>dontShowDdlPreview</tabstop>
+ <tabstop>queryHistorySizeSpin</tabstop>
+ <tabstop>checkBox</tabstop>
+ <tabstop>expandTablesCheck</tabstop>
+ <tabstop>activeStyleCombo</tabstop>
+ <tabstop>previewTabs</tabstop>
+ <tabstop>previewCheckBox</tabstop>
+ <tabstop>previewVerticalSlider</tabstop>
+ <tabstop>previewPushButton</tabstop>
+ <tabstop>previewSpinBox</tabstop>
+ <tabstop>previewTreeWidget</tabstop>
+ <tabstop>previewToolButton</tabstop>
+ <tabstop>previewLineEdit</tabstop>
+ <tabstop>previewRadioButton</tabstop>
+ <tabstop>previewComboBox</tabstop>
+ <tabstop>previewTextEdit</tabstop>
+ <tabstop>fontsScrollArea</tabstop>
+ <tabstop>scrollArea</tabstop>
+ <tabstop>sqlEditorKeywordFgButton</tabstop>
+ <tabstop>sqlEditorStringFgButton</tabstop>
+ <tabstop>sqlEditorCommentFgButton</tabstop>
+ <tabstop>sqlEditorRegularFgButton</tabstop>
+ <tabstop>sqlEditorCurrLineBgButton</tabstop>
+ <tabstop>sqlEditorLineNumAreaBgButton</tabstop>
+ <tabstop>sqlEditorParBgButton</tabstop>
+ <tabstop>sqlEditorBlobFgButton</tabstop>
+ <tabstop>sqlEditorNumberFgButton</tabstop>
+ <tabstop>sqlEditorBindParamFgButton</tabstop>
+ <tabstop>sqlEditorValidObjectsButton</tabstop>
+ <tabstop>dataViewUncommitedButton</tabstop>
+ <tabstop>dataViewErrorButton</tabstop>
+ <tabstop>dataViewNullFgButton</tabstop>
+ <tabstop>dataViewDeletedRowBgButton</tabstop>
+ <tabstop>dbTreeLabelsButton</tabstop>
+ <tabstop>statusFieldInfoButton</tabstop>
+ <tabstop>statusFieldWarnButton</tabstop>
+ <tabstop>statusFieldErrorButton</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp
new file mode 100644
index 0000000..0094ad0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.cpp
@@ -0,0 +1,213 @@
+#include "constraintdialog.h"
+#include "ui_constraintdialog.h"
+#include "iconmanager.h"
+#include "constraints/constraintpanel.h"
+#include <QDebug>
+#include <QPushButton>
+
+ConstraintDialog::ConstraintDialog(Mode mode, SqliteCreateTable::Constraint* constraint, SqliteCreateTable* createTable, Db* db, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::ConstraintDialog),
+ mode(mode),
+ db(db)
+{
+ ui->setupUi(this);
+ type = TABLE;
+ constrStatement = constraint;
+ this->createTable = createTable;
+ init();
+}
+
+ConstraintDialog::ConstraintDialog(Mode mode, SqliteCreateTable::Column::Constraint* constraint, SqliteCreateTable::Column* column, Db* db, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::ConstraintDialog),
+ mode(mode),
+ db(db)
+{
+ ui->setupUi(this);
+ type = COLUMN;
+ constrStatement = constraint;
+ this->columnStmt = column;
+ createTable = dynamic_cast<SqliteCreateTable*>(column->parent());
+ init();
+}
+
+ConstraintDialog::~ConstraintDialog()
+{
+ delete ui;
+}
+
+SqliteStatement* ConstraintDialog::getConstraint()
+{
+ return constrStatement;
+}
+
+void ConstraintDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void ConstraintDialog::init()
+{
+ switch (mode)
+ {
+ case ConstraintDialog::NEW:
+ setWindowTitle(tr("New constraint", "constraint dialog"));
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create", "constraint dialog"));
+ break;
+ case ConstraintDialog::EDIT:
+ setWindowTitle(tr("Edit constraint", "dialog window"));
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Apply", "constraint dialog"));
+ break;
+ }
+
+ connect(this, SIGNAL(accepted()), this, SLOT(storeConfiguration()));
+
+ // Panel object
+ currentPanel = createConstraintPanel();
+ if (!currentPanel)
+ {
+ qCritical() << "The constraint panel was not constructed. Probably the constraint type was invalid.";
+ return;
+ }
+
+ currentPanel->setDb(db);
+ currentPanel->setConstraint(constrStatement);
+
+ connect(currentPanel, SIGNAL(updateValidation()), this, SLOT(validate()));
+ validate();
+
+ // Put everything in place
+ updateDefinitionHeader();
+ ui->definitionWidget->layout()->addWidget(currentPanel);
+
+ adjustSize();
+ currentPanel->setFocus();
+}
+
+ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint()
+{
+ switch (type)
+ {
+ case ConstraintDialog::TABLE:
+ return getSelectedConstraint(dynamic_cast<SqliteCreateTable::Constraint*>(constrStatement));
+ case ConstraintDialog::COLUMN:
+ return getSelectedConstraint(dynamic_cast<SqliteCreateTable::Column::Constraint*>(constrStatement));
+ }
+ return UNKNOWN;
+}
+
+ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint(SqliteCreateTable::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Constraint::PRIMARY_KEY:
+ return PK;
+ case SqliteCreateTable::Constraint::UNIQUE:
+ return UNIQUE;
+ case SqliteCreateTable::Constraint::CHECK:
+ return CHECK;
+ case SqliteCreateTable::Constraint::FOREIGN_KEY:
+ return FK;
+ case SqliteCreateTable::Constraint::NAME_ONLY:
+ break;
+ }
+ return UNKNOWN;
+}
+
+ConstraintDialog::Constraint ConstraintDialog::getSelectedConstraint(SqliteCreateTable::Column::Constraint* constraint)
+{
+ switch (constraint->type)
+ {
+ case SqliteCreateTable::Column::Constraint::PRIMARY_KEY:
+ return PK;
+ case SqliteCreateTable::Column::Constraint::NOT_NULL:
+ return NOTNULL;
+ 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 FK;
+ case SqliteCreateTable::Column::Constraint::NULL_:
+ case SqliteCreateTable::Column::Constraint::NAME_ONLY:
+ case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY:
+ break;
+ }
+ return UNKNOWN;
+}
+
+ConstraintPanel* ConstraintDialog::createConstraintPanel()
+{
+ if (!constrStatement)
+ return nullptr;
+
+ if (type == COLUMN)
+ return ConstraintPanel::produce(dynamic_cast<SqliteCreateTable::Column::Constraint*>(constrStatement));
+ else
+ return ConstraintPanel::produce(dynamic_cast<SqliteCreateTable::Constraint*>(constrStatement));
+}
+
+void ConstraintDialog::updateDefinitionHeader()
+{
+ switch (getSelectedConstraint())
+ {
+ case ConstraintDialog::UNKNOWN:
+ return;
+ case ConstraintDialog::PK:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_PRIMARY_KEY);
+ ui->titleLabel->setText(tr("Primary key", "table constraints"));
+ break;
+ case ConstraintDialog::FK:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_FOREIGN_KEY);
+ ui->titleLabel->setText(tr("Foreign key", "table constraints"));
+ break;
+ case ConstraintDialog::UNIQUE:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_UNIQUE);
+ ui->titleLabel->setText(tr("Unique", "table constraints"));
+ break;
+ case ConstraintDialog::NOTNULL:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_NOT_NULL);
+ ui->titleLabel->setText(tr("Not NULL", "table constraints"));
+ break;
+ case ConstraintDialog::CHECK:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_CHECK);
+ ui->titleLabel->setText(tr("Check", "table constraints"));
+ break;
+ case ConstraintDialog::COLLATE:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_COLLATION);
+ ui->titleLabel->setText(tr("Collate", "table constraints"));
+ break;
+ case ConstraintDialog::DEFAULT:
+ ui->titleIcon->setPixmap(ICONS.CONSTRAINT_DEFAULT);
+ ui->titleLabel->setText(tr("Default", "table constraints"));
+ break;
+ }
+}
+
+void ConstraintDialog::validate()
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(currentPanel->validate());
+}
+
+void ConstraintDialog::storeConfiguration()
+{
+ if (!currentPanel)
+ {
+ qWarning() << "Called to store constraint configuration, but there's no current panel.";
+ return;
+ }
+
+ currentPanel->storeDefinition();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h
new file mode 100644
index 0000000..fe24c0f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.h
@@ -0,0 +1,79 @@
+#ifndef CONSTRAINTDIALOG_H
+#define CONSTRAINTDIALOG_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "db/db.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QPointer>
+
+namespace Ui {
+ class ConstraintDialog;
+}
+
+class ConstraintPanel;
+
+class GUI_API_EXPORT ConstraintDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ enum Mode
+ {
+ NEW,
+ EDIT
+ };
+
+ enum Constraint
+ {
+ PK,
+ FK,
+ UNIQUE,
+ NOTNULL,
+ CHECK,
+ COLLATE,
+ DEFAULT,
+ UNKNOWN
+ };
+
+ enum Type
+ {
+ TABLE,
+ COLUMN
+ };
+
+ explicit ConstraintDialog(Mode mode, SqliteCreateTable::Constraint* constraint, SqliteCreateTable* createTable, Db* db,
+ QWidget *parent = 0);
+ explicit ConstraintDialog(Mode mode, SqliteCreateTable::Column::Constraint* constraint, SqliteCreateTable::Column* column, Db* db,
+ QWidget *parent = 0);
+ ~ConstraintDialog();
+
+ SqliteStatement* getConstraint();
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ Constraint getSelectedConstraint();
+ Constraint getSelectedConstraint(SqliteCreateTable::Constraint* constraint);
+ Constraint getSelectedConstraint(SqliteCreateTable::Column::Constraint* constraint);
+ ConstraintPanel* createConstraintPanel();
+ void updateDefinitionHeader();
+
+ Ui::ConstraintDialog *ui = nullptr;
+ Type type;
+ Mode mode;
+ Db* db = nullptr;
+ SqliteStatement* constrStatement = nullptr;
+ QPointer<SqliteCreateTable> createTable;
+ QPointer<SqliteCreateTable::Column> columnStmt;
+ QHash<int,QWidget> panels;
+ ConstraintPanel* currentPanel = nullptr;
+
+ private slots:
+ void validate();
+ void storeConfiguration();
+};
+
+#endif // CONSTRAINTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui
new file mode 100644
index 0000000..7df34d8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/constraintdialog.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConstraintDialog</class>
+ <widget class="QDialog" name="ConstraintDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="titleWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="titleIcon">
+ <property name="maximumSize">
+ <size>
+ <width>18</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="titleLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="definitionWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2"/>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConstraintDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConstraintDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp
new file mode 100644
index 0000000..c94546e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.cpp
@@ -0,0 +1,219 @@
+#include "dbconverterdialog.h"
+#include "ui_dbconverterdialog.h"
+#include "common/global.h"
+#include "dblistmodel.h"
+#include "db/db.h"
+#include "common/utils_sql.h"
+#include "dbversionconverter.h"
+#include "services/dbmanager.h"
+#include "iconmanager.h"
+#include "uiutils.h"
+#include "versionconvertsummarydialog.h"
+#include "mainwindow.h"
+#include "errorsconfirmdialog.h"
+#include "parser/ast/sqlitecreatetable.h"
+#include "services/pluginmanager.h"
+#include "plugins/dbplugin.h"
+#include "db/sqlquery.h"
+#include "services/notifymanager.h"
+#include "common/widgetcover.h"
+#include <QDebug>
+#include <QFileInfo>
+#include <QPushButton>
+
+DbConverterDialog::DbConverterDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::DbConverterDialog)
+{
+ init();
+}
+
+DbConverterDialog::~DbConverterDialog()
+{
+ delete ui;
+ safe_delete(converter);
+}
+
+void DbConverterDialog::setDb(Db* db)
+{
+ ui->srcDbCombo->setCurrentText(db->getName());
+ srcDb = db;
+ srcDbChanged();
+}
+
+void DbConverterDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+ setWindowTitle(tr("Convert database"));
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->setVisible(false);
+ widgetCover->initWithInterruptContainer();
+
+ ui->trgFileButton->setIcon(ICONS.OPEN_FILE);
+
+ converter = new DbVersionConverter();
+
+ dbListModel = new DbListModel(this);
+ ui->srcDbCombo->setModel(dbListModel);
+
+ connect(ui->srcDbCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(srcDbChanged(int)));
+ connect(ui->trgVersionCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState()));
+ connect(ui->trgFileEdit, SIGNAL(textChanged(QString)), this, SLOT(updateState()));
+ connect(ui->trgNameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateState()));
+ connect(converter, SIGNAL(conversionFailed(QString)), this, SLOT(processingFailed(QString)));
+ connect(converter, SIGNAL(conversionSuccessful()), this, SLOT(processingSuccessful()));
+ connect(converter, SIGNAL(conversionAborted()), this, SLOT(processingAborted()));
+ connect(widgetCover, SIGNAL(cancelClicked()), converter, SLOT(interrupt()));
+}
+
+void DbConverterDialog::srcDbChanged()
+{
+ dontUpdateState = true;
+ ui->srcDbVersionCombo->clear();
+ ui->trgVersionCombo->clear();
+ if (srcDb)
+ {
+ // Source version
+ QList<Dialect> dialects = converter->getSupportedVersions();
+ QStringList versionNames = converter->getSupportedVersionNames();
+ Dialect dialect = srcDb->getDialect();
+ int idx = dialects.indexOf(dialect);
+ QString type = versionNames[idx];
+ ui->srcDbVersionCombo->addItem(type);
+ ui->srcDbVersionCombo->setCurrentText(type);
+
+ // Target version
+ QString oldTrgVersion = ui->trgVersionCombo->currentText();
+ versionNames.removeAt(idx);
+ ui->trgVersionCombo->addItems(versionNames);
+ if (versionNames.contains(oldTrgVersion))
+ ui->trgVersionCombo->setCurrentText(oldTrgVersion);
+ else if (versionNames.size() > 0)
+ ui->trgVersionCombo->setCurrentIndex(0);
+
+ // File
+ QString trgFile = srcDb->getPath() + "_new";
+ int i = 0;
+ while (QFileInfo(trgFile).exists())
+ {
+ trgFile = srcDb->getPath() + "_new" + QString::number(i++);
+ }
+
+ ui->trgFileEdit->setText(trgFile);
+
+ // Name
+ QString generatedName = generateUniqueName(srcDb->getName() + "_new", DBLIST->getDbNames());
+ ui->trgNameEdit->setText(generatedName);
+ }
+ else
+ {
+ ui->srcDbVersionCombo->setCurrentText("");
+ ui->trgFileEdit->setText("");
+ ui->trgVersionCombo->setCurrentText("");
+ ui->trgNameEdit->setText("");
+ }
+ dontUpdateState = false;
+ updateState();
+}
+
+bool DbConverterDialog::validate()
+{
+ bool srcDbOk = (srcDb != nullptr);
+ setValidState(ui->srcDbCombo, srcDbOk, tr("Select source database"));
+
+ QString dstDbPath = ui->trgFileEdit->text();
+ QFileInfo dstDbFi(dstDbPath);
+ bool dstDbOk = (!dstDbFi.exists() || dstDbFi.isWritable()) && dstDbFi != QFileInfo(srcDb->getPath());
+ bool dstExists = dstDbFi.exists();
+ setValidState(ui->trgFileEdit, dstDbOk, tr("Enter valid and writable file path."));
+ if (dstExists && dstDbOk)
+ setValidStateInfo(ui->trgFileEdit, tr("Entered file exists and will be overwritten."));
+
+ QString name = ui->trgNameEdit->text();
+ bool nameOk = !name.isEmpty() && !DBLIST->getDbNames().contains(name);
+ setValidState(ui->trgNameEdit, nameOk, tr("Enter a not empty, unique name (as in the list of databases on the left)."));
+
+ bool dstDialectOk = ui->trgVersionCombo->currentIndex() > -1;
+ QString msg;
+ if (!dstDialectOk && ui->trgVersionCombo->count() == 0)
+ msg = tr("No valid target dialect available. Conversion not possible.");
+ else
+ msg = tr("Select valid target dialect.");
+
+ setValidState(ui->trgVersionCombo, dstDialectOk, msg);
+
+ return (srcDbOk && nameOk && dstDbOk && dstDialectOk);
+}
+
+void DbConverterDialog::accept()
+{
+ if (!validate())
+ return;
+
+ QStringList versionNames = converter->getSupportedVersionNames();
+ QList<Dialect> dialects = converter->getSupportedVersions();
+ QString trgDialectName = ui->trgVersionCombo->currentText();
+ int idx = versionNames.indexOf(trgDialectName);
+ if (idx == -1)
+ {
+ qCritical() << "Could not find target dialect on list of supported dialects in DbConverterDialog::accept()";
+ return;
+ }
+
+ Dialect srcDialect = srcDb->getDialect();
+ Dialect trgDialect = dialects[idx];
+ QString trgFile = ui->trgFileEdit->text();
+ QString trgName = ui->trgNameEdit->text();
+ widgetCover->show();
+ converter->convert(srcDialect, trgDialect, srcDb, trgFile, trgName, &DbConverterDialog::confirmConversion, &DbConverterDialog::confirmConversionErrors);
+}
+
+void DbConverterDialog::srcDbChanged(int index)
+{
+ srcDb = dbListModel->getDb(index);
+ srcDbChanged();
+}
+
+void DbConverterDialog::updateState()
+{
+ if (dontUpdateState)
+ return;
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate());
+}
+
+void DbConverterDialog::processingFailed(const QString& errorMessage)
+{
+ widgetCover->hide();
+ notifyError(errorMessage);
+}
+
+void DbConverterDialog::processingSuccessful()
+{
+ notifyInfo(tr("Database %1 has been successfully converted and now is available under new name: %2").arg(srcDb->getName(), ui->trgNameEdit->text()));
+ QDialog::accept();
+}
+
+void DbConverterDialog::processingAborted()
+{
+ widgetCover->hide();
+}
+
+bool DbConverterDialog::confirmConversion(const QList<QPair<QString, QString> >& diffs)
+{
+ VersionConvertSummaryDialog dialog(MAINWINDOW);
+ dialog.setWindowTitle(tr("SQL statements conversion"));
+ dialog.setSides(diffs);
+ return dialog.exec() == QDialog::Accepted;
+}
+
+bool DbConverterDialog::confirmConversionErrors(const QSet<QString>& errors)
+{
+ ErrorsConfirmDialog dialog(MAINWINDOW);
+ dialog.setTopLabel(tr("Following error occurred while converting SQL statements to the target SQLite version:"));
+ dialog.setBottomLabel(tr("Would you like to ignore those errors and proceed?"));
+ dialog.setErrors(errors);
+ return dialog.exec() == QDialog::Accepted;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h
new file mode 100644
index 0000000..4267a1b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.h
@@ -0,0 +1,52 @@
+#ifndef DBCONVERTERDIALOG_H
+#define DBCONVERTERDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+class DbListModel;
+class Db;
+class DbVersionConverter;
+class WidgetCover;
+
+namespace Ui {
+ class DbConverterDialog;
+}
+
+class GUI_API_EXPORT DbConverterDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit DbConverterDialog(QWidget *parent = 0);
+ ~DbConverterDialog();
+
+ void setDb(Db* db);
+
+ private:
+ void init();
+ void srcDbChanged();
+ bool validate();
+
+ static bool confirmConversion(const QList<QPair<QString, QString> >& diffs);
+ static bool confirmConversionErrors(const QSet<QString>& errors);
+
+ Ui::DbConverterDialog *ui = nullptr;
+ DbListModel* dbListModel = nullptr;
+ Db* srcDb = nullptr;
+ DbVersionConverter* converter = nullptr;
+ bool dontUpdateState = false;
+ WidgetCover* widgetCover = nullptr;
+
+ public slots:
+ void accept();
+
+ private slots:
+ void srcDbChanged(int index);
+ void updateState();
+ void processingFailed(const QString& errorMessage);
+ void processingSuccessful();
+ void processingAborted();
+};
+
+#endif // DBCONVERTERDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui
new file mode 100644
index 0000000..d328e99
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbconverterdialog.ui
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DbConverterDialog</class>
+ <widget class="QDialog" name="DbConverterDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>251</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="srcGroup">
+ <property name="title">
+ <string>Source database</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0" colspan="2">
+ <widget class="QComboBox" name="srcDbCombo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="srcDbVersionLabel">
+ <property name="text">
+ <string>Source database version:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="srcDbVersionCombo">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="trgGroup">
+ <property name="title">
+ <string>Target database</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="trgVersionLabel">
+ <property name="text">
+ <string>Target version:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="trgFileEdit">
+ <property name="toolTip">
+ <string>This is the file that will be created as a result of the conversion.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="trgFileLabel">
+ <property name="text">
+ <string>Target file:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="trgNameLabel">
+ <property name="text">
+ <string>Name of the new database:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QToolButton" name="trgFileButton">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <widget class="QComboBox" name="trgVersionCombo"/>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLineEdit" name="trgNameEdit">
+ <property name="toolTip">
+ <string>This is the name that the converted database will be added to SQLiteStudio with.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DbConverterDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DbConverterDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp
new file mode 100644
index 0000000..c9a7f28
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.cpp
@@ -0,0 +1,590 @@
+#include "dbdialog.h"
+#include "ui_dbdialog.h"
+#include "services/pluginmanager.h"
+#include "plugins/dbplugin.h"
+#include "uiutils.h"
+#include "common/utils.h"
+#include "uiconfig.h"
+#include "services/dbmanager.h"
+#include "common/global.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include <QDateTimeEdit>
+#include <QSpinBox>
+#include <QDebug>
+#include <QPushButton>
+#include <QFileDialog>
+#include <QComboBox>
+
+DbDialog::DbDialog(Mode mode, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::DbDialog),
+ mode(mode)
+{
+ init();
+}
+
+DbDialog::~DbDialog()
+{
+ delete ui;
+}
+
+void DbDialog::setDb(Db* db)
+{
+ this->db = db;
+}
+
+void DbDialog::setPermanent(bool perm)
+{
+ ui->permamentCheckBox->setChecked(perm);
+}
+
+QString DbDialog::getPath()
+{
+ return ui->fileEdit->text();
+}
+
+void DbDialog::setPath(const QString& path)
+{
+ ui->fileEdit->setText(path);
+}
+
+QString DbDialog::getName()
+{
+ return ui->nameEdit->text();
+}
+
+Db* DbDialog::getDb()
+{
+ if (ui->typeCombo->currentIndex() < 0)
+ return nullptr;
+
+ Db* testDb = nullptr;
+ QHash<QString, QVariant> options = collectOptions();
+ QString path = ui->fileEdit->text();
+ foreach (DbPlugin* plugin, dbPlugins)
+ {
+ if (options.contains(DB_PLUGIN) && options[DB_PLUGIN].toString() != plugin->getName())
+ continue;
+
+ testDb = plugin->getInstance("", path, options);
+ if (testDb)
+ return testDb;
+ }
+ return testDb;
+}
+
+bool DbDialog::isPermanent()
+{
+ return ui->permamentCheckBox->isChecked();
+}
+
+void DbDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void DbDialog::showEvent(QShowEvent *e)
+{
+ if (db)
+ {
+ int idx = ui->typeCombo->findText(db->getTypeLabel());
+ ui->typeCombo->setCurrentIndex(idx);
+ ui->typeCombo->setEnabled(false); // converting to other type is in separate dialog, it's different feature
+
+ ui->generateCheckBox->setChecked(false);
+ ui->fileEdit->setText(db->getPath());
+ ui->nameEdit->setText(db->getName());
+ }
+ else if (ui->typeCombo->count() > 0)
+ {
+ int idx = ui->typeCombo->findText("SQLite3"); // we should have SQLite3 plugin
+ if (idx > -1)
+ ui->typeCombo->setCurrentIndex(idx);
+ else
+ ui->typeCombo->setCurrentIndex(0);
+ }
+
+ existingDatabaseNames = DBLIST->getDbNames();
+ if (mode == EDIT)
+ existingDatabaseNames.removeOne(db->getName());
+
+ updateOptions();
+ updateState();
+
+ QDialog::showEvent(e);
+}
+
+void DbDialog::init()
+{
+ ui->setupUi(this);
+
+ ui->browseButton->setIcon(ICONS.DATABASE_FILE);
+ dbPlugins = PLUGINS->getLoadedPlugins<DbPlugin>();
+ foreach (DbPlugin* dbPlugin, dbPlugins)
+ {
+ ui->typeCombo->addItem(dbPlugin->getLabel());
+ }
+
+ ui->browseButton->setVisible(true);
+ ui->testConnIcon->setVisible(false);
+
+ connect(ui->fileEdit, SIGNAL(textChanged(QString)), this, SLOT(fileChanged(QString)));
+ connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameModified(QString)));
+ connect(ui->generateCheckBox, SIGNAL(toggled(bool)), this, SLOT(generateNameSwitched(bool)));
+ connect(ui->browseButton, SIGNAL(clicked()), this, SLOT(browseClicked()));
+ connect(ui->testConnButton, SIGNAL(clicked()), this, SLOT(testConnectionClicked()));
+ connect(ui->typeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(dbTypeChanged(int)));
+
+ generateNameSwitched(true);
+}
+
+void DbDialog::updateOptions()
+{
+ setUpdatesEnabled(false);
+
+ // Remove olds
+ foreach (QWidget* w, optionWidgets)
+ {
+ ui->gridLayout->removeWidget(w);
+ delete w;
+ }
+ adjustSize();
+
+ optionWidgets.clear();
+ optionKeyToWidget.clear();
+ optionKeyToType.clear();
+ helperToKey.clear();
+
+ lastWidgetInTabOrder = ui->permamentCheckBox;
+
+ // Retrieve new list
+ DbPlugin* plugin = nullptr;
+ if (dbPlugins.count() > 0)
+ {
+ int idx = ui->typeCombo->currentIndex();
+ if (idx > -1 )
+ {
+ plugin = dbPlugins[idx];
+ QList<DbPluginOption> optList = plugin->getOptionsList();
+ if (optList.size() > 0)
+ {
+ // Add new options
+ int row = ADDITIONAL_ROWS_BEGIN_INDEX;
+ foreach (DbPluginOption opt, optList)
+ addOption(opt, row++);
+ }
+ }
+ }
+
+ adjustSize();
+ setUpdatesEnabled(true);
+}
+
+void DbDialog::addOption(const DbPluginOption& option, int row)
+{
+ QLabel* label = new QLabel(option.label, this);
+ QWidget* editor = nullptr;
+ QWidget* editorHelper = nullptr; // TODO, based on plugins for Url handlers
+
+ editor = getEditor(option, editorHelper);
+ Q_ASSERT(editor != nullptr);
+
+ if (!option.toolTip.isNull())
+ editor->setToolTip(option.toolTip);
+
+ optionWidgets << label << editor;
+
+ optionKeyToWidget[option.key] = editor;
+ optionKeyToType[option.key] = option.type;
+ ui->gridLayout->addWidget(label, row, 0);
+ ui->gridLayout->addWidget(editor, row, 1);
+
+ setTabOrder(lastWidgetInTabOrder, editor);
+ lastWidgetInTabOrder = editor;
+
+ if (editorHelper)
+ {
+ ui->gridLayout->addWidget(editorHelper, row, 2);
+ optionWidgets << editorHelper;
+ helperToKey[editorHelper] = option.key;
+
+ setTabOrder(lastWidgetInTabOrder, editorHelper);
+ lastWidgetInTabOrder = editorHelper;
+ }
+
+ if (db && db->getConnectionOptions().contains(option.key))
+ setValueFor(option.type, editor, db->getConnectionOptions()[option.key]);
+}
+
+QWidget *DbDialog::getEditor(const DbPluginOption& opt, QWidget*& editorHelper)
+{
+ QWidget* editor = nullptr;
+ QLineEdit* le = nullptr;
+ editorHelper = nullptr;
+ switch (opt.type)
+ {
+ case DbPluginOption::STRING:
+ {
+ editor = new QLineEdit(this);
+ le = dynamic_cast<QLineEdit*>(editor);
+ connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::PASSWORD:
+ {
+ editor = new QLineEdit(this);
+ le = dynamic_cast<QLineEdit*>(editor);
+ le->setEchoMode(QLineEdit::Password);
+ connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::CHOICE:
+ {
+ QComboBox* cb = new QComboBox(this);
+ editor = cb;
+ cb->setEditable(!opt.choiceReadOnly);
+ cb->addItems(opt.choiceValues);
+ cb->setCurrentText(opt.defaultValue.toString());
+ connect(cb, SIGNAL(currentIndexChanged(QString)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::INT:
+ {
+ QSpinBox* sb = new QSpinBox(this);
+ editor = sb;
+ if (!opt.minValue.isNull())
+ sb->setMinimum(opt.minValue.toInt());
+
+ if (!opt.maxValue.isNull())
+ sb->setMaximum(opt.maxValue.toInt());
+
+ if (!opt.defaultValue.isNull())
+ sb->setValue(opt.defaultValue.toInt());
+
+ connect(sb, SIGNAL(valueChanged(int)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::FILE:
+ {
+ editor = new QLineEdit(this);
+ le = dynamic_cast<QLineEdit*>(editor);
+ editorHelper = new QPushButton(tr("Browse"), this);
+ connect(le, SIGNAL(textChanged(QString)), this, SLOT(propertyChanged()));
+ connect(editorHelper, SIGNAL(pressed()), this, SLOT(browseForFile()));
+ break;
+ }
+ case DbPluginOption::BOOL:
+ {
+ QCheckBox* cb = new QCheckBox(this);
+ editor = cb;
+ if (!opt.defaultValue.isNull())
+ cb->setChecked(opt.defaultValue.toBool());
+
+ connect(cb, SIGNAL(stateChanged(int)), this, SLOT(propertyChanged()));
+ break;
+ }
+ case DbPluginOption::DOUBLE:
+ {
+ QDoubleSpinBox* sb = new QDoubleSpinBox(this);
+ editor = sb;
+ if (!opt.minValue.isNull())
+ sb->setMinimum(opt.minValue.toDouble());
+
+ if (!opt.maxValue.isNull())
+ sb->setMaximum(opt.maxValue.toDouble());
+
+ if (!opt.defaultValue.isNull())
+ sb->setValue(opt.defaultValue.toDouble());
+
+ connect(sb, SIGNAL(valueChanged(double)), this, SLOT(propertyChanged()));
+ break;
+ }
+ default:
+ // TODO plugin based handling of custom editors
+ qWarning() << "Unhandled DbDialog option for creating editor.";
+ break;
+ }
+
+ if (le)
+ {
+ le->setPlaceholderText(opt.placeholderText);
+ le->setText(opt.defaultValue.toString());
+ }
+
+ return editor;
+}
+
+QVariant DbDialog::getValueFrom(DbPluginOption::Type type, QWidget *editor)
+{
+ QVariant value;
+ switch (type)
+ {
+ case DbPluginOption::STRING:
+ case DbPluginOption::PASSWORD:
+ case DbPluginOption::FILE:
+ value = dynamic_cast<QLineEdit*>(editor)->text();
+ break;
+ case DbPluginOption::INT:
+ value = dynamic_cast<QSpinBox*>(editor)->value();
+ break;
+ case DbPluginOption::BOOL:
+ value = dynamic_cast<QCheckBox*>(editor)->isChecked();
+ break;
+ case DbPluginOption::DOUBLE:
+ value = dynamic_cast<QDoubleSpinBox*>(editor)->value();
+ break;
+ case DbPluginOption::CHOICE:
+ value = dynamic_cast<QComboBox*>(editor)->currentText();
+ break;
+ default:
+ // TODO plugin based handling of custom editors
+ qWarning() << "Unhandled DbDialog option for value.";
+ break;
+ }
+ return value;
+}
+
+void DbDialog::setValueFor(DbPluginOption::Type type, QWidget *editor, const QVariant &value)
+{
+ switch (type)
+ {
+ case DbPluginOption::STRING:
+ case DbPluginOption::FILE:
+ case DbPluginOption::PASSWORD:
+ dynamic_cast<QLineEdit*>(editor)->setText(value.toString());
+ break;
+ case DbPluginOption::INT:
+ dynamic_cast<QSpinBox*>(editor)->setValue(value.toInt());
+ break;
+ case DbPluginOption::BOOL:
+ dynamic_cast<QCheckBox*>(editor)->setChecked(value.toBool());
+ break;
+ case DbPluginOption::DOUBLE:
+ dynamic_cast<QDoubleSpinBox*>(editor)->setValue(value.toDouble());
+ break;
+ case DbPluginOption::CHOICE:
+ dynamic_cast<QComboBox*>(editor)->setCurrentText(value.toString());
+ break;
+ default:
+ qWarning() << "Unhandled DbDialog option to set value.";
+ // TODO plugin based handling of custom editors
+ break;
+ }
+}
+
+void DbDialog::updateType()
+{
+ QFileInfo file(ui->fileEdit->text());
+ if (!file.exists() || file.isDir())
+ {
+ ui->typeCombo->setEnabled(true);
+ return;
+ }
+
+ DbPlugin* validPlugin = nullptr;
+ QHash<QString,QVariant> options;
+ QString path = ui->fileEdit->text();
+ Db* probeDb = nullptr;
+ foreach (DbPlugin* plugin, dbPlugins)
+ {
+ probeDb = plugin->getInstance("", path, options);
+ if (probeDb)
+ {
+ delete probeDb;
+ probeDb = nullptr;
+
+ validPlugin = plugin;
+ break;
+ }
+ }
+
+ if (validPlugin)
+ ui->typeCombo->setCurrentText(validPlugin->getLabel());
+
+ ui->typeCombo->setEnabled(!validPlugin);
+}
+
+QHash<QString, QVariant> DbDialog::collectOptions()
+{
+ QHash<QString, QVariant> options;
+ if (ui->typeCombo->currentIndex() < 0)
+ return options;
+
+ for (const QString key : optionKeyToWidget.keys())
+ options[key] = getValueFrom(optionKeyToType[key], optionKeyToWidget[key]);
+
+ DbPlugin* plugin = nullptr;
+ if (dbPlugins.count() > 0)
+ {
+ plugin = dbPlugins[ui->typeCombo->currentIndex()];
+ options[DB_PLUGIN] = plugin->getName();
+ }
+
+ return options;
+}
+
+bool DbDialog::testDatabase()
+{
+ QString path = ui->fileEdit->text();
+ bool existed = QFile::exists(path);
+ bool res = getDb() != nullptr;
+ if (!existed)
+ {
+ QFile file(path);
+ file.remove();
+ }
+ return res;
+}
+
+bool DbDialog::validate()
+{
+ if (ui->fileEdit->text().isEmpty())
+ return false;
+
+ if (ui->nameEdit->text().isEmpty())
+ return false;
+
+ if (ui->typeCombo->count() == 0)
+ return false;
+
+ if (ui->typeCombo->currentIndex() < 0)
+ return false;
+
+ return true;
+}
+
+void DbDialog::updateState()
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate());
+}
+
+void DbDialog::propertyChanged()
+{
+ ui->testConnIcon->setVisible(false);
+}
+
+void DbDialog::typeChanged(int index)
+{
+ UNUSED(index);
+ updateOptions();
+ updateState();
+}
+
+void DbDialog::valueForNameGenerationChanged()
+{
+ if (!ui->generateCheckBox->isChecked())
+ {
+ updateState();
+ return;
+ }
+
+ DbPlugin* plugin = nullptr;
+ if (dbPlugins.count() > 0)
+ {
+ plugin = dbPlugins[ui->typeCombo->currentIndex()];
+ QString generatedName = plugin->generateDbName(ui->fileEdit->text());
+ generatedName = generateUniqueName(generatedName, existingDatabaseNames);
+ ui->nameEdit->setText(generatedName);
+ }
+}
+
+void DbDialog::browseForFile()
+{
+ QString dir = getFileDialogInitPath();
+ QString path = QFileDialog::getOpenFileName(0, QString(), dir);
+ if (path.isNull())
+ return;
+
+ QString key = helperToKey[dynamic_cast<QWidget*>(sender())];
+ setValueFor(optionKeyToType[key], optionKeyToWidget[key], path);
+
+ setFileDialogInitPathByFile(path);
+}
+
+void DbDialog::generateNameSwitched(bool checked)
+{
+ if (checked)
+ {
+ ui->nameEdit->setPlaceholderText(tr("The name will be auto-generated"));
+ valueForNameGenerationChanged();
+ }
+ else
+ {
+ ui->nameEdit->setPlaceholderText(tr("Type the name"));
+ }
+
+ ui->nameEdit->setReadOnly(checked);
+}
+
+void DbDialog::fileChanged(const QString &arg1)
+{
+ UNUSED(arg1);
+ valueForNameGenerationChanged();
+ updateType();
+ propertyChanged();
+}
+
+void DbDialog::browseClicked()
+{
+ QFileInfo fileInfo(ui->fileEdit->text());
+ QString dir;
+ if (ui->fileEdit->text().isEmpty())
+ dir = getFileDialogInitPath();
+ else if (fileInfo.exists() && fileInfo.isFile())
+ dir = fileInfo.absolutePath();
+ else if (fileInfo.dir().exists())
+ dir = fileInfo.dir().absolutePath();
+ else
+ dir = getFileDialogInitPath();
+
+ QString path = getDbPath(dir);
+ if (path.isNull())
+ return;
+
+ setFileDialogInitPathByFile(path);
+
+ ui->fileEdit->setText(path);
+ updateState();
+}
+
+void DbDialog::testConnectionClicked()
+{
+ ui->testConnIcon->setPixmap(testDatabase() ? ICONS.TEST_CONN_OK : ICONS.TEST_CONN_ERROR);
+ ui->testConnIcon->setVisible(true);
+}
+
+void DbDialog::dbTypeChanged(int index)
+{
+ typeChanged(index);
+ propertyChanged();
+}
+
+void DbDialog::nameModified(const QString &arg1)
+{
+ UNUSED(arg1);
+ updateState();
+}
+
+void DbDialog::accept()
+{
+ QString name = getName();
+ QString path = getPath();
+ QHash<QString, QVariant> options = collectOptions();
+ bool perm = isPermanent();
+ bool result;
+ if (mode == ADD)
+ result = DBLIST->addDb(name, path, options, perm);
+ else
+ result = DBLIST->updateDb(db, name, path, options, perm);
+
+ if (result)
+ QDialog::accept();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h
new file mode 100644
index 0000000..b2c0d68
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.h
@@ -0,0 +1,89 @@
+#ifndef DBDIALOG_H
+#define DBDIALOG_H
+
+#include "db/db.h"
+#include "db/dbpluginoption.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QList>
+#include <QHash>
+#include <QStringList>
+
+class DbPlugin;
+class QGridLayout;
+struct DbPluginOption;
+
+namespace Ui {
+ class DbDialog;
+}
+
+class GUI_API_EXPORT DbDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ enum Mode
+ {
+ ADD,
+ EDIT
+ };
+
+ DbDialog(Mode mode, QWidget *parent = 0);
+ ~DbDialog();
+
+ void setDb(Db* db);
+ void setPermanent(bool perm);
+
+ QString getPath();
+ void setPath(const QString& path);
+ QString getName();
+ QHash<QString,QVariant> collectOptions();
+ bool isPermanent();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void showEvent(QShowEvent* e);
+
+ private:
+ void init();
+ void updateOptions();
+ void addOption(const DbPluginOption& option, int row);
+ QWidget* getEditor(const DbPluginOption& opt, QWidget *&editorHelper);
+ QVariant getValueFrom(DbPluginOption::Type type, QWidget* editor);
+ void setValueFor(DbPluginOption::Type type, QWidget* editor, const QVariant& value);
+ void updateType();
+ Db* getDb();
+ bool testDatabase();
+ bool validate();
+ void updateState();
+
+ Ui::DbDialog *ui = nullptr;
+ Mode mode;
+ QStringList existingDatabaseNames;
+ Db* db = nullptr;
+ QList<DbPlugin*> dbPlugins;
+ QList<QWidget*> optionWidgets;
+ QHash<QString,QWidget*> optionKeyToWidget;
+ QHash<QString,DbPluginOption::Type> optionKeyToType;
+ QHash<QWidget*,QString> helperToKey;
+ QWidget* lastWidgetInTabOrder = nullptr;
+
+ static const constexpr int ADDITIONAL_ROWS_BEGIN_INDEX = 4;
+
+ private slots:
+ void typeChanged(int index);
+ void valueForNameGenerationChanged();
+ void browseForFile();
+ void generateNameSwitched(bool checked);
+ void fileChanged(const QString &arg1);
+ void browseClicked();
+ void testConnectionClicked();
+ void propertyChanged();
+ void dbTypeChanged(int index);
+ void nameModified(const QString &arg1);
+
+ public slots:
+ void accept();
+};
+
+#endif // DBDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui
new file mode 100644
index 0000000..fb53428
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/dbdialog.ui
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DbDialog</class>
+ <widget class="QDialog" name="DbDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>455</width>
+ <height>200</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>450</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Database</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <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>
+ <item>
+ <widget class="QComboBox" name="typeCombo">
+ <property name="toolTip">
+ <string>Database driver</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="nameEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="typeLabel">
+ <property name="text">
+ <string>Type</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="fileEdit"/>
+ </item>
+ <item row="0" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QToolButton" name="browseButton">
+ <property name="toolTip">
+ <string>Browse for database file on local computer</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="fileLabel">
+ <property name="text">
+ <string>File</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="generateCheckBox">
+ <property name="toolTip">
+ <string>Generate name basing on file path</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Permanent</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="permamentCheckBox">
+ <property name="toolTip">
+ <string extracomment="aasfd">&lt;p&gt;Enable this if you want the database to be stored in configuration file and restored every time SQLiteStudio is started.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomWidget" 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="QPushButton" name="testConnButton">
+ <property name="text">
+ <string>Test database connection</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="testConnIcon">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ <property name="centerButtons">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>fileEdit</tabstop>
+ <tabstop>browseButton</tabstop>
+ <tabstop>nameEdit</tabstop>
+ <tabstop>generateCheckBox</tabstop>
+ <tabstop>typeCombo</tabstop>
+ <tabstop>permamentCheckBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DbDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DbDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp
new file mode 100644
index 0000000..e86f9cd
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.cpp
@@ -0,0 +1,58 @@
+#include "ddlpreviewdialog.h"
+#include "ui_ddlpreviewdialog.h"
+#include "services/codeformatter.h"
+#include "uiconfig.h"
+#include "sqlitestudio.h"
+#include "db/db.h"
+
+DdlPreviewDialog::DdlPreviewDialog(Db* db, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::DdlPreviewDialog),
+ db(db)
+{
+ ui->setupUi(this);
+}
+
+DdlPreviewDialog::~DdlPreviewDialog()
+{
+ delete ui;
+}
+
+void DdlPreviewDialog::setDdl(const QString& ddl)
+{
+ QString formatted = SQLITESTUDIO->getCodeFormatter()->format("sql", ddl, db);
+ ui->ddlEdit->setPlainText(formatted);
+}
+
+void DdlPreviewDialog::setDdl(const QStringList& ddlList)
+{
+ QStringList fixedList;
+ QString newDdl;
+ foreach (const QString& ddl, ddlList)
+ {
+ newDdl = ddl.trimmed();
+ if (!newDdl.endsWith(";"))
+ newDdl.append(";");
+
+ fixedList << SQLITESTUDIO->getCodeFormatter()->format("sql", newDdl, db);
+ }
+ setDdl(fixedList.join("\n"));
+}
+
+void DdlPreviewDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void DdlPreviewDialog::accept()
+{
+ CFG_UI.General.DontShowDdlPreview.set(ui->dontShowAgainCheck->isChecked());
+ QDialog::accept();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h
new file mode 100644
index 0000000..403405f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.h
@@ -0,0 +1,35 @@
+#ifndef DDLPREVIEWDIALOG_H
+#define DDLPREVIEWDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+class Db;
+
+namespace Ui {
+ class DdlPreviewDialog;
+}
+
+class GUI_API_EXPORT DdlPreviewDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit DdlPreviewDialog(Db* db, QWidget *parent = 0);
+ ~DdlPreviewDialog();
+
+ void setDdl(const QString& ddl);
+ void setDdl(const QStringList& ddlList);
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ Ui::DdlPreviewDialog *ui = nullptr;
+ Db* db = nullptr;
+
+ public slots:
+ void accept();
+};
+
+#endif // DDLPREVIEWDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui
new file mode 100644
index 0000000..8c3678a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/ddlpreviewdialog.ui
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DdlPreviewDialog</class>
+ <widget class="QDialog" name="DdlPreviewDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>527</width>
+ <height>351</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Queries to be executed</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="SqlView" name="ddlEdit">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomWidget" 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="QCheckBox" name="dontShowAgainCheck">
+ <property name="text">
+ <string>Don't show again</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ <property name="centerButtons">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>DdlPreviewDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>DdlPreviewDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp
new file mode 100644
index 0000000..c0a73f3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.cpp
@@ -0,0 +1,47 @@
+#include "errorsconfirmdialog.h"
+#include "ui_errorsconfirmdialog.h"
+#include "iconmanager.h"
+
+ErrorsConfirmDialog::ErrorsConfirmDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ErrorsConfirmDialog)
+{
+ ui->setupUi(this);
+}
+
+ErrorsConfirmDialog::~ErrorsConfirmDialog()
+{
+ delete ui;
+}
+
+void ErrorsConfirmDialog::setErrors(const QHash<QString,QSet<QString>>& errors)
+{
+ ui->list->clear();
+
+ for (const QString& key : errors.keys())
+ {
+ for (const QString& err : errors[key])
+ ui->list->addItem(QString("[%1] %2").arg(key, err));
+ }
+
+ for (int i = 0, total = ui->list->count(); i < total; ++i)
+ ui->list->item(i)->setIcon(ICONS.STATUS_ERROR);
+}
+
+void ErrorsConfirmDialog::setErrors(const QSet<QString>& errors)
+{
+ ui->list->clear();
+ ui->list->addItems(errors.toList());
+ for (int i = 0, total = ui->list->count(); i < total; ++i)
+ ui->list->item(i)->setIcon(ICONS.STATUS_ERROR);
+}
+
+void ErrorsConfirmDialog::setTopLabel(const QString& text)
+{
+ ui->topLabel->setText(text);
+}
+
+void ErrorsConfirmDialog::setBottomLabel(const QString& text)
+{
+ ui->bottomLabel->setText(text);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h
new file mode 100644
index 0000000..3e71d7b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.h
@@ -0,0 +1,28 @@
+#ifndef ERRORSCONFIRMDIALOG_H
+#define ERRORSCONFIRMDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class ErrorsConfirmDialog;
+}
+
+class GUI_API_EXPORT ErrorsConfirmDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit ErrorsConfirmDialog(QWidget *parent = 0);
+ ~ErrorsConfirmDialog();
+
+ void setErrors(const QHash<QString, QSet<QString> >& errors);
+ void setErrors(const QSet<QString>& errors);
+ void setTopLabel(const QString& text);
+ void setBottomLabel(const QString& text);
+
+ private:
+ Ui::ErrorsConfirmDialog *ui = nullptr;
+};
+
+#endif // ERRORSCONFIRMDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui
new file mode 100644
index 0000000..81cdb17
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/errorsconfirmdialog.ui
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ErrorsConfirmDialog</class>
+ <widget class="QDialog" name="ErrorsConfirmDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="topLabel">
+ <property name="text">
+ <string>Following errors occured:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="list">
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="bottomLabel">
+ <property name="text">
+ <string>Would you like to proceed?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::No|QDialogButtonBox::Yes</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ErrorsConfirmDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ErrorsConfirmDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp
new file mode 100644
index 0000000..e495dd9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.cpp
@@ -0,0 +1,737 @@
+#include "exportdialog.h"
+#include "ui_exportdialog.h"
+#include "dblistmodel.h"
+#include "dbobjlistmodel.h"
+#include "services/dbmanager.h"
+#include "uiutils.h"
+#include "services/pluginmanager.h"
+#include "formmanager.h"
+#include "plugins/exportplugin.h"
+#include "configmapper.h"
+#include "selectabledbobjmodel.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "schemaresolver.h"
+#include "common/widgetcover.h"
+#include "services/notifymanager.h"
+#include "uiconfig.h"
+#include <QClipboard>
+#include <QDebug>
+#include <QDir>
+#include <QFileDialog>
+#include <QTextCodec>
+#include <QUiLoader>
+#include <QMimeData>
+
+ExportDialog::ExportDialog(QWidget *parent) :
+ QWizard(parent),
+ ui(new Ui::ExportDialog)
+{
+ init();
+}
+
+ExportDialog::~ExportDialog()
+{
+ EXPORT_MANAGER->interrupt();
+ safe_delete(configMapper);
+ delete ui;
+}
+
+void ExportDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+
+#ifdef Q_OS_MACX
+ resize(width() + 150, height());
+ setPixmap(QWizard::BackgroundPixmap, addOpacity(ICONS.DATABASE_EXPORT_WIZARD.toQIcon().pixmap(800, 800), 0.3));
+#endif
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer(tr("Cancel"));
+ connect(widgetCover, SIGNAL(cancelClicked()), EXPORT_MANAGER, SLOT(interrupt()));
+ widgetCover->setVisible(false);
+
+ initPageOrder();
+
+ initModePage();
+ initTablePage();
+ initFormatPage();
+ initQueryPage();
+ initDbObjectsPage();
+
+ connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(pageChanged(int)));
+ connect(EXPORT_MANAGER, SIGNAL(exportSuccessful()), this, SLOT(success()));
+ connect(EXPORT_MANAGER, SIGNAL(exportFinished()), this, SLOT(hideCoverWidget()));
+ connect(EXPORT_MANAGER, SIGNAL(storeInClipboard(QByteArray, QString)), this, SLOT(storeInClipboard(QByteArray, QString)));
+ connect(EXPORT_MANAGER, SIGNAL(storeInClipboard(QString)), this, SLOT(storeInClipboard(QString)));
+ connect(EXPORT_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(handleValidationResultFromPlugin(bool,CfgEntry*,QString)));
+ connect(EXPORT_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)));
+}
+
+void ExportDialog::setTableMode(Db* db, const QString& table)
+{
+ if (!db->isOpen())
+ {
+ qWarning() << "Cannot export from closed database.";
+ return;
+ }
+
+ setStartId(pageId(ui->tablePage));
+ exportMode = ExportManager::TABLE;
+ this->db = db;
+ this->table = table;
+
+ ui->exportTableDbNameCombo->addItem(db->getName());
+ ui->exportTableDbNameCombo->setCurrentText(db->getName());
+ ui->exportTableDbNameCombo->setEnabled(false);
+ ui->exportTableNameCombo->addItem(table);
+ ui->exportTableNameCombo->setCurrentText(table);
+ ui->exportTableNameCombo->setEnabled(false);
+}
+
+void ExportDialog::setQueryMode(Db* db, const QString& query)
+{
+ if (!db->isOpen())
+ {
+ qWarning() << "Cannot export from closed database.";
+ return;
+ }
+
+ setStartId(pageId(ui->queryPage));
+ exportMode = ExportManager::QUERY_RESULTS;
+ this->db = db;
+ this->query = query;
+
+ ui->queryDatabaseCombo->addItem(db->getName());
+ ui->queryDatabaseCombo->setCurrentText(db->getName());
+ ui->queryDatabaseCombo->setEnabled(false);
+ ui->queryEdit->setPlainText(query);
+ updateQueryEditDb();
+ ui->queryEdit->checkSyntaxNow();
+}
+
+void ExportDialog::setDatabaseMode(Db* db)
+{
+ if (!db->isOpen())
+ {
+ qWarning() << "Cannot export from closed database.";
+ return;
+ }
+
+ setStartId(pageId(ui->databaseObjectsPage));
+ exportMode = ExportManager::DATABASE;
+ this->db = db;
+}
+
+void ExportDialog::initModePage()
+{
+ connect(ui->subjectDatabaseRadio, SIGNAL(clicked()), this, SLOT(updateExportMode()));
+ connect(ui->subjectTableRadio, SIGNAL(clicked()), this, SLOT(updateExportMode()));
+ connect(ui->subjectQueryRadio, SIGNAL(clicked()), this, SLOT(updateExportMode()));
+}
+
+void ExportDialog::initTablePage()
+{
+ ui->tablePage->setValidator([=]() -> bool
+ {
+ bool dbOk = ui->exportTableDbNameCombo->currentIndex() > -1;
+ bool tableOk = ui->exportTableNameCombo->currentIndex() > -1;
+
+ setValidState(ui->exportTableDbNameCombo, dbOk, tr("Select database to export."));
+ setValidState(ui->exportTableNameCombo, tableOk, tr("Select table to export."));
+
+ return dbOk && tableOk;
+ });
+
+ dbListModel = new DbListModel(this);
+ dbListModel->setCombo(ui->exportTableDbNameCombo);
+ dbListModel->setSortMode(DbListModel::SortMode::Alphabetical);
+
+ tablesModel = new DbObjListModel(this);
+ tablesModel->setType(DbObjListModel::ObjectType::TABLE);
+
+ connect(this, SIGNAL(tablePageCompleteChanged()), ui->tablePage, SIGNAL(completeChanged()));
+}
+
+void ExportDialog::initQueryPage()
+{
+ ui->queryPage->setValidator([=]() -> bool
+ {
+ bool queryOk = !ui->queryEdit->toPlainText().trimmed().isEmpty();
+ queryOk &= ui->queryEdit->isSyntaxChecked() && !ui->queryEdit->haveErrors();
+ bool dbOk = ui->queryDatabaseCombo->currentIndex() > -1;
+
+ setValidState(ui->queryDatabaseCombo, dbOk, tr("Select database to export."));
+ setValidState(ui->queryEdit, queryOk, tr("Enter valid query to export."));
+
+ return dbOk && queryOk;
+ });
+
+ connect(ui->queryEdit, SIGNAL(errorsChecked(bool)), ui->queryPage, SIGNAL(completeChanged()));
+ connect(ui->queryEdit, SIGNAL(textChanged()), ui->queryPage, SIGNAL(completeChanged()));
+ connect(ui->queryDatabaseCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateQueryEditDb()));
+ connect(this, SIGNAL(queryPageCompleteChanged()), ui->queryPage, SIGNAL(completeChanged()));
+}
+
+void ExportDialog::initDbObjectsPage()
+{
+ selectableDbListModel = new SelectableDbObjModel(this);
+ selectableDbListModel->setSourceModel(DBTREE->getModel());
+ ui->dbObjectsTree->setModel(selectableDbListModel);
+
+ ui->databaseObjectsPage->setValidator([=]() -> bool
+ {
+ bool dbOk = ui->dbObjectsDatabaseCombo->currentIndex() > -1;
+ bool listOk = selectableDbListModel->getCheckedObjects().size() > 0;
+
+ setValidState(ui->dbObjectsDatabaseCombo, dbOk, tr("Select database to export."));
+ setValidState(ui->dbObjectsTree, listOk, tr("Select at least one object to export."));
+
+ return listOk;
+ });
+
+ connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateDbObjTree()));
+ connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(QString)), ui->databaseObjectsPage, SIGNAL(completeChanged()));
+ connect(selectableDbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->databaseObjectsPage, SIGNAL(completeChanged()));
+ connect(ui->objectsSelectAllButton, SIGNAL(clicked()), this, SLOT(dbObjectsSelectAll()));
+ connect(ui->objectsDeselectAllButton, SIGNAL(clicked()), this, SLOT(dbObjectsDeselectAll()));
+}
+
+void ExportDialog::initFormatPage()
+{
+ ui->formatAndOptionsPage->setValidator([=]() -> bool
+ {
+ setValidState(ui->exportFileEdit, true);
+ bool outputFileSupported = currentPlugin && currentPlugin->getSupportedModes().testFlag(ExportManager::FILE);
+ if (outputFileSupported && ui->exportFileRadio->isChecked())
+ {
+ QString path = ui->exportFileEdit->text();
+ if (path.trimmed().isEmpty())
+ {
+ setValidState(ui->exportFileEdit, false, tr("You must provide a file name to export to."));
+ return false;
+ }
+
+ QDir dir(path);
+ if (dir.exists() && QFileInfo(path).isDir())
+ {
+ setValidState(ui->exportFileEdit, false, tr("Path you provided is an existing directory. You cannot overwrite it."));
+ return false;
+ }
+
+ if (!dir.cdUp())
+ {
+ setValidState(ui->exportFileEdit, false, tr("The directory '%1' does not exist.").arg(dir.dirName()));
+ return false;
+ }
+
+ QFileInfo fi(path);
+ if (fi.exists())
+ setValidStateInfo(ui->exportFileEdit, tr("The file '%1' exists and will be overwritten.").arg(fi.fileName()));
+ }
+ return ui->formatCombo->currentIndex() > -1 && ui->encodingCombo->currentIndex() > -1 && isPluginConfigValid();
+ });
+
+ ui->exportFileButton->setIcon(ICONS.EXPORT_FILE_BROWSE);
+ connect(ui->exportFileButton, SIGNAL(clicked()), this, SLOT(browseForExportFile()));
+
+ connect(ui->formatCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(pluginSelected()));
+ connect(ui->formatCombo, SIGNAL(currentTextChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->encodingCombo, SIGNAL(currentTextChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->exportFileEdit, SIGNAL(textChanged(QString)), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->exportFileRadio, SIGNAL(clicked()), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->exportClipboardRadio, SIGNAL(clicked()), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(this, SIGNAL(formatPageCompleteChanged()), ui->formatAndOptionsPage, SIGNAL(completeChanged()));
+ connect(ui->exportFileRadio, SIGNAL(clicked()), this, SLOT(updateOptions()));
+ connect(ui->exportClipboardRadio, SIGNAL(clicked()), this, SLOT(updateOptions()));
+ connect(ui->exportFileRadio, SIGNAL(clicked()), this, SLOT(updateExportOutputOptions()));
+ connect(ui->exportClipboardRadio, SIGNAL(clicked()), this, SLOT(updateExportOutputOptions()));
+}
+
+int ExportDialog::nextId() const
+{
+ if (exportMode == ExportManager::UNDEFINED)
+ return pageId(ui->proxyPage);
+
+ QList<QWizardPage*> order = pageOrder[exportMode];
+
+ int idx = order.indexOf(currentPage());
+ idx++;
+ if (idx < order.size())
+ return pageId(order[idx]);
+
+ return -1;
+}
+
+bool ExportDialog::isPluginConfigValid() const
+{
+ return pluginConfigOk.size() == 0;
+}
+
+void ExportDialog::initPageOrder()
+{
+ setStartId(pageId(ui->exportSubjectPage));
+ pageOrder[ExportManager::DATABASE] = {ui->databaseObjectsPage, ui->formatAndOptionsPage};
+ pageOrder[ExportManager::TABLE] = {ui->tablePage, ui->formatAndOptionsPage};
+ pageOrder[ExportManager::QUERY_RESULTS] = {ui->queryPage, ui->formatAndOptionsPage};
+ updateExportMode();
+}
+
+int ExportDialog::pageId(QWizardPage* wizardPage) const
+{
+ for (int id : pageIds())
+ {
+ if (page(id) == wizardPage)
+ return id;
+ }
+ return -1;
+}
+
+void ExportDialog::tablePageDisplayed()
+{
+ if (!tablePageVisited)
+ {
+ if (table.isNull()) // table mode selected by user, not forced by setTableMode().
+ {
+ ui->exportTableDbNameCombo->setModel(dbListModel);
+ connect(ui->exportTableDbNameCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDbTables()));
+
+ ui->exportTableNameCombo->setModel(tablesModel);
+ connect(ui->exportTableNameCombo, SIGNAL(currentTextChanged(QString)), ui->tablePage, SIGNAL(completeChanged()));
+ }
+ updateDbTables();
+ emit tablePageCompleteChanged();
+ tablePageVisited = true;
+ }
+}
+
+void ExportDialog::queryPageDisplayed()
+{
+ if (!queryPageVisited)
+ {
+ if (query.isNull()) // query mode selected by user, not forced by setQueryMode().
+ {
+ ui->queryDatabaseCombo->setModel(dbListModel);
+ connect(ui->queryDatabaseCombo, SIGNAL(currentIndexChanged(int)), ui->queryPage, SIGNAL(completeChanged()));
+ }
+
+ updateQueryEditDb();
+ emit queryPageCompleteChanged();
+ queryPageVisited = true;
+ }
+}
+
+void ExportDialog::dbObjectsPageDisplayed()
+{
+ if (!dbObjectsPageVisited)
+ {
+ ui->dbObjectsDatabaseCombo->setModel(dbListModel);
+ connect(ui->dbObjectsDatabaseCombo, SIGNAL(currentIndexChanged(int)), ui->queryPage, SIGNAL(completeChanged()));
+
+ if (db)
+ ui->dbObjectsDatabaseCombo->setCurrentText(db->getName());
+
+ dbObjectsPageVisited = true;
+ }
+}
+
+void ExportDialog::formatPageDisplayed()
+{
+ if (!formatPageVisited)
+ {
+ ui->formatCombo->addItems(EXPORT_MANAGER->getAvailableFormats(exportMode));
+
+ ui->encodingCombo->addItems(textCodecNames());
+ ui->encodingCombo->setCurrentText(defaultCodecName());
+
+ formatPageVisited = true;
+ }
+ pluginSelected();
+}
+
+ExportPlugin* ExportDialog::getSelectedPlugin() const
+{
+ return EXPORT_MANAGER->getPluginForFormat(ui->formatCombo->currentText());
+}
+
+void ExportDialog::updateExportMode()
+{
+ if (ui->subjectDatabaseRadio->isChecked())
+ exportMode = ExportManager::DATABASE;
+ else if (ui->subjectTableRadio->isChecked())
+ exportMode = ExportManager::TABLE;
+ else if (ui->subjectQueryRadio->isChecked())
+ exportMode = ExportManager::QUERY_RESULTS;
+ else
+ exportMode = ExportManager::UNDEFINED;
+}
+
+void ExportDialog::pageChanged(int pageId)
+{
+ QWizardPage* wizardPage = page(pageId);
+ if (wizardPage == ui->tablePage)
+ tablePageDisplayed();
+ else if (wizardPage == ui->queryPage)
+ queryPageDisplayed();
+ else if (wizardPage == ui->databaseObjectsPage)
+ dbObjectsPageDisplayed();
+ else if (wizardPage == ui->formatAndOptionsPage)
+ formatPageDisplayed();
+ else if (wizardPage == ui->proxyPage)
+ next();
+}
+
+void ExportDialog::updateDbTables()
+{
+ if (!table.isNull())
+ return; // we don't want tables to be automatically updated if this is strictly set table
+
+ QString dbName = ui->exportTableDbNameCombo->currentText();
+ db = DBLIST->getByName(dbName);
+
+ tablesModel->setDb(db);
+}
+
+void ExportDialog::browseForExportFile()
+{
+ QStringList filters;
+ if (currentPlugin)
+ filters << currentPlugin->getFormatName()+" (*." + currentPlugin->defaultFileExtension() + ")";
+
+ filters << tr("All files (*)");
+
+ QString dir = getFileDialogInitPath();
+ QString fileName = QFileDialog::getSaveFileName(this, tr("Pick file to export to"), dir, filters.join(";;"), 0, QFileDialog::DontConfirmOverwrite);
+ if (fileName.isNull())
+ return;
+
+ if (currentPlugin && !fileName.endsWith("." + currentPlugin->defaultFileExtension()))
+ fileName += "." + currentPlugin->defaultFileExtension();
+
+ ui->exportFileEdit->setText(fileName);
+ setFileDialogInitPathByFile(fileName);
+}
+
+void ExportDialog::pluginSelected()
+{
+ pluginConfigOk.clear();
+
+ currentPlugin = getSelectedPlugin();
+ if (!currentPlugin)
+ {
+ qCritical() << "Could not find export plugin, while it was selected on ui:" << ui->formatCombo->currentText();
+ return;
+ }
+
+ currentPlugin->setExportMode(exportMode);
+
+ updateExportOutputOptions();
+ updateOptions();
+
+ if (currentPlugin->getConfig() && !currentPlugin->getConfig()->isPersistable())
+ currentPlugin->getConfig()->reset();
+}
+
+void ExportDialog::updateExportOutputOptions()
+{
+ ExportManager::StandardConfigFlags options = currentPlugin->standardOptionsToEnable();
+ bool displayCodec = options.testFlag(ExportManager::CODEC) && !ui->exportClipboardRadio->isChecked();
+ bool clipboardSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::CLIPBOARD);
+ bool outputFileSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::FILE);
+
+ bool enabled = outputFileSupported && ui->exportFileRadio->isChecked();
+ ui->exportFileEdit->setEnabled(enabled);
+ ui->exportFileButton->setEnabled(enabled);
+
+ ui->exportClipboardRadio->setVisible(clipboardSupported);
+ ui->exportFileRadio->setVisible(outputFileSupported);
+ ui->exportFileEdit->setVisible(outputFileSupported);
+ ui->exportFileButton->setVisible(outputFileSupported);
+ if (!clipboardSupported && outputFileSupported)
+ ui->exportFileRadio->setChecked(true);
+
+ ui->encodingCombo->setVisible(displayCodec);
+ ui->encodingLabel->setVisible(displayCodec);
+ if (displayCodec)
+ {
+ QString codec = currentPlugin->getDefaultEncoding();
+ int idx = ui->encodingCombo->findText(codec);
+ if (idx > -1)
+ ui->encodingCombo->setCurrentIndex(idx);
+ }
+
+ ui->exportToGroup->setVisible(clipboardSupported || outputFileSupported || displayCodec);
+}
+
+void ExportDialog::updateQueryEditDb()
+{
+ Db* db = getDbForExport(ui->queryDatabaseCombo->currentText());
+ ui->queryEdit->setDb(db);
+}
+
+void ExportDialog::updateOptions()
+{
+ ui->optionsGroup->setVisible(false);
+
+ if (!currentPlugin)
+ {
+ qCritical() << "Could not find export plugin, while it was selected on ui:" << ui->formatCombo->currentText();
+ return;
+ }
+
+ int optionsRow = 0;
+ updatePluginOptions(currentPlugin, optionsRow);
+ ui->optionsGroup->setVisible(optionsRow > 0);
+}
+
+void ExportDialog::updateDbObjTree()
+{
+ selectableDbListModel->setDbName(ui->dbObjectsDatabaseCombo->currentText());
+
+ QModelIndex root = selectableDbListModel->index(0, 0);
+ if (root.isValid())
+ {
+ root = setupNewDbObjTreeRoot(root);
+ ui->dbObjectsTree->setRootIndex(root);
+
+ ui->dbObjectsTree->expand(root);
+ QModelIndex child;
+ for (int i = 0; (child = root.child(i, 0)).isValid(); i++)
+ ui->dbObjectsTree->expand(child);
+ }
+ dbObjectsSelectAll();
+}
+
+void ExportDialog::dbObjectsSelectAll()
+{
+ selectableDbListModel->setRootChecked(true);
+}
+
+void ExportDialog::dbObjectsDeselectAll()
+{
+ selectableDbListModel->setRootChecked(false);
+}
+
+void ExportDialog::hideCoverWidget()
+{
+ widgetCover->hide();
+}
+
+void ExportDialog::storeInClipboard(const QByteArray& bytes, const QString& mimeType)
+{
+ QMimeData* mimeData = new QMimeData;
+ mimeData->setData(mimeType, bytes);
+ QApplication::clipboard()->setMimeData(mimeData);
+}
+
+void ExportDialog::storeInClipboard(const QString& str)
+{
+ QApplication::clipboard()->setText(str);
+}
+
+void ExportDialog::success()
+{
+ QWizard::accept();
+}
+
+void ExportDialog::accept()
+{
+ doExport();
+}
+
+void ExportDialog::updatePluginOptions(ExportPlugin* plugin, int& optionsRow)
+{
+ safe_delete(pluginOptionsWidget);
+
+ QString formName = plugin->getExportConfigFormName();
+ CfgMain* cfgMain = plugin->getConfig();
+ if (formName.isNull() || !cfgMain)
+ {
+ if (!formName.isNull())
+ {
+ qWarning() << "FormName is given, but cfgMain is null in ExportDialog::updatePluginOptions() for plugin:" << plugin->getName()
+ << ", formName:" << formName;
+ }
+ return;
+ }
+
+ if (!FORMS->hasWidget(formName))
+ {
+ qWarning() << "Export plugin" << plugin->getName() << "requested for form named" << formName << "but FormManager doesn't have it."
+ << "Available forms are:" << FORMS->getAvailableForms();
+ return;
+ }
+
+ safe_delete(configMapper);
+
+ QGridLayout* grid = dynamic_cast<QGridLayout*>(ui->optionsGroup->layout());
+
+ pluginOptionsWidget = FORMS->createWidget(formName);
+
+ if (pluginOptionsWidget->layout())
+ pluginOptionsWidget->layout()->setMargin(0);
+
+ grid->addWidget(pluginOptionsWidget, 1, 0, 1, 2);
+ optionsRow++;
+
+ configMapper = new ConfigMapper(cfgMain);
+ configMapper->bindToConfig(pluginOptionsWidget);
+ connect(configMapper, SIGNAL(modified()), this, SLOT(updateValidation()));
+ plugin->validateOptions();
+}
+
+void ExportDialog::updateValidation()
+{
+ if (!currentPlugin)
+ return;
+
+ currentPlugin->validateOptions();
+ emit formatPageCompleteChanged();
+}
+
+void ExportDialog::doExport()
+{
+ widgetCover->show();
+
+ ExportManager::StandardExportConfig stdConfig = getExportConfig();
+ QString format = ui->formatCombo->currentText();
+ switch (exportMode)
+ {
+ case ExportManager::DATABASE:
+ exportDatabase(stdConfig, format);
+ break;
+ case ExportManager::TABLE:
+ exportTable(stdConfig, format);
+ break;
+ case ExportManager::QUERY_RESULTS:
+ exportQuery(stdConfig, format);
+ break;
+ case ExportManager::UNDEFINED:
+ qCritical() << "Finished export dialog with undefined mode.";
+ notifyInternalError();
+ break;
+ case ExportManager::FILE:
+ case ExportManager::CLIPBOARD:
+ break;
+ }
+}
+
+void ExportDialog::exportDatabase(const ExportManager::StandardExportConfig& stdConfig, const QString& format)
+{
+ Db* db = getDbForExport(ui->dbObjectsDatabaseCombo->currentText());
+ if (!db || !db->isValid())
+ return;
+
+ EXPORT_MANAGER->configure(format, stdConfig);
+ EXPORT_MANAGER->exportDatabase(db, selectableDbListModel->getCheckedObjects());
+}
+
+void ExportDialog::exportTable(const ExportManager::StandardExportConfig& stdConfig, const QString& format)
+{
+ Db* db = getDbForExport(ui->exportTableDbNameCombo->currentText());
+ if (!db || !db->isValid())
+ return;
+
+ EXPORT_MANAGER->configure(format, stdConfig);
+ // TODO when dbnames are fully supported, pass the dbname below
+ EXPORT_MANAGER->exportTable(db, QString::null, ui->exportTableNameCombo->currentText());
+}
+
+void ExportDialog::exportQuery(const ExportManager::StandardExportConfig& stdConfig, const QString& format)
+{
+ Db* db = getDbForExport(ui->queryDatabaseCombo->currentText());
+ if (!db || !db->isValid())
+ return;
+
+ EXPORT_MANAGER->configure(format, stdConfig);
+ EXPORT_MANAGER->exportQueryResults(db, ui->queryEdit->toPlainText());
+}
+
+ExportManager::StandardExportConfig ExportDialog::getExportConfig() const
+{
+ bool clipboardSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::CLIPBOARD);
+ bool outputFileSupported = currentPlugin->getSupportedModes().testFlag(ExportManager::FILE);
+ bool clipboard = clipboardSupported && ui->exportClipboardRadio->isChecked();
+
+ ExportManager::StandardExportConfig stdConfig;
+ stdConfig.intoClipboard = clipboard;
+
+ if (clipboard)
+ stdConfig.outputFileName = QString::null;
+ else if (outputFileSupported)
+ stdConfig.outputFileName = ui->exportFileEdit->text();
+
+ if (exportMode == ExportManager::DATABASE)
+ stdConfig.exportData = ui->exportDbDataCheck->isChecked();
+ else if (exportMode == ExportManager::TABLE)
+ stdConfig.exportData = ui->exportTableDataCheck->isChecked();
+ else
+ stdConfig.exportData = false;
+
+ if (ui->encodingCombo->isVisible() && ui->encodingCombo->currentIndex() > -1)
+ stdConfig.codec = ui->encodingCombo->currentText();
+ else
+ stdConfig.codec = defaultCodecName();
+
+ return stdConfig;
+}
+
+Db* ExportDialog::getDbForExport(const QString& name)
+{
+ Db* db = DBLIST->getByName(name);
+ if (!db)
+ {
+ qCritical() << "Could not find db selected in combo:" << name;
+ notifyInternalError();
+ return nullptr;
+ }
+ return db;
+}
+
+void ExportDialog::notifyInternalError()
+{
+ notifyError(tr("Internal error during export. This is a bug. Please report it."));
+}
+
+QModelIndex ExportDialog::setupNewDbObjTreeRoot(const QModelIndex& root)
+{
+ QModelIndex newRoot = root;
+ DbTreeItem* item = nullptr;
+ while (newRoot.isValid())
+ {
+ item = selectableDbListModel->getItemForIndex(newRoot);
+ if (item->getType() == DbTreeItem::Type::DB)
+ return newRoot;
+
+ newRoot = newRoot.child(0, 0);
+ }
+ return newRoot;
+}
+
+void ExportDialog::handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (w)
+ setValidState(w, valid, errorMsg);
+
+ if (valid == pluginConfigOk.contains(key)) // if state changed
+ {
+ if (!valid)
+ pluginConfigOk[key] = false;
+ else
+ pluginConfigOk.remove(key);
+
+ emit formatPageCompleteChanged();
+ }
+}
+
+void ExportDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (!w)
+ return;
+
+ w->setVisible(visible);
+ w->setEnabled(enabled);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h
new file mode 100644
index 0000000..296aa4d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.h
@@ -0,0 +1,105 @@
+#ifndef EXPORTDIALOG_H
+#define EXPORTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include "services/exportmanager.h"
+#include <QWizard>
+
+namespace Ui {
+ class ExportDialog;
+}
+
+class DbListModel;
+class DbObjListModel;
+class SelectableDbObjModel;
+class WidgetCover;
+class ConfigMapper;
+
+class GUI_API_EXPORT ExportDialog : public QWizard
+{
+ Q_OBJECT
+
+ public:
+ explicit ExportDialog(QWidget *parent = 0);
+ ~ExportDialog();
+
+ void setTableMode(Db* db, const QString& table);
+ void setQueryMode(Db* db, const QString& query);
+ void setDatabaseMode(Db* db);
+ int nextId() const;
+ bool isPluginConfigValid() const;
+
+ private:
+ void init();
+ void initModePage();
+ void initTablePage();
+ void initQueryPage();
+ void initDbObjectsPage();
+ void initFormatPage();
+ void initPageOrder();
+ int pageId(QWizardPage* wizardPage) const;
+ void tablePageDisplayed();
+ void queryPageDisplayed();
+ void dbObjectsPageDisplayed();
+ void formatPageDisplayed();
+ ExportPlugin* getSelectedPlugin() const;
+ void updatePluginOptions(ExportPlugin* plugin, int& optionsRow);
+ void doExport();
+ void exportDatabase(const ExportManager::StandardExportConfig& stdConfig, const QString& format);
+ void exportTable(const ExportManager::StandardExportConfig& stdConfig, const QString& format);
+ void exportQuery(const ExportManager::StandardExportConfig& stdConfig, const QString& format);
+ ExportManager::StandardExportConfig getExportConfig() const;
+ Db* getDbForExport(const QString& name);
+ void notifyInternalError();
+ QModelIndex setupNewDbObjTreeRoot(const QModelIndex& root);
+
+ QHash<ExportManager::ExportMode,QList<QWizardPage*>> pageOrder;
+
+ Ui::ExportDialog *ui = nullptr;
+ ExportManager::ExportMode exportMode = ExportManager::UNDEFINED;
+ Db* db = nullptr;
+ QString query;
+ QString table;
+ DbListModel* dbListModel = nullptr;
+ DbObjListModel* tablesModel = nullptr;
+ SelectableDbObjModel* selectableDbListModel = nullptr;
+ QWidget* pluginOptionsWidget = nullptr;
+ bool tablePageVisited = false;
+ bool queryPageVisited = false;
+ bool dbObjectsPageVisited = false;
+ bool formatPageVisited = false;
+ WidgetCover* widgetCover = nullptr;
+ ConfigMapper* configMapper = nullptr;
+ QHash<CfgEntry*,bool> pluginConfigOk;
+ ExportPlugin* currentPlugin = nullptr;
+
+ private slots:
+ void handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg);
+ void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled);
+ void updateExportMode();
+ void pageChanged(int pageId);
+ void updateDbTables();
+ void browseForExportFile();
+ void pluginSelected();
+ void updateExportOutputOptions();
+ void updateQueryEditDb();
+ void updateOptions();
+ void updateDbObjTree();
+ void dbObjectsSelectAll();
+ void dbObjectsDeselectAll();
+ void hideCoverWidget();
+ void storeInClipboard(const QByteArray& bytes, const QString& mimeType);
+ void storeInClipboard(const QString& str);
+ void success();
+ void updateValidation();
+
+ public slots:
+ void accept();
+
+ signals:
+ void formatPageCompleteChanged();
+ void tablePageCompleteChanged();
+ void queryPageCompleteChanged();
+};
+
+#endif // EXPORTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui
new file mode 100644
index 0000000..9f84232
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/exportdialog.ui
@@ -0,0 +1,438 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportDialog</class>
+ <widget class="QWizard" name="ExportDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>515</width>
+ <height>414</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Export dialog</string>
+ </property>
+ <property name="options">
+ <set>QWizard::CancelButtonOnLeft|QWizard::NoDefaultButton</set>
+ </property>
+ <widget class="QWizardPage" name="exportSubjectPage">
+ <property name="title">
+ <string>What do you want to export?</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QFrame" name="exportSubjectFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QRadioButton" name="subjectDatabaseRadio">
+ <property name="text">
+ <string>A database</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="subjectTableRadio">
+ <property name="text">
+ <string>A single table</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="subjectQueryRadio">
+ <property name="text">
+ <string>Query results</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="proxyPage"/>
+ <widget class="VerifiableWizardPage" name="tablePage">
+ <property name="title">
+ <string>Table to export</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QFrame" name="exportTableFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="2">
+ <widget class="QComboBox" name="exportTableNameCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QComboBox" name="exportTableDbNameCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="exportTableDbNameLabel">
+ <property name="text">
+ <string>Database</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="exportTableNameLabel">
+ <property name="text">
+ <string>Table</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="exportTableSeparatorLabel">
+ <property name="text">
+ <string>.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="tableOptionsGroup">
+ <property name="title">
+ <string>Options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QCheckBox" name="exportTableDataCheck">
+ <property name="toolTip">
+ <string>When this option is unchecked, then only table DDL (CREATE TABLE statement) is exported.</string>
+ </property>
+ <property name="text">
+ <string>Export table data</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="exportTableIndexesCheck">
+ <property name="text">
+ <string>Export table indexes</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="exportTableTriggersCheck">
+ <property name="text">
+ <string>Export table triggers</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Note, that exporting table indexes and triggers may be unsupported by some output formats.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="VerifiableWizardPage" name="databaseObjectsPage">
+ <property name="title">
+ <string>Select database objects to export</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0" colspan="2">
+ <widget class="QTreeView" name="dbObjectsTree">
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QCheckBox" name="exportDbDataCheck">
+ <property name="text">
+ <string>Export data from tables</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QPushButton" name="objectsSelectAllButton">
+ <property name="text">
+ <string>Select all</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QPushButton" name="objectsDeselectAllButton">
+ <property name="text">
+ <string>Deselect all</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="dbObjectsDatabaseCombo"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="dbObjectsDatabaseLabel">
+ <property name="text">
+ <string>Database:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="VerifiableWizardPage" name="queryPage">
+ <property name="title">
+ <string>Query to export results for</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="2" column="0" colspan="2">
+ <widget class="SqlEditor" name="queryEdit"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="queryDatabaseCombo"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="queryDatabaseLabel">
+ <property name="text">
+ <string>Database:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="queryLabel">
+ <property name="text">
+ <string>Query to be executed for results:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="VerifiableWizardPage" name="formatAndOptionsPage">
+ <property name="title">
+ <string>Export format and options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QScrollArea" name="formatScrollArea">
+ <property name="styleSheet">
+ <string notr="true">#formatScrollArea { background: transparent; }</string>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="formatScrollAreaContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>298</width>
+ <height>288</height>
+ </rect>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#formatScrollAreaContents { background: transparent; }</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <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="QGroupBox" name="formatGroup">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="title">
+ <string>Export format</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QComboBox" name="formatCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="exportToGroup">
+ <property name="title">
+ <string>Output</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="exportFileEdit">
+ <property name="placeholderText">
+ <string>Exported file path</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="exportClipboardRadio">
+ <property name="text">
+ <string>Clipboard</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QToolButton" name="exportFileButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QRadioButton" name="exportFileRadio">
+ <property name="text">
+ <string>File</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="3">
+ <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="encodingLabel">
+ <property name="text">
+ <string>Exported text encoding:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="encodingCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="optionsGroup">
+ <property name="title">
+ <string>Export format options</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2"/>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>VerifiableWizardPage</class>
+ <extends>QWizardPage</extends>
+ <header>common/verifiablewizardpage.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp
new file mode 100644
index 0000000..32ec30f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.cpp
@@ -0,0 +1,376 @@
+#include "importdialog.h"
+#include "dblistmodel.h"
+#include "dbobjlistmodel.h"
+#include "common/widgetstateindicator.h"
+#include "uiutils.h"
+#include "common/widgetcover.h"
+#include "services/dbmanager.h"
+#include "services/pluginmanager.h"
+#include "services/importmanager.h"
+#include "sqlitestudio.h"
+#include "plugins/importplugin.h"
+#include "ui_importdialog.h"
+#include "configmapper.h"
+#include "formmanager.h"
+#include "common/utils.h"
+#include "uiconfig.h"
+#include <QDir>
+#include <QDebug>
+#include <QFileDialog>
+
+ImportDialog::ImportDialog(QWidget *parent) :
+ QWizard(parent),
+ ui(new Ui::ImportDialog)
+{
+ init();
+}
+
+ImportDialog::~ImportDialog()
+{
+ IMPORT_MANAGER->interrupt();
+ safe_delete(configMapper);
+ delete ui;
+}
+
+void ImportDialog::setDbAndTable(Db* db, const QString& table)
+{
+ if (!db)
+ return;
+
+ ui->dbNameCombo->setCurrentText(db->getName());
+ ui->tableNameCombo->setCurrentText(table);
+}
+
+void ImportDialog::setDb(Db* db)
+{
+ if (!db)
+ return;
+
+ ui->dbNameCombo->setCurrentText(db->getName());
+}
+
+bool ImportDialog::isPluginConfigValid() const
+{
+ return pluginConfigOk.size() == 0;
+}
+
+void ImportDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+
+#ifdef Q_OS_MACX
+ resize(width() + 150, height());
+ setPixmap(QWizard::BackgroundPixmap, addOpacity(ICONS.DATABASE_IMPORT_WIZARD.toQIcon().pixmap(800, 800), 0.3));
+#endif
+
+ initTablePage();
+ initDataSourcePage();
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer(tr("Cancel"));
+ connect(widgetCover, SIGNAL(cancelClicked()), IMPORT_MANAGER, SLOT(interrupt()));
+ widgetCover->setVisible(false);
+
+ connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(pageChanged()));
+ connect(IMPORT_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(handleValidationResultFromPlugin(bool,CfgEntry*,QString)));
+ connect(IMPORT_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)));
+ connect(IMPORT_MANAGER, SIGNAL(importSuccessful()), this, SLOT(success()));
+ connect(IMPORT_MANAGER, SIGNAL(importFinished()), this, SLOT(hideCoverWidget()));
+}
+
+void ImportDialog::initTablePage()
+{
+ dbListModel = new DbListModel(this);
+ dbListModel->setCombo(ui->dbNameCombo);
+ dbListModel->setSortMode(DbListModel::SortMode::Alphabetical);
+ ui->dbNameCombo->setModel(dbListModel);
+
+ tablesModel = new DbObjListModel(this);
+ tablesModel->setIncludeSystemObjects(false);
+ tablesModel->setType(DbObjListModel::ObjectType::TABLE);
+ ui->tableNameCombo->setModel(tablesModel);
+ refreshTables();
+
+ connect(ui->dbNameCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshTables()));
+ connect(ui->tableNameCombo, SIGNAL(currentTextChanged(QString)), ui->tablePage, SIGNAL(completeChanged()));
+
+ ui->tablePage->setValidator([=]() -> bool
+ {
+ bool valid = !ui->tableNameCombo->currentText().isEmpty();
+ setValidStateWihtTooltip(ui->tableNameCombo, tr("If you type table name that doesn't exist, it will be created."), valid, tr("Enter the table name"));
+ return valid;
+ });
+}
+
+void ImportDialog::initDataSourcePage()
+{
+ ui->inputFileButton->setIcon(ICONS.OPEN_FILE);
+ connect(ui->inputFileButton, SIGNAL(clicked()), this, SLOT(browseForInputFile()));
+
+ ui->codecCombo->addItems(textCodecNames());
+ ui->codecCombo->setCurrentText(defaultCodecName());
+
+ ui->dsPage->setValidator([=]() -> bool
+ {
+ setValidState(ui->dsTypeCombo, true);
+ if (!currentPlugin)
+ {
+ setValidState(ui->dsTypeCombo, false, tr("Select import plugin."));
+ return false;
+ }
+
+ if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME))
+ {
+ QString path = ui->inputFileEdit->text();
+ if (path.trimmed().isEmpty())
+ {
+ setValidState(ui->inputFileEdit, false, tr("You must provide a file to import from."));
+ return false;
+ }
+
+ QFileInfo file(path);
+ if (!file.exists())
+ {
+ setValidState(ui->inputFileEdit, false, tr("The file '%1' does not exist.").arg(path));
+ return false;
+ }
+
+ if (file.exists() && file.isDir())
+ {
+ setValidState(ui->inputFileEdit, false, tr("Path you provided is a directory. A regular file is required."));
+ return false;
+ }
+ setValidState(ui->inputFileEdit, true);
+ }
+ return ui->dsTypeCombo->currentIndex() > -1 && ui->codecCombo->currentIndex() > -1 && isPluginConfigValid();
+ });
+
+ connect(this, SIGNAL(dsPageCompleteChanged()), ui->dsPage, SIGNAL(completeChanged()));
+ connect(ui->dsTypeCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(pluginSelected()));
+ connect(ui->dsTypeCombo, SIGNAL(currentTextChanged(QString)), ui->dsPage, SIGNAL(completeChanged()));
+ connect(ui->codecCombo, SIGNAL(currentTextChanged(QString)), ui->dsPage, SIGNAL(completeChanged()));
+ connect(ui->inputFileEdit, SIGNAL(textChanged(QString)), ui->dsPage, SIGNAL(completeChanged()));
+
+ ui->dsTypeCombo->addItems(IMPORT_MANAGER->getImportDataSourceTypes());
+}
+
+void ImportDialog::removeOldOptions()
+{
+ safe_delete(configMapper);
+ safe_delete(pluginOptionsWidget);
+}
+
+void ImportDialog::updateStandardOptions()
+{
+ bool showFileName = currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME);
+ bool showCodec = currentPlugin->standardOptionsToEnable().testFlag(ImportManager::CODEC);
+
+ if (!showFileName && !showCodec)
+ {
+ ui->dsOptionsGroup->setVisible(false);
+ return;
+ }
+
+ ui->dsOptionsGroup->setVisible(true);
+
+ int row = 0;
+ QGridLayout* grid = dynamic_cast<QGridLayout*>(ui->dsOptionsGroup->layout());
+ if (showFileName)
+ {
+ grid->addWidget(ui->inputFileLabel, row, 0);
+ grid->addWidget(ui->inputFileWidget, row, 1);
+ row++;
+ }
+ else
+ {
+ grid->removeWidget(ui->inputFileLabel);
+ grid->removeWidget(ui->inputFileWidget);
+ }
+
+ ui->inputFileLabel->setVisible(showFileName);
+ ui->inputFileWidget->setVisible(showFileName);
+
+ if (showCodec)
+ {
+ grid->addWidget(ui->codecLabel, row, 0);
+ grid->addWidget(ui->codecCombo, row, 1);
+ row++;
+ }
+ else
+ {
+ grid->removeWidget(ui->codecLabel);
+ grid->removeWidget(ui->codecCombo);
+ }
+
+ ui->codecLabel->setVisible(showCodec);
+ ui->codecCombo->setVisible(showCodec);
+}
+
+void ImportDialog::updatePluginOptions(int& rows)
+{
+ QString formName = currentPlugin->getImportConfigFormName();
+ CfgMain* cfgMain = currentPlugin->getConfig();
+ ui->dsPluginOptionsGroup->setVisible(false);
+ if (formName.isNull() || !cfgMain)
+ {
+ if (!formName.isNull())
+ {
+ qWarning() << "FormName is given, but cfgMain is null in ImportDialog::updatePluginOptions() for plugin:" << currentPlugin->getName()
+ << ", formName:" << formName;
+ }
+ return;
+ }
+
+ if (!FORMS->hasWidget(formName))
+ {
+ qWarning() << "Import plugin" << currentPlugin->getName() << "requested for form named" << formName << "but FormManager doesn't have it."
+ << "Available forms are:" << FORMS->getAvailableForms();
+ return;
+ }
+
+ pluginOptionsWidget = FORMS->createWidget(formName);
+ if (!pluginOptionsWidget)
+ {
+ qWarning() << "Import plugin" << currentPlugin->getName() << "requested for form named" << formName << "but FormManager returned null.";
+ return;
+ }
+
+ ui->dsPluginOptionsGroup->setVisible(true);
+
+ if (pluginOptionsWidget->layout())
+ pluginOptionsWidget->layout()->setMargin(0);
+
+ ui->dsPluginOptionsGroup->layout()->addWidget(pluginOptionsWidget);
+ rows++;
+
+ configMapper = new ConfigMapper(cfgMain);
+ configMapper->bindToConfig(pluginOptionsWidget);
+ connect(configMapper, SIGNAL(modified()), this, SLOT(updateValidation()));
+ updateValidation();
+}
+
+void ImportDialog::handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (w)
+ setValidState(w, valid, errorMsg);
+
+ if (valid == pluginConfigOk.contains(key)) // if state changed
+ {
+ if (!valid)
+ pluginConfigOk[key] = false;
+ else
+ pluginConfigOk.remove(key);
+ }
+}
+
+void ImportDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (!w)
+ return;
+
+ w->setVisible(visible);
+ w->setEnabled(enabled);
+}
+
+void ImportDialog::refreshTables()
+{
+ Db* db = DBLIST->getByName(ui->dbNameCombo->currentText());
+ if (db)
+ tablesModel->setDb(db);
+}
+
+void ImportDialog::pluginSelected()
+{
+ ui->dsPluginOptionsGroup->setVisible(false);
+ removeOldOptions();
+ currentPlugin = IMPORT_MANAGER->getPluginForDataSourceType(ui->dsTypeCombo->currentText());
+ if (!currentPlugin)
+ return;
+
+ updateStandardOptions();
+
+ int rows = 0;
+ updatePluginOptions(rows);
+ ui->dsPluginOptionsGroup->setVisible(rows > 0);
+}
+
+void ImportDialog::updateValidation()
+{
+ if (!currentPlugin)
+ return;
+
+ currentPlugin->validateOptions();
+ emit dsPageCompleteChanged();
+}
+
+void ImportDialog::pageChanged()
+{
+ if (currentPage() == ui->dsPage)
+ updateValidation();
+}
+
+void ImportDialog::browseForInputFile()
+{
+ if (!currentPlugin)
+ {
+ qCritical() << "Called ImportDialog::browseForInputFile(), but no ImportPlugin is selected.";
+ return;
+ }
+
+ QString dir = getFileDialogInitPath();
+ QString filter = currentPlugin->getFileFilter();
+ QString fileName = QFileDialog::getOpenFileName(this, tr("Pick file to import from"), dir, filter);
+ if (fileName.isNull())
+ return;
+
+ ui->inputFileEdit->setText(fileName);
+ setFileDialogInitPathByFile(fileName);
+}
+
+void ImportDialog::success()
+{
+ QWizard::accept();
+}
+
+void ImportDialog::hideCoverWidget()
+{
+ widgetCover->hide();
+}
+
+void ImportDialog::accept()
+{
+ if (!currentPlugin)
+ {
+ qCritical() << "Called ImportDialog::accept(), but no ImportPlugin is selected.";
+ return;
+ }
+
+ ImportManager::StandardImportConfig stdConfig;
+ if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::FILE_NAME))
+ stdConfig.inputFileName = ui->inputFileEdit->text();
+
+ if (currentPlugin->standardOptionsToEnable().testFlag(ImportManager::CODEC))
+ stdConfig.codec = ui->codecCombo->currentText();
+
+ Db* db = DBLIST->getByName(ui->dbNameCombo->currentText());;
+ if (!db)
+ {
+ qCritical() << "Called ImportDialog::accept(), but no database is selected.";
+ return;
+ }
+
+ QString table = ui->tableNameCombo->currentText();
+
+ widgetCover->show();
+ IMPORT_MANAGER->configure(currentPlugin->getDataSourceTypeName(), stdConfig);
+ IMPORT_MANAGER->importToTable(db, table);
+}
+
+void ImportDialog::showEvent(QShowEvent* e)
+{
+ QWizard::showEvent(e);
+ ui->tableNameCombo->setFocus();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h
new file mode 100644
index 0000000..c50703f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.h
@@ -0,0 +1,69 @@
+#ifndef IMPORTDIALOG_H
+#define IMPORTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QWizard>
+
+namespace Ui {
+ class ImportDialog;
+}
+
+class DbListModel;
+class DbObjListModel;
+class ImportPlugin;
+class ConfigMapper;
+class CfgEntry;
+class WidgetCover;
+class Db;
+
+class GUI_API_EXPORT ImportDialog : public QWizard
+{
+ Q_OBJECT
+
+ public:
+ explicit ImportDialog(QWidget *parent = 0);
+ ~ImportDialog();
+
+ void setDbAndTable(Db* db, const QString& table);
+ void setDb(Db* db);
+
+ protected:
+ void showEvent(QShowEvent* e);
+
+ private:
+ void init();
+ void initTablePage();
+ void initDataSourcePage();
+ void removeOldOptions();
+ void updateStandardOptions();
+ void updatePluginOptions(int& rows);
+ bool isPluginConfigValid() const;
+
+ Ui::ImportDialog *ui = nullptr;
+ DbListModel* dbListModel = nullptr;
+ DbObjListModel* tablesModel = nullptr;
+ ConfigMapper* configMapper = nullptr;
+ QWidget* pluginOptionsWidget = nullptr;
+ ImportPlugin* currentPlugin = nullptr;
+ QHash<CfgEntry*,bool> pluginConfigOk;
+ WidgetCover* widgetCover = nullptr;
+
+ private slots:
+ void handleValidationResultFromPlugin(bool valid, CfgEntry* key, const QString& errorMsg);
+ void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled);
+ void refreshTables();
+ void pluginSelected();
+ void updateValidation();
+ void pageChanged();
+ void browseForInputFile();
+ void success();
+ void hideCoverWidget();
+
+ public slots:
+ void accept();
+
+ signals:
+ void dsPageCompleteChanged();
+};
+
+#endif // IMPORTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui
new file mode 100644
index 0000000..b853ab8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/importdialog.ui
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ImportDialog</class>
+ <widget class="QWizard" name="ImportDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>511</width>
+ <height>406</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Import data</string>
+ </property>
+ <property name="options">
+ <set>QWizard::CancelButtonOnLeft|QWizard::NoDefaultButton</set>
+ </property>
+ <widget class="VerifiableWizardPage" name="tablePage">
+ <property name="title">
+ <string>Table to import to</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QFrame" name="existingTableFrame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="3">
+ <widget class="QLabel" name="tableNameLabel">
+ <property name="text">
+ <string>Table</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="dbNameLabel">
+ <property name="text">
+ <string>Database</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="tableSeparatorLabel">
+ <property name="text">
+ <string>.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QComboBox" name="dbNameCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QComboBox" name="tableNameCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="VerifiableWizardPage" name="dsPage">
+ <property name="title">
+ <string>Data source to import from</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="styleSheet">
+ <string notr="true">#scrollArea { background: transparent; }</string>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>269</width>
+ <height>280</height>
+ </rect>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#scrollAreaWidgetContents { background: transparent; }</string>
+ </property>
+ <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="QGroupBox" name="dsTypeGroup">
+ <property name="title">
+ <string>Data source type</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QComboBox" name="dsTypeCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dsOptionsGroup">
+ <property name="title">
+ <string>Options</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="inputFileLabel">
+ <property name="text">
+ <string>Input file:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QWidget" name="inputFileWidget" 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="QLineEdit" name="inputFileEdit"/>
+ </item>
+ <item>
+ <widget class="QToolButton" name="inputFileButton">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="codecLabel">
+ <property name="text">
+ <string>Text encoding:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="codecCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="dsPluginOptionsGroup">
+ <property name="title">
+ <string>Data source options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4"/>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>VerifiableWizardPage</class>
+ <extends>QWizardPage</extends>
+ <header>common/verifiablewizardpage.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp
new file mode 100644
index 0000000..d835dd1
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.cpp
@@ -0,0 +1,468 @@
+#include "indexdialog.h"
+#include "ui_indexdialog.h"
+#include "schemaresolver.h"
+#include "parser/ast/sqliteindexedcolumn.h"
+#include "services/notifymanager.h"
+#include "common/utils_sql.h"
+#include "db/chainexecutor.h"
+#include "dbtree/dbtree.h"
+#include "ddlpreviewdialog.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include "uiutils.h"
+#include "sqlite3.h"
+#include "windows/editorwindow.h"
+#include "services/codeformatter.h"
+#include <QDebug>
+#include <QGridLayout>
+#include <QSignalMapper>
+#include <QScrollBar>
+#include <QPushButton>
+#include <QMessageBox>
+
+IndexDialog::IndexDialog(Db* db, QWidget *parent) :
+ QDialog(parent),
+ db(db),
+ ui(new Ui::IndexDialog)
+{
+ init();
+}
+
+IndexDialog::IndexDialog(Db* db, const QString& index, QWidget* parent) :
+ QDialog(parent),
+ db(db),
+ index(index),
+ ui(new Ui::IndexDialog)
+{
+ existingIndex = true;
+ init();
+}
+
+IndexDialog::~IndexDialog()
+{
+ delete ui;
+}
+
+void IndexDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void IndexDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+ if (!db || !db->isOpen())
+ {
+ qCritical() << "Created IndexDialog for null or closed database.";
+ notifyError(tr("Tried to open index dialog for closed or inexisting database."));
+ reject();
+ return;
+ }
+
+ ui->columnsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+ ui->partialIndexEdit->setDb(db);
+
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
+
+ columnStateSignalMapping = new QSignalMapper(this);
+ connect(columnStateSignalMapping, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int)));
+
+ SchemaResolver resolver(db);
+ ui->tableCombo->addItem(QString::null);
+ ui->tableCombo->addItems(resolver.getTables());
+ connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateTable(QString)));
+ connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateValidation()));
+ if (existingIndex)
+ ui->tableCombo->setEnabled(false);
+
+ if (db->getDialect() == Dialect::Sqlite3)
+ {
+ connect(ui->partialIndexCheck, SIGNAL(toggled(bool)), this, SLOT(updatePartialConditionState()));
+ connect(ui->partialIndexEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation()));
+ connect(ui->partialIndexEdit, SIGNAL(textChanged()), this, SLOT(updateValidation()));
+ ui->partialIndexEdit->setVirtualSqlExpression("SELECT %1");
+ updatePartialConditionState();
+ ui->columnsTable->setColumnHidden(1, false);
+ }
+ else
+ {
+ ui->partialIndexCheck->setVisible(false);
+ ui->partialIndexEdit->setVisible(false);
+ ui->columnsTable->setColumnHidden(1, true);
+ }
+
+ readCollations();
+
+ ui->ddlEdit->setSqliteVersion(db->getVersion());
+
+ if (index.isNull())
+ createIndex = SqliteCreateIndexPtr::create();
+ else
+ readIndex();
+
+ originalCreateIndex = SqliteCreateIndexPtr::create(*createIndex);
+
+ ui->nameEdit->setText(index);
+ setTable(createIndex->table);
+
+ if (!index.isNull())
+ applyIndex();
+
+ updateValidation();
+
+ ui->nameEdit->setFocus();
+}
+
+void IndexDialog::readIndex()
+{
+ SchemaResolver resolver(db);
+ SqliteQueryPtr parsedObject = resolver.getParsedObject(index, SchemaResolver::INDEX);
+ if (!parsedObject.dynamicCast<SqliteCreateIndex>())
+ {
+ notifyError(tr("Could not process index %1 correctly. Unable to open an index dialog.").arg(index));
+ reject();
+ return;
+ }
+
+ createIndex = parsedObject.dynamicCast<SqliteCreateIndex>();
+}
+
+void IndexDialog::buildColumns()
+{
+ // Clean up
+ ui->columnsTable->setRowCount(0);
+ columnCheckBoxes.clear();
+ sortComboBoxes.clear();
+ collateComboBoxes.clear();
+
+ totalColumns = tableColumns.size();
+ ui->columnsTable->setRowCount(totalColumns);
+
+ int row = 0;
+ foreach (const QString& column, tableColumns)
+ buildColumn(column, row++);
+}
+
+void IndexDialog::updateTable(const QString& value)
+{
+ table = value;
+
+ SchemaResolver resolver(db);
+ tableColumns = resolver.getTableColumns(table);
+
+ buildColumns();
+}
+
+void IndexDialog::updateValidation()
+{
+ bool tableOk = ui->tableCombo->currentIndex() > 0;
+ bool colSelected = false;
+
+ if (tableOk)
+ {
+ foreach (QCheckBox* cb, columnCheckBoxes)
+ {
+ if (cb->isChecked())
+ {
+ colSelected = true;
+ break;
+ }
+ }
+ }
+
+ bool partialConditionOk = (!ui->partialIndexCheck->isChecked() ||
+ (ui->partialIndexEdit->isSyntaxChecked() && !ui->partialIndexEdit->haveErrors()));
+
+ setValidState(ui->tableCombo, tableOk, tr("Pick the table for the index."));
+ setValidState(ui->columnsTable, colSelected, tr("Select at least one column."));
+ setValidState(ui->partialIndexCheck, partialConditionOk, tr("Enter a valid condition."));
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(colSelected && partialConditionOk);
+}
+
+void IndexDialog::setTable(const QString& value)
+{
+ ui->tableCombo->setCurrentText(value);
+}
+
+void IndexDialog::readCollations()
+{
+ SchemaResolver resolver(db);
+ QStringList collList = resolver.getCollations();
+
+ if (collList.size() > 0)
+ collList.prepend("");
+
+ collations.setStringList(collList);
+}
+
+void IndexDialog::buildColumn(const QString& name, int row)
+{
+ int col = 0;
+
+ QWidget* checkParent = new QWidget();
+ QHBoxLayout* layout = new QHBoxLayout();
+ QMargins margins = layout->contentsMargins();
+ margins.setTop(0);
+ margins.setBottom(0);
+ margins.setLeft(4);
+ margins.setRight(4);
+ layout->setContentsMargins(margins);
+ checkParent->setLayout(layout);
+
+ QCheckBox* check = new QCheckBox(name);
+ checkParent->layout()->addWidget(check);
+
+ ui->columnsTable->setCellWidget(row, col++, checkParent);
+ columnStateSignalMapping->setMapping(check, row);
+ connect(check, SIGNAL(toggled(bool)), columnStateSignalMapping, SLOT(map()));
+ connect(check, SIGNAL(toggled(bool)), this, SLOT(updateValidation()));
+ columnCheckBoxes << check;
+
+ QComboBox* collation = nullptr;
+ if (db->getDialect() == Dialect::Sqlite3)
+ {
+ collation = new QComboBox();
+ collation->setEditable(true);
+ collation->lineEdit()->setPlaceholderText(tr("default", "index dialog"));
+ collation->setModel(&collations);
+ ui->columnsTable->setCellWidget(row, col++, collation);
+ collateComboBoxes << collation;
+ }
+ else
+ {
+ col++;
+ }
+
+ QComboBox* sortOrder = new QComboBox();
+ sortOrder->setToolTip(tr("Sort order", "table constraints"));
+ ui->columnsTable->setCellWidget(row, col++, sortOrder);
+ sortComboBoxes << sortOrder;
+
+ QStringList sortList = {"", sqliteSortOrder(SqliteSortOrder::ASC), sqliteSortOrder(SqliteSortOrder::DESC)};
+ sortOrder->addItems(sortList);
+
+ totalColumns++;
+
+ updateColumnState(row);
+}
+
+void IndexDialog::updateColumnState(int row)
+{
+ bool enabled = columnCheckBoxes[row]->isChecked();
+ sortComboBoxes[row]->setEnabled(enabled);
+ if (db->getDialect() == Dialect::Sqlite3)
+ collateComboBoxes[row]->setEnabled(enabled);
+}
+
+void IndexDialog::updatePartialConditionState()
+{
+ ui->partialIndexEdit->setEnabled(ui->partialIndexCheck->isChecked());
+ updateValidation();
+}
+
+void IndexDialog::updateDdl()
+{
+ rebuildCreateIndex();
+ QString formatted = FORMATTER->format("sql", createIndex->detokenize(), db);
+ ui->ddlEdit->setPlainText(formatted);
+}
+
+void IndexDialog::tabChanged(int tab)
+{
+ if (tab == 1)
+ updateDdl();
+}
+
+void IndexDialog::applyColumnValues()
+{
+ Dialect dialect = db->getDialect();
+ int row;
+ foreach (SqliteIndexedColumn* idxCol, createIndex->indexedColumns)
+ {
+ row = indexOf(tableColumns, idxCol->name, Qt::CaseInsensitive);
+ if (row == -1)
+ {
+ qCritical() << "Cannot find column from index in the table columns! Indexed column:" << idxCol->name
+ << ", table columns:" << tableColumns << ", index name:" << index << ", table name:" << table;
+ continue;
+ }
+
+ columnCheckBoxes[row]->setChecked(true);
+ updateColumnState(row);
+ sortComboBoxes[row]->setCurrentText(sqliteSortOrder(idxCol->sortOrder));
+ if (dialect == Dialect::Sqlite3)
+ collateComboBoxes[row]->setCurrentText(idxCol->collate);
+ }
+}
+
+void IndexDialog::applyIndex()
+{
+ applyColumnValues();
+
+ ui->partialIndexCheck->setChecked(createIndex->where != nullptr);
+ if (createIndex->where)
+ ui->partialIndexEdit->setPlainText(createIndex->where->detokenize());
+}
+
+SqliteIndexedColumn* IndexDialog::addIndexedColumn(const QString& name)
+{
+ SqliteIndexedColumn* idxCol = new SqliteIndexedColumn();
+ idxCol->name = name;
+ idxCol->setParent(createIndex.data());
+ createIndex->indexedColumns << idxCol;
+ return idxCol;
+}
+
+void IndexDialog::rebuildCreateIndex()
+{
+ createIndex = SqliteCreateIndexPtr::create();
+ createIndex->index = ui->nameEdit->text();
+ if (ui->tableCombo->currentIndex() > -1)
+ createIndex->table = ui->tableCombo->currentText();
+
+ createIndex->uniqueKw = ui->uniqueCheck->isChecked();
+
+ SqliteIndexedColumn* idxCol = nullptr;
+ int i = -1;
+ for (const QString& column : tableColumns)
+ {
+ i++;
+
+ if (!columnCheckBoxes[i]->isChecked())
+ continue;
+
+ idxCol = addIndexedColumn(column);
+ if (!collateComboBoxes[i]->currentText().isEmpty())
+ idxCol->collate = collateComboBoxes[i]->currentText();
+
+ if (sortComboBoxes[i]->currentIndex() > 0)
+ idxCol->sortOrder = sqliteSortOrder(sortComboBoxes[i]->currentText());
+ }
+
+ if (ui->partialIndexCheck->isChecked())
+ {
+ if (createIndex->where)
+ delete createIndex->where;
+
+ Parser parser(db->getDialect());
+ SqliteExpr* expr = parser.parseExpr(ui->partialIndexEdit->toPlainText());
+
+ if (expr)
+ {
+ expr->setParent(createIndex.data());
+ createIndex->where = expr;
+ }
+ else
+ {
+ qCritical() << "Could not parse expression from partial index condition: " << ui->partialIndexEdit->toPlainText()
+ << ", the CREATE INDEX statement will be incomplete.";
+ }
+ }
+
+ createIndex->rebuildTokens();
+}
+
+void IndexDialog::queryDuplicates()
+{
+ static QString queryTpl = QStringLiteral("SELECT %1 FROM %2 GROUP BY %3 HAVING %4;\n");
+ static QString countTpl = QStringLiteral("count(%1) AS %2");
+ static QString countColNameTpl = QStringLiteral("count(%1)");
+ static QString countConditionTpl = QStringLiteral("count(%1) > 1");
+
+ Dialect dialect = db->getDialect();
+
+ QStringList cols;
+ QStringList grpCols;
+ QStringList countCols;
+ QString wrappedCol;
+ QString countColName;
+ int i = 0;
+ for (const QString& column : tableColumns)
+ {
+ if (!columnCheckBoxes[i++]->isChecked())
+ continue;
+
+ wrappedCol = wrapObjIfNeeded(column, dialect);
+ cols << wrappedCol;
+ grpCols << wrappedCol;
+ countColName = wrapObjIfNeeded(countColNameTpl.arg(column), dialect);
+ cols << countTpl.arg(wrappedCol, countColName);
+ countCols << countConditionTpl.arg(wrappedCol);
+ }
+
+ EditorWindow* editor = MAINWINDOW->openSqlEditor();
+ editor->setCurrentDb(db);
+
+ QString sqlCols = cols.join(", ");
+ QString sqlGrpCols = grpCols.join(", ");
+ QString sqlCntCols = countCols.join(" AND ");
+ QString sqlTable = wrapObjIfNeeded(ui->tableCombo->currentText(), dialect);
+ editor->setContents(queryTpl.arg(sqlCols, sqlTable, sqlGrpCols, sqlCntCols));
+ editor->execute();
+}
+
+void IndexDialog::accept()
+{
+ rebuildCreateIndex();
+
+ Dialect dialect = db->getDialect();
+
+ QStringList sqls;
+ if (existingIndex)
+ sqls << QString("DROP INDEX %1").arg(wrapObjIfNeeded(originalCreateIndex->index, dialect));
+
+ sqls << createIndex->detokenize();
+
+ if (!CFG_UI.General.DontShowDdlPreview.get())
+ {
+ DdlPreviewDialog dialog(db, this);
+ dialog.setDdl(sqls);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ ChainExecutor executor;
+ executor.setDb(db);
+ executor.setAsync(false);
+ executor.setQueries(sqls);
+ executor.exec();
+
+ if (executor.getSuccessfulExecution())
+ {
+ CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
+
+ QDialog::accept();
+ DBTREE->refreshSchema(db);
+ return;
+ }
+
+ if (executor.getErrors().size() == 1 && executor.getErrors().first().first == SQLITE_CONSTRAINT)
+ {
+ int res = QMessageBox::critical(this,
+ tr("Error", "index dialog"),
+ tr("Cannot create unique index, because values in selected columns are not unique. "
+ "Would you like to execute SELECT query to see problematic values?"),
+ QMessageBox::Yes,
+ QMessageBox::No);
+ if (res == QMessageBox::Yes)
+ {
+ QDialog::reject();
+ queryDuplicates();
+ }
+ }
+ else
+ {
+ QMessageBox::critical(this, tr("Error", "index dialog"), tr("An error occurred while executing SQL statements:\n%1")
+ .arg(executor.getErrorsMessages().join(",\n")), QMessageBox::Ok);
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h
new file mode 100644
index 0000000..1f9d1f8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.h
@@ -0,0 +1,72 @@
+#ifndef INDEXDIALOG_H
+#define INDEXDIALOG_H
+
+#include "db/db.h"
+#include "guiSQLiteStudio_global.h"
+#include "parser/ast/sqlitecreateindex.h"
+#include <QDialog>
+#include <QStringListModel>
+
+namespace Ui {
+ class IndexDialog;
+}
+
+class QGridLayout;
+class QSignalMapper;
+class QCheckBox;
+class QComboBox;
+
+class GUI_API_EXPORT IndexDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ IndexDialog(Db* db, QWidget *parent = 0);
+ IndexDialog(Db* db, const QString& index, QWidget *parent = 0);
+ ~IndexDialog();
+
+ void setTable(const QString& value);
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ void readIndex();
+ void readCollations();
+ void buildColumn(const QString& name, int row);
+ void applyColumnValues();
+ void applyIndex();
+ SqliteIndexedColumn* addIndexedColumn(const QString& name);
+ void rebuildCreateIndex();
+ void queryDuplicates();
+
+ bool existingIndex = false;
+ Db* db = nullptr;
+ QString table;
+ QString index;
+ SqliteCreateIndexPtr createIndex;
+ SqliteCreateIndexPtr originalCreateIndex;
+ QStringList tableColumns;
+ QSignalMapper* columnStateSignalMapping = nullptr;
+ QStringListModel collations;
+ QList<QCheckBox*> columnCheckBoxes;
+ QList<QComboBox*> sortComboBoxes;
+ QList<QComboBox*> collateComboBoxes;
+ int totalColumns = 0;
+ Ui::IndexDialog *ui = nullptr;
+
+ private slots:
+ void updateValidation();
+ void buildColumns();
+ void updateTable(const QString& value);
+ void updateColumnState(int row);
+ void updatePartialConditionState();
+ void updateDdl();
+ void tabChanged(int tab);
+
+ public slots:
+ void accept();
+};
+
+#endif // INDEXDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui
new file mode 100644
index 0000000..e231550
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/indexdialog.ui
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>IndexDialog</class>
+ <widget class="QDialog" name="IndexDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>491</width>
+ <height>410</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Index dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="indexTab">
+ <attribute name="title">
+ <string>Index</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="0">
+ <widget class="QLabel" name="tableLabel">
+ <property name="text">
+ <string>On table:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Index name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="2">
+ <widget class="QCheckBox" name="partialIndexCheck">
+ <property name="text">
+ <string>Partial index condition</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="2">
+ <widget class="SqlEditor" name="partialIndexEdit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QCheckBox" name="uniqueCheck">
+ <property name="text">
+ <string>Unique index</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QTableWidget" name="columnsTable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>2</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <column>
+ <property name="text">
+ <string>Column</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Collation</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Sort</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="tableCombo"/>
+ </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>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>partialIndexCheck</tabstop>
+ <tabstop>partialIndexEdit</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>ddlEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>IndexDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>IndexDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp
new file mode 100644
index 0000000..fb78367
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.cpp
@@ -0,0 +1,97 @@
+#include "messagelistdialog.h"
+#include "iconmanager.h"
+#include "ui_messagelistdialog.h"
+#include <QDebug>
+#include <QLinearGradient>
+
+MessageListDialog::MessageListDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::MessageListDialog)
+{
+ ui->setupUi(this);
+ ui->message->setVisible(false);
+}
+
+MessageListDialog::MessageListDialog(const QString& message, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::MessageListDialog)
+{
+ ui->setupUi(this);
+ ui->buttonBox->clear();
+ ui->buttonBox->addButton(QDialogButtonBox::Yes);
+ ui->buttonBox->addButton(QDialogButtonBox::No);
+ ui->message->setText(message);
+}
+
+MessageListDialog::~MessageListDialog()
+{
+ delete ui;
+}
+
+void MessageListDialog::addMessage(const QString& text, const QBrush& background)
+{
+ addMessage(QIcon(), text, background);
+}
+
+void MessageListDialog::addMessage(const QIcon& icon, const QString& text, const QBrush& background)
+{
+ QListWidgetItem* item = new QListWidgetItem();
+ item->setText(text);
+ item->setBackground(background);
+ item->setIcon(icon);
+ ui->listWidget->addItem(item);
+}
+
+void MessageListDialog::addInfo(const QString& text)
+{
+ addMessage(ICONS.STATUS_INFO, text, getGradient(0, 0, 1, 0.2));
+}
+
+void MessageListDialog::addWarning(const QString& text)
+{
+ addMessage(ICONS.STATUS_WARNING, text, getGradient(0.8, 0.8, 0, 0.4));
+}
+
+void MessageListDialog::addError(const QString& text)
+{
+ addMessage(ICONS.STATUS_ERROR, text, getGradient(0.6, 0, 0, 0.6));
+}
+
+void MessageListDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+QBrush MessageListDialog::getGradient(qreal r, qreal g, qreal b, qreal a) const
+{
+ QLinearGradient gradient(0, 0, 20, 120);
+ gradient.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0));
+ gradient.setColorAt(1, QColor::fromRgbF(r, g, b, a));
+
+ return QBrush(gradient);
+}
+
+void MessageListDialog::showEvent(QShowEvent*)
+{
+ adjustSize();
+}
+
+void MessageListDialog::resizeEvent(QResizeEvent*)
+{
+ QFontMetrics metrics = ui->listWidget->fontMetrics();
+ QRect rect = ui->listWidget->rect();
+ int cnt = ui->listWidget->count();
+ QListWidgetItem* item = nullptr;
+ for (int row = 0; row < cnt; row++)
+ {
+ item = ui->listWidget->item(row);
+ item->setSizeHint(metrics.boundingRect(rect, Qt::TextWordWrap|Qt::TextLongestVariant, item->text()).size() + QSize(0, 10));
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h
new file mode 100644
index 0000000..e20d9f8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.h
@@ -0,0 +1,37 @@
+#ifndef MESSAGELISTDIALOG_H
+#define MESSAGELISTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class MessageListDialog;
+}
+
+class GUI_API_EXPORT MessageListDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit MessageListDialog(QWidget *parent = 0);
+ explicit MessageListDialog(const QString& message, QWidget *parent = 0);
+ ~MessageListDialog();
+
+ void addMessage(const QString& text, const QBrush& background = QBrush());
+ void addMessage(const QIcon& icon, const QString& text, const QBrush& background = QBrush());
+ void addInfo(const QString& text);
+ void addWarning(const QString& text);
+ void addError(const QString& text);
+
+ protected:
+ void changeEvent(QEvent *e);
+ void showEvent(QShowEvent*);
+ void resizeEvent(QResizeEvent*);
+
+ private:
+ QBrush getGradient(qreal r, qreal g, qreal b, qreal a) const;
+
+ Ui::MessageListDialog *ui = nullptr;
+};
+
+#endif // MESSAGELISTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui
new file mode 100644
index 0000000..10ee6b8
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/messagelistdialog.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MessageListDialog</class>
+ <widget class="QDialog" name="MessageListDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="message">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="listWidget">
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>MessageListDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>MessageListDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp
new file mode 100644
index 0000000..36b400b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.cpp
@@ -0,0 +1,275 @@
+#include "newconstraintdialog.h"
+#include "ui_newconstraintdialog.h"
+#include "iconmanager.h"
+#include <QCommandLinkButton>
+
+NewConstraintDialog::NewConstraintDialog(SqliteCreateTable* createTable, Db* db, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::NewConstraintDialog),
+ type(ConstraintDialog::TABLE),
+ db(db),
+ createTable(createTable)
+{
+ ui->setupUi(this);
+ init();
+}
+
+NewConstraintDialog::NewConstraintDialog(SqliteCreateTable::Column* column, Db* db, QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::NewConstraintDialog),
+ type(ConstraintDialog::COLUMN),
+ db(db),
+ columnStmt(column)
+{
+ ui->setupUi(this);
+ createTable = dynamic_cast<SqliteCreateTable*>(column->parent());
+ init();
+}
+
+NewConstraintDialog::NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable* createTable, Db* db, QWidget* parent) :
+ NewConstraintDialog(createTable, db, parent)
+{
+ predefinedConstraintType = constraintType;
+}
+
+NewConstraintDialog::NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable::Column* column, Db* db, QWidget* parent) :
+ NewConstraintDialog(column, db, parent)
+{
+ predefinedConstraintType = constraintType;
+}
+
+NewConstraintDialog::~NewConstraintDialog()
+{
+ delete ui;
+ if (constraintDialog)
+ delete constraintDialog;
+}
+
+SqliteStatement* NewConstraintDialog::getConstraint()
+{
+ return constrStatement;
+}
+
+void NewConstraintDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void NewConstraintDialog::init()
+{
+ switch (type)
+ {
+ case ConstraintDialog::TABLE:
+ {
+ initTable();
+ break;
+ }
+ case ConstraintDialog::COLUMN:
+ {
+ initColumn();
+ break;
+ }
+ }
+ adjustSize();
+ setMaximumSize(size());
+ setMinimumSize(size());
+}
+
+void NewConstraintDialog::initTable()
+{
+ addButton(ICONS.CONSTRAINT_PRIMARY_KEY, tr("Primary Key", "new constraint dialog"), SLOT(createTablePk()));
+ if (createTable->dialect == Dialect::Sqlite3)
+ addButton(ICONS.CONSTRAINT_FOREIGN_KEY, tr("Foreign Key", "new constraint dialog"), SLOT(createTableFk()));
+
+ addButton(ICONS.CONSTRAINT_UNIQUE, tr("Unique", "new constraint dialog"), SLOT(createTableUnique()));
+ addButton(ICONS.CONSTRAINT_CHECK, tr("Check", "new constraint dialog"), SLOT(createTableCheck()));
+}
+
+void NewConstraintDialog::initColumn()
+{
+ addButton(ICONS.CONSTRAINT_PRIMARY_KEY, tr("Primary Key", "new constraint dialog"), SLOT(createColumnPk()));
+ if (createTable->dialect == Dialect::Sqlite3)
+ addButton(ICONS.CONSTRAINT_FOREIGN_KEY, tr("Foreign Key", "new constraint dialog"), SLOT(createColumnFk()));
+
+ addButton(ICONS.CONSTRAINT_UNIQUE, tr("Unique", "new constraint dialog"), SLOT(createColumnUnique()));
+ addButton(ICONS.CONSTRAINT_CHECK, tr("Check", "new constraint dialog"), SLOT(createColumnCheck()));
+ addButton(ICONS.CONSTRAINT_NOT_NULL, tr("Not NULL", "new constraint dialog"), SLOT(createColumnNotNull()));
+ if (createTable->dialect == Dialect::Sqlite3)
+ addButton(ICONS.CONSTRAINT_COLLATION, tr("Collate", "new constraint dialog"), SLOT(createColumnCollate()));
+
+ addButton(ICONS.CONSTRAINT_DEFAULT, tr("Default", "new constraint dialog"), SLOT(createColumnDefault()));
+}
+
+void NewConstraintDialog::addButton(const Icon& icon, const QString text, const char* slot)
+{
+ QCommandLinkButton* btn = new QCommandLinkButton();
+ btn->setIcon(icon);
+ btn->setText(text);
+ connect(btn, SIGNAL(clicked()), this, slot);
+ ui->container->layout()->addWidget(btn);
+}
+
+int NewConstraintDialog::createColumnConstraint(ConstraintDialog::Constraint constraintType)
+{
+ SqliteCreateTable::Column::Constraint* constraint = new SqliteCreateTable::Column::Constraint();
+ switch (constraintType)
+ {
+ case ConstraintDialog::PK:
+ constraint->type = SqliteCreateTable::Column::Constraint::PRIMARY_KEY;
+ break;
+ case ConstraintDialog::FK:
+ constraint->type = SqliteCreateTable::Column::Constraint::FOREIGN_KEY;
+ break;
+ case ConstraintDialog::UNIQUE:
+ constraint->type = SqliteCreateTable::Column::Constraint::UNIQUE;
+ break;
+ case ConstraintDialog::NOTNULL:
+ constraint->type = SqliteCreateTable::Column::Constraint::NOT_NULL;
+ break;
+ case ConstraintDialog::CHECK:
+ constraint->type = SqliteCreateTable::Column::Constraint::CHECK;
+ break;
+ case ConstraintDialog::COLLATE:
+ constraint->type = SqliteCreateTable::Column::Constraint::COLLATE;
+ break;
+ case ConstraintDialog::DEFAULT:
+ constraint->type = SqliteCreateTable::Column::Constraint::DEFAULT;
+ break;
+ case ConstraintDialog::UNKNOWN:
+ break;
+ }
+
+ constrStatement = constraint;
+ constrStatement->setParent(columnStmt);
+
+ return editConstraint();
+}
+
+int NewConstraintDialog::createTableConstraint(ConstraintDialog::Constraint constraintType)
+{
+ SqliteCreateTable::Constraint* constraint = new SqliteCreateTable::Constraint();
+ switch (constraintType)
+ {
+ case ConstraintDialog::PK:
+ constraint->type = SqliteCreateTable::Constraint::PRIMARY_KEY;
+ break;
+ case ConstraintDialog::FK:
+ constraint->type = SqliteCreateTable::Constraint::FOREIGN_KEY;
+ break;
+ case ConstraintDialog::UNIQUE:
+ constraint->type = SqliteCreateTable::Constraint::UNIQUE;
+ break;
+ case ConstraintDialog::CHECK:
+ constraint->type = SqliteCreateTable::Constraint::CHECK;
+ break;
+ case ConstraintDialog::NOTNULL:
+ case ConstraintDialog::COLLATE:
+ case ConstraintDialog::DEFAULT:
+ case ConstraintDialog::UNKNOWN:
+ break;
+ }
+
+ constrStatement = constraint;
+ constrStatement->setParent(createTable);
+
+ return editConstraint();
+}
+
+int NewConstraintDialog::editConstraint()
+{
+ switch (type)
+ {
+ case ConstraintDialog::TABLE:
+ constraintDialog = new ConstraintDialog(ConstraintDialog::NEW, dynamic_cast<SqliteCreateTable::Constraint*>(constrStatement),
+ createTable.data(), db, parentWidget());
+ break;
+ case ConstraintDialog::COLUMN:
+ constraintDialog = new ConstraintDialog(ConstraintDialog::NEW, dynamic_cast<SqliteCreateTable::Column::Constraint*>(constrStatement),
+ columnStmt.data(), db, parentWidget());
+ break;
+ }
+
+ connect(constraintDialog, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(constraintDialog, SIGNAL(accepted()), this, SLOT(accept()));
+
+ hide();
+ return constraintDialog->exec();
+}
+
+void NewConstraintDialog::createTablePk()
+{
+ createTableConstraint(ConstraintDialog::PK);
+}
+
+void NewConstraintDialog::createTableFk()
+{
+ createTableConstraint(ConstraintDialog::FK);
+}
+
+void NewConstraintDialog::createTableUnique()
+{
+ createTableConstraint(ConstraintDialog::UNIQUE);
+}
+
+void NewConstraintDialog::createTableCheck()
+{
+ createTableConstraint(ConstraintDialog::CHECK);
+}
+
+void NewConstraintDialog::createColumnPk()
+{
+ createColumnConstraint(ConstraintDialog::PK);
+}
+
+void NewConstraintDialog::createColumnFk()
+{
+ createColumnConstraint(ConstraintDialog::FK);
+}
+
+void NewConstraintDialog::createColumnUnique()
+{
+ createColumnConstraint(ConstraintDialog::UNIQUE);
+}
+
+void NewConstraintDialog::createColumnCheck()
+{
+ createColumnConstraint(ConstraintDialog::CHECK);
+}
+
+void NewConstraintDialog::createColumnNotNull()
+{
+ createColumnConstraint(ConstraintDialog::NOTNULL);
+}
+
+void NewConstraintDialog::createColumnDefault()
+{
+ createColumnConstraint(ConstraintDialog::DEFAULT);
+}
+
+void NewConstraintDialog::createColumnCollate()
+{
+ createColumnConstraint(ConstraintDialog::COLLATE);
+}
+
+int NewConstraintDialog::exec()
+{
+ if (predefinedConstraintType == ConstraintDialog::UNKNOWN)
+ return QDialog::exec();
+
+ switch (type)
+ {
+ case ConstraintDialog::TABLE:
+ return createTableConstraint(predefinedConstraintType);
+ case ConstraintDialog::COLUMN:
+ return createColumnConstraint(predefinedConstraintType);
+ }
+
+ return QDialog::Rejected;
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h
new file mode 100644
index 0000000..e374312
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.h
@@ -0,0 +1,65 @@
+#ifndef NEWCONSTRAINTDIALOG_H
+#define NEWCONSTRAINTDIALOG_H
+
+#include "parser/ast/sqlitecreatetable.h"
+#include "db/db.h"
+#include "dialogs/constraintdialog.h"
+#include "iconmanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+#include <QPointer>
+
+namespace Ui {
+ class NewConstraintDialog;
+}
+
+class GUI_API_EXPORT NewConstraintDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit NewConstraintDialog(SqliteCreateTable* createTable, Db* db, QWidget *parent = 0);
+ explicit NewConstraintDialog(SqliteCreateTable::Column* column, Db* db, QWidget *parent = 0);
+ explicit NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable* createTable, Db* db, QWidget *parent = 0);
+ explicit NewConstraintDialog(ConstraintDialog::Constraint constraintType, SqliteCreateTable::Column* column, Db* db, QWidget *parent = 0);
+ ~NewConstraintDialog();
+
+ SqliteStatement* getConstraint();
+ int exec();
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ void initTable();
+ void initColumn();
+ void addButton(const Icon& icon, const QString text, const char* slot);
+ int createColumnConstraint(ConstraintDialog::Constraint constraintType);
+ int createTableConstraint(ConstraintDialog::Constraint constraintType);
+ int editConstraint();
+
+ Ui::NewConstraintDialog *ui = nullptr;
+ ConstraintDialog::Type type;
+ Db* db = nullptr;
+ ConstraintDialog::Constraint predefinedConstraintType = ConstraintDialog::UNKNOWN;
+ SqliteStatement* constrStatement = nullptr;
+ QPointer<SqliteCreateTable> createTable;
+ QPointer<SqliteCreateTable::Column> columnStmt;
+ ConstraintDialog* constraintDialog = nullptr;
+
+ private slots:
+ void createTablePk();
+ void createTableFk();
+ void createTableUnique();
+ void createTableCheck();
+ void createColumnPk();
+ void createColumnFk();
+ void createColumnUnique();
+ void createColumnCheck();
+ void createColumnNotNull();
+ void createColumnDefault();
+ void createColumnCollate();
+};
+
+#endif // NEWCONSTRAINTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui
new file mode 100644
index 0000000..20fc5ce
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newconstraintdialog.ui
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NewConstraintDialog</class>
+ <widget class="QDialog" name="NewConstraintDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>New constraint</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="container" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2"/>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>NewConstraintDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>NewConstraintDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp
new file mode 100644
index 0000000..1c73de0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.cpp
@@ -0,0 +1,68 @@
+#include "newversiondialog.h"
+#include "services/pluginmanager.h"
+#include "sqlitestudio.h"
+#include "ui_newversiondialog.h"
+#include "services/config.h"
+#include <QInputDialog>
+
+NewVersionDialog::NewVersionDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::NewVersionDialog)
+{
+ init();
+}
+
+NewVersionDialog::~NewVersionDialog()
+{
+ delete ui;
+}
+
+void NewVersionDialog::setUpdates(const QList<UpdateManager::UpdateEntry>& updates)
+{
+ QTableWidgetItem* item = nullptr;
+ QString currVersion;
+ int row = 0;
+ ui->updateList->setRowCount(updates.size());
+ for (const UpdateManager::UpdateEntry& entry : updates)
+ {
+ if (entry.compontent == "SQLiteStudio")
+ currVersion = SQLITESTUDIO->getVersionString();
+ else
+ currVersion = PLUGINS->getPrintableVersion(entry.compontent);
+
+ item = new QTableWidgetItem(entry.compontent);
+ ui->updateList->setItem(row, 0, item);
+
+ item = new QTableWidgetItem(currVersion);
+ ui->updateList->setItem(row, 1, item);
+
+ item = new QTableWidgetItem(entry.version);
+ ui->updateList->setItem(row, 2, item);
+
+ row++;
+ }
+ ui->updateList->resizeColumnsToContents();
+}
+
+void NewVersionDialog::init()
+{
+ ui->setupUi(this);
+
+ connect(ui->abortButton, SIGNAL(clicked()), this, SLOT(reject()));
+ connect(ui->updateButton, SIGNAL(clicked()), this, SLOT(installUpdates()));
+ connect(ui->checkOnStartupCheck, &QCheckBox::clicked, [=](bool checked)
+ {
+ CFG_CORE.General.CheckUpdatesOnStartup.set(checked);
+ });
+}
+
+void NewVersionDialog::installUpdates()
+{
+ UPDATES->update();
+ close();
+}
+
+void NewVersionDialog::showEvent(QShowEvent*)
+{
+ ui->checkOnStartupCheck->setChecked(CFG_CORE.General.CheckUpdatesOnStartup.get());
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h
new file mode 100644
index 0000000..784c6cf
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.h
@@ -0,0 +1,34 @@
+#ifndef NEWVERSIONDIALOG_H
+#define NEWVERSIONDIALOG_H
+
+#include "services/updatemanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class NewVersionDialog;
+}
+
+class GUI_API_EXPORT NewVersionDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit NewVersionDialog(QWidget *parent = 0);
+ ~NewVersionDialog();
+
+ void setUpdates(const QList<UpdateManager::UpdateEntry>& updates);
+
+ protected:
+ void showEvent(QShowEvent*);
+
+ private:
+ void init();
+
+ Ui::NewVersionDialog *ui = nullptr;
+
+ private slots:
+ void installUpdates();
+};
+
+#endif // NEWVERSIONDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui
new file mode 100644
index 0000000..6f50e7f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/newversiondialog.ui
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NewVersionDialog</class>
+ <widget class="QDialog" name="NewVersionDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>307</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>SQLiteStudio updates</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="topLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>New updates are available!</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTableWidget" name="updateList">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="verticalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="verticalHeaderDefaultSectionSize">
+ <number>20</number>
+ </attribute>
+ <attribute name="verticalHeaderMinimumSectionSize">
+ <number>20</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Component</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Current version</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Update version</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="checkOnStartupWidget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QCheckBox" name="checkOnStartupCheck">
+ <property name="text">
+ <string>Check for updates on startup</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCommandLinkButton" name="updateButton">
+ <property name="text">
+ <string>Update to new version!</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/get_update.png</normaloff>:/icons/img/get_update.png</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="description">
+ <string>The update will be automatically downloaded and installed. This will also restart application at the end.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCommandLinkButton" name="abortButton">
+ <property name="text">
+ <string>Not now.</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/abort24.png</normaloff>:/icons/img/abort24.png</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="description">
+ <string>Don't install the update and close this window.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp
new file mode 100644
index 0000000..5dc506f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.cpp
@@ -0,0 +1,119 @@
+#include "populateconfigdialog.h"
+#include "ui_populateconfigdialog.h"
+#include "plugins/populateplugin.h"
+#include "services/populatemanager.h"
+#include "sqlitestudio.h"
+#include "formmanager.h"
+#include "configmapper.h"
+#include "uiutils.h"
+#include <QPushButton>
+#include <QDebug>
+#include <QSpinBox>
+
+PopulateConfigDialog::PopulateConfigDialog(PopulateEngine* engine, const QString& column, const QString& pluginName, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::PopulateConfigDialog),
+ engine(engine),
+ column(column),
+ pluginName(pluginName)
+{
+ init();
+}
+
+PopulateConfigDialog::~PopulateConfigDialog()
+{
+ safe_delete(configMapper);
+ delete ui;
+}
+
+int PopulateConfigDialog::exec()
+{
+ QString formName = engine->getPopulateConfigFormName();
+ if (formName.isNull())
+ {
+ qCritical() << "Null form name from populating engine.";
+ return QDialog::Rejected;
+ }
+
+ innerWidget = FORMS->createWidget(formName);
+ if (!innerWidget)
+ return QDialog::Rejected;
+
+ configMapper->bindToConfig(innerWidget);
+ ui->contents->layout()->addWidget(innerWidget);
+ adjustSize();
+ validateEngine();
+ return QDialog::exec();
+}
+
+void PopulateConfigDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+
+ QString headerString = tr("Configuring <b>%1</b> for column <b>%2</b>").arg(pluginName, column);
+ ui->headerLabel->setText(headerString );
+
+ configMapper = new ConfigMapper(engine->getConfig());
+ connect(configMapper, SIGNAL(modified()), this, SLOT(validateEngine()));
+
+ connect(POPULATE_MANAGER, SIGNAL(validationResultFromPlugin(bool,CfgEntry*,QString)), this, SLOT(validationResultFromPlugin(bool,CfgEntry*,QString)));
+ connect(POPULATE_MANAGER, SIGNAL(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)), this, SLOT(stateUpdateRequestFromPlugin(CfgEntry*,bool,bool)));
+ connect(POPULATE_MANAGER, SIGNAL(widgetPropertyFromPlugin(CfgEntry*,QString,QVariant)), this, SLOT(widgetPropertyFromPlugin(CfgEntry*,QString,QVariant)));
+}
+
+void PopulateConfigDialog::validateEngine()
+{
+ engine->validateOptions();
+}
+
+void PopulateConfigDialog::validationResultFromPlugin(bool valid, CfgEntry* key, const QString& msg)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (w)
+ setValidState(w, valid, msg);
+
+ if (valid == pluginConfigOk.contains(key)) // if state changed
+ {
+ if (!valid)
+ pluginConfigOk[key] = false;
+ else
+ pluginConfigOk.remove(key);
+ }
+ updateState();
+}
+
+void PopulateConfigDialog::stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (!w)
+ return;
+
+ w->setVisible(visible);
+ w->setEnabled(enabled);
+}
+
+
+void PopulateConfigDialog::widgetPropertyFromPlugin(CfgEntry* key, const QString& propName, const QVariant& value)
+{
+ QWidget* w = configMapper->getBindWidgetForConfig(key);
+ if (!w)
+ return;
+
+ w->setProperty(propName.toLatin1().constData(), value);
+}
+
+void PopulateConfigDialog::updateState()
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(pluginConfigOk.size() == 0);
+}
+
+
+void PopulateConfigDialog::showEvent(QShowEvent* e)
+{
+ QVariant prop = innerWidget->property("initialSize");
+ if (prop.isValid())
+ resize(prop.toSize() + QSize(0, ui->headerLabel->height() + ui->line->height()));
+
+ QDialog::showEvent(e);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h
new file mode 100644
index 0000000..45bc333
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.h
@@ -0,0 +1,47 @@
+#ifndef POPULATECONFIGDIALOG_H
+#define POPULATECONFIGDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+class PopulateEngine;
+class ConfigMapper;
+class CfgEntry;
+
+namespace Ui {
+ class PopulateConfigDialog;
+}
+
+class GUI_API_EXPORT PopulateConfigDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit PopulateConfigDialog(PopulateEngine* engine, const QString& column, const QString& pluginName, QWidget *parent = 0);
+ ~PopulateConfigDialog();
+
+ int exec();
+
+ protected:
+ void showEvent(QShowEvent* e);
+
+ private:
+ void init();
+
+ Ui::PopulateConfigDialog *ui = nullptr;
+ PopulateEngine* engine = nullptr;
+ ConfigMapper* configMapper = nullptr;
+ QHash<CfgEntry*,bool> pluginConfigOk;
+ QString column;
+ QString pluginName;
+ QWidget* innerWidget = nullptr;
+
+ private slots:
+ void validateEngine();
+ void validationResultFromPlugin(bool valid, CfgEntry* key, const QString& msg);
+ void stateUpdateRequestFromPlugin(CfgEntry* key, bool visible, bool enabled);
+ void widgetPropertyFromPlugin(CfgEntry* key, const QString& propName, const QVariant& value);
+ void updateState();
+};
+
+#endif // POPULATECONFIGDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui
new file mode 100644
index 0000000..ac90f02
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populateconfigdialog.ui
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateConfigDialog</class>
+ <widget class="QDialog" name="PopulateConfigDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Populating configuration</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="headerLabel">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="contents" native="true">
+ <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>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PopulateConfigDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PopulateConfigDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp
new file mode 100644
index 0000000..ca3fd31
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.cpp
@@ -0,0 +1,332 @@
+#include "populatedialog.h"
+#include "ui_populatedialog.h"
+#include "dblistmodel.h"
+#include "dbobjlistmodel.h"
+#include "services/dbmanager.h"
+#include "schemaresolver.h"
+#include "services/pluginmanager.h"
+#include "plugins/populateplugin.h"
+#include "populateconfigdialog.h"
+#include "uiutils.h"
+#include "services/populatemanager.h"
+#include "common/widgetcover.h"
+#include <QPushButton>
+#include <QGridLayout>
+#include <QCheckBox>
+#include <QToolButton>
+#include <QDebug>
+#include <QSignalMapper>
+
+PopulateDialog::PopulateDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::PopulateDialog)
+{
+ init();
+}
+
+PopulateDialog::~PopulateDialog()
+{
+ delete ui;
+}
+
+void PopulateDialog::setDbAndTable(Db* db, const QString& table)
+{
+ ui->databaseCombo->setCurrentText(db->getName());
+ ui->tableCombo->setCurrentText(table);
+}
+
+void PopulateDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Populate", "populate dialog button"));
+
+ plugins = PLUGINS->getLoadedPlugins<PopulatePlugin>();
+ qSort(plugins.begin(), plugins.end(), [](PopulatePlugin* p1, PopulatePlugin* p2) -> bool
+ {
+ return p1->getTitle().compare(p2->getTitle()) < 0;
+ });
+
+ for (PopulatePlugin* plugin : plugins)
+ pluginTitles << plugin->getTitle();
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->setVisible(false);
+
+ ui->scrollArea->setAutoFillBackground(false);
+ ui->scrollArea->viewport()->setAutoFillBackground(false);
+ ui->columnsWidget->setAutoFillBackground(false);
+
+ dbListModel = new DbListModel(this);
+ dbListModel->setCombo(ui->databaseCombo);
+ dbListModel->setSortMode(DbListModel::SortMode::Alphabetical);
+ ui->databaseCombo->setModel(dbListModel);
+
+ tablesModel = new DbObjListModel(this);
+ tablesModel->setIncludeSystemObjects(false);
+ tablesModel->setType(DbObjListModel::ObjectType::TABLE);
+ ui->tableCombo->setModel(tablesModel);
+ refreshTables();
+
+ connect(ui->databaseCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshTables()));
+ connect(ui->tableCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(refreshColumns()));
+ connect(POPULATE_MANAGER, SIGNAL(populatingFinished()), widgetCover, SLOT(hide()));
+ connect(POPULATE_MANAGER, SIGNAL(populatingSuccessful()), this, SLOT(finished()));
+}
+
+PopulateEngine* PopulateDialog::getEngine(int selectedPluginIndex)
+{
+ if (selectedPluginIndex < 0 || selectedPluginIndex >= plugins.size())
+ {
+ qCritical() << "Selected populate plugin out of range!";
+ return nullptr;
+ }
+
+ return plugins[selectedPluginIndex]->createEngine();
+}
+
+void PopulateDialog::deleteEngines(const QList<PopulateEngine*>& engines)
+{
+ for (PopulateEngine* engine : engines)
+ delete engine;
+}
+
+void PopulateDialog::rebuildEngines()
+{
+ int row = 0;
+ for (const ColumnEntry& entry : columnEntries)
+ {
+ pluginSelected(entry.combo, entry.combo->currentIndex());
+ updateColumnState(row++, false);
+ }
+}
+
+void PopulateDialog::refreshTables()
+{
+ db = DBLIST->getByName(ui->databaseCombo->currentText());
+ if (db)
+ tablesModel->setDb(db);
+
+ updateState();
+}
+
+void PopulateDialog::refreshColumns()
+{
+ for (const ColumnEntry& entry : columnEntries)
+ {
+ delete entry.check;
+ delete entry.combo;
+ delete entry.button;
+ }
+ columnEntries.clear();
+ safe_delete(buttonMapper);
+ safe_delete(checkMapper);
+
+ delete ui->columnsLayout;
+ ui->columnsLayout = new QGridLayout();
+ ui->columnsWidget->setLayout(ui->columnsLayout);
+
+ if (!db)
+ {
+ qCritical() << "No Db while refreshing columns in PopulateDialog!";
+ return;
+ }
+
+ buttonMapper = new QSignalMapper(this);
+ connect(buttonMapper, SIGNAL(mapped(int)), this, SLOT(configurePlugin(int)));
+
+ checkMapper = new QSignalMapper(this);
+ connect(checkMapper, SIGNAL(mapped(int)), this, SLOT(updateColumnState(int)));
+
+ SchemaResolver resolver(db);
+ QStringList columns = resolver.getTableColumns(ui->tableCombo->currentText());
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ QToolButton* btn = nullptr;
+ int row = 0;
+ for (const QString& column : columns)
+ {
+ check = new QCheckBox(column);
+ connect(check, SIGNAL(toggled(bool)), checkMapper, SLOT(map()));
+ checkMapper->setMapping(check, row);
+
+ combo = new QComboBox();
+ combo->addItems(pluginTitles);
+ connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(pluginSelected(int)));
+
+ btn = new QToolButton();
+ btn->setText(tr("Configure"));
+ connect(btn, SIGNAL(clicked()), buttonMapper, SLOT(map()));
+ buttonMapper->setMapping(btn, row);
+
+ ui->columnsLayout->addWidget(check, row, 0);
+ ui->columnsLayout->addWidget(combo, row, 1);
+ ui->columnsLayout->addWidget(btn, row, 2);
+ columnEntries << ColumnEntry(check, combo, btn);
+ row++;
+ }
+
+ rebuildEngines();
+
+ QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
+ ui->columnsLayout->addItem(spacer, row, 0, 1, 3);
+
+ updateState();
+}
+
+void PopulateDialog::pluginSelected(int index)
+{
+ QComboBox* cb = dynamic_cast<QComboBox*>(sender());
+ pluginSelected(cb, index);
+}
+
+void PopulateDialog::pluginSelected(QComboBox* combo, int index)
+{
+ if (!combo)
+ return;
+
+ ColumnEntry* entry = nullptr;
+
+ int columnIndex = 0;
+ for (ColumnEntry& e : columnEntries)
+ {
+ if (e.combo == combo)
+ {
+ entry = &e;
+ break;
+ }
+ columnIndex++;
+ }
+
+ if (!entry)
+ return;
+
+ safe_delete(entry->engine);
+
+ if (index < 0 || index >= plugins.size())
+ return;
+
+ entry->engine = plugins[index]->createEngine();
+ updateColumnState(columnIndex);
+}
+
+void PopulateDialog::configurePlugin(int index)
+{
+ if (index < 0 || index >= columnEntries.size())
+ {
+ qCritical() << "Plugin configure index out of range:" << index << "," << columnEntries.size();
+ return;
+ }
+
+ PopulateEngine* engine = columnEntries[index].engine;
+ if (!engine->getConfig())
+ {
+ qWarning() << "Called config on populate plugin, but it has no CfgMain.";
+ return;
+ }
+
+ engine->getConfig()->savepoint();
+
+ PopulateConfigDialog dialog(engine, columnEntries[index].check->text(), columnEntries[index].combo->currentText(), this);
+ if (dialog.exec() != QDialog::Accepted)
+ engine->getConfig()->restore();
+
+ engine->getConfig()->release();
+
+ updateColumnState(index);
+}
+
+void PopulateDialog::updateColumnState(int index, bool updateGlobalState)
+{
+ if (index < 0 || index >= columnEntries.size())
+ {
+ qCritical() << "Column update called but index out of range:" << index << "," << columnEntries.size();
+ return;
+ }
+
+ bool checked = columnEntries[index].check->isChecked();
+ bool hasConfig = columnEntries[index].engine->getConfig() != nullptr;
+ columnEntries[index].combo->setEnabled(checked);
+ columnEntries[index].button->setEnabled(checked && hasConfig);
+
+ bool valid = true;
+ if (checked && hasConfig)
+ {
+ valid = columnEntries[index].engine->validateOptions();
+ setValidState(columnEntries[index].button, valid, tr("Populating configuration for this column is invalid or incomplete."));
+ }
+
+ if (valid == columnsValid.contains(index)) // if state changed
+ {
+ if (!valid)
+ columnsValid[index] = false;
+ else
+ columnsValid.remove(index);
+ }
+
+ if (updateGlobalState)
+ updateState();
+}
+
+void PopulateDialog::updateState()
+{
+ bool columnsOk = columnsValid.size() == 0;
+ bool dbOk = !ui->databaseCombo->currentText().isNull();
+ bool tableOk = !ui->tableCombo->currentText().isNull();
+
+ bool colCountOk = false;
+ for (const ColumnEntry& entry : columnEntries)
+ {
+ if (entry.check->isChecked())
+ {
+ colCountOk = true;
+ break;
+ }
+ }
+
+ setValidState(ui->databaseCombo, dbOk, tr("Select database with table to populate"));
+ setValidState(ui->tableCombo, tableOk, tr("Select table to populate"));
+ setValidState(ui->columnsGroup, (!tableOk || colCountOk), tr("You have to select at least one column."));
+
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(columnsOk && tableOk && colCountOk);
+}
+
+void PopulateDialog::finished()
+{
+ QDialog::accept();
+}
+
+void PopulateDialog::accept()
+{
+ if (!db)
+ return;
+
+ QHash<QString,PopulateEngine*> engines;
+ for (ColumnEntry& entry : columnEntries)
+ {
+ if (!entry.check->isChecked())
+ continue;
+
+ if (!entry.engine)
+ return;
+
+ engines[entry.check->text()] = entry.engine;
+// entry.engine = nullptr; // to avoid deleting it in the entry's destructor - worker will delete it after it's done
+ }
+
+ QString table = ui->tableCombo->currentText();
+ qint64 rows = ui->rowsSpin->value();
+
+ widgetCover->show();
+ POPULATE_MANAGER->populate(db, table, engines, rows);
+}
+
+PopulateDialog::ColumnEntry::ColumnEntry(QCheckBox* check, QComboBox* combo, QToolButton* button) :
+ check(check), combo(combo), button(button)
+{
+}
+
+PopulateDialog::ColumnEntry::~ColumnEntry()
+{
+ safe_delete(engine);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h
new file mode 100644
index 0000000..0ecc318
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.h
@@ -0,0 +1,76 @@
+#ifndef POPULATEDIALOG_H
+#define POPULATEDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class PopulateDialog;
+}
+
+class PopulatePlugin;
+class PopulateEngine;
+class QGridLayout;
+class DbListModel;
+class DbObjListModel;
+class Db;
+class QComboBox;
+class QCheckBox;
+class QToolButton;
+class QSignalMapper;
+class WidgetCover;
+
+class GUI_API_EXPORT PopulateDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit PopulateDialog(QWidget *parent = 0);
+ ~PopulateDialog();
+ void setDbAndTable(Db* db, const QString& table);
+
+ private:
+ struct GUI_API_EXPORT ColumnEntry
+ {
+ ColumnEntry(QCheckBox* check, QComboBox* combo, QToolButton* button);
+ ~ColumnEntry();
+
+ QCheckBox* check = nullptr;
+ QComboBox* combo = nullptr;
+ QToolButton* button = nullptr;
+ PopulateEngine* engine = nullptr;
+ };
+
+ void init();
+ PopulateEngine* getEngine(int selectedPluginIndex);
+ void deleteEngines(const QList<PopulateEngine*>& engines);
+ void rebuildEngines();
+
+ Ui::PopulateDialog *ui = nullptr;
+ QGridLayout* columnsGrid = nullptr;
+ DbListModel* dbListModel = nullptr;
+ DbObjListModel* tablesModel = nullptr;
+ Db* db = nullptr;
+ QStringList pluginTitles;
+ QList<PopulatePlugin*> plugins;
+ QList<ColumnEntry> columnEntries;
+ QSignalMapper* checkMapper = nullptr;
+ QSignalMapper* buttonMapper = nullptr;
+ QHash<int,bool> columnsValid;
+ WidgetCover* widgetCover = nullptr;
+
+ private slots:
+ void refreshTables();
+ void refreshColumns();
+ void pluginSelected(int index);
+ void pluginSelected(QComboBox* combo, int index);
+ void configurePlugin(int index);
+ void updateColumnState(int index, bool updateGlobalState = true);
+ void updateState();
+ void finished();
+
+ public:
+ void accept();
+};
+
+#endif // POPULATEDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui
new file mode 100644
index 0000000..811b185
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/populatedialog.ui
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PopulateDialog</class>
+ <widget class="QDialog" name="PopulateDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>447</width>
+ <height>358</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Populate table</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="databaseGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Database</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QComboBox" name="databaseCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QGroupBox" name="tableGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Table</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QComboBox" name="tableCombo"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QGroupBox" name="columnsGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Columns</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="columnsWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>409</width>
+ <height>144</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="columnsLayout"/>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QGroupBox" name="rowsGroup">
+ <property name="title">
+ <string>Number of rows to populate:</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QSpinBox" name="rowsSpin">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PopulateDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PopulateDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp
new file mode 100644
index 0000000..a55464d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.cpp
@@ -0,0 +1,42 @@
+#include "quitconfirmdialog.h"
+#include "ui_quitconfirmdialog.h"
+
+QuitConfirmDialog::QuitConfirmDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::QuitConfirmDialog)
+{
+ init();
+}
+
+QuitConfirmDialog::~QuitConfirmDialog()
+{
+ delete ui;
+}
+
+void QuitConfirmDialog::addMessage(const QString& msg)
+{
+ ui->itemList->addItem(msg);
+}
+
+void QuitConfirmDialog::setMessages(const QStringList& messages)
+{
+ for (const QString& msg : messages)
+ addMessage(msg);
+}
+
+int QuitConfirmDialog::getMessageCount() const
+{
+ return ui->itemList->count();
+}
+
+void QuitConfirmDialog::init()
+{
+ ui->setupUi(this);
+
+ QStyle* style = QApplication::style();
+ int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize);
+ QIcon icon = style->standardIcon(QStyle::SP_MessageBoxQuestion);
+
+ if (!icon.isNull())
+ ui->iconLabel->setPixmap(icon.pixmap(iconSize, iconSize));
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h
new file mode 100644
index 0000000..0907c9b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.h
@@ -0,0 +1,28 @@
+#ifndef QUITCONFIRMDIALOG_H
+#define QUITCONFIRMDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+ class QuitConfirmDialog;
+}
+
+class QuitConfirmDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit QuitConfirmDialog(QWidget *parent = 0);
+ ~QuitConfirmDialog();
+
+ void addMessage(const QString& msg);
+ void setMessages(const QStringList& messages);
+ int getMessageCount() const;
+
+ private:
+ void init();
+
+ Ui::QuitConfirmDialog *ui = nullptr;
+};
+
+#endif // QUITCONFIRMDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui
new file mode 100644
index 0000000..6f97934
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/quitconfirmdialog.ui
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QuitConfirmDialog</class>
+ <widget class="QDialog" name="QuitConfirmDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>489</width>
+ <height>186</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Uncommited changes</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLabel" name="msgLabel">
+ <property name="text">
+ <string>Are you sure you want to quit the application?
+
+Following items are pending:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::No|QDialogButtonBox::Yes</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" rowspan="2">
+ <widget class="QLabel" name="iconLabel">
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QListWidget" name="itemList"/>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QuitConfirmDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>QuitConfirmDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp
new file mode 100644
index 0000000..87a6d88
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.cpp
@@ -0,0 +1,75 @@
+#include "searchtextdialog.h"
+#include "ui_searchtextdialog.h"
+#include "searchtextlocator.h"
+#include "common/unused.h"
+
+SearchTextDialog::SearchTextDialog(SearchTextLocator* textLocator, QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::SearchTextDialog), textLocator(textLocator)
+{
+ ui->setupUi(this);
+ connect(textLocator, SIGNAL(replaceAvailable(bool)), this, SLOT(setReplaceAvailable(bool)));
+}
+
+SearchTextDialog::~SearchTextDialog()
+{
+ delete ui;
+}
+
+void SearchTextDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void SearchTextDialog::showEvent(QShowEvent* e)
+{
+ UNUSED(e);
+ ui->findEdit->setFocus();
+ ui->findEdit->selectAll();
+ configModifiedState = true;
+ setReplaceAvailable(false);
+}
+
+void SearchTextDialog::applyConfigToLocator()
+{
+ if (!configModifiedState)
+ return;
+
+ textLocator->setCaseSensitive(ui->caseSensitiveCheck->isChecked());
+ textLocator->setSearchBackwards(ui->backwardsCheck->isChecked());
+ textLocator->setRegularExpression(ui->regExpCheck->isChecked());
+ textLocator->setLookupString(ui->findEdit->text());
+ configModifiedState = false;
+}
+
+void SearchTextDialog::setReplaceAvailable(bool available)
+{
+ ui->replaceButton->setEnabled(available);
+}
+
+void SearchTextDialog::on_findButton_clicked()
+{
+ applyConfigToLocator();
+ textLocator->find();
+}
+
+void SearchTextDialog::on_replaceButton_clicked()
+{
+ applyConfigToLocator();
+ textLocator->setReplaceString(ui->replaceEdit->text());
+ textLocator->replaceAndFind();
+}
+
+void SearchTextDialog::on_replaceAllButton_clicked()
+{
+ applyConfigToLocator();
+ textLocator->setReplaceString(ui->replaceEdit->text());
+ textLocator->replaceAll();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h
new file mode 100644
index 0000000..54f6f72
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.h
@@ -0,0 +1,39 @@
+#ifndef SEARCHTEXTDIALOG_H
+#define SEARCHTEXTDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class SearchTextDialog;
+}
+
+class SearchTextLocator;
+
+class GUI_API_EXPORT SearchTextDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit SearchTextDialog(SearchTextLocator* textLocator, QWidget *parent = 0);
+ ~SearchTextDialog();
+
+ protected:
+ void changeEvent(QEvent *e);
+ void showEvent(QShowEvent* e);
+
+ private:
+ void applyConfigToLocator();
+
+ Ui::SearchTextDialog *ui = nullptr;
+ SearchTextLocator* textLocator = nullptr;
+ bool configModifiedState = false;
+
+ private slots:
+ void setReplaceAvailable(bool available);
+ void on_findButton_clicked();
+ void on_replaceButton_clicked();
+ void on_replaceAllButton_clicked();
+};
+
+#endif // SEARCHTEXTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui
new file mode 100644
index 0000000..ce9e12e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/searchtextdialog.ui
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SearchTextDialog</class>
+ <widget class="QDialog" name="SearchTextDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>403</width>
+ <height>184</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="findEdit"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="findLabel">
+ <property name="text">
+ <string>Find:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" rowspan="3" colspan="2">
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="caseSensitiveCheck">
+ <property name="text">
+ <string>Case sensitive</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="backwardsCheck">
+ <property name="text">
+ <string>Search backwards</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="regExpCheck">
+ <property name="text">
+ <string>Regular expression matching</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="replaceButton">
+ <property name="text">
+ <string>Replace &amp;&amp;
+find next</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0" colspan="3">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="replaceLabel">
+ <property name="text">
+ <string>Replace with:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="replaceEdit"/>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="replaceAllButton">
+ <property name="text">
+ <string>Replace all</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="findButton">
+ <property name="text">
+ <string>Find</string>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>findEdit</tabstop>
+ <tabstop>replaceEdit</tabstop>
+ <tabstop>caseSensitiveCheck</tabstop>
+ <tabstop>regExpCheck</tabstop>
+ <tabstop>backwardsCheck</tabstop>
+ <tabstop>findButton</tabstop>
+ <tabstop>replaceButton</tabstop>
+ <tabstop>replaceAllButton</tabstop>
+ <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>SearchTextDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>SearchTextDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp
new file mode 100644
index 0000000..b1451eb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.cpp
@@ -0,0 +1,248 @@
+#include "sortdialog.h"
+#include "ui_sortdialog.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+#include <QComboBox>
+#include <QDebug>
+#include <QPushButton>
+
+SortDialog::SortDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::SortDialog)
+{
+ ui->setupUi(this);
+
+ initActions();
+ ui->list->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+ connect(ui->list->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateButtons()));
+ connect(ui->list, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(itemChanged(QTreeWidgetItem*,int)));
+ connect(ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(reset()));
+ connect(ui->list->model(), &QAbstractItemModel::rowsInserted, [=](const QModelIndex & parent, int start, int end)
+ {
+ UNUSED(parent);
+ UNUSED(end);
+ rebuildComboForItem(ui->list->topLevelItem(start));
+ });
+}
+
+SortDialog::~SortDialog()
+{
+ delete ui;
+}
+
+void SortDialog::setColumns(const QStringList& columns)
+{
+ originalColumns = columns;
+ ui->list->clear();
+
+ QTreeWidgetItem* item = nullptr;
+ for (int row = 0, total = columns.size(); row < total; ++row)
+ {
+ item = new QTreeWidgetItem({columns[row], "ASC"});
+ item->setData(2, Qt::UserRole, row);
+ fixItemFlags(item);
+ ui->list->insertTopLevelItem(row, item);
+ item->setCheckState(0, Qt::Unchecked);
+ }
+ ui->list->setHeaderLabels({tr("Column"), tr("Order")});
+ updateButtons();
+}
+
+QueryExecutor::SortList SortDialog::getSortOrder() const
+{
+ QueryExecutor::SortList sortOrder;
+
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row)
+ {
+ item = ui->list->topLevelItem(row);
+ if (item->checkState(0) != Qt::Checked)
+ continue;
+
+ combo = dynamic_cast<QComboBox*>(ui->list->itemWidget(item, 1));
+ sortOrder << QueryExecutor::Sort((combo->currentText() == "ASC" ? Qt::AscendingOrder : Qt::DescendingOrder), item->data(2, Qt::UserRole).toInt());
+ }
+ return sortOrder;
+}
+
+void SortDialog::setSortOrder(const QueryExecutor::SortList& sortOrder)
+{
+ // Translate sort order into more usable (in here) form
+ QHash<int,QueryExecutor::Sort::Order> checkedColumns;
+ QList<int> checkedColumnsOrder;
+ for (const QueryExecutor::Sort& sort : sortOrder)
+ {
+ checkedColumns[sort.column] = sort.order;
+ checkedColumnsOrder << sort.column;
+ }
+
+ // Select proper columns and set order
+ bool checked;
+ QTreeWidgetItem* item = nullptr;
+ QComboBox* combo = nullptr;
+ for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row)
+ {
+ item = ui->list->topLevelItem(row);
+ checked = checkedColumns.contains(item->data(2, Qt::UserRole).toInt());
+ item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
+
+ combo = dynamic_cast<QComboBox*>(ui->list->itemWidget(item, 1));
+ combo->setCurrentText(checkedColumns[row] == QueryExecutor::Sort::DESC ? "DESC" : "ASC");
+ }
+
+ // Get selected items as an ordered list of items (in order as defined in the sort order), so we can easly relocate them
+ QList<QTreeWidgetItem*> orderedItems;
+ for (int row : checkedColumnsOrder)
+ orderedItems << ui->list->topLevelItem(row);
+
+ // Move selected items in front, in the same order as they were mentioned in the sort order
+ int newRow = 0;
+ for (QTreeWidgetItem* itemToMove : orderedItems)
+ {
+ ui->list->takeTopLevelItem(ui->list->indexOfTopLevelItem(itemToMove));
+ ui->list->insertTopLevelItem(newRow++, itemToMove);
+ }
+
+ updateState();
+}
+
+QToolBar* SortDialog::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+void SortDialog::updateState(QTreeWidgetItem* item)
+{
+ QComboBox* combo = dynamic_cast<QComboBox*>(ui->list->itemWidget(item, 1));
+ if (!combo)
+ return;
+
+ combo->setEnabled(item->checkState(0) == Qt::Checked);
+}
+
+void SortDialog::updateState()
+{
+ for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row)
+ updateState(ui->list->topLevelItem(row));
+}
+
+void SortDialog::fixItemFlags(QTreeWidgetItem* item)
+{
+ Qt::ItemFlags flags = item->flags();
+ flags |= Qt::ItemNeverHasChildren;
+ flags |= Qt::ItemIsUserCheckable;
+ flags ^= Qt::ItemIsDropEnabled;
+ flags ^= Qt::ItemIsEditable;
+ item->setFlags(flags);
+}
+
+void SortDialog::rebuildComboForItem(QTreeWidgetItem* item)
+{
+ QComboBox* combo = new QComboBox();
+ combo->addItems({"ASC", "DESC"});
+ combo->setCurrentText(item->text(1));
+ combo->setEnabled(item->checkState(0) == Qt::Checked);
+ ui->list->setItemWidget(item, 1, combo);
+ item->setSizeHint(1, combo->sizeHint()); // bug in Qt? without this comboboxes were misaligned vertically
+
+ connect(combo, &QComboBox::currentTextChanged, [item](const QString& newText)
+ {
+ item->setText(1, newText);
+ });
+
+ updateSortLabel();
+}
+
+void SortDialog::updateSortLabel()
+{
+ QStringList entries;
+ QTreeWidgetItem* item = nullptr;
+ for (int row = 0, total = ui->list->topLevelItemCount(); row < total; ++row)
+ {
+ item = ui->list->topLevelItem(row);
+ if (item->checkState(0) != Qt::Checked)
+ continue;
+
+ entries << item->text(0) + " " + item->text(1);
+ }
+
+ if (entries.size() == 0)
+ {
+ ui->sortByLabel->setVisible(false);
+ }
+ else
+ {
+ static QString label = tr("Sort by: %1");
+ ui->sortByLabel->setText(label.arg(entries.join(", ")));
+ ui->sortByLabel->setVisible(true);
+ }
+}
+
+void SortDialog::itemChanged(QTreeWidgetItem* item, int column)
+{
+ if (column == 0)
+ updateState(item);
+
+ updateSortLabel();
+}
+
+void SortDialog::reset()
+{
+ setColumns(originalColumns);
+}
+
+void SortDialog::updateButtons()
+{
+ QTreeWidgetItem* item = ui->list->currentItem();
+ actionMap[MOVE_UP]->setEnabled(item && ui->list->itemAbove(item) != nullptr);
+ actionMap[MOVE_DOWN]->setEnabled(item && ui->list->itemBelow(item) != nullptr);
+}
+
+void SortDialog::moveCurrentUp()
+{
+ QTreeWidgetItem* item = ui->list->currentItem();
+ if (!item)
+ return;
+
+ int row = ui->list->indexOfTopLevelItem(item);
+ if (row < 1)
+ return;
+
+ ui->list->takeTopLevelItem(row);
+ ui->list->insertTopLevelItem(row - 1, item);
+
+ QModelIndex idx = ui->list->model()->index(row - 1, 0);
+ ui->list->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Rows|QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current);
+ updateButtons();
+}
+
+void SortDialog::moveCurrentDown()
+{
+ QTreeWidgetItem* item = ui->list->currentItem();
+ if (!item)
+ return;
+
+ int row = ui->list->indexOfTopLevelItem(item);
+ if (row + 1 >= ui->list->topLevelItemCount())
+ return;
+
+ ui->list->takeTopLevelItem(row);
+ ui->list->insertTopLevelItem(row + 1, item);
+
+ QModelIndex idx = ui->list->model()->index(row + 1, 0);
+ ui->list->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Rows|QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current);
+ updateButtons();
+}
+
+void SortDialog::createActions()
+{
+ createAction(MOVE_UP, ICONS.MOVE_UP, tr("Move column up"), this, SLOT(moveCurrentUp()), ui->toolbar, this);
+ createAction(MOVE_DOWN, ICONS.MOVE_DOWN, tr("Move column down"), this, SLOT(moveCurrentDown()), ui->toolbar, this);
+}
+
+void SortDialog::setupDefShortcuts()
+{
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h
new file mode 100644
index 0000000..a103d90
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.h
@@ -0,0 +1,60 @@
+#ifndef SORTDIALOG_H
+#define SORTDIALOG_H
+
+#include "db/queryexecutor.h"
+#include "common/extactioncontainer.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class SortDialog;
+}
+
+class QTreeWidgetItem;
+
+class GUI_API_EXPORT SortDialog : public QDialog, public ExtActionContainer
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ MOVE_UP,
+ MOVE_DOWN
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit SortDialog(QWidget *parent = 0);
+ ~SortDialog();
+
+ void setColumns(const QStringList& columns);
+ QueryExecutor::SortList getSortOrder() const;
+ void setSortOrder(const QueryExecutor::SortList& sortOrder);
+ QToolBar* getToolBar(int toolbar) const;
+
+ protected:
+ void createActions();
+ void setupDefShortcuts();
+
+ private:
+ void updateState(QTreeWidgetItem* item);
+ void updateState();
+ void fixItemFlags(QTreeWidgetItem* item);
+ void rebuildComboForItem(QTreeWidgetItem* item);
+ void updateSortLabel();
+
+ Ui::SortDialog *ui = nullptr;
+ QStringList originalColumns;
+
+ private slots:
+ void itemChanged(QTreeWidgetItem* item, int column);
+ void reset();
+ void updateButtons();
+ void moveCurrentUp();
+ void moveCurrentDown();
+};
+
+#endif // SORTDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui
new file mode 100644
index 0000000..23bee81
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/sortdialog.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SortDialog</class>
+ <widget class="QDialog" name="SortDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>457</width>
+ <height>357</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Sort by columns</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QToolBar" name="toolbar"/>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="list">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DragDrop</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::MoveAction</enum>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <column>
+ <property name="text">
+ <string>Column</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Order</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="sortByLabel">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>SortDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>SortDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp
new file mode 100644
index 0000000..462e57f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.cpp
@@ -0,0 +1,52 @@
+#include "triggercolumnsdialog.h"
+#include "ui_triggercolumnsdialog.h"
+
+#include <QCheckBox>
+
+TriggerColumnsDialog::TriggerColumnsDialog(QWidget *parent) :
+ QDialog(parent, Qt::Popup),
+ ui(new Ui::TriggerColumnsDialog)
+{
+ ui->setupUi(this);
+}
+
+TriggerColumnsDialog::~TriggerColumnsDialog()
+{
+ delete ui;
+}
+
+void TriggerColumnsDialog::addColumn(const QString& name, bool checked)
+{
+ QCheckBox* cb = new QCheckBox(name);
+ cb->setChecked(checked);
+ ui->mainWidget->layout()->addWidget(cb);
+ checkBoxList << cb;
+}
+
+QStringList TriggerColumnsDialog::getCheckedColumns() const
+{
+ QStringList columns;
+ foreach (QCheckBox* cb, checkBoxList)
+ {
+ if (cb->isChecked())
+ columns << cb->text();
+ }
+ return columns;
+}
+
+void TriggerColumnsDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void TriggerColumnsDialog::showEvent(QShowEvent*)
+{
+ adjustSize();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h
new file mode 100644
index 0000000..1ba0d69
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.h
@@ -0,0 +1,33 @@
+#ifndef TRIGGERCOLUMNSDIALOG_H
+#define TRIGGERCOLUMNSDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class TriggerColumnsDialog;
+}
+
+class QCheckBox;
+
+class GUI_API_EXPORT TriggerColumnsDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit TriggerColumnsDialog(QWidget *parent = 0);
+ ~TriggerColumnsDialog();
+
+ void addColumn(const QString& name, bool checked);
+ QStringList getCheckedColumns() const;
+
+ protected:
+ void changeEvent(QEvent *e);
+ void showEvent(QShowEvent*);
+
+ private:
+ QList<QCheckBox*> checkBoxList;
+ Ui::TriggerColumnsDialog *ui = nullptr;
+};
+
+#endif // TRIGGERCOLUMNSDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui
new file mode 100644
index 0000000..4326fca
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggercolumnsdialog.ui
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TriggerColumnsDialog</class>
+ <widget class="QDialog" name="TriggerColumnsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>334</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Triggering columns:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="mainWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>239</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>TriggerColumnsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>TriggerColumnsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp
new file mode 100644
index 0000000..0707bd3
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.cpp
@@ -0,0 +1,413 @@
+#include "triggerdialog.h"
+#include "ui_triggerdialog.h"
+#include "parser/ast/sqliteselect.h"
+#include "services/notifymanager.h"
+#include "parser/ast/sqliteexpr.h"
+#include "triggercolumnsdialog.h"
+#include "common/utils_sql.h"
+#include "schemaresolver.h"
+#include "parser/parser.h"
+#include "iconmanager.h"
+#include "db/chainexecutor.h"
+#include "dbtree/dbtree.h"
+#include "ddlpreviewdialog.h"
+#include "uiconfig.h"
+#include "services/config.h"
+#include "uiutils.h"
+#include "services/codeformatter.h"
+#include <QDebug>
+#include <QMessageBox>
+#include <QPushButton>
+
+TriggerDialog::TriggerDialog(Db* db, QWidget *parent) :
+ QDialog(parent),
+ db(db),
+ ui(new Ui::TriggerDialog)
+{
+ init();
+}
+
+TriggerDialog::~TriggerDialog()
+{
+ delete ui;
+}
+
+void TriggerDialog::setParentTable(const QString& name)
+{
+ forTable = true;
+ table = name;
+ initTrigger();
+}
+
+void TriggerDialog::setParentView(const QString& name)
+{
+ forTable = false;
+ view = name;
+ initTrigger();
+}
+
+void TriggerDialog::setTrigger(const QString& name)
+{
+ trigger = name;
+ originalTriggerName = name;
+ existingTrigger = true;
+ initTrigger();
+}
+
+void TriggerDialog::changeEvent(QEvent *e)
+{
+ QDialog::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void TriggerDialog::init()
+{
+ ui->setupUi(this);
+ limitDialogWidth(this);
+
+ connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(updateDdlTab(int)));
+ connect(ui->actionColumns, SIGNAL(clicked()), this, SLOT(showColumnsDialog()));
+
+ // On object combo
+ ui->onCombo->setEnabled(false);
+ connect(ui->onCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(tableChanged(QString)));
+
+ // Action combo
+ ui->actionCombo->addItems({
+ SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::DELETE),
+ SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::INSERT),
+ SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::UPDATE),
+ SqliteCreateTrigger::Event::typeToString(SqliteCreateTrigger::Event::UPDATE_OF)
+ });
+ connect(ui->actionCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateState()));
+
+ // Scope combo
+ ui->scopeCombo->addItems({
+ SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::null),
+ SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::FOR_EACH_ROW)
+ });
+ if (db->getDialect() == Dialect::Sqlite2)
+ {
+ ui->scopeCombo->addItems({
+ SqliteCreateTrigger::scopeToString(SqliteCreateTrigger::Scope::FOR_EACH_STATEMENT)
+ });
+ }
+
+ // Precondition
+ connect(ui->preconditionCheck, SIGNAL(clicked()), this, SLOT(updateState()));
+ connect(ui->preconditionEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation()));
+ connect(ui->preconditionEdit, SIGNAL(textChanged()), this, SLOT(updateValidation()));
+ ui->preconditionEdit->setDb(db);
+
+ // Code
+ connect(ui->codeEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateValidation()));
+ connect(ui->codeEdit, SIGNAL(textChanged()), this, SLOT(updateValidation()));
+ ui->codeEdit->setDb(db);
+}
+
+void TriggerDialog::initTrigger()
+{
+ // Name edit
+ ui->nameEdit->setText(trigger);
+
+ if (trigger.isNull())
+ {
+ createTrigger = SqliteCreateTriggerPtr::create();
+ createTrigger->event = new SqliteCreateTrigger::Event();
+ }
+ else
+ {
+ parseDdl();
+ readTrigger();
+ }
+
+ // Event combo
+ if (forTable)
+ {
+ ui->whenCombo->addItems({
+ SqliteCreateTrigger::time(SqliteCreateTrigger::Time::null),
+ SqliteCreateTrigger::time(SqliteCreateTrigger::Time::BEFORE),
+ SqliteCreateTrigger::time(SqliteCreateTrigger::Time::AFTER)
+ });
+ }
+ else
+ {
+ ui->whenCombo->addItems({
+ SqliteCreateTrigger::time(SqliteCreateTrigger::Time::INSTEAD_OF)
+ });
+ ui->whenCombo->setEnabled(false);
+ ui->onLabel->setText(tr("On view:"));
+ }
+
+ if (!view.isNull() || !table.isNull())
+ {
+ readColumns();
+ QString target = getTargetObjectName();
+ ui->onCombo->addItem(target);
+ ui->onCombo->setCurrentText(target);
+ }
+
+ // Precondition and code edits
+ setupVirtualSqls();
+
+ updateState();
+}
+
+void TriggerDialog::parseDdl()
+{
+ SchemaResolver resolver(db);
+ SqliteQueryPtr parsedObject = resolver.getParsedObject(trigger, SchemaResolver::TRIGGER);
+ if (!parsedObject.dynamicCast<SqliteCreateTrigger>())
+ {
+ notifyError(tr("Could not process trigger %1 correctly. Unable to open a trigger dialog.").arg(trigger));
+ reject();
+ return;
+ }
+
+ createTrigger = parsedObject.dynamicCast<SqliteCreateTrigger>();
+ ddl = createTrigger->detokenize();
+}
+
+void TriggerDialog::readTrigger()
+{
+ if (!createTrigger)
+ return;
+
+ forTable = createTrigger->eventTime != SqliteCreateTrigger::Time::INSTEAD_OF;
+ if (forTable)
+ table = createTrigger->table;
+ else
+ view = createTrigger->table;
+
+ ui->onCombo->addItem(createTrigger->table);
+ ui->onCombo->setCurrentText(createTrigger->table);
+ ui->whenCombo->setCurrentText(SqliteCreateTrigger::time(createTrigger->eventTime));
+ ui->actionCombo->setCurrentText(SqliteCreateTrigger::Event::typeToString(createTrigger->event->type));
+ ui->scopeCombo->setCurrentText(SqliteCreateTrigger::scopeToString(createTrigger->scope));
+ if (createTrigger->precondition)
+ {
+ ui->preconditionCheck->setChecked(true);
+ ui->preconditionEdit->setPlainText(createTrigger->precondition->detokenize());
+ }
+
+ if (createTrigger->queries.size() > 0)
+ {
+ QStringList sqls;
+ foreach (SqliteQuery* query, createTrigger->queries)
+ sqls << query->detokenize();
+
+ ui->codeEdit->setPlainText(sqls.join(";\n")+";");
+ }
+}
+
+void TriggerDialog::setupVirtualSqls()
+{
+ Dialect dialect = db->getDialect();
+ static QString preconditionVirtSql = QStringLiteral("CREATE TRIGGER %1 BEFORE INSERT ON %2 WHEN %3 BEGIN SELECT 1; END;");
+ static QString codeVirtSql = QStringLiteral("CREATE TRIGGER %1 BEFORE INSERT ON %2 BEGIN %3 END;");
+ ui->codeEdit->setVirtualSqlCompleteSemicolon(true);
+ if (!trigger.isNull())
+ {
+ if (createTrigger) // if false, then there was a parsing error in parseDdl().
+ {
+ ui->preconditionEdit->setVirtualSqlExpression(
+ preconditionVirtSql.arg(wrapObjIfNeeded(trigger, dialect),
+ wrapObjIfNeeded(createTrigger->table, dialect),
+ "%1"));
+
+ ui->codeEdit->setVirtualSqlExpression(
+ codeVirtSql.arg(
+ wrapObjIfNeeded(trigger, dialect),
+ wrapObjIfNeeded(createTrigger->table, dialect),
+ "%1"));
+ }
+ }
+ else if (!table.isNull() || !view.isNull())
+ {
+ ui->preconditionEdit->setVirtualSqlExpression(
+ preconditionVirtSql.arg("trig",
+ wrapObjIfNeeded(getTargetObjectName(), dialect),
+ "%1"));
+
+ ui->codeEdit->setVirtualSqlExpression(
+ codeVirtSql.arg("trig",
+ wrapObjIfNeeded(getTargetObjectName(), dialect),
+ "%1"));
+ }
+ else
+ {
+ qCritical() << "TriggerDialog is in invalid state. Called initTrigger() but none of trigger/table/view values are set.";
+ }
+}
+
+void TriggerDialog::readColumns()
+{
+ SchemaResolver resolver(db);
+ if (!table.isNull())
+ targetColumns = resolver.getTableColumns(table);
+ else if (!view.isNull())
+ targetColumns = resolver.getViewColumns(view);
+ else
+ targetColumns.clear();
+
+ if (createTrigger)
+ selectedColumns = createTrigger->event->columnNames;
+}
+
+QString TriggerDialog::getTargetObjectName() const
+{
+ if (!table.isNull())
+ return table;
+
+ return view;
+}
+
+void TriggerDialog::rebuildTrigger()
+{
+ /*
+ * Trigger is not rebuilt into SqliteCreateTrigger, because it's impossible to parse
+ * precondition or queries if they are invalid and we still need an invalid queries
+ * to be presented on the DDL tab.
+ */
+ static const QString tempDdl = QStringLiteral("CREATE TRIGGER %1%2 %3%4 ON %5%6%7 BEGIN %8 END;");
+
+ Dialect dialect = db->getDialect();
+ QString trigName = wrapObjIfNeeded(ui->nameEdit->text(), dialect);
+ QString when = ui->whenCombo->currentText();
+ QString action = ui->actionCombo->currentText();
+ QString columns = "";
+ QString target = wrapObjIfNeeded(getTargetObjectName(), dialect);
+ QString scope = ui->scopeCombo->currentText();
+ QString precondition = "";
+ QString queries = ui->codeEdit->toPlainText();
+
+ // Columns
+ SqliteCreateTrigger::Event::Type actionType = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText());
+ if (actionType == SqliteCreateTrigger::Event::UPDATE_OF)
+ {
+ QStringList colNames;
+ foreach (const QString& colName, selectedColumns)
+ colNames << wrapObjIfNeeded(colName, dialect);
+
+ columns = " "+colNames.join(", ");
+ }
+
+ // Precondition
+ if (ui->preconditionCheck->isChecked())
+ precondition = " WHEN "+ui->preconditionEdit->toPlainText();
+
+ // Queries
+ if (!queries.trimmed().endsWith(";"))
+ queries += ";";
+
+ // When
+ if (!when.isNull())
+ when.prepend(" ");
+
+ // Scope
+ if (!scope.isNull())
+ scope.prepend(" ");
+
+ ddl = tempDdl.arg(trigName).arg(when).arg(action).arg(columns).arg(target).arg(scope).arg(precondition).arg(queries);
+}
+
+void TriggerDialog::updateState()
+{
+ SqliteCreateTrigger::Event::Type type = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText());
+ ui->actionColumns->setEnabled(type == SqliteCreateTrigger::Event::UPDATE_OF);
+ ui->preconditionEdit->setEnabled(ui->preconditionCheck->isChecked());
+ updateValidation();
+}
+
+void TriggerDialog::updateValidation()
+{
+ SqliteCreateTrigger::Event::Type type = SqliteCreateTrigger::Event::stringToType(ui->actionCombo->currentText());
+ bool columnsOk = (type != SqliteCreateTrigger::Event::UPDATE_OF || selectedColumns.size() > 0);
+
+ bool preconditionOk = (!ui->preconditionCheck->isChecked() ||
+ (ui->preconditionEdit->isSyntaxChecked() && !ui->preconditionEdit->haveErrors()));
+
+ bool codeOk = (ui->codeEdit->isSyntaxChecked() && !ui->codeEdit->haveErrors());
+
+ setValidState(ui->preconditionCheck, preconditionOk, tr("Enter a valid condition."));
+ setValidState(ui->codeEdit, codeOk, tr("Enter a valid trigger code."));
+ ui->actionColumns->setIcon(columnsOk ? ICONS.TRIGGER_COLUMNS : ICONS.TRIGGER_COLUMNS_INVALID);
+
+ QPushButton* okButton = ui->buttonBox->button(QDialogButtonBox::Ok);
+ okButton->setEnabled(columnsOk && preconditionOk && codeOk);
+}
+
+void TriggerDialog::showColumnsDialog()
+{
+ TriggerColumnsDialog dialog(this);
+ foreach (const QString& colName, targetColumns)
+ dialog.addColumn(colName, selectedColumns.contains(colName, Qt::CaseInsensitive));
+
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+
+ QStringList newColumns = dialog.getCheckedColumns();
+ selectedColumns = newColumns;
+ updateValidation();
+}
+
+void TriggerDialog::updateDdlTab(int tabIdx)
+{
+ if (tabIdx != 1)
+ return;
+
+ rebuildTrigger();
+ QString formatted = FORMATTER->format("sql", ddl, db);
+ ui->ddlEdit->setPlainText(formatted);
+}
+
+void TriggerDialog::tableChanged(const QString& newValue)
+{
+ ui->preconditionEdit->setTriggerContext(newValue);
+ ui->codeEdit->setTriggerContext(newValue);
+}
+
+void TriggerDialog::accept()
+{
+ rebuildTrigger();
+
+ Dialect dialect = db->getDialect();
+
+ QStringList sqls;
+ if (existingTrigger)
+ sqls << QString("DROP TRIGGER %1").arg(wrapObjIfNeeded(originalTriggerName, dialect));
+
+ sqls << ddl;
+
+ if (!CFG_UI.General.DontShowDdlPreview.get())
+ {
+ DdlPreviewDialog dialog(db, this);
+ dialog.setDdl(sqls);
+ if (dialog.exec() != QDialog::Accepted)
+ return;
+ }
+
+ ChainExecutor executor;
+ executor.setDb(db);
+ executor.setAsync(false);
+ executor.setQueries(sqls);
+ executor.exec();
+
+ if (executor.getSuccessfulExecution())
+ {
+ CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
+
+ QDialog::accept();
+ DBTREE->refreshSchema(db);
+ return;
+ }
+
+ QMessageBox::critical(this, tr("Error", "trigger dialog"), tr("An error occurred while executing SQL statements:\n%1")
+ .arg(executor.getErrorsMessages().join(",\n")), QMessageBox::Ok);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h
new file mode 100644
index 0000000..d8e7ed4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.h
@@ -0,0 +1,62 @@
+#ifndef TRIGGERDIALOG_H
+#define TRIGGERDIALOG_H
+
+#include "db/db.h"
+#include "parser/ast/sqlitecreatetrigger.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class TriggerDialog;
+}
+
+class GUI_API_EXPORT TriggerDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit TriggerDialog(Db* db, QWidget *parent = 0);
+ ~TriggerDialog();
+
+ void setParentTable(const QString& name);
+ void setParentView(const QString& name);
+ void setTrigger(const QString& name);
+
+ protected:
+ void changeEvent(QEvent *e);
+
+ private:
+ void init();
+ void initTrigger();
+ void parseDdl();
+ void readTrigger();
+ void setupVirtualSqls();
+ void readColumns();
+ QString getTargetObjectName() const;
+ void rebuildTrigger();
+
+ QString originalTriggerName;
+ QString trigger;
+ QString table;
+ QString view;
+ Db* db = nullptr;
+ bool forTable = true;
+ bool existingTrigger = false;
+ QStringList targetColumns;
+ QStringList selectedColumns;
+ QString ddl;
+ SqliteCreateTriggerPtr createTrigger;
+ Ui::TriggerDialog *ui = nullptr;
+
+ private slots:
+ void updateState();
+ void updateValidation();
+ void showColumnsDialog();
+ void updateDdlTab(int tabIdx);
+ void tableChanged(const QString& newValue);
+
+ public slots:
+ void accept();
+};
+
+#endif // TRIGGERDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui
new file mode 100644
index 0000000..bf3da0a
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/triggerdialog.ui
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TriggerDialog</class>
+ <widget class="QDialog" name="TriggerDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>490</width>
+ <height>480</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Trigger dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="triggerTab">
+ <attribute name="title">
+ <string>Trigger</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="2">
+ <widget class="QLabel" name="onLabel">
+ <property name="text">
+ <string>On table:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QComboBox" name="whenCombo"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="actionLabel">
+ <property name="text">
+ <string>Action:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QComboBox" name="actionCombo"/>
+ </item>
+ <item row="3" column="2">
+ <widget class="QComboBox" name="onCombo"/>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QLineEdit" name="nameEdit"/>
+ </item>
+ <item row="6" column="0" colspan="3">
+ <widget class="QCheckBox" name="preconditionCheck">
+ <property name="toolTip">
+ <string>&lt;p&gt;SQL condition that will be evaluated before the actual trigger code. In case the condition returns false, the trigger will not be fired for that row.&lt;/p&gt;</string>
+ </property>
+ <property name="text">
+ <string>Pre-condition:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="3">
+ <widget class="QComboBox" name="scopeCombo">
+ <property name="toolTip">
+ <string>The scope is still not fully supported by the SQLite database.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Trigger name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="whenLabel">
+ <property name="text">
+ <string>When:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QToolButton" name="actionColumns">
+ <property name="toolTip">
+ <string>List of columns for UPDATE OF action.</string>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="3">
+ <widget class="QLabel" name="scopeLabel">
+ <property name="text">
+ <string>Scope:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QLabel" name="codeLabel">
+ <property name="text">
+ <string>Code:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="0" colspan="3">
+ <widget class="SqlEditor" name="codeEdit">
+ <property name="toolTip">
+ <string>Trigger statements to be executed.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0" colspan="3">
+ <widget class="SqlEditor" name="preconditionEdit">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>80</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>&lt;p&gt;SQL condition that will be evaluated before the actual trigger code. In case the condition returns false, the trigger will not be fired for that row.&lt;/p&gt;</string>
+ </property>
+ </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"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlView</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqlview.h</header>
+ </customwidget>
+ <customwidget>
+ <class>SqlEditor</class>
+ <extends>QPlainTextEdit</extends>
+ <header>sqleditor.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>nameEdit</tabstop>
+ <tabstop>whenCombo</tabstop>
+ <tabstop>actionCombo</tabstop>
+ <tabstop>actionColumns</tabstop>
+ <tabstop>onCombo</tabstop>
+ <tabstop>scopeCombo</tabstop>
+ <tabstop>preconditionCheck</tabstop>
+ <tabstop>preconditionEdit</tabstop>
+ <tabstop>codeEdit</tabstop>
+ <tabstop>buttonBox</tabstop>
+ <tabstop>ddlEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>TriggerDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>TriggerDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp
new file mode 100644
index 0000000..5521245
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.cpp
@@ -0,0 +1,31 @@
+#include "versionconvertsummarydialog.h"
+#include "ui_versionconvertsummarydialog.h"
+
+VersionConvertSummaryDialog::VersionConvertSummaryDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::VersionConvertSummaryDialog)
+{
+ ui->setupUi(this);
+
+ ui->diffTable->setLeftLabel(tr("Before"));
+ ui->diffTable->setRightLabel(tr("After"));
+ ui->diffTable->horizontalHeader()->setVisible(true);
+}
+
+VersionConvertSummaryDialog::~VersionConvertSummaryDialog()
+{
+ delete ui;
+}
+
+void VersionConvertSummaryDialog::setSides(const QList<QPair<QString, QString> >& data)
+{
+ ui->diffTable->setSides(data);
+}
+
+
+void VersionConvertSummaryDialog::showEvent(QShowEvent* e)
+{
+ QDialog::showEvent(e);
+ ui->diffTable->updateSizes();
+
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h
new file mode 100644
index 0000000..fc63076
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.h
@@ -0,0 +1,28 @@
+#ifndef VERSIONCONVERTSUMMARYDIALOG_H
+#define VERSIONCONVERTSUMMARYDIALOG_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QDialog>
+
+namespace Ui {
+ class VersionConvertSummaryDialog;
+}
+
+class GUI_API_EXPORT VersionConvertSummaryDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit VersionConvertSummaryDialog(QWidget *parent = 0);
+ ~VersionConvertSummaryDialog();
+
+ void setSides(const QList<QPair<QString, QString>>& data);
+
+ protected:
+ void showEvent(QShowEvent* e);
+
+ private:
+ Ui::VersionConvertSummaryDialog *ui = nullptr;
+};
+
+#endif // VERSIONCONVERTSUMMARYDIALOG_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui
new file mode 100644
index 0000000..a67db1e
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dialogs/versionconvertsummarydialog.ui
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VersionConvertSummaryDialog</class>
+ <widget class="QDialog" name="VersionConvertSummaryDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>600</width>
+ <height>497</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Database version convert</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Following changes to the SQL statements will be made:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="SqlCompareView" name="diffTable">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderVisible">
+ <bool>true</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>SqlCompareView</class>
+ <extends>QTableWidget</extends>
+ <header>sqlcompareview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>VersionConvertSummaryDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>VersionConvertSummaryDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>