summaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/guiSQLiteStudio/windows
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/windows')
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp155
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h65
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui55
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp8
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h2
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui6
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp22
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h2
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp8
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp2
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp98
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h25
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui8
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp10
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui18
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp18
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.cpp439
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.h94
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.ui191
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditormodel.cpp234
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditormodel.h65
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp8
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp10
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp18
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp14
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h1
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui8
27 files changed, 1232 insertions, 352 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp
deleted file mode 100644
index 7632c8f..0000000
--- a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.cpp
+++ /dev/null
@@ -1,155 +0,0 @@
-#include "bugreporthistorywindow.h"
-#include "ui_bugreporthistorywindow.h"
-#include "common/unused.h"
-#include "services/config.h"
-#include <QDebug>
-#include <QLabel>
-
-CFG_KEYS_DEFINE(BugReportHistoryWindow)
-
-BugReportHistoryWindow::BugReportHistoryWindow(QWidget *parent) :
- MdiChild(parent),
- ui(new Ui::BugReportHistoryWindow)
-{
- init();
-}
-
-BugReportHistoryWindow::~BugReportHistoryWindow()
-{
- delete ui;
-}
-
-bool BugReportHistoryWindow::restoreSessionNextTime()
-{
- return false;
-}
-
-QVariant BugReportHistoryWindow::saveSession()
-{
- return QVariant();
-}
-
-bool BugReportHistoryWindow::restoreSession(const QVariant& sessionValue)
-{
- UNUSED(sessionValue);
- return false;
-}
-
-Icon* BugReportHistoryWindow::getIconNameForMdiWindow()
-{
- return ICONS.BUG_LIST;
-}
-
-QString BugReportHistoryWindow::getTitleForMdiWindow()
-{
- return tr("Reports history");
-}
-
-void BugReportHistoryWindow::createActions()
-{
- createAction(CLEAR_HISTORY, ICONS.CLEAR_HISTORY, tr("Clear reports history"), this, SLOT(clearHistory()), ui->toolBar);
- createAction(DELETE_SELECTED, ICONS.DELETE_ROW, tr("Delete selected entry"), this, SLOT(deleteSelected()), ui->toolBar);
-}
-
-void BugReportHistoryWindow::setupDefShortcuts()
-{
- setShortcutContext({
- DELETE_SELECTED
- },
- Qt::WidgetWithChildrenShortcut);
-
- BIND_SHORTCUTS(BugReportHistoryWindow, Action);
-}
-
-QToolBar* BugReportHistoryWindow::getToolBar(int toolbar) const
-{
- UNUSED(toolbar);
- return ui->toolBar;
-}
-
-void BugReportHistoryWindow::init()
-{
- ui->setupUi(this);
- initActions();
-
- reload();
- connect(ui->reportsList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState()));
- connect(CFG, SIGNAL(reportsHistoryRefreshNeeded()), this, SLOT(reload()));
-
- updateState();
-}
-
-void BugReportHistoryWindow::updateState()
-{
- actionMap[DELETE_SELECTED]->setEnabled(ui->reportsList->selectedItems().size() > 0);
-}
-
-void BugReportHistoryWindow::reload()
-{
- static_qstring(urlTpl, "<a href=\"%1\">%2</a>");
- QString invalidUrlTpl = tr("Invalid response from server.");
-
- QList<Config::ReportHistoryEntryPtr> entries = CFG->getReportHistory();
- ui->reportsList->clear();
- ui->reportsList->setRowCount(entries.size());
-
- QTableWidgetItem* item = nullptr;
- QLabel* urlLabel = nullptr;
- int row = 0;
- for (const Config::ReportHistoryEntryPtr& entry : entries)
- {
- item = new QTableWidgetItem((entry->isFeatureRequest ? ICONS.FEATURE_REQUEST : ICONS.BUG), entry->title);
- item->setData(ENTRY_ID, entry->id);
- ui->reportsList->setItem(row, 0, item);
-
- item = new QTableWidgetItem(QDateTime::fromTime_t(entry->timestamp).toString("yyyy-MM-dd HH:mm:ss"));
- ui->reportsList->setItem(row, 1, item);
-
- if (entry->url.startsWith("http://"))
- urlLabel = new QLabel(urlTpl.arg(entry->url, entry->url));
- else
- urlLabel = new QLabel(invalidUrlTpl);
-
- urlLabel->setOpenExternalLinks(true);
- ui->reportsList->setCellWidget(row, 2, urlLabel);
-
- row++;
- }
-
- ui->reportsList->setHorizontalHeaderLabels({tr("Title"), tr("Reported at"), tr("URL")});
- ui->reportsList->resizeColumnsToContents();
-}
-
-void BugReportHistoryWindow::clearHistory()
-{
- CFG->clearReportHistory();
-}
-
-void BugReportHistoryWindow::deleteSelected()
-{
- QList<QTableWidgetItem*> items = ui->reportsList->selectedItems();
- if (items.size() == 0)
- {
- qDebug() << "Called BugReportHistoryWindow::deleteSelected(), but there's no row selected.";
- return;
- }
-
- int id = items.first()->data(ENTRY_ID).toInt();
- if (id == 0)
- {
- qDebug() << "Called BugReportHistoryWindow::deleteSelected(), but there's no ID in selected row.";
- return;
- }
-
- CFG->deleteReport(id);
-}
-
-bool BugReportHistoryWindow::isUncommitted() const
-{
- return false;
-}
-
-QString BugReportHistoryWindow::getQuitUncommittedConfirmMessage() const
-{
- return QString();
-}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h
deleted file mode 100644
index 42e518a..0000000
--- a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.h
+++ /dev/null
@@ -1,65 +0,0 @@
-#ifndef BUGREPORTHISTORYWINDOW_H
-#define BUGREPORTHISTORYWINDOW_H
-
-#include "mdichild.h"
-#include <QWidget>
-
-namespace Ui {
- class BugReportHistoryWindow;
-}
-
-CFG_KEY_LIST(BugReportHistoryWindow, QObject::tr("Reports history window"),
- CFG_KEY_ENTRY(DELETE_SELECTED, Qt::Key_Delete, QObject::tr("Delete selected entry"))
-)
-
-class GUI_API_EXPORT BugReportHistoryWindow : public MdiChild
-{
- Q_OBJECT
- Q_ENUMS(Action)
-
- public:
- enum Action
- {
- DELETE_SELECTED,
- CLEAR_HISTORY
- };
-
- enum ToolBar
- {
- TOOLBAR
- };
-
- explicit BugReportHistoryWindow(QWidget *parent = 0);
- ~BugReportHistoryWindow();
-
- bool restoreSessionNextTime();
- bool isUncommitted() const;
- QString getQuitUncommittedConfirmMessage() const;
-
- protected:
- QVariant saveSession();
- bool restoreSession(const QVariant &sessionValue);
- Icon* getIconNameForMdiWindow();
- QString getTitleForMdiWindow();
- void createActions();
- void setupDefShortcuts();
- QToolBar* getToolBar(int toolbar) const;
-
- private:
- enum UserRole
- {
- ENTRY_ID = Qt::UserRole + 1
- };
-
- void init();
-
- Ui::BugReportHistoryWindow *ui = nullptr;
-
- private slots:
- void updateState();
- void reload();
- void clearHistory();
- void deleteSelected();
-};
-
-#endif // BUGREPORTHISTORYWINDOW_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui
deleted file mode 100644
index 2211ec2..0000000
--- a/SQLiteStudio3/guiSQLiteStudio/windows/bugreporthistorywindow.ui
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>BugReportHistoryWindow</class>
- <widget class="QWidget" name="BugReportHistoryWindow">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>400</width>
- <height>300</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string notr="true">Form</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QToolBar" name="toolBar"/>
- </item>
- <item>
- <widget class="QTableWidget" name="reportsList">
- <property name="selectionMode">
- <enum>QAbstractItemView::SingleSelection</enum>
- </property>
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectRows</enum>
- </property>
- <property name="verticalScrollMode">
- <enum>QAbstractItemView::ScrollPerPixel</enum>
- </property>
- <attribute name="horizontalHeaderStretchLastSection">
- <bool>true</bool>
- </attribute>
- <column>
- <property name="text">
- <string>Title</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Reported at</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>URL</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp
index 286924b..5d36683 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.cpp
@@ -106,11 +106,11 @@ void CollationsEditor::init()
connect(CFG_UI.Fonts.SqlEditor, SIGNAL(changed(QVariant)), this, SLOT(changeFont(QVariant)));
// Language plugins
- foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>())
ui->langCombo->addItem(plugin->getLanguage());
// Syntax highlighting plugins
- foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
+ for (SyntaxHighlighterPlugin* plugin : PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
highlighterPlugins[plugin->getLanguageName()] = plugin;
updateState();
@@ -156,7 +156,7 @@ void CollationsEditor::collationSelected(int row)
ui->selectedDatabasesRadio->setChecked(true);
updatesForSelection = false;
- currentModified = false;
+ currentModified = model->isModified(row);
updateCurrentCollationState();
}
@@ -190,7 +190,7 @@ void CollationsEditor::setFont(const QFont& font)
void CollationsEditor::help()
{
- static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_collations");
+ static const QString url = QStringLiteral("https://github.com/pawelsalawa/sqlitestudio/wiki/User_Manual#custom-collations");
QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h
index a75e66b..7b2e469 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.h
@@ -36,7 +36,7 @@ class GUI_API_EXPORT CollationsEditor : public MdiChild
TOOLBAR
};
- explicit CollationsEditor(QWidget *parent = 0);
+ explicit CollationsEditor(QWidget *parent = nullptr);
~CollationsEditor();
bool restoreSessionNextTime();
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui
index 97c4e0a..454c12a 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditor.ui
@@ -167,7 +167,11 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <widget class="QPlainTextEdit" name="codeEdit"/>
+ <widget class="QPlainTextEdit" name="codeEdit">
+ <property name="lineWrapMode">
+ <enum>QPlainTextEdit::NoWrap</enum>
+ </property>
+ </widget>
</item>
</layout>
</widget>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp
index 05ca4e1..f04e023 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.cpp
@@ -27,7 +27,7 @@ CollationsEditorModel::CollationsEditorModel(QObject *parent) :
void CollationsEditorModel::clearModified()
{
beginResetModel();
- foreach (Collation* coll, collationList)
+ for (Collation* coll : collationList)
coll->modified = false;
listModified = false;
@@ -41,7 +41,7 @@ bool CollationsEditorModel::isModified() const
if (collationList != originalCollationList)
return true;
- foreach (Collation* coll, collationList)
+ for (Collation* coll : collationList)
{
if (coll->modified)
return true;
@@ -121,7 +121,7 @@ void CollationsEditorModel::setValid(int row, bool valid)
bool CollationsEditorModel::isValid() const
{
- foreach (Collation* coll, collationList)
+ for (Collation* coll : collationList)
{
if (!coll->valid)
return false;
@@ -133,13 +133,12 @@ void CollationsEditorModel::setData(const QList<CollationManager::CollationPtr>&
{
beginResetModel();
- Collation* collationPtr = nullptr;
- foreach (collationPtr, collationList)
+ for (Collation* collationPtr : collationList)
delete collationPtr;
collationList.clear();
- foreach (const CollationManager::CollationPtr& coll, collations)
+ for (const CollationManager::CollationPtr& coll : collations)
collationList << new Collation(coll);
listModified = false;
@@ -178,8 +177,7 @@ void CollationsEditorModel::deleteCollation(int row)
QList<CollationManager::CollationPtr> CollationsEditorModel::getCollations() const
{
QList<CollationManager::CollationPtr> results;
-
- foreach (Collation* coll, collationList)
+ for (Collation* coll : collationList)
results << coll->data;
return results;
@@ -188,7 +186,7 @@ QList<CollationManager::CollationPtr> CollationsEditorModel::getCollations() con
QStringList CollationsEditorModel::getCollationNames() const
{
QStringList names;
- foreach (Collation* coll, collationList)
+ for (Collation* coll : collationList)
names << coll->data->name;
return names;
@@ -199,7 +197,7 @@ void CollationsEditorModel::validateNames()
StrHash<QList<int>> counter;
int row = 0;
- foreach (Collation* coll, collationList)
+ for (Collation* coll : collationList)
{
coll->valid &= true;
counter[coll->data->name] << row++;
@@ -211,7 +209,7 @@ void CollationsEditorModel::validateNames()
cntIt.next();
if (cntIt.value().size() > 1)
{
- foreach (int cntRow, cntIt.value())
+ for (int cntRow : cntIt.value())
setValid(cntRow, false);
}
}
@@ -265,7 +263,7 @@ QVariant CollationsEditorModel::data(const QModelIndex& index, int role) const
void CollationsEditorModel::init()
{
- foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>())
langToIcon[plugin->getLanguage()] = QIcon(plugin->getIconPath());
}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h
index 0c17c5b..46f7ab5 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/collationseditormodel.h
@@ -13,7 +13,7 @@ class GUI_API_EXPORT CollationsEditorModel : public QAbstractListModel
public:
using QAbstractItemModel::setData;
- explicit CollationsEditorModel(QObject *parent = 0);
+ explicit CollationsEditorModel(QObject *parent = nullptr);
void clearModified();
bool isModified() const;
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp
index 2d8897b..1144fda 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/constrainttabmodel.cpp
@@ -16,7 +16,7 @@ int ConstraintTabModel::rowCount(const QModelIndex& parent) const
return 0;
int cnt = 0;
- foreach (SqliteCreateTable::Column* col, createTable->columns)
+ for (SqliteCreateTable::Column* col : createTable->columns)
cnt += col->constraints.size();
cnt += createTable->constraints.size();
@@ -36,9 +36,9 @@ QVariant ConstraintTabModel::data(const QModelIndex& index, int role) const
int constrIdx = index.row();
int currIdx = -1;
- foreach (SqliteCreateTable::Column* column, createTable->columns)
+ for (SqliteCreateTable::Column* column : createTable->columns)
{
- foreach (SqliteCreateTable::Column::Constraint* constr, column->constraints)
+ for (SqliteCreateTable::Column::Constraint* constr : column->constraints)
{
currIdx++;
@@ -47,7 +47,7 @@ QVariant ConstraintTabModel::data(const QModelIndex& index, int role) const
}
}
- foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ for (SqliteCreateTable::Constraint* constr : createTable->constraints)
{
currIdx++;
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp
index 15b49e5..795158a 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/ddlhistorywindow.cpp
@@ -76,7 +76,7 @@ void DdlHistoryWindow::activated(const QModelIndex& current, const QModelIndex&
QStringList contentEntries;
QList<Config::DdlHistoryEntryPtr> entries = CFG->getDdlHistoryFor(dbName, dbFile, date);
- foreach (Config::DdlHistoryEntryPtr entry, entries)
+ for (Config::DdlHistoryEntryPtr entry : entries)
{
contentEntries << templ.arg(entry->dbName).arg(entry->dbFile)
.arg(entry->timestamp.toString("yyyy-MM-dd HH:mm:ss"))
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp
index 56ab6fe..cd3e135 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.cpp
@@ -19,12 +19,14 @@
#include "parser/parser.h"
#include "dbobjectdialogs.h"
#include "dialogs/exportdialog.h"
+#include "themetuner.h"
+#include "dialogs/bindparamsdialog.h"
+#include "common/bindparam.h"
#include <QComboBox>
#include <QDebug>
#include <QStringListModel>
#include <QActionGroup>
#include <QMessageBox>
-#include <themetuner.h>
CFG_KEYS_DEFINE(EditorWindow)
EditorWindow::ResultsDisplayMode EditorWindow::resultsDisplayMode;
@@ -100,6 +102,7 @@ void EditorWindow::init()
createDbCombo();
initActions();
updateShortcutTips();
+ setupSqlHistoryMenu();
Db* treeSelectedDb = DBTREE->getSelectedOpenDb();
if (treeSelectedDb)
@@ -115,10 +118,12 @@ void EditorWindow::init()
// SQL history list
ui->historyList->setModel(CFG->getSqlHistoryModel());
+ ui->historyList->hideColumn(0);
ui->historyList->resizeColumnToContents(1);
connect(ui->historyList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
this, SLOT(historyEntrySelected(QModelIndex,QModelIndex)));
connect(ui->historyList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(historyEntryActivated(QModelIndex)));
+ connect(ui->historyList, &QWidget::customContextMenuRequested, this, &EditorWindow::sqlHistoryContextMenuRequested);
updateState();
}
@@ -390,6 +395,7 @@ void EditorWindow::createActions()
createAction(SHOW_PREV_TAB, tr("Show previous tab", "sql editor"), this, SLOT(showPrevTab()), this);
createAction(FOCUS_RESULTS_BELOW, tr("Focus results below", "sql editor"), this, SLOT(focusResultsBelow()), this);
createAction(FOCUS_EDITOR_ABOVE, tr("Focus SQL editor above", "sql editor"), this, SLOT(focusEditorAbove()), this);
+ createAction(DELETE_SINGLE_HISTORY_SQL, tr("Delete selected SQL history entries", "sql editor"), this, SLOT(deleteSelectedSqlHistory()), ui->historyList);
// Static action triggers
connect(staticActions[RESULTS_IN_TAB], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode()));
@@ -470,9 +476,15 @@ void EditorWindow::updateShortcutTips()
void EditorWindow::execQuery(bool explain)
{
QString sql = getQueryToExecute(true);
+ QHash<QString, QVariant> bindParams;
+ bool proceed = processBindParams(sql, bindParams);
+ if (!proceed)
+ return;
+
resultsModel->setDb(getCurrentDb());
resultsModel->setExplainMode(explain);
resultsModel->setQuery(sql);
+ resultsModel->setParams(bindParams);
resultsModel->setQueryCountLimitForSmartMode(queryLimitForSmartExecution);
ui->dataView->refreshData();
updateState();
@@ -490,6 +502,61 @@ void EditorWindow::explainQuery()
execQuery(true);
}
+bool EditorWindow::processBindParams(QString& sql, QHash<QString, QVariant>& queryParams)
+{
+ // Determin dialect
+ Dialect dialect = Dialect::Sqlite3;
+ Db* db = getCurrentDb();
+ if (db && db->isValid())
+ dialect = db->getDialect();
+
+ // Get all bind parameters from the query
+ TokenList tokens = Lexer::tokenize(sql, dialect);
+ TokenList bindTokens = tokens.filter(Token::BIND_PARAM);
+
+ // No bind tokens? Return fast.
+ if (bindTokens.isEmpty())
+ return true;
+
+ // Process bind tokens, prepare list for a dialog.
+ static_qstring(paramTpl, ":arg%1");
+ QString arg;
+ QVector<BindParam*> bindParams;
+ BindParam* bindParam = nullptr;
+ int i = 0;
+ for (const TokenPtr& token : bindTokens)
+ {
+ bindParam = new BindParam();
+ bindParam->position = i;
+ bindParam->originalName = token->value;
+ bindParam->newName = paramTpl.arg(i);
+ bindParams << bindParam;
+ i++;
+
+ token->value = bindParam->newName;
+ }
+
+ // Show dialog to query user for values
+ BindParamsDialog dialog(MAINWINDOW);
+ dialog.setBindParams(bindParams);
+ bool accepted = (dialog.exec() == QDialog::Accepted);
+
+ // Transfer values from dialog to arguments for query
+ if (accepted)
+ {
+ for (BindParam* bindParam : bindParams)
+ queryParams[bindParam->newName] = bindParam->value;
+
+ sql = tokens.detokenize();
+ }
+
+ // Cleanup
+ for (BindParam* bindParam : bindParams)
+ delete bindParam;
+
+ return accepted;
+}
+
void EditorWindow::dbChanged()
{
Db* currentDb = getCurrentDb();
@@ -597,17 +664,29 @@ void EditorWindow::focusEditorAbove()
void EditorWindow::historyEntrySelected(const QModelIndex& current, const QModelIndex& previous)
{
UNUSED(previous);
- QString sql = ui->historyList->model()->index(current.row(), 4).data().toString();
+ QString sql = ui->historyList->model()->index(current.row(), 5).data().toString();
ui->historyContents->setPlainText(sql);
}
void EditorWindow::historyEntryActivated(const QModelIndex& current)
{
- QString sql = ui->historyList->model()->index(current.row(), 4).data().toString();
+ QString sql = ui->historyList->model()->index(current.row(), 5).data().toString();
ui->sqlEdit->setPlainText(sql);
ui->tabWidget->setCurrentIndex(0);
}
+void EditorWindow::deleteSelectedSqlHistory()
+{
+ if (ui->historyList->selectionModel()->selectedIndexes().isEmpty())
+ return;
+
+ QList<qint64> ids;
+ for (const QModelIndex& idx : ui->historyList->selectionModel()->selectedRows(0))
+ ids += idx.data().toLongLong();
+
+ CFG->deleteSqlHistory(ids);
+}
+
void EditorWindow::clearHistory()
{
QMessageBox::StandardButton res = QMessageBox::question(this, tr("Clear execution history"), tr("Are you sure you want to erase the entire SQL execution history? "
@@ -618,6 +697,19 @@ void EditorWindow::clearHistory()
CFG->clearSqlHistory();
}
+void EditorWindow::sqlHistoryContextMenuRequested(const QPoint &pos)
+{
+ actionMap[DELETE_SINGLE_HISTORY_SQL]->setEnabled(!ui->historyList->selectionModel()->selectedIndexes().isEmpty());
+
+ sqlHistoryMenu->popup(ui->historyList->mapToGlobal(pos));
+}
+
+void EditorWindow::setupSqlHistoryMenu()
+{
+ sqlHistoryMenu = new QMenu(this);
+ sqlHistoryMenu->addAction(actionMap[DELETE_SINGLE_HISTORY_SQL]);
+}
+
void EditorWindow::exportResults()
{
if (!ExportManager::isAnyPluginAvailable())
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h
index 12486a8..296a9e2 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.h
@@ -24,14 +24,15 @@ class SqlQueryItem;
class SqlEditor;
CFG_KEY_LIST(EditorWindow, QObject::tr("SQL editor window"),
- CFG_KEY_ENTRY(EXEC_QUERY, Qt::Key_F9, QObject::tr("Execute query"))
- CFG_KEY_ENTRY(EXPLAIN_QUERY, Qt::Key_F8, QObject::tr("Execute \"%1\" query").arg("EXPLAIN"))
- CFG_KEY_ENTRY(PREV_DB, Qt::CTRL + Qt::Key_Up, QObject::tr("Switch current working database to previous on the list"))
- CFG_KEY_ENTRY(NEXT_DB, Qt::CTRL + Qt::Key_Down, QObject::tr("Switch current working database to next on the list"))
- CFG_KEY_ENTRY(SHOW_NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next editor tab"))
- CFG_KEY_ENTRY(SHOW_PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous editor tab"))
- CFG_KEY_ENTRY(FOCUS_RESULTS_BELOW, Qt::ALT + Qt::Key_PageDown, QObject::tr("Move keyboard input focus to the results view below"))
- CFG_KEY_ENTRY(FOCUS_EDITOR_ABOVE, Qt::ALT + Qt::Key_PageUp, QObject::tr("Move keyboard input focus to the SQL editor above"))
+ CFG_KEY_ENTRY(EXEC_QUERY, Qt::Key_F9, QObject::tr("Execute query"))
+ CFG_KEY_ENTRY(EXPLAIN_QUERY, Qt::Key_F8, QObject::tr("Execute \"%1\" query").arg("EXPLAIN"))
+ CFG_KEY_ENTRY(PREV_DB, Qt::CTRL + Qt::Key_Up, QObject::tr("Switch current working database to previous on the list"))
+ CFG_KEY_ENTRY(NEXT_DB, Qt::CTRL + Qt::Key_Down, QObject::tr("Switch current working database to next on the list"))
+ CFG_KEY_ENTRY(SHOW_NEXT_TAB, Qt::ALT + Qt::Key_Right, QObject::tr("Go to next editor tab"))
+ CFG_KEY_ENTRY(SHOW_PREV_TAB, Qt::ALT + Qt::Key_Left, QObject::tr("Go to previous editor tab"))
+ CFG_KEY_ENTRY(FOCUS_RESULTS_BELOW, Qt::ALT + Qt::Key_PageDown, QObject::tr("Move keyboard input focus to the results view below"))
+ CFG_KEY_ENTRY(FOCUS_EDITOR_ABOVE, Qt::ALT + Qt::Key_PageUp, QObject::tr("Move keyboard input focus to the SQL editor above"))
+ CFG_KEY_ENTRY(DELETE_SINGLE_HISTORY_SQL, QKeySequence::Delete, QObject::tr("Delete selected SQL history entries"))
)
class GUI_API_EXPORT EditorWindow : public MdiChild
@@ -61,7 +62,8 @@ class GUI_API_EXPORT EditorWindow : public MdiChild
FOCUS_EDITOR_ABOVE,
CLEAR_HISTORY,
EXPORT_RESULTS,
- CREATE_VIEW_FROM_QUERY
+ CREATE_VIEW_FROM_QUERY,
+ DELETE_SINGLE_HISTORY_SQL
};
enum ToolBar
@@ -114,6 +116,8 @@ class GUI_API_EXPORT EditorWindow : public MdiChild
void setupDefShortcuts();
void selectCurrentQuery(bool fallBackToPreviousIfNecessary = false);
void updateShortcutTips();
+ void setupSqlHistoryMenu();
+ bool processBindParams(QString& sql, QHash<QString, QVariant>& queryParams);
static const int queryLimitForSmartExecution = 100;
@@ -129,6 +133,7 @@ class GUI_API_EXPORT EditorWindow : public MdiChild
int sqlEditorNum = 1;
qint64 lastQueryHistoryId = 0;
QString lastSuccessfulQuery;
+ QMenu* sqlHistoryMenu = nullptr;
private slots:
void execQuery(bool explain = false);
@@ -146,7 +151,9 @@ class GUI_API_EXPORT EditorWindow : public MdiChild
void focusEditorAbove();
void historyEntrySelected(const QModelIndex& current, const QModelIndex& previous);
void historyEntryActivated(const QModelIndex& current);
+ void deleteSelectedSqlHistory();
void clearHistory();
+ void sqlHistoryContextMenuRequested(const QPoint &pos);
void exportResults();
void createViewFromQuery();
void updateState();
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui
index 924f895..f3f44e3 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/editorwindow.ui
@@ -51,6 +51,9 @@
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
+ <property name="lineWrapMode">
+ <enum>QPlainTextEdit::NoWrap</enum>
+ </property>
</widget>
<widget class="QWidget" name="resultsContainer" native="true">
<layout class="QVBoxLayout" name="verticalLayout_5">
@@ -118,11 +121,14 @@
<enum>Qt::Vertical</enum>
</property>
<widget class="QTableView" name="historyList">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
- <enum>QAbstractItemView::SingleSelection</enum>
+ <enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp
index 1bfd1f7..9894098 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.cpp
@@ -140,13 +140,13 @@ void FunctionsEditor::init()
model->setData(FUNCTIONS->getAllScriptFunctions());
// Language plugins
- foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>())
scriptingPlugins[plugin->getLanguage()] = plugin;
ui->langCombo->addItems(scriptingPlugins.keys());
// Syntax highlighting plugins
- foreach (SyntaxHighlighterPlugin* plugin, PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
+ for (SyntaxHighlighterPlugin* plugin : PLUGINS->getLoadedPlugins<SyntaxHighlighterPlugin>())
highlighterPlugins[plugin->getLanguageName()] = plugin;
updateState();
@@ -204,7 +204,7 @@ void FunctionsEditor::functionSelected(int row)
// Arguments
ui->argsList->clear();
QListWidgetItem* item = nullptr;
- foreach (const QString& arg, model->getArguments(row))
+ for (const QString& arg : model->getArguments(row))
{
item = new QListWidgetItem(arg);
item->setFlags(item->flags() | Qt::ItemIsEditable);
@@ -232,7 +232,7 @@ void FunctionsEditor::functionSelected(int row)
}
updatesForSelection = false;
- currentModified = false;
+ currentModified = model->isModified(row);
updateCurrentFunctionState();
}
@@ -606,7 +606,7 @@ void FunctionsEditor::applyFilter(const QString& value)
void FunctionsEditor::help()
{
- static const QString url = QStringLiteral("http://wiki.sqlitestudio.pl/index.php/User_Manual#Custom_SQL_functions");
+ static const QString url = QStringLiteral("https://github.com/pawelsalawa/sqlitestudio/wiki/User_Manual#custom-sql-functions");
QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui
index b99efe1..17c3859 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditor.ui
@@ -294,7 +294,11 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <widget class="QPlainTextEdit" name="initCodeEdit"/>
+ <widget class="QPlainTextEdit" name="initCodeEdit">
+ <property name="lineWrapMode">
+ <enum>QPlainTextEdit::NoWrap</enum>
+ </property>
+ </widget>
</item>
</layout>
</widget>
@@ -310,7 +314,11 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
- <widget class="QPlainTextEdit" name="mainCodeEdit"/>
+ <widget class="QPlainTextEdit" name="mainCodeEdit">
+ <property name="lineWrapMode">
+ <enum>QPlainTextEdit::NoWrap</enum>
+ </property>
+ </widget>
</item>
</layout>
</widget>
@@ -326,7 +334,11 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
- <widget class="QPlainTextEdit" name="finalCodeEdit"/>
+ <widget class="QPlainTextEdit" name="finalCodeEdit">
+ <property name="lineWrapMode">
+ <enum>QPlainTextEdit::NoWrap</enum>
+ </property>
+ </widget>
</item>
</layout>
</widget>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp
index cf7efdf..623ebd8 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/functionseditormodel.cpp
@@ -28,7 +28,7 @@ FunctionsEditorModel::FunctionsEditorModel(QObject *parent) :
void FunctionsEditorModel::clearModified()
{
beginResetModel();
- foreach (Function* func, functionList)
+ for (Function* func : functionList)
func->modified = false;
listModified = false;
@@ -42,7 +42,7 @@ bool FunctionsEditorModel::isModified() const
if (functionList != originalFunctionList)
return true;
- foreach (Function* func, functionList)
+ for (Function* func : functionList)
{
if (func->modified)
return true;
@@ -62,7 +62,7 @@ void FunctionsEditorModel::setModified(int row, bool modified)
bool FunctionsEditorModel::isValid() const
{
- foreach (Function* func, functionList)
+ for (Function* func : functionList)
{
if (!func->valid)
return false;
@@ -199,7 +199,7 @@ void FunctionsEditorModel::setData(const QList<FunctionManager::ScriptFunction*>
functionList.clear();
- foreach (FunctionManager::ScriptFunction* func, functions)
+ for (FunctionManager::ScriptFunction* func : functions)
functionList << new Function(func);
listModified = false;
@@ -239,7 +239,7 @@ QList<FunctionManager::ScriptFunction*> FunctionsEditorModel::generateFunctions(
{
QList<FunctionManager::ScriptFunction*> results;
- foreach (Function* func, functionList)
+ for (Function* func : functionList)
results << new FunctionManager::ScriptFunction(func->data);
return results;
@@ -248,7 +248,7 @@ QList<FunctionManager::ScriptFunction*> FunctionsEditorModel::generateFunctions(
QStringList FunctionsEditorModel::getFunctionNames() const
{
QStringList names;
- foreach (Function* func, functionList)
+ for (Function* func : functionList)
names << func->data.name;
return names;
@@ -259,7 +259,7 @@ void FunctionsEditorModel::validateNames()
StrHash<QList<int>> counter;
int row = 0;
- foreach (Function* func, functionList)
+ for (Function* func : functionList)
{
func->valid &= true;
counter[func->data.name] << row++;
@@ -271,7 +271,7 @@ void FunctionsEditorModel::validateNames()
cntIt.next();
if (cntIt.value().size() > 1)
{
- foreach (int cntRow, cntIt.value())
+ for (int cntRow : cntIt.value())
setValid(cntRow, false);
}
}
@@ -322,7 +322,7 @@ QVariant FunctionsEditorModel::data(const QModelIndex& index, int role) const
void FunctionsEditorModel::init()
{
- foreach (ScriptingPlugin* plugin, PLUGINS->getLoadedPlugins<ScriptingPlugin>())
+ for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>())
langToIcon[plugin->getLanguage()] = QIcon(plugin->getIconPath());
}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.cpp
new file mode 100644
index 0000000..ca45eff
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.cpp
@@ -0,0 +1,439 @@
+#include "sqliteextensioneditor.h"
+#include "sqliteextensioneditormodel.h"
+#include "ui_sqliteextensioneditor.h"
+#include "selectabledbmodel.h"
+#include "dbtree/dbtree.h"
+#include "dbtree/dbtreemodel.h"
+#include "common/unused.h"
+#include "uiutils.h"
+#include "uiconfig.h"
+#include "db/db.h"
+#include "services/dbmanager.h"
+#include "services/notifymanager.h"
+#include "common/lazytrigger.h"
+#include <QDesktopServices>
+#include <QFileDialog>
+#include <QSortFilterProxyModel>
+
+SqliteExtensionEditor::SqliteExtensionEditor(QWidget *parent) :
+ MdiChild(parent),
+ ui(new Ui::SqliteExtensionEditor)
+{
+ init();
+}
+
+SqliteExtensionEditor::~SqliteExtensionEditor()
+{
+ delete ui;
+ probingDb->closeQuiet();
+}
+
+bool SqliteExtensionEditor::restoreSessionNextTime()
+{
+ return false;
+}
+
+bool SqliteExtensionEditor::isUncommitted() const
+{
+ return model->isModified();
+}
+
+QString SqliteExtensionEditor::getQuitUncommittedConfirmMessage() const
+{
+ return tr("Extension manager window has uncommitted modifications.");
+}
+
+QVariant SqliteExtensionEditor::saveSession()
+{
+ return QVariant();
+}
+
+bool SqliteExtensionEditor::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+ return true;
+}
+
+Icon*SqliteExtensionEditor::getIconNameForMdiWindow()
+{
+ return ICONS.EXTENSION;
+}
+
+QString SqliteExtensionEditor::getTitleForMdiWindow()
+{
+ return tr("Extension manager");
+}
+
+void SqliteExtensionEditor::createActions()
+{
+ createAction(COMMIT, ICONS.COMMIT, tr("Commit all extension changes"), this, SLOT(commit()), ui->toolbar);
+ createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback all extension changes"), this, SLOT(rollback()), ui->toolbar);
+ ui->toolbar->addSeparator();
+ createAction(ADD, ICONS.EXTENSION_ADD, tr("Add new extension"), this, SLOT(newExtension()), ui->toolbar);
+ createAction(DELETE, ICONS.EXTENSION_DELETE, tr("Remove selected extension"), this, SLOT(deleteExtension()), ui->toolbar);
+ ui->toolbar->addSeparator();
+ createAction(HELP, ICONS.HELP, tr("Editing extensions manual"), this, SLOT(help()), ui->toolbar);
+}
+
+void SqliteExtensionEditor::setupDefShortcuts()
+{
+}
+
+QToolBar* SqliteExtensionEditor::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return ui->toolbar;
+}
+
+void SqliteExtensionEditor::init()
+{
+ ui->setupUi(this);
+ initActions();
+
+ statusUpdateTrigger = new LazyTrigger(500, this, SLOT(updateCurrentExtensionState()));
+
+ model = new SqliteExtensionEditorModel(this);
+ extensionFilterModel = new QSortFilterProxyModel(this);
+ extensionFilterModel->setSourceModel(model);
+ ui->extensionList->setModel(extensionFilterModel);
+
+ dbListModel = new SelectableDbModel(this);
+ dbListModel->setDisabledVersion(2);
+ dbListModel->setSourceModel(DBTREE->getModel());
+ ui->databaseList->setModel(dbListModel);
+ ui->databaseList->expandAll();
+
+ model->setData(SQLITE_EXTENSIONS->getAllExtensions());
+
+ connect(ui->extensionList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(extensionSelected(QItemSelection,QItemSelection)));
+ connect(ui->extensionList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateState()));
+ connect(ui->fileEdit, SIGNAL(textChanged(QString)), this, SLOT(updateModified()));
+ connect(ui->initEdit, SIGNAL(textChanged(QString)), this, SLOT(updateModified()));
+ connect(ui->allDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->selectedDatabasesRadio, SIGNAL(clicked()), this, SLOT(updateModified()));
+ connect(ui->fileBrowse, SIGNAL(clicked()), this, SLOT(browseForFile()));
+ connect(ui->fileEdit, SIGNAL(textChanged(QString)), statusUpdateTrigger, SLOT(schedule()));
+ connect(ui->fileEdit, SIGNAL(textChanged(QString)), this, SLOT(generateName()));
+ connect(ui->initEdit, SIGNAL(textChanged(QString)), statusUpdateTrigger, SLOT(schedule()));
+ connect(dbListModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateModified()));
+
+ probingDb = DBLIST->createInMemDb(true);
+ if (!probingDb->openQuiet())
+ qWarning() << "Could not open in-memory dtabase for Extension manager window. Probing files will be impossible.";
+
+ initStateForAll();
+ updateState();
+ updateCurrentExtensionState();
+}
+
+int SqliteExtensionEditor::getCurrentExtensionRow() const
+{
+ QModelIndexList idxList = ui->extensionList->selectionModel()->selectedIndexes();
+ if (idxList.size() == 0)
+ return -1;
+
+ return idxList.first().row();
+}
+
+void SqliteExtensionEditor::extensionDeselected(int row)
+{
+ statusUpdateTrigger->cancel();
+
+ model->setFilePath(row, ui->fileEdit->text());
+ model->setInitFunction(row, ui->initEdit->text());
+ model->setAllDatabases(row, ui->allDatabasesRadio->isChecked());
+ model->setModified(row, currentModified);
+
+ if (ui->selectedDatabasesRadio->isChecked())
+ model->setDatabases(row, getCurrentDatabases());
+
+ model->setValid(row, validateExtension(row));
+}
+
+void SqliteExtensionEditor::extensionSelected(int row)
+{
+ updatesForSelection = true;
+ ui->fileEdit->setText(model->getFilePath(row));
+ ui->initEdit->setText(model->getInitFunction(row));
+
+ // Databases
+ dbListModel->setDatabases(model->getDatabases(row));
+ ui->databaseList->expandAll();
+
+ if (model->getAllDatabases(row))
+ ui->allDatabasesRadio->setChecked(true);
+ else
+ ui->selectedDatabasesRadio->setChecked(true);
+
+ updatesForSelection = false;
+ currentModified = model->isModified(row);
+
+ updateCurrentExtensionState();
+}
+
+void SqliteExtensionEditor::clearEdits()
+{
+ ui->fileEdit->setText(QString::null);
+ ui->initEdit->setText(QString::null);
+ ui->allDatabasesRadio->setChecked(true);
+}
+
+void SqliteExtensionEditor::selectExtension(int row)
+{
+ if (!model->isValidRowIndex(row))
+ return;
+
+ ui->extensionList->selectionModel()->setCurrentIndex(model->index(row), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+QStringList SqliteExtensionEditor::getCurrentDatabases() const
+{
+ return dbListModel->getDatabases();
+}
+
+bool SqliteExtensionEditor::tryToLoad(const QString& filePath, const QString& initFunc, QString* resultError)
+{
+ if (!probingDb->isOpen())
+ {
+ qWarning() << "Probing database is closed. Cannot evaluate if file" << filePath << "is loadable.";
+ return true;
+ }
+
+ bool loadedOk = probingDb->loadExtension(filePath, initFunc.isEmpty() ? QString() : initFunc);
+ if (!loadedOk && resultError)
+ *resultError = probingDb->getErrorText();
+
+ probingDb->closeQuiet();
+ probingDb->openQuiet();
+
+ return loadedOk;
+}
+
+bool SqliteExtensionEditor::validateExtension(bool* fileOk, bool* initOk, QString* fileError)
+{
+ QString filePath = ui->fileEdit->text();
+ QString initFunc = ui->initEdit->text();
+ return validateExtension(filePath, initFunc, fileOk, initOk, fileError);
+}
+
+bool SqliteExtensionEditor::validateExtension(int row)
+{
+ QString filePath = model->getFilePath(row);
+ QString initFunc = model->getInitFunction(row);
+ return validateExtension(filePath, initFunc);
+}
+
+bool SqliteExtensionEditor::validateExtension(const QString& filePath, const QString& initFunc, bool* fileOk, bool* initOk, QString* fileError)
+{
+ bool localFileOk = true;
+ bool localInitOk = true;
+
+ QFileInfo fileInfo(filePath);
+ if (!fileInfo.exists() || !fileInfo.isReadable())
+ {
+ localFileOk = false;
+ *fileError = tr("File with given path does not exist or is not readable.");
+ }
+ else
+ localFileOk = tryToLoad(filePath, initFunc, fileError);
+
+ if (!localFileOk && fileError && fileError->isEmpty())
+ *fileError = tr("Unable to load extension: %1").arg(filePath);
+
+ static const QRegularExpression initFuncRegExp("^[a-zA-Z0-9_]*$");
+ localInitOk = initFuncRegExp.match(initFunc).hasMatch();
+
+ if (fileOk)
+ *fileOk = localFileOk;
+
+ if (initOk)
+ *initOk = localInitOk;
+
+ return localFileOk && localInitOk;
+}
+
+void SqliteExtensionEditor::initStateForAll()
+{
+ for (int i = 0, total = model->rowCount(); i < total; ++i)
+ {
+ model->setName(i, QFileInfo(model->getFilePath(i)).baseName());
+ model->setValid(i, validateExtension(i));
+ }
+}
+
+void SqliteExtensionEditor::help()
+{
+ static const QString url = QStringLiteral("https://github.com/pawelsalawa/sqlitestudio/wiki/User_Manual#sqlite-extensions");
+ QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode));
+}
+
+void SqliteExtensionEditor::commit()
+{
+ int row = getCurrentExtensionRow();
+ if (model->isValidRowIndex(row))
+ extensionDeselected(row);
+
+ QList<SqliteExtensionManager::ExtensionPtr> extensions = model->getExtensions();
+
+ SQLITE_EXTENSIONS->setExtensions(extensions);
+ model->clearModified();
+ currentModified = false;
+
+ if (model->isValidRowIndex(row))
+ selectExtension(row);
+
+ updateState();
+}
+
+void SqliteExtensionEditor::rollback()
+{
+ int selectedBefore = getCurrentExtensionRow();
+
+ model->setData(SQLITE_EXTENSIONS->getAllExtensions());
+ currentModified = false;
+ clearEdits();
+
+ if (model->isValidRowIndex(selectedBefore))
+ selectExtension(selectedBefore);
+
+ updateState();
+}
+
+void SqliteExtensionEditor::newExtension()
+{
+ model->addExtension(SqliteExtensionManager::ExtensionPtr::create());
+ selectExtension(model->rowCount() - 1);
+}
+
+void SqliteExtensionEditor::deleteExtension()
+{
+ nameGenerationActive = false;
+ int row = getCurrentExtensionRow();
+ model->deleteExtension(row);
+ clearEdits();
+
+ row = getCurrentExtensionRow();
+ if (model->isValidRowIndex(row))
+ extensionSelected(row);
+ else
+ updateCurrentExtensionState();
+
+ nameGenerationActive = true;
+ updateState();
+}
+
+void SqliteExtensionEditor::updateState()
+{
+ bool modified = model->isModified() || currentModified;
+ bool valid = model->isValid();
+
+ actionMap[COMMIT]->setEnabled(modified && valid);
+ actionMap[ROLLBACK]->setEnabled(modified);
+ actionMap[DELETE]->setEnabled(ui->extensionList->selectionModel()->selectedIndexes().size() > 0);
+ ui->databaseList->setEnabled(ui->selectedDatabasesRadio->isChecked());
+}
+
+void SqliteExtensionEditor::updateCurrentExtensionState()
+{
+ int row = getCurrentExtensionRow();
+ bool validRow = model->isValidRowIndex(row);
+ ui->rightWidget->setEnabled(validRow);
+ if (!validRow)
+ {
+ setValidState(ui->fileEdit, true);
+ setValidState(ui->initEdit, true);
+ return;
+ }
+
+ bool fileOk = true;
+ bool initOk = true;
+ QString fileError;
+ bool allOk = validateExtension(&fileOk, &initOk, &fileError);
+
+ // Display results
+ setValidState(ui->fileEdit, fileOk, fileError);
+ setValidState(ui->initEdit, initOk, tr("Invalid initialization function name. Function name can contain only alpha-numeric characters and underscore."));
+ ui->databasesGroup->setEnabled(allOk);
+ model->setValid(row, allOk);
+
+ updateState();
+}
+
+void SqliteExtensionEditor::extensionSelected(const QItemSelection& selected, const QItemSelection& deselected)
+{
+ int deselCnt = deselected.indexes().size();
+ int selCnt = selected.indexes().size();
+
+ if (deselCnt > 0)
+ extensionDeselected(deselected.indexes().first().row());
+
+ if (selCnt > 0)
+ extensionSelected(selected.indexes().first().row());
+
+ if (deselCnt > 0 && selCnt == 0)
+ {
+ currentModified = false;
+ clearEdits();
+ }
+}
+
+void SqliteExtensionEditor::updateModified()
+{
+ if (updatesForSelection)
+ return;
+
+ int row = getCurrentExtensionRow();
+ if (model->isValidRowIndex(row))
+ {
+ bool fileDiff = model->getFilePath(row) != ui->fileEdit->text();
+ bool initDiff = model->getInitFunction(row) != ui->initEdit->text();
+ bool allDatabasesDiff = model->getAllDatabases(row) != ui->allDatabasesRadio->isChecked();
+ bool dbDiff = getCurrentDatabases().toSet() != model->getDatabases(row).toSet(); // QSet to ignore order
+
+ currentModified = (fileDiff || initDiff || allDatabasesDiff || dbDiff);
+ }
+
+ statusUpdateTrigger->schedule();
+}
+
+void SqliteExtensionEditor::generateName()
+{
+ if (!nameGenerationActive)
+ return;
+
+ int row = getCurrentExtensionRow();
+ if (model->isValidRowIndex(row))
+ model->setName(row, QFileInfo(ui->fileEdit->text()).baseName());
+}
+
+void SqliteExtensionEditor::applyFilter(const QString& value)
+{
+ int row = getCurrentExtensionRow();
+ ui->extensionList->selectionModel()->clearSelection();
+
+ extensionFilterModel->setFilterFixedString(value);
+
+ selectExtension(row);
+}
+
+void SqliteExtensionEditor::browseForFile()
+{
+ QString dir = getFileDialogInitPath();
+ QString filter =
+#if defined(Q_OS_WIN)
+ tr("Dynamic link libraries (*.dll);;All files (*)");
+#elif defined(Q_OS_LINUX)
+ tr("Shared objects (*.so);;All files (*)");
+#elif defined(Q_OS_OSX)
+ tr("Dynamic libraries (*.dylib);;All files (*)");
+#else
+ tr("All files (*)");
+#endif
+ QString filePath = QFileDialog::getOpenFileName(this, tr("Open file"), dir, filter);
+ if (filePath.isNull())
+ return;
+
+ setFileDialogInitPathByFile(filePath);
+
+ ui->fileEdit->setText(filePath);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.h b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.h
new file mode 100644
index 0000000..c8ea3d0
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.h
@@ -0,0 +1,94 @@
+#ifndef SQLITEEXTENSIONEDITOR_H
+#define SQLITEEXTENSIONEDITOR_H
+
+#include "icon.h"
+#include "mdichild.h"
+#include <QItemSelection>
+#include <QWidget>
+
+namespace Ui {
+ class SqliteExtensionEditor;
+}
+
+class QToolBar;
+class SqliteExtensionEditorModel;
+class QSortFilterProxyModel;
+class SelectableDbModel;
+class Db;
+class LazyTrigger;
+
+class SqliteExtensionEditor : public MdiChild
+{
+ Q_OBJECT
+
+ public:
+ enum Action
+ {
+ COMMIT,
+ ROLLBACK,
+ ADD,
+ DELETE,
+ HELP
+ };
+
+ enum ToolBar
+ {
+ TOOLBAR
+ };
+
+ explicit SqliteExtensionEditor(QWidget *parent = nullptr);
+ ~SqliteExtensionEditor();
+
+ bool restoreSessionNextTime();
+ bool isUncommitted() const;
+ QString getQuitUncommittedConfirmMessage() const;
+
+ protected:
+ QVariant saveSession();
+ bool restoreSession(const QVariant &sessionValue);
+ Icon* getIconNameForMdiWindow();
+ QString getTitleForMdiWindow();
+ void createActions();
+ void setupDefShortcuts();
+ QToolBar* getToolBar(int toolbar) const;
+
+ private:
+ void init();
+ int getCurrentExtensionRow() const;
+ void extensionDeselected(int row);
+ void extensionSelected(int row);
+ void clearEdits();
+ void selectExtension(int row);
+ QStringList getCurrentDatabases() const;
+ bool tryToLoad(const QString& filePath, const QString& initFunc, QString* resultError);
+ bool validateExtension(bool* fileOk = nullptr, bool* initOk = nullptr, QString* fileError = nullptr);
+ bool validateExtension(int row);
+ bool validateExtension(const QString& filePath, const QString& initFunc, bool* fileOk = nullptr, bool* initOk = nullptr, QString* fileError = nullptr);
+ void initStateForAll();
+
+ Ui::SqliteExtensionEditor *ui;
+ SqliteExtensionEditorModel* model = nullptr;
+ QSortFilterProxyModel* extensionFilterModel = nullptr;
+ SelectableDbModel* dbListModel = nullptr;
+ bool currentModified = false;
+ bool updatesForSelection = false;
+ Db* probingDb = nullptr;
+ LazyTrigger* statusUpdateTrigger = nullptr;
+ bool nameGenerationActive = true;
+
+ private slots:
+ void help();
+ void commit();
+ void rollback();
+ void newExtension();
+ void deleteExtension();
+ void updateState();
+ void updateCurrentExtensionState();
+ void extensionSelected(const QItemSelection& selected, const QItemSelection& deselected);
+ void updateModified();
+ void generateName();
+ void applyFilter(const QString& value);
+ void browseForFile();
+};
+
+#endif // SQLITEEXTENSIONEDITOR_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.ui b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.ui
new file mode 100644
index 0000000..747aa7f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditor.ui
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SqliteExtensionEditor</class>
+ <widget class="QWidget" name="SqliteExtensionEditor">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>980</width>
+ <height>659</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string notr="true">Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QToolBar" name="toolbar"/>
+ </item>
+ <item>
+ <widget class="QWidget" name="mainWidgt" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QWidget" name="leftWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="lineEdit">
+ <property name="placeholderText">
+ <string>Filter extensions</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="extensionList"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="rightWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>4</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <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="widget" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item row="3" column="0">
+ <widget class="QLineEdit" name="initEdit">
+ <property name="placeholderText">
+ <string>Leave empty to use default function</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="fileEdit"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QToolButton" name="fileBrowse">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="../icons.qrc">
+ <normaloff>:/icons/img/directory_open.png</normaloff>:/icons/img/directory_open.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="fileLabel">
+ <property name="text">
+ <string>Extension file</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="initLabel">
+ <property name="text">
+ <string>Initialization function</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="databasesGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Databases</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QRadioButton" name="allDatabasesRadio">
+ <property name="text">
+ <string>Register in all databases</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="selectedDatabasesRadio">
+ <property name="text">
+ <string>Register in following databases:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="databaseList">
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditormodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditormodel.cpp
new file mode 100644
index 0000000..6e94a64
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditormodel.cpp
@@ -0,0 +1,234 @@
+#include "sqliteextensioneditormodel.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+
+#include <QFileInfo>
+
+#define SETTER(X, Y) \
+ if (!isValidRowIndex(row) || X == Y) \
+ return; \
+ \
+ X = Y; \
+ emitDataChanged(row);
+
+#define GETTER(X, Y) \
+ if (!isValidRowIndex(row)) \
+ return Y; \
+ \
+ return X;
+
+SqliteExtensionEditorModel::SqliteExtensionEditorModel(QObject* parent) :
+ QAbstractListModel(parent)
+{
+}
+
+void SqliteExtensionEditorModel::clearModified()
+{
+ beginResetModel();
+ for (Extension* ext : extensionList)
+ ext->modified = false;
+
+ listModified = false;
+ originalExtensionList = extensionList;
+ endResetModel();
+}
+
+bool SqliteExtensionEditorModel::isModified() const
+{
+ if (extensionList != originalExtensionList)
+ return true;
+
+ for (Extension* ext : extensionList)
+ {
+ if (ext->modified)
+ return true;
+ }
+ return false;
+}
+
+bool SqliteExtensionEditorModel::isModified(int row) const
+{
+ GETTER(extensionList[row]->modified, false);
+}
+
+void SqliteExtensionEditorModel::setModified(int row, bool modified)
+{
+ SETTER(extensionList[row]->modified, modified);
+}
+
+QString SqliteExtensionEditorModel::getName(int row) const
+{
+ GETTER(extensionList[row]->name, QString());
+}
+
+void SqliteExtensionEditorModel::setName(int row, const QString& name)
+{
+ SETTER(extensionList[row]->name, name);
+
+ QModelIndex idx = index(0);
+ emit dataChanged(idx, idx, {Qt::DisplayRole});
+}
+
+void SqliteExtensionEditorModel::setFilePath(int row, const QString& filePath)
+{
+ SETTER(extensionList[row]->data->filePath, filePath);
+}
+
+QString SqliteExtensionEditorModel::getFilePath(int row) const
+{
+ GETTER(extensionList[row]->data->filePath, QString());
+}
+
+void SqliteExtensionEditorModel::setInitFunction(int row, const QString& initFunc)
+{
+ SETTER(extensionList[row]->data->initFunc, initFunc);
+}
+
+QString SqliteExtensionEditorModel::getInitFunction(int row) const
+{
+ GETTER(extensionList[row]->data->initFunc, QString());
+}
+
+void SqliteExtensionEditorModel::setAllDatabases(int row, bool allDatabases)
+{
+ SETTER(extensionList[row]->data->allDatabases, allDatabases);
+}
+
+bool SqliteExtensionEditorModel::getAllDatabases(int row) const
+{
+ GETTER(extensionList[row]->data->allDatabases, true);
+}
+
+void SqliteExtensionEditorModel::setDatabases(int row, const QStringList& databases)
+{
+ SETTER(extensionList[row]->data->databases, databases);
+}
+
+QStringList SqliteExtensionEditorModel::getDatabases(int row)
+{
+ GETTER(extensionList[row]->data->databases, QStringList());
+}
+
+bool SqliteExtensionEditorModel::isValid(int row) const
+{
+ GETTER(extensionList[row]->valid, true);
+}
+
+void SqliteExtensionEditorModel::setValid(int row, bool valid)
+{
+ SETTER(extensionList[row]->valid, valid);
+
+ QModelIndex idx = index(0);
+ emit dataChanged(idx, idx, {Qt::DecorationRole});
+}
+
+bool SqliteExtensionEditorModel::isValid() const
+{
+ for (Extension* ext : extensionList)
+ {
+ if (!ext->valid)
+ return false;
+ }
+ return true;
+}
+
+void SqliteExtensionEditorModel::setData(const QList<SqliteExtensionManager::ExtensionPtr>& extensions)
+{
+ beginResetModel();
+
+ for (Extension* extPtr : extensionList)
+ delete extPtr;
+
+ extensionList.clear();
+
+ for (const SqliteExtensionManager::ExtensionPtr& ext : extensions)
+ extensionList << new Extension(ext);
+
+ listModified = false;
+ originalExtensionList = extensionList;
+
+ endResetModel();
+}
+
+void SqliteExtensionEditorModel::addExtension(const SqliteExtensionManager::ExtensionPtr& extension)
+{
+ int row = extensionList.size();
+
+ beginInsertRows(QModelIndex(), row, row);
+
+ extensionList << new Extension(extension);
+ listModified = true;
+
+ endInsertRows();
+}
+
+void SqliteExtensionEditorModel::deleteExtension(int row)
+{
+ if (!isValidRowIndex(row))
+ return;
+
+ beginRemoveRows(QModelIndex(), row, row);
+
+ delete extensionList[row];
+ extensionList.removeAt(row);
+
+ listModified = true;
+
+ endRemoveRows();
+}
+
+QList<SqliteExtensionManager::ExtensionPtr> SqliteExtensionEditorModel::getExtensions() const
+{
+ QList<SqliteExtensionManager::ExtensionPtr> results;
+ for (Extension* ext : extensionList)
+ results << ext->data;
+
+ return results;
+}
+
+bool SqliteExtensionEditorModel::isValidRowIndex(int row) const
+{
+ return (row >= 0 && row < extensionList.size());
+}
+
+int SqliteExtensionEditorModel::rowCount(const QModelIndex& parent) const
+{
+ UNUSED(parent);
+ return extensionList.size();
+}
+
+QVariant SqliteExtensionEditorModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid() || !isValidRowIndex(index.row()))
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ return getName(index.row());
+
+ if (role == Qt::DecorationRole)
+ {
+ QIcon icon = ICONS.EXTENSION;
+ if (!isValid(index.row()))
+ icon = Icon::merge(icon, Icon::ERROR);
+
+ return icon;
+ }
+
+ return QVariant();
+}
+
+void SqliteExtensionEditorModel::emitDataChanged(int row)
+{
+ QModelIndex idx = index(row);
+ emit dataChanged(idx, idx);
+}
+
+SqliteExtensionEditorModel::Extension::Extension()
+{
+ data = SqliteExtensionManager::ExtensionPtr::create();
+}
+
+SqliteExtensionEditorModel::Extension::Extension(const SqliteExtensionManager::ExtensionPtr& other)
+{
+ data = SqliteExtensionManager::ExtensionPtr::create(*other);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditormodel.h b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditormodel.h
new file mode 100644
index 0000000..085edd9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/sqliteextensioneditormodel.h
@@ -0,0 +1,65 @@
+#ifndef SQLITEEXTENSIONEDITORMODEL_H
+#define SQLITEEXTENSIONEDITORMODEL_H
+
+#include "guiSQLiteStudio_global.h"
+#include "services/sqliteextensionmanager.h"
+#include <QIcon>
+#include <QHash>
+#include <QAbstractListModel>
+
+class GUI_API_EXPORT SqliteExtensionEditorModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ public:
+ using QAbstractItemModel::setData;
+
+ explicit SqliteExtensionEditorModel(QObject* parent = nullptr);
+
+ void clearModified();
+ bool isModified() const;
+ bool isModified(int row) const;
+ void setModified(int row, bool modified);
+ QString getName(int row) const;
+ void setName(int row, const QString& name);
+ void setFilePath(int row, const QString& filePath);
+ QString getFilePath(int row) const;
+ void setInitFunction(int row, const QString& initFunc);
+ QString getInitFunction(int row) const;
+ void setAllDatabases(int row, bool allDatabases);
+ bool getAllDatabases(int row) const;
+ void setDatabases(int row, const QStringList& databases);
+ QStringList getDatabases(int row);
+ bool isValid(int row) const;
+ void setValid(int row, bool valid);
+ bool isValid() const;
+ void setData(const QList<SqliteExtensionManager::ExtensionPtr>& extensions);
+ void addExtension(const SqliteExtensionManager::ExtensionPtr& extension);
+ void deleteExtension(int row);
+ QList<SqliteExtensionManager::ExtensionPtr> getExtensions() const;
+ bool isValidRowIndex(int row) const;
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+
+ private:
+ struct Extension
+ {
+ Extension();
+ Extension(const SqliteExtensionManager::ExtensionPtr& other);
+
+ SqliteExtensionManager::ExtensionPtr data;
+ QString name;
+ bool modified = false;
+ bool valid = true;
+ };
+
+ void emitDataChanged(int row);
+
+ QList<Extension*> extensionList;
+ QList<Extension*> originalExtensionList;
+ QHash<QString, QIcon> langToIcon;
+ bool listModified = false;
+};
+
+#endif // SQLITEEXTENSIONEDITORMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp
index 850d8a7..0f95a98 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tableconstraintsmodel.cpp
@@ -395,7 +395,7 @@ QString TableConstraintsModel::getConstrDetails(SqliteCreateTable::Constraint* c
void TableConstraintsModel::columnRenamed(SqliteCreateTable::Constraint* constr, const QString& oldColumn, const QString& newColumn)
{
- foreach (SqliteIndexedColumn* idxCol, constr->indexedColumns)
+ for (SqliteIndexedColumn* idxCol : constr->indexedColumns)
{
if (idxCol->name.compare(oldColumn, Qt::CaseInsensitive) == 0)
{
@@ -444,7 +444,7 @@ void TableConstraintsModel::columnModified(const QString& oldColumn, SqliteCreat
return;
int idx = 0;
- foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ for (SqliteCreateTable::Constraint* constr : createTable->constraints)
{
if (constr->doesAffectColumn(oldColumn))
{
@@ -461,7 +461,7 @@ void TableConstraintsModel::columnDeleted(const QString& column)
{
QList<int> toDelete;
int idx = 0;
- foreach (SqliteCreateTable::Constraint* constr, createTable->constraints)
+ for (SqliteCreateTable::Constraint* constr : createTable->constraints)
{
if (constr->doesAffectColumn(column))
{
@@ -477,6 +477,6 @@ void TableConstraintsModel::columnDeleted(const QString& column)
idx++;
}
- foreach (int idx, toDelete)
+ for (int idx : toDelete)
delConstraint(idx);
}
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp
index 1c817de..62b6613 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablestructuremodel.cpp
@@ -283,7 +283,7 @@ void TableStructureModel::moveColumnTo(int colIdx, int newIdx)
QModelIndex TableStructureModel::findColumn(const QString& columnName, Qt::CaseSensitivity cs) const
{
int row = 0;
- foreach (SqliteCreateTable::Column* col, createTable->columns)
+ for (SqliteCreateTable::Column* col : createTable->columns)
{
if (col->name.compare(columnName, cs) == 0)
return createIndex(row, 0);
@@ -432,7 +432,7 @@ bool TableStructureModel::isColumnPk(SqliteCreateTable::Column* column) const
return true;
QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY);
- foreach (SqliteCreateTable::Constraint* constr, constraints)
+ for (SqliteCreateTable::Constraint* constr : constraints)
if (constr->doesAffectColumn(column->name))
return true;
@@ -445,7 +445,7 @@ bool TableStructureModel::isColumnFk(SqliteCreateTable::Column* column) const
return true;
QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::FOREIGN_KEY);
- foreach (SqliteCreateTable::Constraint* constr, constraints)
+ for (SqliteCreateTable::Constraint* constr : constraints)
if (constr->doesAffectColumn(column->name))
return true;
@@ -458,7 +458,7 @@ bool TableStructureModel::isColumnUnique(SqliteCreateTable::Column* column) cons
return true;
QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::UNIQUE);
- foreach (SqliteCreateTable::Constraint* constr, constraints)
+ for (SqliteCreateTable::Constraint* constr : constraints)
if (constr->doesAffectColumn(column->name))
return true;
@@ -471,7 +471,7 @@ bool TableStructureModel::isColumnCheck(SqliteCreateTable::Column* column) const
return true;
QList<SqliteCreateTable::Constraint*> constraints = createTable->getConstraints(SqliteCreateTable::Constraint::CHECK);
- foreach (SqliteCreateTable::Constraint* constr, constraints)
+ for (SqliteCreateTable::Constraint* constr : constraints)
if (constr->expr->getContextColumns(false).contains(column->name, Qt::CaseInsensitive))
return true;
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp
index 286aad7..526ae1b 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/tablewindow.cpp
@@ -338,10 +338,10 @@ void TableWindow::executeStructureChanges()
MessageListDialog dialog(tr("Following problems will take place while modifying the table.\n"
"Would you like to proceed?", "table window"));
dialog.setWindowTitle(tr("Table modification", "table window"));
- foreach (const QString& error, tableModifier->getErrors())
+ for (const QString& error : tableModifier->getErrors())
dialog.addError(error);
- foreach (const QString& warn, tableModifier->getWarnings())
+ for (const QString& warn : tableModifier->getWarnings())
dialog.addWarning(warn);
if (dialog.exec() != QDialog::Accepted)
@@ -844,9 +844,9 @@ void TableWindow::changesSuccessfullyCommitted()
tableModifier->getModifiedTriggers(),
tableModifier->getModifiedViews()
};
- foreach (const QStringList& objList, modifiedObjects)
+ for (const QStringList& objList : modifiedObjects)
{
- foreach (const QString& obj, objList)
+ for (const QString& obj : objList)
{
if (obj.compare(oldTable, Qt::CaseInsensitive) == 0)
continue;
@@ -996,7 +996,7 @@ bool TableWindow::validate(bool skipWarning)
hasPk = true;
SqliteCreateTable::Column::Constraint* colConstraint = nullptr;
- foreach (SqliteCreateTable::Column* column, createTable->columns)
+ for (SqliteCreateTable::Column* column : createTable->columns)
{
colConstraint = column->getConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY);
if (colConstraint)
@@ -1112,8 +1112,8 @@ void TableWindow::updateDdlTab()
void TableWindow::updateNewTableState()
{
- for (int i = 1; i < 5; i++)
- ui->tabWidget->setTabEnabled(i, existingTable);
+ for (QWidget* tab : {ui->dataTab, ui->constraintsTab, ui->indexesTab, ui->triggersTab})
+ ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), existingTable);
actionMap[EXPORT]->setEnabled(existingTable);
actionMap[IMPORT]->setEnabled(existingTable);
@@ -1466,7 +1466,7 @@ void TableWindow::updateIndexes()
QTableWidgetItem* item = nullptr;
int row = 0;
- foreach (SqliteCreateIndexPtr index, indexes)
+ for (SqliteCreateIndexPtr index : indexes)
{
item = new QTableWidgetItem(index->index);
item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
@@ -1518,7 +1518,7 @@ void TableWindow::updateTriggers()
QTableWidgetItem* item = nullptr;
QString timeAndEvent;
int row = 0;
- foreach (SqliteCreateTriggerPtr trig, triggers)
+ for (SqliteCreateTriggerPtr trig : triggers)
{
item = new QTableWidgetItem(trig->trigger);
item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp
index be805af..c7ec7d8 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.cpp
@@ -259,6 +259,13 @@ void ViewWindow::init()
updateQueryToolbarStatus();
updateTriggersState();
updateColumnButtons();
+ updateAfterInit();
+}
+
+void ViewWindow::updateAfterInit()
+{
+ for (QWidget* tab : {ui->dataTab, ui->triggersTab})
+ ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), existingView);
}
void ViewWindow::newView()
@@ -624,6 +631,7 @@ void ViewWindow::changesSuccessfullyCommitted()
initView();
updateQueryToolbarStatus();
updateWindowTitle();
+ updateAfterInit();
if (oldView.compare(view, Qt::CaseInsensitive) == 0)
notifyInfo(tr("Committed changes for view '%1' successfully.").arg(view));
@@ -874,7 +882,7 @@ void ViewWindow::refreshTriggers()
QTableWidgetItem* item = nullptr;
QString event;
int row = 0;
- foreach (SqliteCreateTriggerPtr trig, triggers)
+ for (SqliteCreateTriggerPtr trig : triggers)
{
item = new QTableWidgetItem(trig->trigger);
item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
@@ -1017,10 +1025,10 @@ void ViewWindow::executeStructureChanges()
MessageListDialog dialog(tr("Following problems will take place while modifying the view.\n"
"Would you like to proceed?", "view window"));
dialog.setWindowTitle(tr("View modification", "view window"));
- foreach (const QString& error, viewModifier->getErrors())
+ for (const QString& error : viewModifier->getErrors())
dialog.addError(error);
- foreach (const QString& warn, viewModifier->getWarnings())
+ for (const QString& warn : viewModifier->getWarnings())
dialog.addWarning(warn);
if (dialog.exec() != QDialog::Accepted)
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h
index 6144ad8..1a8b5b3 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.h
@@ -96,6 +96,7 @@ class GUI_API_EXPORT ViewWindow : public MdiChild
private:
void init();
+ void updateAfterInit();
void newView();
void initView();
void setupCoverWidget();
diff --git a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui
index 9112280..8c17205 100644
--- a/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui
+++ b/SQLiteStudio3/guiSQLiteStudio/windows/viewwindow.ui
@@ -29,7 +29,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
- <number>2</number>
+ <number>0</number>
</property>
<widget class="QWidget" name="queryTab">
<attribute name="title">
@@ -143,7 +143,11 @@
</item>
</layout>
</widget>
- <widget class="SqlEditor" name="queryEdit"/>
+ <widget class="SqlEditor" name="queryEdit">
+ <property name="lineWrapMode">
+ <enum>QPlainTextEdit::NoWrap</enum>
+ </property>
+ </widget>
</widget>
</item>
</layout>