From 229bb1c6535a466f535ee10835a4047a6ecca80f Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Mon, 18 Dec 2017 09:21:26 +0100 Subject: [PATCH 01/11] - [NEW] Add new interface and functionality to manage database file extension. (Implements feature request #659) --- src/FileDialog.cpp | 10 +++ src/FileDialog.h | 5 +- src/FileExtensionManager.cpp | 109 +++++++++++++++++++++++++ src/FileExtensionManager.h | 30 +++++++ src/FileExtensionManager.ui | 153 +++++++++++++++++++++++++++++++++++ src/PreferencesDialog.cpp | 13 +++ src/PreferencesDialog.h | 4 + src/PreferencesDialog.ui | 18 ++++- src/src.pro | 9 ++- 9 files changed, 342 insertions(+), 9 deletions(-) create mode 100644 src/FileExtensionManager.cpp create mode 100644 src/FileExtensionManager.h create mode 100644 src/FileExtensionManager.ui diff --git a/src/FileDialog.cpp b/src/FileDialog.cpp index e7d4b188..983f568b 100644 --- a/src/FileDialog.cpp +++ b/src/FileDialog.cpp @@ -70,3 +70,13 @@ void FileDialog::setFileDialogPath(const QString& new_path) break; // Do nothing } } + +QString FileDialog::getSqlDatabaseFileFilter() +{ + QString extensions = Settings::getValue("General", "DBFileExtensions").toString(); + if ( extensions.isEmpty() ) + { + extensions = QObject::tr("SQLite database files (*.db *.sqlite *.sqlite3 *.db3);;All files (*)"); + } + return extensions; +} diff --git a/src/FileDialog.h b/src/FileDialog.h index 7b19bcaf..d23a8dd1 100644 --- a/src/FileDialog.h +++ b/src/FileDialog.h @@ -20,10 +20,7 @@ public: static QString getExistingDirectory(QWidget* parent = nullptr, const QString& caption = QString(), Options options = 0); - static QString getSqlDatabaseFileFilter() - { - return QObject::tr("SQLite database files (*.db *.sqlite *.sqlite3 *.db3);;All files (*)"); - } + static QString getSqlDatabaseFileFilter(); private: static QString getFileDialogPath(); diff --git a/src/FileExtensionManager.cpp b/src/FileExtensionManager.cpp new file mode 100644 index 00000000..7efb388e --- /dev/null +++ b/src/FileExtensionManager.cpp @@ -0,0 +1,109 @@ +#include "FileExtensionManager.h" +#include "ui_FileExtensionManager.h" + +#include "FileDialog.h" + +FileExtensionManager::FileExtensionManager( QWidget *parent ) : + QDialog(parent), + ui(new Ui::FileExtensionManager) +{ + ui->setupUi(this); + + QStringList currentList = FileDialog::getSqlDatabaseFileFilter().split(";;"); + int i = 0; + foreach( QString itemString, currentList ) + { + ui->tableExtensions->insertRow( i ); + QString description = itemString.left( itemString.indexOf('(') ).trimmed(); + QString extension = itemString; + extension = extension.remove ( 0, itemString.indexOf('(')+1 ).remove(')').simplified().trimmed(); + QTableWidgetItem *newItemDescription = new QTableWidgetItem( description ); + QTableWidgetItem *newItemExtension = new QTableWidgetItem( extension ); + ui->tableExtensions->setItem( i, 0, newItemDescription ); + ui->tableExtensions->setItem( i, 1, newItemExtension ); + i++; + } + + connect( ui->buttonAdd, SIGNAL(clicked(bool)), this, SLOT(addItem()) ); + connect( ui->buttonRemove, SIGNAL(clicked(bool)), this, SLOT(removeItem()) ); + + connect( ui->buttonDown, SIGNAL(clicked(bool)), this, SLOT(downItem()) ); + connect( ui->buttonUp, SIGNAL(clicked(bool)), this, SLOT(upItem()) ); +} + +FileExtensionManager::~FileExtensionManager() +{ + delete ui; +} + +void FileExtensionManager::addItem() +{ + int i = ui->tableExtensions->rowCount(); + ui->tableExtensions->insertRow( i ); + QTableWidgetItem *newItemDescription = new QTableWidgetItem( "Description" ); + QTableWidgetItem *newItemExtension = new QTableWidgetItem( ".extension" ); + ui->tableExtensions->setItem( i, 0, newItemDescription ); + ui->tableExtensions->setItem( i, 1, newItemExtension ); +} + +void FileExtensionManager::removeItem() +{ + QList selectedRows; + foreach ( QTableWidgetItem *item, ui->tableExtensions->selectedItems() ) + { + if ( selectedRows.contains(item->row()) == false ) + { + selectedRows.append(item->row()); + } + } + + qSort(selectedRows); + + for ( int i = selectedRows.size()-1; i >= 0; --i ) + { + ui->tableExtensions->removeRow( selectedRows[i] ); + } +} + +void FileExtensionManager::upItem() +{ + if ( ui->tableExtensions->selectedItems().isEmpty() ) return; + + int selectedRow = ui->tableExtensions->selectedItems().first()->row(); + + QTableWidgetItem *t1, *t2; + t1 = ui->tableExtensions->takeItem( selectedRow, 0 ); + t2 = ui->tableExtensions->takeItem( selectedRow, 1 ); + ui->tableExtensions->removeRow(selectedRow); + ui->tableExtensions->insertRow( selectedRow-1 ); + ui->tableExtensions->setItem( selectedRow-1, 0, t1 ); + ui->tableExtensions->setItem( selectedRow-1, 1, t2 ); + ui->tableExtensions->selectRow( selectedRow-1 ); +} + +void FileExtensionManager::downItem() +{ + if ( ui->tableExtensions->selectedItems().isEmpty() ) return; + + int selectedRow = ui->tableExtensions->selectedItems().first()->row(); + + QTableWidgetItem *t1, *t2; + t1 = ui->tableExtensions->takeItem( selectedRow, 0 ); + t2 = ui->tableExtensions->takeItem( selectedRow, 1 ); + ui->tableExtensions->removeRow( selectedRow ); + ui->tableExtensions->insertRow( selectedRow+1 ); + ui->tableExtensions->setItem( selectedRow+1, 0, t1 ); + ui->tableExtensions->setItem( selectedRow+1, 1, t2 ); + ui->tableExtensions->selectRow( selectedRow+1 ); +} + +QStringList FileExtensionManager::getDBFileExtesions() +{ + QStringList result; + for ( int i = 0; i < ui->tableExtensions->rowCount(); ++i ) + { + result.append( QString( "%1 (%2)").arg(ui->tableExtensions->item(i, 0)->text()).arg(ui->tableExtensions->item(i, 1)->text()) ); + } + return result; +} + diff --git a/src/FileExtensionManager.h b/src/FileExtensionManager.h new file mode 100644 index 00000000..ae6babac --- /dev/null +++ b/src/FileExtensionManager.h @@ -0,0 +1,30 @@ +#ifndef FILEEXTENSIONMANAGER_H +#define FILEEXTENSIONMANAGER_H + +#include + +namespace Ui { +class FileExtensionManager; +} + +class FileExtensionManager : public QDialog +{ + Q_OBJECT + +public: + explicit FileExtensionManager(QWidget *parent = 0); + ~FileExtensionManager(); + + QStringList getDBFileExtesions(); + +private: + Ui::FileExtensionManager *ui; + +public slots: + void addItem(); + void removeItem(); + void upItem(); + void downItem(); +}; + +#endif // FILEEXTENSIONMANAGER_H diff --git a/src/FileExtensionManager.ui b/src/FileExtensionManager.ui new file mode 100644 index 00000000..681e8a25 --- /dev/null +++ b/src/FileExtensionManager.ui @@ -0,0 +1,153 @@ + + + FileExtensionManager + + + + 0 + 0 + 578 + 463 + + + + Dialog + + + + + + + + &Up + + + + :/icons/up:/icons/up + + + + + + + &Down + + + + :/icons/down:/icons/down + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Add + + + + :/icons/field_add:/icons/field_add + + + + + + + &Remove + + + + :/icons/field_delete:/icons/field_delete + + + + + + + + + QAbstractScrollArea::AdjustToContents + + + true + + + 100 + + + 100 + + + + Description + + + + + Extensions + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + FileExtensionManager + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FileExtensionManager + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index a80edacf..240bd539 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -5,6 +5,7 @@ #include "Application.h" #include "MainWindow.h" #include "RemoteDatabase.h" +#include "FileExtensionManager.h" #include #include @@ -277,6 +278,8 @@ void PreferencesDialog::saveSettings() Settings::setValue("General", "language", newLanguage); Settings::setValue("General", "toolbarStyle", ui->toolbarStyleComboBox->currentIndex()); + Settings::setValue("General", "DBFileExtensions", m_dbFileExtensions.join(";;") ); + accept(); } @@ -561,3 +564,13 @@ void PreferencesDialog::updatePreviewFont() ui->txtBlob->setFont(textFont); } } + +void PreferencesDialog::on_buttonManageFileExtension_clicked() +{ + FileExtensionManager *manager = new FileExtensionManager( this ); + if ( manager->exec() == QDialog::Accepted ) + { + m_dbFileExtensions = manager->getDBFileExtesions(); + } + delete manager; +} diff --git a/src/PreferencesDialog.h b/src/PreferencesDialog.h index 94b32eb2..eba8cbc3 100644 --- a/src/PreferencesDialog.h +++ b/src/PreferencesDialog.h @@ -36,9 +36,13 @@ private slots: void chooseRemoteCloneDirectory(); void updatePreviewFont(); + void on_buttonManageFileExtension_clicked(); + private: Ui::PreferencesDialog *ui; + QStringList m_dbFileExtensions; + void fillLanguageBox(); void loadColorSetting(QFrame *frame, const QString &name); void setColorSetting(QFrame *frame, const QColor &color); diff --git a/src/PreferencesDialog.ui b/src/PreferencesDialog.ui index bb2519f6..af4468ba 100644 --- a/src/PreferencesDialog.ui +++ b/src/PreferencesDialog.ui @@ -6,7 +6,7 @@ 0 0 - 597 + 758 614 @@ -216,6 +216,20 @@ + + + + DB file extension + + + + + + + Manage + + + @@ -256,7 +270,7 @@ Open databases with foreign keys enabled. - &Foreign keys + Foreign &keys foreignKeysCheckBox diff --git a/src/src.pro b/src/src.pro index 70c08e01..16f6d66a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -61,7 +61,8 @@ HEADERS += \ RemotePushDialog.h \ jsontextedit.h \ FindReplaceDialog.h \ - ExtendedScintilla.h + ExtendedScintilla.h \ + FileExtensionManager.h SOURCES += \ sqlitedb.cpp \ @@ -100,7 +101,8 @@ SOURCES += \ RemotePushDialog.cpp \ jsontextedit.cpp \ FindReplaceDialog.cpp \ - ExtendedScintilla.cpp + ExtendedScintilla.cpp \ + FileExtensionManager.cpp RESOURCES += icons/icons.qrc \ translations/flags/flags.qrc \ @@ -124,7 +126,8 @@ FORMS += \ PlotDock.ui \ RemoteDock.ui \ RemotePushDialog.ui \ - FindReplaceDialog.ui + FindReplaceDialog.ui \ + FileExtensionManager.ui TRANSLATIONS += \ translations/sqlb_ar_SA.ts \ From c709abb9dd85c8264dfe582de431ca1b58e55637 Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Mon, 18 Dec 2017 11:13:56 +0100 Subject: [PATCH 02/11] - [FIX] Fixed CMake file to src.pro --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b85e12a..c53666bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,7 @@ set(SQLB_MOC_HDR src/RemotePushDialog.h src/FindReplaceDialog.h src/ExtendedScintilla.h + src/FileExtensionManager.h ) set(SQLB_SRC @@ -163,6 +164,7 @@ set(SQLB_SRC src/RemotePushDialog.cpp src/FindReplaceDialog.cpp src/ExtendedScintilla.cpp + src/FileExtensionManager.cpp ) set(SQLB_FORMS @@ -183,6 +185,7 @@ set(SQLB_FORMS src/RemoteDock.ui src/RemotePushDialog.ui src/FindReplaceDialog.ui + src/FileExtensionManager.ui ) set(SQLB_RESOURCES From 438ec222b18cadb296a71116131854e3dea31408 Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Tue, 19 Dec 2017 10:15:46 +0100 Subject: [PATCH 03/11] - Applied changes requested by mgrojo to uniform code with sqlitebrowser standards - Add "history" when closing editor window, but reopen before closing preferences - Revert some changes done by QtCreator --- src/FileDialog.cpp | 7 +--- src/FileExtensionManager.cpp | 81 ++++++++++++++++++------------------ src/FileExtensionManager.h | 4 +- src/PreferencesDialog.cpp | 14 ++++--- src/PreferencesDialog.ui | 6 +-- src/Settings.cpp | 3 ++ 6 files changed, 58 insertions(+), 57 deletions(-) diff --git a/src/FileDialog.cpp b/src/FileDialog.cpp index 983f568b..3a59e553 100644 --- a/src/FileDialog.cpp +++ b/src/FileDialog.cpp @@ -73,10 +73,5 @@ void FileDialog::setFileDialogPath(const QString& new_path) QString FileDialog::getSqlDatabaseFileFilter() { - QString extensions = Settings::getValue("General", "DBFileExtensions").toString(); - if ( extensions.isEmpty() ) - { - extensions = QObject::tr("SQLite database files (*.db *.sqlite *.sqlite3 *.db3);;All files (*)"); - } - return extensions; + return Settings::getValue("General", "DBFileExtensions").toString(); } diff --git a/src/FileExtensionManager.cpp b/src/FileExtensionManager.cpp index 7efb388e..defd866a 100644 --- a/src/FileExtensionManager.cpp +++ b/src/FileExtensionManager.cpp @@ -3,32 +3,31 @@ #include "FileDialog.h" -FileExtensionManager::FileExtensionManager( QWidget *parent ) : +FileExtensionManager::FileExtensionManager(QStringList init, QWidget *parent) : QDialog(parent), ui(new Ui::FileExtensionManager) { ui->setupUi(this); - QStringList currentList = FileDialog::getSqlDatabaseFileFilter().split(";;"); int i = 0; - foreach( QString itemString, currentList ) + foreach(QString itemString, init) { - ui->tableExtensions->insertRow( i ); - QString description = itemString.left( itemString.indexOf('(') ).trimmed(); + ui->tableExtensions->insertRow(i); + QString description = itemString.left(itemString.indexOf('(')).trimmed(); QString extension = itemString; - extension = extension.remove ( 0, itemString.indexOf('(')+1 ).remove(')').simplified().trimmed(); - QTableWidgetItem *newItemDescription = new QTableWidgetItem( description ); - QTableWidgetItem *newItemExtension = new QTableWidgetItem( extension ); - ui->tableExtensions->setItem( i, 0, newItemDescription ); - ui->tableExtensions->setItem( i, 1, newItemExtension ); + extension = extension.remove (0, itemString.indexOf('(')+1).remove(')').simplified().trimmed(); + QTableWidgetItem *newItemDescription = new QTableWidgetItem(description); + QTableWidgetItem *newItemExtension = new QTableWidgetItem(extension); + ui->tableExtensions->setItem(i, 0, newItemDescription); + ui->tableExtensions->setItem(i, 1, newItemExtension); i++; } - connect( ui->buttonAdd, SIGNAL(clicked(bool)), this, SLOT(addItem()) ); - connect( ui->buttonRemove, SIGNAL(clicked(bool)), this, SLOT(removeItem()) ); + connect(ui->buttonAdd, SIGNAL(clicked(bool)), this, SLOT(addItem())); + connect(ui->buttonRemove, SIGNAL(clicked(bool)), this, SLOT(removeItem())); - connect( ui->buttonDown, SIGNAL(clicked(bool)), this, SLOT(downItem()) ); - connect( ui->buttonUp, SIGNAL(clicked(bool)), this, SLOT(upItem()) ); + connect(ui->buttonDown, SIGNAL(clicked(bool)), this, SLOT(downItem())); + connect(ui->buttonUp, SIGNAL(clicked(bool)), this, SLOT(upItem())); } FileExtensionManager::~FileExtensionManager() @@ -39,19 +38,19 @@ FileExtensionManager::~FileExtensionManager() void FileExtensionManager::addItem() { int i = ui->tableExtensions->rowCount(); - ui->tableExtensions->insertRow( i ); - QTableWidgetItem *newItemDescription = new QTableWidgetItem( "Description" ); - QTableWidgetItem *newItemExtension = new QTableWidgetItem( ".extension" ); - ui->tableExtensions->setItem( i, 0, newItemDescription ); - ui->tableExtensions->setItem( i, 1, newItemExtension ); + ui->tableExtensions->insertRow(i); + QTableWidgetItem *newItemDescription = new QTableWidgetItem("Description"); + QTableWidgetItem *newItemExtension = new QTableWidgetItem("*.extension"); + ui->tableExtensions->setItem(i, 0, newItemDescription); + ui->tableExtensions->setItem(i, 1, newItemExtension); } void FileExtensionManager::removeItem() { QList selectedRows; - foreach ( QTableWidgetItem *item, ui->tableExtensions->selectedItems() ) + foreach (QTableWidgetItem *item, ui->tableExtensions->selectedItems()) { - if ( selectedRows.contains(item->row()) == false ) + if (selectedRows.contains(item->row()) == false) { selectedRows.append(item->row()); } @@ -59,50 +58,50 @@ void FileExtensionManager::removeItem() qSort(selectedRows); - for ( int i = selectedRows.size()-1; i >= 0; --i ) + for (int i = selectedRows.size()-1; i >= 0; --i) { - ui->tableExtensions->removeRow( selectedRows[i] ); + ui->tableExtensions->removeRow(selectedRows[i]); } } void FileExtensionManager::upItem() { - if ( ui->tableExtensions->selectedItems().isEmpty() ) return; + if (ui->tableExtensions->selectedItems().isEmpty()) return; int selectedRow = ui->tableExtensions->selectedItems().first()->row(); QTableWidgetItem *t1, *t2; - t1 = ui->tableExtensions->takeItem( selectedRow, 0 ); - t2 = ui->tableExtensions->takeItem( selectedRow, 1 ); + t1 = ui->tableExtensions->takeItem(selectedRow, 0); + t2 = ui->tableExtensions->takeItem(selectedRow, 1); ui->tableExtensions->removeRow(selectedRow); - ui->tableExtensions->insertRow( selectedRow-1 ); - ui->tableExtensions->setItem( selectedRow-1, 0, t1 ); - ui->tableExtensions->setItem( selectedRow-1, 1, t2 ); - ui->tableExtensions->selectRow( selectedRow-1 ); + ui->tableExtensions->insertRow(selectedRow-1); + ui->tableExtensions->setItem(selectedRow-1, 0, t1); + ui->tableExtensions->setItem(selectedRow-1, 1, t2); + ui->tableExtensions->selectRow(selectedRow-1); } void FileExtensionManager::downItem() { - if ( ui->tableExtensions->selectedItems().isEmpty() ) return; + if (ui->tableExtensions->selectedItems().isEmpty()) return; int selectedRow = ui->tableExtensions->selectedItems().first()->row(); QTableWidgetItem *t1, *t2; - t1 = ui->tableExtensions->takeItem( selectedRow, 0 ); - t2 = ui->tableExtensions->takeItem( selectedRow, 1 ); - ui->tableExtensions->removeRow( selectedRow ); - ui->tableExtensions->insertRow( selectedRow+1 ); - ui->tableExtensions->setItem( selectedRow+1, 0, t1 ); - ui->tableExtensions->setItem( selectedRow+1, 1, t2 ); - ui->tableExtensions->selectRow( selectedRow+1 ); + t1 = ui->tableExtensions->takeItem(selectedRow, 0); + t2 = ui->tableExtensions->takeItem(selectedRow, 1); + ui->tableExtensions->removeRow(selectedRow); + ui->tableExtensions->insertRow(selectedRow+1); + ui->tableExtensions->setItem(selectedRow+1, 0, t1); + ui->tableExtensions->setItem(selectedRow+1, 1, t2); + ui->tableExtensions->selectRow(selectedRow+1); } -QStringList FileExtensionManager::getDBFileExtesions() +QStringList FileExtensionManager::getDBFileExtensions() { QStringList result; - for ( int i = 0; i < ui->tableExtensions->rowCount(); ++i ) + for (int i = 0; i < ui->tableExtensions->rowCount(); ++i) { - result.append( QString( "%1 (%2)").arg(ui->tableExtensions->item(i, 0)->text()).arg(ui->tableExtensions->item(i, 1)->text()) ); + result.append(QString("%1 (%2)").arg(ui->tableExtensions->item(i, 0)->text()).arg(ui->tableExtensions->item(i, 1)->text())); } return result; } diff --git a/src/FileExtensionManager.h b/src/FileExtensionManager.h index ae6babac..379c89e4 100644 --- a/src/FileExtensionManager.h +++ b/src/FileExtensionManager.h @@ -12,10 +12,10 @@ class FileExtensionManager : public QDialog Q_OBJECT public: - explicit FileExtensionManager(QWidget *parent = 0); + explicit FileExtensionManager(QStringList init, QWidget *parent = 0); ~FileExtensionManager(); - QStringList getDBFileExtesions(); + QStringList getDBFileExtensions(); private: Ui::FileExtensionManager *ui; diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index 240bd539..ce13752c 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -15,7 +15,8 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : QDialog(parent), - ui(new Ui::PreferencesDialog) + ui(new Ui::PreferencesDialog), + m_dbFileExtensions(FileDialog::getSqlDatabaseFileFilter().split(";;")) { ui->setupUi(this); ui->treeSyntaxHighlighting->setColumnHidden(0, true); @@ -47,6 +48,9 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) PreferencesDialog::~PreferencesDialog() { delete ui; + + if(m_manager != NULL) + delete m_manager; } void PreferencesDialog::chooseLocation() @@ -567,10 +571,10 @@ void PreferencesDialog::updatePreviewFont() void PreferencesDialog::on_buttonManageFileExtension_clicked() { - FileExtensionManager *manager = new FileExtensionManager( this ); - if ( manager->exec() == QDialog::Accepted ) + FileExtensionManager *manager = new FileExtensionManager(m_dbFileExtensions, this); + + if(manager->exec() == QDialog::Accepted) { - m_dbFileExtensions = manager->getDBFileExtesions(); + m_dbFileExtensions = manager->getDBFileExtensions(); } - delete manager; } diff --git a/src/PreferencesDialog.ui b/src/PreferencesDialog.ui index af4468ba..1d4d5998 100644 --- a/src/PreferencesDialog.ui +++ b/src/PreferencesDialog.ui @@ -6,7 +6,7 @@ 0 0 - 758 + 597 614 @@ -219,7 +219,7 @@ - DB file extension + DB file extensions @@ -270,7 +270,7 @@ Open databases with foreign keys enabled. - Foreign &keys + &Foreign keys foreignKeysCheckBox diff --git a/src/Settings.cpp b/src/Settings.cpp index 6d6efbaa..5084c855 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -152,6 +152,9 @@ QVariant Settings::getDefaultValue(const QString& group, const QString& name) if(group == "General" && name == "toolbarStyle") return static_cast(Qt::ToolButtonTextBesideIcon); + if(group == "General" && name == "DBFileExtensions") + return QObject::tr("SQLite database files (*.db *.sqlite *.sqlite3 *.db3);;All files (*)"); + // checkversion group? if(group == "checkversion") { From c135e6ba45f13b63bf881bc2e20c6e6a8fb4f86d Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Tue, 19 Dec 2017 10:29:58 +0100 Subject: [PATCH 04/11] Refuse from previous commit --- src/PreferencesDialog.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index ce13752c..1ff59270 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -48,9 +48,6 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) PreferencesDialog::~PreferencesDialog() { delete ui; - - if(m_manager != NULL) - delete m_manager; } void PreferencesDialog::chooseLocation() From cc2fb78e7ebd9ad9e23a4b3987f03d7cf0c12e96 Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Mon, 8 Jan 2018 13:53:51 +0100 Subject: [PATCH 05/11] Additional changes requested by MKleusberg: - [CHG] Always add "All files (*)" to filters - [FIX] Removed unused include --- src/FileDialog.cpp | 2 +- src/FileExtensionManager.cpp | 17 +++++++++-------- src/FileExtensionManager.h | 2 +- src/Settings.cpp | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/FileDialog.cpp b/src/FileDialog.cpp index 3a59e553..a8cb3d91 100644 --- a/src/FileDialog.cpp +++ b/src/FileDialog.cpp @@ -73,5 +73,5 @@ void FileDialog::setFileDialogPath(const QString& new_path) QString FileDialog::getSqlDatabaseFileFilter() { - return Settings::getValue("General", "DBFileExtensions").toString(); + return Settings::getValue("General", "DBFileExtensions").toString() + ";;" + QObject::tr("All files (*)"); //Always add "All files (*)" to the available filters } diff --git a/src/FileExtensionManager.cpp b/src/FileExtensionManager.cpp index defd866a..d385aa21 100644 --- a/src/FileExtensionManager.cpp +++ b/src/FileExtensionManager.cpp @@ -1,8 +1,6 @@ #include "FileExtensionManager.h" #include "ui_FileExtensionManager.h" -#include "FileDialog.h" - FileExtensionManager::FileExtensionManager(QStringList init, QWidget *parent) : QDialog(parent), ui(new Ui::FileExtensionManager) @@ -12,15 +10,18 @@ FileExtensionManager::FileExtensionManager(QStringList init, QWidget *parent) : int i = 0; foreach(QString itemString, init) { - ui->tableExtensions->insertRow(i); QString description = itemString.left(itemString.indexOf('(')).trimmed(); QString extension = itemString; extension = extension.remove (0, itemString.indexOf('(')+1).remove(')').simplified().trimmed(); - QTableWidgetItem *newItemDescription = new QTableWidgetItem(description); - QTableWidgetItem *newItemExtension = new QTableWidgetItem(extension); - ui->tableExtensions->setItem(i, 0, newItemDescription); - ui->tableExtensions->setItem(i, 1, newItemExtension); - i++; + if ( extension.compare("*") != 0 ) //We exclude "All files" from the table + { + QTableWidgetItem *newItemDescription = new QTableWidgetItem(description); + QTableWidgetItem *newItemExtension = new QTableWidgetItem(extension); + ui->tableExtensions->insertRow(i); + ui->tableExtensions->setItem(i, 0, newItemDescription); + ui->tableExtensions->setItem(i, 1, newItemExtension); + i++; + } } connect(ui->buttonAdd, SIGNAL(clicked(bool)), this, SLOT(addItem())); diff --git a/src/FileExtensionManager.h b/src/FileExtensionManager.h index 379c89e4..3b6e9597 100644 --- a/src/FileExtensionManager.h +++ b/src/FileExtensionManager.h @@ -12,7 +12,7 @@ class FileExtensionManager : public QDialog Q_OBJECT public: - explicit FileExtensionManager(QStringList init, QWidget *parent = 0); + explicit FileExtensionManager(QStringList init, QWidget *parent = nullptr); ~FileExtensionManager(); QStringList getDBFileExtensions(); diff --git a/src/Settings.cpp b/src/Settings.cpp index 5084c855..43ea4c48 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -153,7 +153,7 @@ QVariant Settings::getDefaultValue(const QString& group, const QString& name) return static_cast(Qt::ToolButtonTextBesideIcon); if(group == "General" && name == "DBFileExtensions") - return QObject::tr("SQLite database files (*.db *.sqlite *.sqlite3 *.db3);;All files (*)"); + return QObject::tr("SQLite database files (*.db *.sqlite *.sqlite3 *.db3)"); // checkversion group? if(group == "checkversion") From fec9e74025dea6cd3ced3bad1910a3a367ce864a Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Mon, 8 Jan 2018 14:00:05 +0100 Subject: [PATCH 06/11] merged from master --- BUILDING.md | 5 +- CMakeLists.txt | 6 +- src/AboutDialog.ui | 2 +- src/Application.cpp | 6 +- src/Data.cpp | 59 +++++++++++++ src/Data.h | 20 +++++ src/DbStructureModel.cpp | 63 +++++++++----- src/EditDialog.cpp | 26 ++++-- src/EditDialog.h | 1 + src/ExtendedScintilla.cpp | 2 +- src/ExtendedTableWidget.cpp | 163 +++++++++++++++++++++++++----------- src/ExtendedTableWidget.h | 17 ++++ src/FindReplaceDialog.ui | 2 +- src/MainWindow.cpp | 40 ++++++++- src/MainWindow.h | 2 + src/MainWindow.ui | 57 +++++++++---- src/PlotDock.cpp | 75 +++++++++++++---- src/PreferencesDialog.cpp | 3 + src/PreferencesDialog.ui | 116 ++++++++++++++++--------- src/Settings.cpp | 4 + src/SqlExecutionArea.ui | 2 +- src/SqlUiLexer.cpp | 8 +- src/sqlitedb.cpp | 1 - src/sqlitetablemodel.cpp | 9 +- src/sqlitetypes.cpp | 26 ++++++ src/sqlitetypes.h | 6 ++ src/src.pro | 6 +- src/tests/CMakeLists.txt | 4 + src/tests/TestRegex.cpp | 13 +++ 29 files changed, 567 insertions(+), 177 deletions(-) create mode 100644 src/Data.cpp create mode 100644 src/Data.h diff --git a/BUILDING.md b/BUILDING.md index dccf83ad..9df79344 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -54,12 +54,13 @@ The same process works for building the code in any platform supported by Qt ### Ubuntu Linux ```bash -$ sudo apt install build-essential git cmake libsqlite3-dev qt5-default qttools5-dev-tools +$ sudo apt install build-essential git cmake libsqlite3-dev qt5-default qttools5-dev-tools \ + libsqlcipher-dev $ git clone https://github.com/sqlitebrowser/sqlitebrowser $ cd sqlitebrowser $ mkdir build $ cd build -$ cmake -Wno-dev .. +$ cmake -Dsqlcipher=1 -Wno-dev .. $ make $ sudo make install ``` diff --git a/CMakeLists.txt b/CMakeLists.txt index c53666bf..62f4ed8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,7 @@ set(SQLB_HDR src/grammar/sqlite3TokenTypes.hpp src/grammar/Sqlite3Lexer.hpp src/grammar/Sqlite3Parser.hpp + src/Data.h ) set(SQLB_MOC_HDR @@ -164,7 +165,8 @@ set(SQLB_SRC src/RemotePushDialog.cpp src/FindReplaceDialog.cpp src/ExtendedScintilla.cpp - src/FileExtensionManager.cpp + src/FileExtensionManager.cpp + src/Data.cpp ) set(SQLB_FORMS @@ -185,7 +187,7 @@ set(SQLB_FORMS src/RemoteDock.ui src/RemotePushDialog.ui src/FindReplaceDialog.ui - src/FileExtensionManager.ui + src/FileExtensionManager.ui ) set(SQLB_RESOURCES diff --git a/src/AboutDialog.ui b/src/AboutDialog.ui index adeacc95..e2844807 100644 --- a/src/AboutDialog.ui +++ b/src/AboutDialog.ui @@ -99,7 +99,7 @@ - <html><head/><body><p>DB Browser for SQLite is an open source, freeware visual tool used to create, design and edit SQLite database files.</p><p>It is bi-licensed under the Mozilla Public License Version 2, as well as the GNU General Public License Version 3 or later. You can modify or redistribute it under the conditions of these licenses.</p><p>See <a href="http://www.gnu.org/licenses/gpl.html"><span style=" text-decoration: underline; color:#0000ff;">http://www.gnu.org/licenses/gpl.html</span></a> and <a href="https://www.mozilla.org/MPL/2.0/index.txt"><span style=" text-decoration: underline; color:#0000ff;">https://www.mozilla.org/MPL/2.0/index.txt</span></a> for details.</p><p>For more information on this program please visit our website at: <a href="http://sqlitebrowser.org"><span style=" text-decoration: underline; color:#0000ff;">http://sqlitebrowser.org</span></a></p><p><span style=" font-size:small;">This software uses the GPL/LGPL Qt Toolkit from </span><a href="http://qt-project.org/"><span style=" font-size:small; text-decoration: underline; color:#0000ff;">http://qt-project.org/</span></a><span style=" font-size:small;"><br/>See </span><a href="http://qt-project.org/doc/qt-5/licensing.html"><span style=" font-size:small; text-decoration: underline; color:#0000ff;">http://qt-project.org/doc/qt-5/licensing.html</span></a><span style=" font-size:small;"> for licensing terms and information.</span></p><p><span style=" font-size:small;">It also uses the Silk icon set by Mark James licensed under a Creative Commons Attribution 2.5 and 3.0 license.<br/>See </span><a href="http://www.famfamfam.com/lab/icons/silk/"><span style=" font-size:small; text-decoration: underline; color:#0000ff;">http://www.famfamfam.com/lab/icons/silk/</span></a><span style=" font-size:small;"> for details.</span></p></body></html> + <html><head/><body><p>DB Browser for SQLite is an open source, freeware visual tool used to create, design and edit SQLite database files.</p><p>It is bi-licensed under the Mozilla Public License Version 2, as well as the GNU General Public License Version 3 or later. You can modify or redistribute it under the conditions of these licenses.</p><p>See <a href="http://www.gnu.org/licenses/gpl.html">http://www.gnu.org/licenses/gpl.html</a> and <a href="https://www.mozilla.org/MPL/2.0/index.txt">https://www.mozilla.org/MPL/2.0/index.txt</a> for details.</p><p>For more information on this program please visit our website at: <a href="http://sqlitebrowser.org">http://sqlitebrowser.org</a></p><p><span style=" font-size:small;">This software uses the GPL/LGPL Qt Toolkit from </span><a href="http://qt-project.org/"><span style=" font-size:small;">http://qt-project.org/</span></a><span style=" font-size:small;"><br/>See </span><a href="http://qt-project.org/doc/qt-5/licensing.html"><span style=" font-size:small;">http://qt-project.org/doc/qt-5/licensing.html</span></a><span style=" font-size:small;"> for licensing terms and information.</span></p><p><span style=" font-size:small;">It also uses the Silk icon set by Mark James licensed under a Creative Commons Attribution 2.5 and 3.0 license.<br/>See </span><a href="http://www.famfamfam.com/lab/icons/silk/"><span style=" font-size:small;">http://www.famfamfam.com/lab/icons/silk/</span></a><span style=" font-size:small;"> for details.</span></p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop diff --git a/src/Application.cpp b/src/Application.cpp index 5b07774e..1c590063 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -75,6 +75,7 @@ Application::Application(int& argc, char** argv) : QString fileToOpen; QString tableToBrowse; QStringList sqlToExecute; + bool readOnly = false; m_dontShowMainWindow = false; for(int i=1;ifileOpen(fileToOpen)) + if(m_mainWindow->fileOpen(fileToOpen, false, readOnly)) { // If database could be opened run the SQL scripts for(const QString& f : sqlToExecute) diff --git a/src/Data.cpp b/src/Data.cpp new file mode 100644 index 00000000..e3a5e491 --- /dev/null +++ b/src/Data.cpp @@ -0,0 +1,59 @@ +#include "Data.h" + +#include + +// Note that these aren't all possible BOMs. But they are probably the most common ones. +// The size is needed at least for the ones with character zero in them. +static const QByteArray bom3("\xEF\xBB\xBF", 3); +static const QByteArray bom2a("\xFE\xFF", 2); +static const QByteArray bom2b("\xFF\xFE", 2); +static const QByteArray bom4a("\x00\x00\xFE\xFF", 4); +static const QByteArray bom4b("\xFF\xFE\x00\x00", 4); + +bool isTextOnly(QByteArray data, const QString& encoding, bool quickTest) +{ + // If the data starts with a Unicode BOM, we always assume it is text + if(startsWithBom(data)) + return true; + + // Truncate to the first couple of bytes for quick testing + if(quickTest) + data = data.left(512); + + // Convert to Unicode if necessary + if(!encoding.isEmpty()) + data = QTextCodec::codecForName(encoding.toUtf8())->toUnicode(data).toUtf8(); + + // Perform check + return QString(data).toUtf8() == data; +} + +bool startsWithBom(const QByteArray& data) +{ + if(data.startsWith(bom3) || + data.startsWith(bom2a) || data.startsWith(bom2b) || + data.startsWith(bom4a) || data.startsWith(bom4b)) + return true; + else + return false; +} + +QByteArray removeBom(QByteArray& data) +{ + if(data.startsWith(bom3)) + { + QByteArray bom = data.left(3); + data.remove(0, 3); + return bom; + } else if(data.startsWith(bom2a) || data.startsWith(bom2b)) { + QByteArray bom = data.left(2); + data.remove(0, 2); + return bom; + } else if(data.startsWith(bom4a) || data.startsWith(bom4b)) { + QByteArray bom = data.left(4); + data.remove(0, 4); + return bom; + } else { + return QByteArray(); + } +} diff --git a/src/Data.h b/src/Data.h new file mode 100644 index 00000000..2cbf9ddc --- /dev/null +++ b/src/Data.h @@ -0,0 +1,20 @@ +#ifndef DATA_H +#define DATA_H + +#include + +// This returns false if the data in the data parameter contains binary data. If it is text only, the function returns +// true. If the second parameter is specified, it will be used to convert the data from the given encoding to Unicode +// before doing the check. The third parameter can be used to only check the first couple of bytes which speeds up the +// text but makes it less reliable +bool isTextOnly(QByteArray data, const QString& encoding = QString(), bool quickTest = false); + +// This function returns true if the data in the data parameter starts with a Unicode BOM. Otherwise it returns false. +bool startsWithBom(const QByteArray& data); + +// This function checks if the data in the data parameter starts with a Unicode BOM. If so, the BOM is removed from the +// byte array and passed back to the caller separately as the return value of the function. If the data does not start +// with a BOM an empty byte array is returned and the original data is not modified. +QByteArray removeBom(QByteArray& data); + +#endif diff --git a/src/DbStructureModel.cpp b/src/DbStructureModel.cpp index 9bb1d2f4..73fdf1cc 100644 --- a/src/DbStructureModel.cpp +++ b/src/DbStructureModel.cpp @@ -66,9 +66,9 @@ Qt::ItemFlags DbStructureModel::flags(const QModelIndex &index) const // All items are enabled and selectable Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; - // Only enable dragging for entire table objects + // Only enable dragging for entire table objects and for fields (composition in SQL text editor) QString type = data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString(); - if(type == "table" || type == "view" || type == "index" || type == "trigger") + if(type == "table" || type == "field" || type == "view" || type == "index" || type == "trigger") flags |= Qt::ItemIsDragEnabled; return flags; @@ -190,32 +190,43 @@ QStringList DbStructureModel::mimeTypes() const QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const { + // We store the SQL data and the names data separately + QByteArray sqlData, namesData; + // Loop through selected indices - QByteArray d; for(const QModelIndex& index : indices) { - // Only export data for valid indices and only for the SQL column, i.e. only once per row - if(index.isValid() && index.column() == ColumnSQL) - { - // Add the SQL code used to create the object - d = d.append(data(index, Qt::DisplayRole).toString() + ";\n"); + // Only export data for valid indices and only once per row (SQL column or Name column). + // For names, export an escaped identifier of the item for statement composition in SQL editor. + // Commas are included for a list of identifiers. + if(index.isValid()) { + QString objectType = data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString(); - // If it is a table also add the content - if(data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString() == "table") + if(index.column() == ColumnName) + namesData.append(sqlb::escapeIdentifier(data(index, Qt::DisplayRole).toString()) + ", "); + + if(objectType != "field" && index.column() == ColumnSQL) { - SqliteTableModel tableModel(m_db); - sqlb::ObjectIdentifier objid(data(index.sibling(index.row(), ColumnSchema), Qt::DisplayRole).toString(), - data(index.sibling(index.row(), ColumnName), Qt::DisplayRole).toString()); - tableModel.setTable(objid); - tableModel.waitForFetchingFinished(); - for(int i=0; i < tableModel.rowCount(); ++i) + // Add the SQL code used to create the object + sqlData.append(data(index, Qt::DisplayRole).toString() + ";\n"); + + // If it is a table also add the content + if(objectType == "table") { - QString insertStatement = "INSERT INTO " + objid.toString() + " VALUES("; - for(int j=1; j < tableModel.columnCount(); ++j) - insertStatement += QString("'%1',").arg(tableModel.data(tableModel.index(i, j)).toString()); - insertStatement.chop(1); - insertStatement += ");\n"; - d = d.append(insertStatement); + SqliteTableModel tableModel(m_db); + sqlb::ObjectIdentifier objid(data(index.sibling(index.row(), ColumnSchema), Qt::DisplayRole).toString(), + data(index.sibling(index.row(), ColumnName), Qt::DisplayRole).toString()); + tableModel.setTable(objid); + tableModel.waitForFetchingFinished(); + for(int i=0; i < tableModel.rowCount(); ++i) + { + QString insertStatement = "INSERT INTO " + objid.toString() + " VALUES("; + for(int j=1; j < tableModel.columnCount(); ++j) + insertStatement += QString("'%1',").arg(tableModel.data(tableModel.index(i, j), Qt::EditRole).toString()); + insertStatement.chop(1); + insertStatement += ");\n"; + sqlData.append(insertStatement); + } } } } @@ -224,7 +235,13 @@ QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const // Create the MIME data object QMimeData* mime = new QMimeData(); mime->setProperty("db_file", m_db.currentFile()); // Also save the file name to avoid dropping an object on the same database as it comes from - mime->setData("text/plain", d); + // When we have both SQL and Names data (probable row selection mode) we give precedence to the SQL data + if (sqlData.length() == 0 && namesData.length() > 0) { + // Remove last ", " + namesData.chop(2); + mime->setData("text/plain", namesData); + } else + mime->setData("text/plain", sqlData); return mime; } diff --git a/src/EditDialog.cpp b/src/EditDialog.cpp index 8fd73e30..2a56b3ab 100644 --- a/src/EditDialog.cpp +++ b/src/EditDialog.cpp @@ -4,6 +4,7 @@ #include "Settings.h" #include "src/qhexedit.h" #include "FileDialog.h" +#include "Data.h" #include #include @@ -92,6 +93,9 @@ void EditDialog::loadData(const QByteArray& data) QImage img; QString textData; + // Clear previously removed BOM + removedBom.clear(); + // Determine the data type, saving that info in the class variable dataType = checkDataType(data); @@ -149,25 +153,28 @@ void EditDialog::loadData(const QByteArray& data) case Text: case JSON: - // Set enabled any of the text widgets ui->editorText->setEnabled(true); jsonEdit->setEnabled(true); switch (editMode) { case TextEditor: + { // The text widget buffer is now the main data source dataSource = TextBuffer; - // Load the text into the text editor - textData = QString::fromUtf8(data.constData(), data.size()); + // Load the text into the text editor, remove BOM first if there is one + QByteArray dataWithoutBom = data; + removedBom = removeBom(dataWithoutBom); + + textData = QString::fromUtf8(dataWithoutBom.constData(), dataWithoutBom.size()); ui->editorText->setPlainText(textData); // Select all of the text by default ui->editorText->selectAll(); break; - + } case JsonEditor: // The JSON widget buffer is now the main data source dataSource = JsonBuffer; @@ -372,6 +379,7 @@ void EditDialog::setNull() hexEdit->setData(QByteArray()); jsonEdit->clear(); dataType = Null; + removedBom.clear(); // Check if in text editor mode int editMode = ui->editorStack->currentIndex(); @@ -424,10 +432,10 @@ void EditDialog::accept() } else { // It's not NULL, so proceed with normal text string checking QString oldData = currentIndex.data(Qt::EditRole).toString(); - QString newData = ui->editorText->toPlainText(); + QString newData = removedBom + ui->editorText->toPlainText(); if (oldData != newData) // The data is different, so commit it back to the database - emit recordTextUpdated(currentIndex, newData.toUtf8(), false); + emit recordTextUpdated(currentIndex, removedBom + newData.toUtf8(), false); } break; case JsonBuffer: @@ -508,7 +516,7 @@ void EditDialog::editModeChanged(int newMode) case HexEditor: // Switching to the hex editor // Convert the text widget buffer for the hex widget - hexEdit->setData(ui->editorText->toPlainText().toUtf8()); + hexEdit->setData(removedBom + ui->editorText->toPlainText().toUtf8()); // The hex widget buffer is now the main data source dataSource = HexBuffer; @@ -619,8 +627,8 @@ int EditDialog::checkDataType(const QByteArray& data) return Image; // Check if it's text only - if (QString(cellData).toUtf8() == cellData) { // Is there a better way to check this? - + if(isTextOnly(cellData)) + { QJsonDocument jsonDoc = QJsonDocument::fromJson(QString(cellData).toUtf8()); if (!jsonDoc.isNull()) return JSON; diff --git a/src/EditDialog.h b/src/EditDialog.h index 56981abb..490f3eae 100644 --- a/src/EditDialog.h +++ b/src/EditDialog.h @@ -57,6 +57,7 @@ private: bool textNullSet; bool isReadOnly; bool mustIndentAndCompact; + QByteArray removedBom; enum DataSources { TextBuffer, diff --git a/src/ExtendedScintilla.cpp b/src/ExtendedScintilla.cpp index 5e9e3aa1..266bddaf 100644 --- a/src/ExtendedScintilla.cpp +++ b/src/ExtendedScintilla.cpp @@ -111,7 +111,7 @@ void ExtendedScintilla::reloadSettings() if(Settings::getValue("editor", "auto_completion").toBool()) { setAutoCompletionThreshold(3); - setAutoCompletionCaseSensitivity(true); + setAutoCompletionCaseSensitivity(false); setAutoCompletionShowSingle(true); setAutoCompletionSource(QsciScintilla::AcsAPIs); } else { diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index 4ad6b3d9..9587d496 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -14,8 +14,12 @@ #include #include #include +#include +#include +#include QList ExtendedTableWidget::m_buffer; +QString ExtendedTableWidget::m_generatorStamp; namespace { @@ -87,6 +91,46 @@ QList parseClipboard(QString clipboard) } + +ExtendedTableWidgetEditorDelegate::ExtendedTableWidgetEditorDelegate(QObject* parent) + : QStyledItemDelegate(parent) +{ +} + +QWidget* ExtendedTableWidgetEditorDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const +{ + // Just create a normal line editor but set the maximum length to the highest possible value instead of the default 32768. + QLineEdit* editor = new QLineEdit(parent); + editor->setMaxLength(std::numeric_limits::max()); + return editor; +} + +void ExtendedTableWidgetEditorDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + QLineEdit* lineedit = static_cast(editor); + + // Set the data for the line editor + QString data = index.data(Qt::EditRole).toString(); + lineedit->setText(data); + + // Put the editor in read only mode if the actual data is larger than the maximum length to avoid accidental truncation of the data + lineedit->setReadOnly(data.size() > lineedit->maxLength()); +} + +void ExtendedTableWidgetEditorDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + // Only apply the data back to the model if the editor is not in read only mode to avoid accidental truncation of the data + QLineEdit* lineedit = static_cast(editor); + if(!lineedit->isReadOnly()) + model->setData(index, lineedit->text()); +} + +void ExtendedTableWidgetEditorDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const +{ + editor->setGeometry(option.rect); +} + + ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : QTableView(parent) { @@ -120,6 +164,10 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : m_contextMenu->addAction(pasteAction); setContextMenuPolicy(Qt::CustomContextMenu); + // Create and set up delegate + m_editorDelegate = new ExtendedTableWidgetEditorDelegate(this); + setItemDelegate(m_editorDelegate); + // This is only for displaying the shortcut in the context menu. // An entry in keyPressEvent is still needed. nullAction->setShortcut(QKeySequence(tr("Alt+Del"))); @@ -185,7 +233,7 @@ void ExtendedTableWidget::copy(const bool withHeaders) while (i.hasNext()) { if (isColumnHidden(i.next().column())) i.remove(); - } + } // Abort if there's nothing to copy if (indices.isEmpty()) @@ -223,49 +271,37 @@ void ExtendedTableWidget::copy(const bool withHeaders) } } - // If any of the selected cells contains binary data, we use the internal copy-paste buffer - bool containsBinary = false; - for(const QModelIndex& index : indices) - { - if (m->isBinary(index)) { // TODO: Should we check for NULL values, too? - containsBinary = true; - break; - } - } + // If we got here, there are multiple selected cells, or copy with headers was requested. + // In this case, we copy selected data into internal copy-paste buffer and then + // we write a table both in HTML and text formats to the system clipboard. - if (containsBinary) + // Copy selected data into internal copy-paste buffer + int last_row = indices.first().row(); + QByteArrayList lst; + for(int i=0;iclipboard()->setText(QString()); // Calling clear() alone doesn't seem to work on all systems - qApp->clipboard()->clear(); - - // Copy selected data into internal copy-paste buffer - int last_row = indices.first().row(); - QByteArrayList lst; - for(int i=0;i"; - htmlResult.append (""); - htmlResult.append (""); - htmlResult.append (QString("").arg(QApplication::applicationName().toHtmlEscaped())); - htmlResult.append (""); + htmlResult.append(""); + htmlResult.append(""); + + // The generator-stamp is later used to know whether the data in the system clipboard is still ours. + // In that case we will give precedence to our internal copy buffer. + QString now = QDateTime::currentDateTime().toString("YYYY-MM-DDTHH:mm:ss.zzz"); + m_generatorStamp = QString("").arg(QApplication::applicationName().toHtmlEscaped(), now); + htmlResult.append(m_generatorStamp); + // TODO: is this really needed by Excel, since we use
 for multi-line cells?
+    htmlResult.append("
"); int currentRow = indices.first().row(); @@ -305,15 +341,37 @@ void ExtendedTableWidget::copy(const bool withHeaders) htmlResult.append(fieldSepHtml); } currentRow = index.row(); - QByteArray text = index.data(Qt::EditRole).toByteArray(); - // Table cell data - if (text.contains('\n') || text.contains('\t')) - htmlResult.append("
" + QString(text).toHtmlEscaped() + "
"); - else - htmlResult.append(QString(text).toHtmlEscaped()); + QImage img; + QVariant data = index.data(Qt::EditRole); - result.append(escapeCopiedData(text)); + // Table cell data: image? Store it as an embedded image in HTML and as base 64 in text version + if (img.loadFromData(data.toByteArray())) + { + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "PNG"); + buffer.close(); + + QString imageBase64 = ba.toBase64(); + htmlResult.append("\"Image\""); + } else { + QByteArray text; + if (!m->isBinary(index)) + text = data.toByteArray(); + + // Table cell data: text + if (text.contains('\n') || text.contains('\t')) + htmlResult.append("
" + QString(text).toHtmlEscaped() + "
"); + else + htmlResult.append(QString(text).toHtmlEscaped()); + + result.append(escapeCopiedData(text)); + } } QMimeData *mimeData = new QMimeData; @@ -354,9 +412,10 @@ void ExtendedTableWidget::paste() SqliteTableModel* m = qobject_cast(model()); - // We're also checking for system clipboard data first. Only if there is no data in the system clipboard we're falling back to the internal buffer. - // That's a bit unfortunate because the data in the internal buffer is easier to parse and more accurate, too. However, if we always preferred the - // internal copy-paste buffer there would be no way to copy data from other applications in here once the internal buffer has been filled. + // We're also checking for system clipboard data first. Only if the data in the system clipboard is not ours, we use the system + // clipboard, otherwise we prefer the internal buffer. That's because the data in the internal buffer is easier to parse and more + // accurate, too. However, if we always preferred the internal copy-paste buffer there would be no way to copy data from other + // applications in here once the internal buffer has been filled. // If clipboard contains an image and no text, just insert the image const QMimeData* mimeClipboard = qApp->clipboard()->mimeData(); @@ -375,10 +434,14 @@ void ExtendedTableWidget::paste() // Get the clipboard text QString clipboard = qApp->clipboard()->text(); - // If there is no text but the internal copy-paste buffer is filled, use the internal buffer; otherwise parse the system clipboard contents + + // If data in system clipboard is ours and the internal copy-paste buffer is filled, use the internal buffer; otherwise parse the + // system clipboard contents (case for data copied by other application). + QList clipboardTable; QList* source; - if(clipboard.isEmpty() && !m_buffer.isEmpty()) + + if(mimeClipboard->hasHtml() && mimeClipboard->html().contains(m_generatorStamp) && !m_buffer.isEmpty()) { source = &m_buffer; } else { @@ -583,14 +646,14 @@ void ExtendedTableWidget::dragMoveEvent(QDragMoveEvent* event) void ExtendedTableWidget::dropEvent(QDropEvent* event) { QModelIndex index = indexAt(event->pos()); - + if (!index.isValid()) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().first().isLocalFile()) emit openFileFromDropEvent(event->mimeData()->urls().first().toLocalFile()); return; } - + model()->dropMimeData(event->mimeData(), Qt::CopyAction, index.row(), index.column(), QModelIndex()); event->acceptProposedAction(); } diff --git a/src/ExtendedTableWidget.h b/src/ExtendedTableWidget.h index ccfa5a36..24256c77 100644 --- a/src/ExtendedTableWidget.h +++ b/src/ExtendedTableWidget.h @@ -5,11 +5,26 @@ #include #include #include +#include class QMenu; class FilterTableHeader; namespace sqlb { class ObjectIdentifier; } +// We use this class to provide editor widgets for the ExtendedTableWidget. It's used for every cell in the table view. +class ExtendedTableWidgetEditorDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit ExtendedTableWidgetEditorDelegate(QObject* parent = nullptr); + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override; +}; + class ExtendedTableWidget : public QTableView { Q_OBJECT @@ -40,6 +55,7 @@ private: typedef QList QByteArrayList; static QList m_buffer; + static QString m_generatorStamp; private slots: void vscrollbarChanged(int value); @@ -54,6 +70,7 @@ protected: FilterTableHeader* m_tableHeader; QMenu* m_contextMenu; + ExtendedTableWidgetEditorDelegate* m_editorDelegate; }; #endif diff --git a/src/FindReplaceDialog.ui b/src/FindReplaceDialog.ui index 2569a657..689e6e6a 100644 --- a/src/FindReplaceDialog.ui +++ b/src/FindReplaceDialog.ui @@ -103,7 +103,7 @@ - <html><head/><body><p>When checked, the pattern to find is interpreted as a UNIX regular expression. See <a href="https://en.wikibooks.org/wiki/Regular_Expressions"><span style=" text-decoration: underline; color:#0000ff;">Regular Expression in Wikibooks</span></a>.</p></body></html> + <html><head/><body><p>When checked, the pattern to find is interpreted as a UNIX regular expression. See <a href="https://en.wikibooks.org/wiki/Regular_Expressions">Regular Expression in Wikibooks</a>.</p></body></html> Use regular e&xpressions diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 43cb0ced..d77281b5 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -976,6 +976,8 @@ MainWindow::StatementType MainWindow::getQueryType(const QString& query) const if(query.startsWith("UPDATE", Qt::CaseInsensitive)) return UpdateStatement; if(query.startsWith("DELETE", Qt::CaseInsensitive)) return DeleteStatement; if(query.startsWith("CREATE", Qt::CaseInsensitive)) return CreateStatement; + if(query.startsWith("ATTACH", Qt::CaseInsensitive)) return AttachStatement; + if(query.startsWith("DETACH", Qt::CaseInsensitive)) return DetachStatement; return OtherStatement; } @@ -1156,7 +1158,10 @@ void MainWindow::executeQuery() if(query_part_type == InsertStatement || query_part_type == UpdateStatement || query_part_type == DeleteStatement) stmtHasChangedDatabase = tr(", %1 rows affected").arg(sqlite3_changes(db._db)); - modified = true; + // Attach/Detach statements don't modify the original database + if(query_part_type != StatementType::AttachStatement && query_part_type != StatementType::DetachStatement) + modified = true; + statusMessage = tr("Query executed successfully: %1 (took %2ms%3)").arg(queryPart.trimmed()).arg(timer.elapsed()).arg(stmtHasChangedDatabase); ok = true; break; @@ -1630,6 +1635,8 @@ void MainWindow::browseTableHeaderClicked(int logicalindex) // select the first item in the column so the header is bold // we might try to select the last selected item ui->dataTable->setCurrentIndex(ui->dataTable->currentIndex().sibling(0, logicalindex)); + + plotDock->updatePlot(m_browseTableModel, &browseTableSettings[currentlyBrowsedTableName()]); } void MainWindow::resizeEvent(QResizeEvent*) @@ -2442,14 +2449,39 @@ void MainWindow::showRecordPopupMenu(const QPoint& pos) if (row == -1) return; + // Select the row if it is not already in the selection. + QModelIndexList rowList = ui->dataTable->selectionModel()->selectedRows(); + bool found = false; + for (QModelIndex index : rowList) { + if (row == index.row()) { + found = true; + break; + } + } + if (!found) + ui->dataTable->selectRow(row); + + rowList = ui->dataTable->selectionModel()->selectedRows(); + + QString duplicateText = rowList.count() > 1 ? tr("Duplicate records") : tr("Duplicate record"); + QMenu popupRecordMenu(this); - QAction* action = new QAction("Duplicate record", &popupRecordMenu); + QAction* action = new QAction(duplicateText, &popupRecordMenu); // Set shortcut for documentation purposes (the actual functional shortcut is not set here) action->setShortcut(QKeySequence(tr("Ctrl+\""))); popupRecordMenu.addAction(action); connect(action, &QAction::triggered, [&]() { - duplicateRecord(row); + for (QModelIndex index : rowList) { + duplicateRecord(index.row()); + } + }); + + QAction* deleteRecordAction = new QAction(ui->buttonDeleteRecord->text(), &popupRecordMenu); + popupRecordMenu.addAction(deleteRecordAction); + + connect(deleteRecordAction, &QAction::triggered, [&]() { + deleteRecord(); }); popupRecordMenu.exec(ui->dataTable->verticalHeader()->mapToGlobal(pos)); @@ -2542,7 +2574,7 @@ void MainWindow::browseDataSetTableEncoding(bool forAllTables) if(ok) { // Check if encoding is valid - if(!QTextCodec::codecForName(encoding.toUtf8())) + if(!encoding.isEmpty() && !QTextCodec::codecForName(encoding.toUtf8())) { QMessageBox::warning(this, qApp->applicationName(), tr("This encoding is either not valid or not supported.")); return; diff --git a/src/MainWindow.h b/src/MainWindow.h index f3cf28ea..b5f6faba 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -143,6 +143,8 @@ private: UpdateStatement, DeleteStatement, CreateStatement, + AttachStatement, + DetachStatement, OtherStatement, }; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index af6b40c2..6211e390 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -63,6 +63,11 @@ Qt::CustomContextMenu + + This is the structure of the opened database. +You can drag SQL sentences from an object row and drop them into other applications or into another instance of 'DB Browser for SQLite'. + + true @@ -396,7 +401,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_auto_vacuum"><span style=" text-decoration: underline; color:#0000ff;">Auto Vacuum</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_auto_vacuum">Auto Vacuum</a></p></body></html> true @@ -431,7 +436,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_automatic_index"><span style=" text-decoration: underline; color:#0000ff;">Automatic Index</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_automatic_index">Automatic Index</a></p></body></html> true @@ -451,7 +456,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_checkpoint_fullfsync"><span style=" text-decoration: underline; color:#0000ff;">Checkpoint Full FSYNC</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_checkpoint_fullfsync">Checkpoint Full FSYNC</a></p></body></html> true @@ -471,7 +476,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_foreign_keys"><span style=" text-decoration: underline; color:#0000ff;">Foreign Keys</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_foreign_keys">Foreign Keys</a></p></body></html> true @@ -491,7 +496,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_fullfsync"><span style=" text-decoration: underline; color:#0000ff;">Full FSYNC</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_fullfsync">Full FSYNC</a></p></body></html> true @@ -511,7 +516,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_ignore_check_constraints"><span style=" text-decoration: underline; color:#0000ff;">Ignore Check Constraints</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_ignore_check_constraints">Ignore Check Constraints</a></p></body></html> true @@ -531,7 +536,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_journal_mode"><span style=" text-decoration: underline; color:#0000ff;">Journal Mode</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_journal_mode">Journal Mode</a></p></body></html> true @@ -578,7 +583,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_journal_size_limit"><span style=" text-decoration: underline; color:#0000ff;">Journal Size Limit</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_journal_size_limit">Journal Size Limit</a></p></body></html> true @@ -601,7 +606,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_locking_mode"><span style=" text-decoration: underline; color:#0000ff;">Locking Mode</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_locking_mode">Locking Mode</a></p></body></html> true @@ -628,7 +633,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_max_page_count"><span style=" text-decoration: underline; color:#0000ff;">Max Page Count</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_max_page_count">Max Page Count</a></p></body></html> true @@ -648,7 +653,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_page_size"><span style=" text-decoration: underline; color:#0000ff;">Page Size</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_page_size">Page Size</a></p></body></html> true @@ -671,7 +676,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_recursive_triggers"><span style=" text-decoration: underline; color:#0000ff;">Recursive Triggers</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_recursive_triggers">Recursive Triggers</a></p></body></html> true @@ -691,7 +696,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_secure_delete"><span style=" text-decoration: underline; color:#0000ff;">Secure Delete</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_secure_delete">Secure Delete</a></p></body></html> true @@ -711,7 +716,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous">Synchronous</a></p></body></html> true @@ -743,7 +748,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_temp_store"><span style=" text-decoration: underline; color:#0000ff;">Temp Store</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_temp_store">Temp Store</a></p></body></html> true @@ -775,7 +780,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_user_version"><span style=" text-decoration: underline; color:#0000ff;">User Version</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_user_version">User Version</a></p></body></html> true @@ -795,7 +800,7 @@ - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_wal_autocheckpoint"><span style=" text-decoration: underline; color:#0000ff;">WAL Auto Checkpoint</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_wal_autocheckpoint">WAL Auto Checkpoint</a></p></body></html> true @@ -1119,9 +1124,27 @@ + + This is the structure of the opened database. +You can drag multiple object names from the Name column and drop them into the SQL editor . +You can drag SQL sentences from the Schema column and drop them into the SQL editor or into other applications. + + + + true + + + QAbstractItemView::DragDrop + true + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectItems + QAbstractItemView::ScrollPerPixel diff --git a/src/PlotDock.cpp b/src/PlotDock.cpp index 03e208cf..49959798 100644 --- a/src/PlotDock.cpp +++ b/src/PlotDock.cpp @@ -195,7 +195,7 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett QStringList yAxisLabels; // Clear graphs and axis labels - ui->plotWidget->clearGraphs(); + ui->plotWidget->clearPlottables(); ui->plotWidget->xAxis->setLabel(QString()); ui->plotWidget->yAxis->setLabel(QString()); @@ -231,17 +231,16 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett // leading 16 bit are column index uint itemdata = item->data(0, Qt::UserRole).toUInt(); int column = itemdata >> 16; - QCPGraph* graph = ui->plotWidget->addGraph(); - graph->setPen(QPen(item->backgroundColor(PlotColumnY))); - graph->setSelectable (QCP::stDataRange); + bool isSorted = true; // prepare the data vectors for qcustomplot // possible improvement might be a QVector subclass that directly // access the model data, to save memory, we are copying here - QVector xdata(model->rowCount()), ydata(model->rowCount()); + QVector xdata(model->rowCount()), ydata(model->rowCount()), tdata(model->rowCount()); for(int i = 0; i < model->rowCount(); ++i) { + tdata[i] = i; // convert x type axis if it's datetime if(xtype == QVariant::DateTime) { @@ -258,6 +257,9 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett xdata[i] = model->data(model->index(i, x)).toDouble(); } + if (i != 0) + isSorted &= (xdata[i-1] <= xdata[i]); + // Get the y value for this point. If the selected column is -1, i.e. the row number, just use the current row number from the loop // instead of retrieving some value from the model. QVariant pointdata; @@ -271,14 +273,38 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett else ydata[i] = pointdata.toDouble(); } - - // set some graph styles - graph->setData(xdata, ydata); - graph->setLineStyle((QCPGraph::LineStyle) ui->comboLineType->currentIndex()); // WARN: ssDot is removed int shapeIdx = ui->comboPointShape->currentIndex(); if (shapeIdx > 0) shapeIdx += 1; - graph->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)shapeIdx, 5)); + QCPScatterStyle scatterStyle = QCPScatterStyle(static_cast(shapeIdx), 5); + + QCPAbstractPlottable* plottable; + // When it is already sorted by x, we draw a graph. + // When it is not sorted by x, we draw a curve, so the order selected by the user in the table or in the query is + // respected. In this case the line will have loops and only None and Line is supported as line style. + // TODO: how to make the user aware of this without disturbing. + if (isSorted) { + QCPGraph* graph = ui->plotWidget->addGraph(); + plottable = graph; + graph->setData(xdata, ydata, /*alreadySorted*/ true); + // set some graph styles not supported by the abstract plottable + graph->setLineStyle((QCPGraph::LineStyle) ui->comboLineType->currentIndex()); + graph->setScatterStyle(scatterStyle); + + } else { + QCPCurve* curve = new QCPCurve(ui->plotWidget->xAxis, ui->plotWidget->yAxis); + plottable = curve; + curve->setData(tdata, xdata, ydata, /*alreadySorted*/ true); + // set some curve styles not supported by the abstract plottable + if (ui->comboLineType->currentIndex() == QCPCurve::lsNone) + curve->setLineStyle(QCPCurve::lsNone); + else + curve->setLineStyle(QCPCurve::lsLine); + curve->setScatterStyle(scatterStyle); + } + + plottable->setPen(QPen(item->backgroundColor(PlotColumnY))); + plottable->setSelectable (QCP::stDataRange); // gather Y label column names if(column == RowNumId) @@ -497,14 +523,28 @@ void PlotDock::on_comboLineType_currentIndexChanged(int index) { Q_ASSERT(index >= QCPGraph::lsNone && index <= QCPGraph::lsImpulse); + + bool hasCurves = (ui->plotWidget->plottableCount() > ui->plotWidget->graphCount()); QCPGraph::LineStyle lineStyle = (QCPGraph::LineStyle) index; + if (lineStyle > QCPGraph::lsLine && hasCurves) { + QMessageBox::warning(this, qApp->applicationName(), + tr("There are curves in this plot and the selected line style can only be applied to graphs sorted by X. " + "Either sort the table or query by X to remove curves or select one of the styles supported by curves: " + "None or Line.")); + return; + } for (int i = 0, ie = ui->plotWidget->graphCount(); i < ie; ++i) { QCPGraph * graph = ui->plotWidget->graph(i); if (graph) graph->setLineStyle(lineStyle); } - ui->plotWidget->replot(); + // We have changed the style only for graphs, but not for curves. + // If there are any in the plot, we have to update it completely in order to apply the new style + if (hasCurves) + updatePlot(m_currentPlotModel, m_currentTableSettings, false); + else + ui->plotWidget->replot(); // Save settings for this table if(m_currentTableSettings) @@ -525,6 +565,8 @@ void PlotDock::on_comboPointShape_currentIndexChanged(int index) if (index > 0) index += 1; Q_ASSERT(index >= QCPScatterStyle::ssNone && index < QCPScatterStyle::ssPixmap); + + bool hasCurves = (ui->plotWidget->plottableCount() > ui->plotWidget->graphCount()); QCPScatterStyle::ScatterShape shape = (QCPScatterStyle::ScatterShape) index; for (int i = 0, ie = ui->plotWidget->graphCount(); i < ie; ++i) { @@ -532,7 +574,12 @@ void PlotDock::on_comboPointShape_currentIndexChanged(int index) if (graph) graph->setScatterStyle(QCPScatterStyle(shape, 5)); } - ui->plotWidget->replot(); + // We have changed the style only for graphs, but not for curves. + // If there are any in the plot, we have to update it completely in order to apply the new style + if (hasCurves) + updatePlot(m_currentPlotModel, m_currentTableSettings, false); + else + ui->plotWidget->replot(); // Save settings for this table if(m_currentTableSettings) @@ -601,9 +648,9 @@ void PlotDock::fetchAllData() void PlotDock::selectionChanged() { - for (QCPGraph* graph : ui->plotWidget->selectedGraphs()) { + for (QCPAbstractPlottable* plottable : ui->plotWidget->selectedPlottables()) { - for (QCPDataRange dataRange : graph->selection().dataRanges()) { + for (QCPDataRange dataRange : plottable->selection().dataRanges()) { int index = dataRange.begin(); if (dataRange.length() != 0) { diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index 1ff59270..41b9c59d 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -168,6 +168,8 @@ void PreferencesDialog::loadSettings() ui->spinTabSize->setValue(Settings::getValue("editor", "tabsize").toInt()); ui->spinLogFontSize->setValue(Settings::getValue("log", "fontsize").toInt()); ui->checkAutoCompletion->setChecked(Settings::getValue("editor", "auto_completion").toBool()); + ui->checkCompleteUpper->setEnabled(Settings::getValue("editor", "auto_completion").toBool()); + ui->checkCompleteUpper->setChecked(Settings::getValue("editor", "upper_keywords").toBool()); ui->checkErrorIndicators->setChecked(Settings::getValue("editor", "error_indicators").toBool()); ui->checkHorizontalTiling->setChecked(Settings::getValue("editor", "horizontal_tiling").toBool()); @@ -218,6 +220,7 @@ void PreferencesDialog::saveSettings() Settings::setValue("editor", "tabsize", ui->spinTabSize->value()); Settings::setValue("log", "fontsize", ui->spinLogFontSize->value()); Settings::setValue("editor", "auto_completion", ui->checkAutoCompletion->isChecked()); + Settings::setValue("editor", "upper_keywords", ui->checkCompleteUpper->isChecked()); Settings::setValue("editor", "error_indicators", ui->checkErrorIndicators->isChecked()); Settings::setValue("editor", "horizontal_tiling", ui->checkHorizontalTiling->isChecked()); diff --git a/src/PreferencesDialog.ui b/src/PreferencesDialog.ui index 1d4d5998..1029df6f 100644 --- a/src/PreferencesDialog.ui +++ b/src/PreferencesDialog.ui @@ -974,46 +974,6 @@ - - - - Error indicators - - - checkErrorIndicators - - - - - - - When set, the SQL code lines that caused errors during the last execution are highlighted and the results frame indicates the error in the background - - - enabled - - - - - - - Hori&zontal tiling - - - checkHorizontalTiling - - - - - - - If enabled the SQL code editor and the result table view are shown side by side instead of one over the other. - - - enabled - - - @@ -1031,6 +991,66 @@ + + + + Keywords in &UPPER CASE + + + checkCompleteUpper + + + + + + + When set, the SQL keywords are completed in UPPER CASE letters. + + + enabled + + + + + + + Error indicators + + + checkErrorIndicators + + + + + + + When set, the SQL code lines that caused errors during the last execution are highlighted and the results frame indicates the error in the background + + + enabled + + + + + + + Hori&zontal tiling + + + checkHorizontalTiling + + + + + + + If enabled the SQL code editor and the result table view are shown side by side instead of one over the other. + + + enabled + + + @@ -1587,6 +1607,22 @@ + + checkAutoCompletion + toggled(bool) + checkCompleteUpper + setEnabled(bool) + + + 474 + 464 + + + 474 + 492 + + + saveSettings() diff --git a/src/Settings.cpp b/src/Settings.cpp index 43ea4c48..796ba3ac 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -263,6 +263,10 @@ QVariant Settings::getDefaultValue(const QString& group, const QString& name) if(group == "editor" && name == "auto_completion") return true; + // editor/upper_keywords? + if(group == "editor" && name == "upper_keywords") + return true; + // editor/error_indicators? if(group == "editor" && name == "error_indicators") return true; diff --git a/src/SqlExecutionArea.ui b/src/SqlExecutionArea.ui index 32fb1864..57d5e753 100644 --- a/src/SqlExecutionArea.ui +++ b/src/SqlExecutionArea.ui @@ -143,7 +143,7 @@ Interpret search pattern as a regular expression - <html><head/><body><p>When checked, the pattern to find is interpreted as a UNIX regular expression. See <a href="https://en.wikibooks.org/wiki/Regular_Expressions"><span style=" text-decoration: underline; color:#0000ff;">Regular Expression in Wikibooks</span></a>.</p></body></html> + <html><head/><body><p>When checked, the pattern to find is interpreted as a UNIX regular expression. See <a href="https://en.wikibooks.org/wiki/Regular_Expressions">Regular Expression in Wikibooks</a>.</p></body></html> Regular Expression diff --git a/src/SqlUiLexer.cpp b/src/SqlUiLexer.cpp index 9e7fc6c9..c0884540 100644 --- a/src/SqlUiLexer.cpp +++ b/src/SqlUiLexer.cpp @@ -1,5 +1,6 @@ #include "SqlUiLexer.h" #include "Qsci/qsciapis.h" +#include "Settings.h" SqlUiLexer::SqlUiLexer(QObject* parent) : QsciLexerSQL(parent) @@ -47,10 +48,13 @@ void SqlUiLexer::setupAutoCompletion() << "WHERE" << "WITH" << "WITHOUT" // Data types << "INT" << "INTEGER" << "REAL" << "TEXT" << "BLOB" << "NUMERIC" << "CHAR"; + bool upperKeywords = Settings::getValue("editor", "upper_keywords").toBool(); for(const QString& keyword : keywordPatterns) { - autocompleteApi->add(keyword + "?" + QString::number(ApiCompleterIconIdKeyword)); - autocompleteApi->add(keyword.toLower() + "?" + QString::number(ApiCompleterIconIdKeyword)); + if (upperKeywords) + autocompleteApi->add(keyword + "?" + QString::number(ApiCompleterIconIdKeyword)); + else + autocompleteApi->add(keyword.toLower() + "?" + QString::number(ApiCompleterIconIdKeyword)); } // Functions diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index e30c56c8..c54dc573 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp index dbef08eb..e606e158 100644 --- a/src/sqlitetablemodel.cpp +++ b/src/sqlitetablemodel.cpp @@ -2,8 +2,8 @@ #include "sqlitedb.h" #include "sqlite.h" #include "Settings.h" +#include "Data.h" -#include #include #include #include @@ -685,8 +685,8 @@ void SqliteTableModel::removeCommentsFromQuery(QString& query) { * (?: | )* # code is none or many strings alternating with non-strings * (?:'[^']*') # a string is a quote, followed by none or more non-quotes, followed by a quote * (?:[^'-]|-(?!-))* # non-string is a sequence of characters which aren't quotes or hyphens, - * OR if they are hyphens then they can't be followed immediately by another hyphen */ + QRegExp rxSQL("^((?:(?:[^'-]|-(?!-))*|(?:'[^']*'))*)(--[^\\r\\n]*)([\\r\\n]*)(.*)$"); // set up regex to find end-of-line comment QString result; @@ -871,10 +871,7 @@ void SqliteTableModel::clearCache() bool SqliteTableModel::isBinary(const QModelIndex& index) const { - // We're using the same way to detect binary data here as in the EditDialog class. For performance reasons we're only looking at - // the first couple of bytes though. - QByteArray data = m_data.at(index.row()).at(index.column()).left(512); - return QString(data).toUtf8() != data; + return !isTextOnly(m_data.at(index.row()).at(index.column()), m_encoding, true); } QByteArray SqliteTableModel::encode(const QByteArray& str) const diff --git a/src/sqlitetypes.cpp b/src/sqlitetypes.cpp index 8c1f7429..0e398bc0 100644 --- a/src/sqlitetypes.cpp +++ b/src/sqlitetypes.cpp @@ -92,6 +92,7 @@ public: private: void parsecolumn(Table* table, antlr::RefAST c); + QString parseConflictClause(antlr::RefAST c); private: antlr::RefAST m_root; @@ -219,6 +220,9 @@ QString PrimaryKeyConstraint::toSql(const FieldVector& applyOn) const result += QString("CONSTRAINT %1 ").arg(escapeIdentifier(m_name)); result += QString("PRIMARY KEY(%1)").arg(fieldVectorToFieldNames(applyOn).join(",")); + if(!m_conflictAction.isEmpty()) + result += " ON CONFLICT " + m_conflictAction; + return result; } @@ -794,6 +798,10 @@ TablePtr CreateTableWalker::table() } } while(tc != antlr::nullAST && tc->getType() != sqlite3TokenTypes::RPAREN); + // We're either done now or there is a conflict clause + tc = tc->getNextSibling(); // skip RPAREN + pk->setConflictAction(parseConflictClause(tc)); + tab->addConstraint(fields, ConstraintPtr(pk)); } break; @@ -1026,6 +1034,9 @@ void CreateTableWalker::parsecolumn(Table* table, antlr::RefAST c) table->setFullyParsed(false); con = con->getNextSibling(); //skip } + + primaryKey->setConflictAction(parseConflictClause(con)); + if(con != antlr::nullAST && con->getType() == sqlite3TokenTypes::AUTOINCREMENT) autoincrement = true; } @@ -1145,6 +1156,21 @@ void CreateTableWalker::parsecolumn(Table* table, antlr::RefAST c) } } +QString CreateTableWalker::parseConflictClause(antlr::RefAST c) +{ + QString conflictAction; + + if(c != antlr::nullAST && c->getType() == sqlite3TokenTypes::ON && c->getNextSibling()->getType() == sqlite3TokenTypes::CONFLICT) + { + c = c->getNextSibling(); // skip ON + c = c->getNextSibling(); // skip CONFLICT + conflictAction = identifier(c); + c = c->getNextSibling(); // skip action + } + + return conflictAction; +} + QString IndexedColumn::toString(const QString& indent, const QString& sep) const diff --git a/src/sqlitetypes.h b/src/sqlitetypes.h index b5160733..632727c3 100644 --- a/src/sqlitetypes.h +++ b/src/sqlitetypes.h @@ -265,9 +265,15 @@ class PrimaryKeyConstraint : public Constraint public: PrimaryKeyConstraint() {} + void setConflictAction(const QString& conflict) { m_conflictAction = conflict; } + const QString& conflictAction() const { return m_conflictAction; } + virtual QString toSql(const FieldVector& applyOn) const; virtual ConstraintTypes type() const { return PrimaryKeyConstraintType; } + +private: + QString m_conflictAction; }; class CheckConstraint : public Constraint diff --git a/src/src.pro b/src/src.pro index 16f6d66a..44f6e33b 100644 --- a/src/src.pro +++ b/src/src.pro @@ -62,7 +62,8 @@ HEADERS += \ jsontextedit.h \ FindReplaceDialog.h \ ExtendedScintilla.h \ - FileExtensionManager.h + FileExtensionManager.h \ + Data.h SOURCES += \ sqlitedb.cpp \ @@ -102,7 +103,8 @@ SOURCES += \ jsontextedit.cpp \ FindReplaceDialog.cpp \ ExtendedScintilla.cpp \ - FileExtensionManager.cpp + FileExtensionManager.cpp \ + Data.cpp RESOURCES += icons/icons.qrc \ translations/flags/flags.qrc \ diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 52e30676..8b5672a0 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -11,6 +11,7 @@ set(TESTSQLOBJECTS_SRC ../grammar/Sqlite3Parser.cpp ../Settings.cpp testsqlobjects.cpp + ../Data.cpp ) set(TESTSQLOBJECTS_HDR @@ -18,6 +19,7 @@ set(TESTSQLOBJECTS_HDR ../grammar/Sqlite3Lexer.hpp ../grammar/Sqlite3Parser.hpp ../sqlitetypes.h + ../Data.h ) set(TESTSQLOBJECTS_MOC_HDR @@ -83,6 +85,7 @@ set(TESTREGEX_SRC ../grammar/Sqlite3Parser.cpp ../Settings.cpp TestRegex.cpp + ../Data.cpp ) set(TESTREGEX_HDR @@ -90,6 +93,7 @@ set(TESTREGEX_HDR ../grammar/Sqlite3Lexer.hpp ../grammar/Sqlite3Parser.hpp ../sqlitetypes.h + ../Data.h ) set(TESTREGEX_MOC_HDR diff --git a/src/tests/TestRegex.cpp b/src/tests/TestRegex.cpp index b643b11c..f6625b55 100644 --- a/src/tests/TestRegex.cpp +++ b/src/tests/TestRegex.cpp @@ -57,6 +57,19 @@ void TestRegex::sqlQueryComments_data() "SELECT '-- comment inside quotes'" << // cleanQuery "SELECT '-- comment inside quotes'"; + + /* TODO Fix issue #1270, then activate these + QTest::newRow("single_quote_comment") + << // dirtyQuery + "SELECT 'something--something' -- comment" + << // cleanQuery + "SELECT 'something--something'"; + + QTest::newRow("double_quote_comment") + << // dirtyQuery + "SELECT \"something--something\" -- comment" + << // cleanQuery + "SELECT \"something--something\"";*/ } void TestRegex::sqlQueryComments() From 92619dc1783bbcd326277a57b191dfe19ba1fb27 Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Tue, 16 Jan 2018 18:18:34 +0100 Subject: [PATCH 07/11] Merge sqlitebrowser master --- .travis.yml | 15 ------- src/DbStructureModel.cpp | 3 +- src/DbStructureModel.h | 8 +++- src/EditIndexDialog.cpp | 9 ++++- src/ExtendedTableWidget.cpp | 37 +++++++++++++++++ src/ExtendedTableWidget.h | 2 + src/FileExtensionManager.cpp | 8 +++- src/MainWindow.cpp | 78 +++++++++++++++++------------------- src/MainWindow.h | 7 ++-- src/PlotDock.cpp | 60 ++++++++++++++++++++++----- src/PlotDock.h | 1 + src/sqlitedb.cpp | 2 + src/sqlitetablemodel.cpp | 2 +- src/sqlitetablemodel.h | 2 +- src/sqlitetypes.h | 8 +++- 15 files changed, 162 insertions(+), 80 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8baa5a3c..d7443360 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,20 +51,6 @@ script: - cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -DENABLE_TESTING=ON -Dsqlcipher=1 .. - make - ctest -V - - # AppImage generation - - sudo apt-get -y install checkinstall - - sudo checkinstall --pkgname=app --pkgversion="1" --pkgrelease="1" --backup=no --fstrans=no --default --deldoc - - mkdir appdir ; cd appdir - - dpkg -x ../app_1-1_amd64.deb . ; find . - - cp ./usr/share/applications/sqlitebrowser.desktop . - - cp ./usr/share/icons/hicolor/256x256/apps/sqlitebrowser.png . - - cd .. - - wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" - - chmod a+x linuxdeployqt*.AppImage - - unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH - - ./linuxdeployqt*.AppImage ./appdir/usr/bin/sqlitebrowser -bundle-non-qt-libs - - ./linuxdeployqt*.AppImage ./appdir/usr/bin/sqlitebrowser -appimage - - curl --upload-file ./DB*.AppImage https://transfer.sh/sqlitebrowser-git.$(git rev-parse --short HEAD)-x86_64.AppImage notifications: email: @@ -75,4 +61,3 @@ notifications: - mgrojo@gmail.com on_success: never on_failure: always - diff --git a/src/DbStructureModel.cpp b/src/DbStructureModel.cpp index 73fdf1cc..e9623548 100644 --- a/src/DbStructureModel.cpp +++ b/src/DbStructureModel.cpp @@ -138,6 +138,7 @@ void DbStructureModel::reloadData() if(!m_db.isOpen()) { endResetModel(); + emit structureUpdated(); return; } @@ -179,6 +180,7 @@ void DbStructureModel::reloadData() // Refresh the view endResetModel(); + emit structureUpdated(); } QStringList DbStructureModel::mimeTypes() const @@ -263,7 +265,6 @@ bool DbStructureModel::dropMimeData(const QMimeData* data, Qt::DropAction action if(m_db.executeMultiSQL(d, true, true)) { m_db.updateSchema(); - reloadData(); return true; } else { QMessageBox::warning(nullptr, QApplication::applicationName(), m_db.lastError()); diff --git a/src/DbStructureModel.h b/src/DbStructureModel.h index 83d3446c..43b0d5cd 100644 --- a/src/DbStructureModel.h +++ b/src/DbStructureModel.h @@ -15,8 +15,6 @@ public: explicit DbStructureModel(DBBrowserDB& db, QObject* parent = nullptr); ~DbStructureModel(); - void reloadData(); - QVariant data(const QModelIndex& index, int role) const; Qt::ItemFlags flags(const QModelIndex& index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; @@ -38,6 +36,12 @@ public: ColumnSchema, }; +public slots: + void reloadData(); + +signals: + void structureUpdated(); + private: DBBrowserDB& m_db; QTreeWidgetItem* rootItem; diff --git a/src/EditIndexDialog.cpp b/src/EditIndexDialog.cpp index b3ea640d..3b7b0e98 100644 --- a/src/EditIndexDialog.cpp +++ b/src/EditIndexDialog.cpp @@ -112,7 +112,10 @@ void EditIndexDialog::tableChanged(const QString& new_table, bool initialLoad) void EditIndexDialog::updateColumnLists() { // Fill the table column list - sqlb::FieldInfoList tableFields = pdb.getObjectByName(sqlb::ObjectIdentifier(ui->comboTableName->currentData())).dynamicCast()->fieldInformation(); + sqlb::TablePtr table = pdb.getObjectByName(sqlb::ObjectIdentifier(ui->comboTableName->currentData())).dynamicCast(); + if(!table) + return; + sqlb::FieldInfoList tableFields = table->fieldInformation(); ui->tableTableColumns->setRowCount(tableFields.size()); int tableRows = 0; for(int i=0;ieditIndexName->text().isEmpty()) valid = false; + // Check if a table is selected (this is especially important in the case where there are no tables in the database yet). + if(ui->comboTableName->currentText().isNull()) + valid = false; + // Check if index has any columns if(index.columns().size() == 0) valid = false; diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index 9587d496..1ed5663b 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -657,3 +657,40 @@ void ExtendedTableWidget::dropEvent(QDropEvent* event) model()->dropMimeData(event->mimeData(), Qt::CopyAction, index.row(), index.column(), QModelIndex()); event->acceptProposedAction(); } + +void ExtendedTableWidget::selectTableLine(int lineToSelect) +{ + SqliteTableModel* m = qobject_cast(model()); + + // Are there even that many lines? + if(lineToSelect >= m->totalRowCount()) + return; + + QApplication::setOverrideCursor( Qt::WaitCursor ); + // Make sure this line has already been fetched + while(lineToSelect >= m->rowCount() && m->canFetchMore()) + m->fetchMore(); + + // Select it + clearSelection(); + selectRow(lineToSelect); + scrollTo(currentIndex(), QAbstractItemView::PositionAtTop); + QApplication::restoreOverrideCursor(); +} + +void ExtendedTableWidget::selectTableLines(int firstLine, int count) +{ + SqliteTableModel* m = qobject_cast(model()); + + int lastLine = firstLine+count-1; + // Are there even that many lines? + if(lastLine >= m->totalRowCount()) + return; + + selectTableLine(firstLine); + + QModelIndex topLeft = m->index(firstLine, 0); + QModelIndex bottomRight = m->index(lastLine, m->columnCount()-1); + + selectionModel()->select(QItemSelection(topLeft, bottomRight), QItemSelectionModel::Select | QItemSelectionModel::Rows); +} diff --git a/src/ExtendedTableWidget.h b/src/ExtendedTableWidget.h index 24256c77..fe4fd751 100644 --- a/src/ExtendedTableWidget.h +++ b/src/ExtendedTableWidget.h @@ -40,6 +40,8 @@ public: public slots: void reloadSettings(); + void selectTableLine(int lineToSelect); + void selectTableLines(int firstLine, int count); signals: void foreignKeyClicked(const sqlb::ObjectIdentifier& table, const QString& column, const QByteArray& value); diff --git a/src/FileExtensionManager.cpp b/src/FileExtensionManager.cpp index d385aa21..3cb91fa9 100644 --- a/src/FileExtensionManager.cpp +++ b/src/FileExtensionManager.cpp @@ -40,8 +40,8 @@ void FileExtensionManager::addItem() { int i = ui->tableExtensions->rowCount(); ui->tableExtensions->insertRow(i); - QTableWidgetItem *newItemDescription = new QTableWidgetItem("Description"); - QTableWidgetItem *newItemExtension = new QTableWidgetItem("*.extension"); + QTableWidgetItem *newItemDescription = new QTableWidgetItem(tr("Description")); + QTableWidgetItem *newItemExtension = new QTableWidgetItem(tr("*.extension")); ui->tableExtensions->setItem(i, 0, newItemDescription); ui->tableExtensions->setItem(i, 1, newItemExtension); } @@ -70,6 +70,8 @@ void FileExtensionManager::upItem() if (ui->tableExtensions->selectedItems().isEmpty()) return; int selectedRow = ui->tableExtensions->selectedItems().first()->row(); + if(selectedRow == 0) + return; QTableWidgetItem *t1, *t2; t1 = ui->tableExtensions->takeItem(selectedRow, 0); @@ -86,6 +88,8 @@ void FileExtensionManager::downItem() if (ui->tableExtensions->selectedItems().isEmpty()) return; int selectedRow = ui->tableExtensions->selectedItems().first()->row(); + if(selectedRow == ui->tableExtensions->rowCount() - 1) + return; QTableWidgetItem *t1, *t2; t1 = ui->tableExtensions->takeItem(selectedRow, 0); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index d77281b5..cb7aea68 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -92,7 +92,6 @@ void MainWindow::init() // Connect SQL logging and database state setting to main window connect(&db, SIGNAL(dbChanged(bool)), this, SLOT(dbState(bool))); connect(&db, SIGNAL(sqlExecuted(QString, int)), this, SLOT(logSql(QString,int))); - connect(&db, SIGNAL(structureUpdated()), this, SLOT(populateStructure())); connect(&db, &DBBrowserDB::requestCollation, this, &MainWindow::requestCollation); // Set the validator for the goto line edit @@ -103,10 +102,15 @@ void MainWindow::init() connect(m_browseTableModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataTableSelectionChanged(QModelIndex))); // Select in table the rows correspoding to the selected points in plot - connect(plotDock, SIGNAL(pointsSelected(int,int)), this, SLOT(selectTableLines(int,int))); + connect(plotDock, SIGNAL(pointsSelected(int,int)), ui->dataTable, SLOT(selectTableLines(int,int))); // Set up DB structure tab dbStructureModel = new DbStructureModel(db, this); + connect(&db, &DBBrowserDB::structureUpdated, [this]() { + QString old_table = ui->comboBrowseTable->currentText(); + dbStructureModel->reloadData(); + populateStructure(old_table); + }); ui->dbTreeWidget->setModel(dbStructureModel); ui->dbTreeWidget->setColumnWidth(DbStructureModel::ColumnName, 300); ui->dbTreeWidget->setColumnHidden(DbStructureModel::ColumnObjectType, true); @@ -396,12 +400,9 @@ void MainWindow::fileNew() } } -void MainWindow::populateStructure() +void MainWindow::populateStructure(const QString& old_table) { - QString old_table = ui->comboBrowseTable->currentText(); - // Refresh the structure tab - dbStructureModel->reloadData(); ui->dbTreeWidget->setRootIndex(dbStructureModel->index(1, 0)); // Show the 'All' part of the db structure ui->dbTreeWidget->expandToDepth(0); ui->treeSchemaDock->setRootIndex(dbStructureModel->index(1, 0)); // Show the 'All' part of the db structure @@ -530,7 +531,7 @@ void MainWindow::populateTable() m_browseTableModel->setEncoding(defaultBrowseTableEncoding); // Plot - plotDock->updatePlot(m_browseTableModel, &browseTableSettings[tablename]); + attachPlot(ui->dataTable, m_browseTableModel, &browseTableSettings[tablename]); // The filters can be left empty as they are } else { @@ -587,7 +588,7 @@ void MainWindow::populateTable() m_browseTableModel->setEncoding(storedData.encoding); // Plot - plotDock->updatePlot(m_browseTableModel, &browseTableSettings[tablename], true, false); + attachPlot(ui->dataTable, m_browseTableModel, &browseTableSettings[tablename], false); } // Show/hide menu options depending on whether this is a table or a view @@ -629,8 +630,8 @@ bool MainWindow::fileClose() // Reset the recordset label inside the Browse tab now setRecordsetLabel(); - // Reset the plot dock model - plotDock->updatePlot(nullptr); + // Reset the plot dock model and connection + attachPlot(nullptr, nullptr); activateFields(false); @@ -694,37 +695,22 @@ void MainWindow::deleteRecord() } } -void MainWindow::selectTableLine(int lineToSelect) +void MainWindow::attachPlot(ExtendedTableWidget* tableWidget, SqliteTableModel* model, BrowseDataTableSettings* settings, bool keepOrResetSelection) { - // Are there even that many lines? - if(lineToSelect >= m_browseTableModel->totalRowCount()) - return; + plotDock->updatePlot(model, settings, true, keepOrResetSelection); + // Disconnect previous connection + disconnect(plotDock, SIGNAL(pointsSelected(int,int)), nullptr, nullptr); + if(tableWidget) { + // Connect plot selection to the current table results widget. + connect(plotDock, SIGNAL(pointsSelected(int,int)), tableWidget, SLOT(selectTableLines(int,int))); + connect(tableWidget, SIGNAL(destroyed()), plotDock, SLOT(resetPlot())); - QApplication::setOverrideCursor( Qt::WaitCursor ); - // Make sure this line has already been fetched - while(lineToSelect >= m_browseTableModel->rowCount() && m_browseTableModel->canFetchMore()) - m_browseTableModel->fetchMore(); - - // Select it - ui->dataTable->clearSelection(); - ui->dataTable->selectRow(lineToSelect); - ui->dataTable->scrollTo(ui->dataTable->currentIndex(), QAbstractItemView::PositionAtTop); - QApplication::restoreOverrideCursor(); + } } -void MainWindow::selectTableLines(int firstLine, int count) +void MainWindow::selectTableLine(int lineToSelect) { - int lastLine = firstLine+count-1; - // Are there even that many lines? - if(lastLine >= m_browseTableModel->totalRowCount()) - return; - - selectTableLine(firstLine); - - QModelIndex topLeft = ui->dataTable->model()->index(firstLine, 0); - QModelIndex bottomRight = ui->dataTable->model()->index(lastLine, ui->dataTable->model()->columnCount()-1); - - ui->dataTable->selectionModel()->select(QItemSelection(topLeft, bottomRight), QItemSelectionModel::Select | QItemSelectionModel::Rows); + ui->dataTable->selectTableLine(lineToSelect); } void MainWindow::navigatePrevious() @@ -1183,18 +1169,26 @@ void MainWindow::executeQuery() execution_start_index = execution_end_index; + // Revert to save point now if it wasn't needed. We need to do this here because there are some rare cases where the next statement might + // be affected by what is only a temporary and unnecessary savepoint. For example in this case: + // ATTACH 'xxx' AS 'db2' + // SELECT * FROM db2.xy; -- Savepoint created here + // DETACH db2; -- Savepoint makes this statement fail + if(!modified && !wasdirty && savepoint_created) + { + db.revertToSavepoint(); // better rollback, if the logic is not enough we can tune it. + savepoint_created = false; + } + // Process events to keep the UI responsive qApp->processEvents(); } sqlWidget->finishExecution(statusMessage, ok); - plotDock->updatePlot(sqlWidget->getModel()); + attachPlot(sqlWidget->getTableResult(), sqlWidget->getModel()); connect(sqlWidget->getTableResult(), &ExtendedTableWidget::activated, this, &MainWindow::dataTableSelectionChanged); connect(sqlWidget->getTableResult(), SIGNAL(doubleClicked(QModelIndex)), this, SLOT(doubleClickTable(QModelIndex))); - if(!modified && !wasdirty && savepoint_created) - db.revertToSavepoint(); // better rollback, if the logic is not enough we can tune it. - // If the DB structure was changed by some command in this SQL script, update our schema representations if(structure_updated) db.updateSchema(); @@ -1636,7 +1630,7 @@ void MainWindow::browseTableHeaderClicked(int logicalindex) // we might try to select the last selected item ui->dataTable->setCurrentIndex(ui->dataTable->currentIndex().sibling(0, logicalindex)); - plotDock->updatePlot(m_browseTableModel, &browseTableSettings[currentlyBrowsedTableName()]); + attachPlot(ui->dataTable, m_browseTableModel, &browseTableSettings[currentlyBrowsedTableName()]); } void MainWindow::resizeEvent(QResizeEvent*) @@ -1938,7 +1932,7 @@ void MainWindow::reloadSettings() loadExtensionsFromSettings(); // Refresh view - populateStructure(); + dbStructureModel->reloadData(); populateTable(); // Hide or show the remote dock as needed diff --git a/src/MainWindow.h b/src/MainWindow.h index b5f6faba..be6e567b 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -18,6 +18,7 @@ class DbStructureModel; class RemoteDock; class RemoteDatabase; class FindReplaceDialog; +class ExtendedTableWidget; namespace Ui { class MainWindow; @@ -194,6 +195,8 @@ private: void loadExtensionsFromSettings(); void saveAsView(QString query); void duplicateRecord(int currentRow); + void selectTableLine(int lineToSelect); + void attachPlot(ExtendedTableWidget* tableWidget, SqliteTableModel* model, BrowseDataTableSettings* settings = nullptr, bool keepOrResetSelection = true); sqlb::ObjectIdentifier currentlyBrowsedTableName() const; @@ -213,7 +216,7 @@ public slots: void refresh(); void jumpToRow(const sqlb::ObjectIdentifier& table, QString column, const QByteArray& value); void switchToBrowseDataTab(QString tableToBrowse = QString()); - void populateStructure(); + void populateStructure(const QString& old_table = QString()); private slots: void createTreeContextMenu(const QPoint & qPoint); @@ -224,8 +227,6 @@ private slots: bool fileClose(); void addRecord(); void deleteRecord(); - void selectTableLine( int lineToSelect ); - void selectTableLines(int firstLine, int count); void navigatePrevious(); void navigateNext(); void navigateBegin(); diff --git a/src/PlotDock.cpp b/src/PlotDock.cpp index 49959798..d1e2daf2 100644 --- a/src/PlotDock.cpp +++ b/src/PlotDock.cpp @@ -209,17 +209,33 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett int xtype = xitemdata & (uint)0xFF; // check if we have a x axis with datetime data - if(xtype == QVariant::DateTime) - { + switch (xtype) { + case QVariant::Date: { QSharedPointer ticker(new QCPAxisTickerDateTime); ticker->setDateTimeFormat("yyyy-MM-dd"); ui->plotWidget->xAxis->setTicker(ticker); - } else { + break; + } + case QVariant::DateTime: { + QSharedPointer ticker(new QCPAxisTickerDateTime); + ticker->setDateTimeFormat("yyyy-MM-dd\nhh:mm:ss"); + ui->plotWidget->xAxis->setTicker(ticker); + break; + } + case QVariant::Time: { + QSharedPointer ticker(new QCPAxisTickerDateTime); + ticker->setDateTimeFormat("hh:mm:ss"); + ticker->setDateTimeSpec(Qt::UTC); + ui->plotWidget->xAxis->setTicker(ticker); + break; + } + default: { QSharedPointer ticker(new QCPAxisTickerFixed); ticker->setTickStepStrategy(QCPAxisTicker::tssReadability); ticker->setScaleStrategy(QCPAxisTickerFixed::ssMultiples); ui->plotWidget->xAxis->setTicker(ticker); } + } // add graph for each selected y axis for(int i = 0; i < ui->treePlotColumns->topLevelItemCount(); ++i) @@ -242,12 +258,21 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett { tdata[i] = i; // convert x type axis if it's datetime - if(xtype == QVariant::DateTime) - { + switch (xtype) { + case QVariant::DateTime: + case QVariant::Date: { QString s = model->data(model->index(i, x)).toString(); QDateTime d = QDateTime::fromString(s, Qt::ISODate); xdata[i] = d.toMSecsSinceEpoch() / 1000.0; - } else { + break; + } + case QVariant::Time: { + QString s = model->data(model->index(i, x)).toString(); + QTime t = QTime::fromString(s); + xdata[i] = t.msecsSinceStartOfDay() / 1000.0; + break; + } + default: { // Get the x value for this point. If the selected column is -1, i.e. the row number, just use the current row number from the loop // instead of retrieving some value from the model. if(x == RowNumId) @@ -256,6 +281,7 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett else xdata[i] = model->data(model->index(i, x)).toDouble(); } + } if (i != 0) isSorted &= (xdata[i-1] <= xdata[i]); @@ -326,7 +352,7 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett ui->plotWidget->replot(); // Warn user if not all data has been fetched and hint about the button for loading all the data - if (ui->plotWidget->graphCount() > 0 && model->canFetchMore()) { + if (ui->plotWidget->plottableCount() > 0 && model->canFetchMore()) { ui->buttonLoadAllData->setEnabled(true); ui->buttonLoadAllData->setStyleSheet("QToolButton {color: white; background-color: rgb(255, 102, 102)}"); ui->buttonLoadAllData->setToolTip(tr("Load all data and redraw plot.\n" @@ -339,6 +365,11 @@ void PlotDock::updatePlot(SqliteTableModel* model, BrowseDataTableSettings* sett } } +void PlotDock::resetPlot() +{ + updatePlot(nullptr); +} + void PlotDock::on_treePlotColumns_itemChanged(QTreeWidgetItem* changeitem, int column) { // disable change updates, or we get unwanted redrawing and weird behavior @@ -605,9 +636,18 @@ QVariant::Type PlotDock::guessDataType(SqliteTableModel* model, int column) type = QVariant::Double; } else { QString s = model->data(model->index(i, column)).toString(); - QDate d = QDate::fromString(s, Qt::ISODate); - if(d.isValid()) - type = QVariant::DateTime; + QDateTime dt = QDateTime::fromString(s, Qt::ISODate); + QTime t = QTime::fromString(s); + if (dt.isValid()) + // Since the way to discriminate dates with times and pure dates is that the time part is 0, we must take into account + // that some DateTimes could have "00:00:00" as time part and still the entire column has time information, so a single + // final Date should not set the type to Date if it has already been guessed as DateTime. + if (type != QVariant::DateTime && dt.time().msecsSinceStartOfDay() == 0) + type = QVariant::Date; + else + type = QVariant::DateTime; + else if (t.isValid()) + type = QVariant::Time; else type = QVariant::String; } diff --git a/src/PlotDock.h b/src/PlotDock.h index 05b1b490..5b296012 100644 --- a/src/PlotDock.h +++ b/src/PlotDock.h @@ -66,6 +66,7 @@ public: public slots: void updatePlot(SqliteTableModel* model, BrowseDataTableSettings* settings = nullptr, bool update = true, bool keepOrResetSelection = true); void fetchAllData(); + void resetPlot(); signals: void pointsSelected(int firstIndex, int count); diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index c54dc573..30d6d887 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -948,6 +948,8 @@ QString DBBrowserDB::addRecord(const sqlb::ObjectIdentifier& tablename) if (!isOpen()) return QString(); sqlb::TablePtr table = getObjectByName(tablename).dynamicCast(); + if(!table) + return QString(); // For tables without rowid we have to set the primary key by ourselves. We do so by querying for the largest value in the PK column // and adding one to it. diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp index e606e158..d413ef26 100644 --- a/src/sqlitetablemodel.cpp +++ b/src/sqlitetablemodel.cpp @@ -938,7 +938,7 @@ void SqliteTableModel::setPseudoPk(const QString& pseudoPk) bool SqliteTableModel::isEditable() const { - return !m_sTable.isEmpty(); + return !m_sTable.isEmpty() && (m_db.getObjectByName(m_sTable)->type() == sqlb::Object::Types::Table || !m_pseudoPk.isEmpty()); } void SqliteTableModel::waitForFetchingFinished() diff --git a/src/sqlitetablemodel.h b/src/sqlitetablemodel.h index d61ba0fd..4170013c 100644 --- a/src/sqlitetablemodel.h +++ b/src/sqlitetablemodel.h @@ -64,7 +64,7 @@ public: // This returns true if the model is set up for editing. The model is able to operate in more or less two different modes, table browsing // and query browsing. We only support editing data for the table browsing mode and not for the query mode. This function returns true if - // the model is currently editable, i.e. it's running in table mode. + // the model is currently editable, i.e. it's running in table mode and it isn't a view. bool isEditable() const; // Helper function for removing all comments from a SQL query diff --git a/src/sqlitetypes.h b/src/sqlitetypes.h index 632727c3..2e86b28f 100644 --- a/src/sqlitetypes.h +++ b/src/sqlitetypes.h @@ -40,8 +40,12 @@ public: explicit ObjectIdentifier(QVariant variant) { QStringList str = variant.toStringList(); - m_schema = str.first(); - m_name = str.last(); + if(str.size()) + { + m_schema = str.first(); + if(str.size() >= 2) + m_name = str.last(); + } } bool operator==(const ObjectIdentifier& rhs) const From 4c211ed95a5d13d1798571093debf98c0e489e5e Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Fri, 8 Jun 2018 14:43:48 +0200 Subject: [PATCH 08/11] [NEW] Aggiunta possibilita` di copiare le righe della tabella in SQL per facilitarne l'import --- src/ExtendedTableWidget.cpp | 35 +++++++++++++++++++++++++++++------ src/ExtendedTableWidget.h | 4 +++- src/icons/icons.qrc | 1 + src/icons/page_copy_sql.png | Bin 0 -> 833 bytes 4 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 src/icons/page_copy_sql.png diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index d26e73e5..c2167ec3 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -157,6 +157,7 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : QAction* nullAction = new QAction(tr("Set to NULL"), m_contextMenu); QAction* copyAction = new QAction(QIcon(":/icons/copy"), tr("Copy"), m_contextMenu); QAction* copyWithHeadersAction = new QAction(QIcon(":/icons/special_copy"), tr("Copy with Headers"), m_contextMenu); + QAction* copyAsSQLAction = new QAction(QIcon(":/icons/sql_copy"), tr("Copy as SQL"), m_contextMenu); QAction* pasteAction = new QAction(QIcon(":/icons/paste"), tr("Paste"), m_contextMenu); QAction* filterAction = new QAction(tr("Use as Filter"), m_contextMenu); m_contextMenu->addAction(filterAction); @@ -165,6 +166,7 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : m_contextMenu->addSeparator(); m_contextMenu->addAction(copyAction); m_contextMenu->addAction(copyWithHeadersAction); + m_contextMenu->addAction(copyAsSQLAction); m_contextMenu->addAction(pasteAction); setContextMenuPolicy(Qt::CustomContextMenu); @@ -177,6 +179,7 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : nullAction->setShortcut(QKeySequence(tr("Alt+Del"))); copyAction->setShortcut(QKeySequence::Copy); copyWithHeadersAction->setShortcut(QKeySequence(tr("Ctrl+Shift+C"))); + copyAsSQLAction->setShortcut(QKeySequence(tr("Ctrl+Alt+C"))); pasteAction->setShortcut(QKeySequence::Paste); // Set up context menu actions @@ -188,6 +191,7 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : filterAction->setEnabled(enabled); copyAction->setEnabled(enabled); copyWithHeadersAction->setEnabled(enabled); + copyAsSQLAction->setEnabled(enabled); // Try to find out whether the current view is editable and (de)activate menu options according to that bool editable = editTriggers() != QAbstractItemView::NoEditTriggers; @@ -210,6 +214,9 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : connect(copyWithHeadersAction, &QAction::triggered, [&]() { copy(true); }); + connect(copyAsSQLAction, &QAction::triggered, [&]() { + copySQL(); + }); connect(pasteAction, &QAction::triggered, [&]() { paste(); }); @@ -226,7 +233,7 @@ void ExtendedTableWidget::reloadSettings() verticalHeader()->setDefaultSectionSize(verticalHeader()->fontMetrics().height()+10); } -void ExtendedTableWidget::copy(const bool withHeaders) +void ExtendedTableWidget::copy(const bool withHeaders, const bool inSQL ) { QModelIndexList indices = selectionModel()->selectedIndexes(); @@ -249,7 +256,7 @@ void ExtendedTableWidget::copy(const bool withHeaders) m_buffer.clear(); // If a single cell is selected, copy it to clipboard - if (!withHeaders && indices.size() == 1) { + if (!inSQL && !withHeaders && indices.size() == 1) { QImage img; QVariant data = m->data(indices.first(), Qt::EditRole); @@ -294,6 +301,7 @@ void ExtendedTableWidget::copy(const bool withHeaders) } m_buffer.push_back(lst); + QString sqlResult; QString result; QString htmlResult = ""; htmlResult.append(""); @@ -314,8 +322,9 @@ void ExtendedTableWidget::copy(const bool withHeaders) const QString fieldSepText = "\t"; const QString rowSepText = "\r\n"; + QString sqlInsertStatement = QString("INSERT INTO \"%1\" ( '").arg(m->currentTableName().toString()); // Table headers - if (withHeaders) { + if (withHeaders || inSQL) { htmlResult.append(""); + sqlInsertStatement.append("') VALUES (\""); } // Table data rows for(const QModelIndex& index : indices) { // Separators. For first cell, only opening table row tags must be added for the HTML and nothing for the text version. - if (indices.first() == index) + if (indices.first() == index) { htmlResult.append("
"); int firstColumn = indices.front().column(); for(int i = firstColumn; i <= indices.back().column(); i++) { @@ -323,26 +332,32 @@ void ExtendedTableWidget::copy(const bool withHeaders) if (i != firstColumn) { result.append(fieldSepText); htmlResult.append(""); + sqlInsertStatement.append("', '"); } result.append(escapeCopiedData(headerText)); htmlResult.append(headerText); + sqlInsertStatement.append(escapeCopiedData(headerText)); } result.append(rowSepText); htmlResult.append("
"); - else if (index.row() != currentRow) { + sqlResult.append(sqlInsertStatement); + } else if (index.row() != currentRow) { result.append(rowSepText); htmlResult.append(rowSepHtml); + sqlResult.append("\");\r\n"+sqlInsertStatement); } else { result.append(fieldSepText); htmlResult.append(fieldSepHtml); + sqlResult.append("\", \""); } currentRow = index.row(); @@ -362,6 +377,7 @@ void ExtendedTableWidget::copy(const bool withHeaders) htmlResult.append("\"Image\""); } else { QByteArray text; @@ -375,12 +391,19 @@ void ExtendedTableWidget::copy(const bool withHeaders) htmlResult.append(QString(text).toHtmlEscaped()); result.append(escapeCopiedData(text)); + sqlResult.append(text.replace("\"", "\"\"")); } } + sqlResult.append("\");"); QMimeData *mimeData = new QMimeData; mimeData->setHtml(htmlResult + "
"); - mimeData->setText(result); + if ( inSQL ) + { + mimeData->setText(sqlResult); + } else { + mimeData->setText(result); + } qApp->clipboard()->setMimeData(mimeData); } diff --git a/src/ExtendedTableWidget.h b/src/ExtendedTableWidget.h index be3c8d98..0855c6ea 100644 --- a/src/ExtendedTableWidget.h +++ b/src/ExtendedTableWidget.h @@ -50,7 +50,9 @@ signals: void selectedRowsToBeDeleted(); private: - void copy(const bool withHeaders = false); + void copy(const bool withHeaders, const bool inSQL ); + void copy(const bool withHeaders = false) { copy( withHeaders, false); } + void copySQL() { copy( false, true ); } void paste(); QString escapeCopiedData(const QByteArray& data) const; diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc index 42f1ef9f..f2d512e8 100644 --- a/src/icons/icons.qrc +++ b/src/icons/icons.qrc @@ -60,6 +60,7 @@ page_find.png cross.png page_white_copy.png + page_copy_sql.png text_replace.png picture_save.png application_side_list.png diff --git a/src/icons/page_copy_sql.png b/src/icons/page_copy_sql.png new file mode 100644 index 0000000000000000000000000000000000000000..3b0e3f84b2fd67368ee1f4d4c94257639fad3224 GIT binary patch literal 833 zcmV-H1HSx;P)YQb8gJ!3$Mw+U>R#t#)^2FEiiFIJ1dFSZ8K3 zneUtP|IYcHb6Dk_?M=B}UkL{v;W&;31~LZIGIT8+zm^;_FWugGGv%&kGgf`S`hDY? zM*mM^P-5h0J2r$RATtS%I-2pe^W)RrX#bw39XAr$1UP5h7c*m0BpE>uJzaVi+G1az zDn85|Hx~;^$}0kSYHc=Wca1x67>PjP3S{nqN0Q-{6%1$tD9V|Dl@r&ZC~xe>{|U(M zhLJYdo9A!6p@N_lE)AQCuV|wv49K60S5iATrH?&B%)_a}UoFz6% zseFC;o_pu+6(~N%>1gZp%$YyOIDfQZMEjR^wZu0SElp3ccWVurFFo?qY}xNwIC;@X zOGz)*?8K}Ii^C6ON&%X#@ro4{$-=_XmhCu*cK3QVAKQTCrK`~u@5alIFQA!me2ukX z-q`XC8&y_UJ<(cDb1EH9o;mrm{&0P;@zjNUGi@@T-){?HVQ6p&xvC$1k$!N_|6!x% z_;+(g0LK>>i-xNWKl-A5vJisBhhyBaY&>j!i2mqzgeQlozyzAuM2#MN7<>m!Xpjjw zm0OYyRPPT7#fD)RERYwlm#kcxxP0Pj?zEyAP=hK7Oah&R)3tS!u!E{2yFhLLMK7Ca zL$AMZ7P_8bIjYxjPujMrQXe;e6oy`D`1JV`dcH+){QL=6j(}wgq^vX;3A$pkaI9$U z<3po_ZRR^b((Pn=!|(B;uCflTuUiou8OnsqAyMOSfO^(V$K@wqS$A=n+Y5hNRdcmz z3rjAWT}Cl+*mV7Aj-YSiM{?PX*vKOJ-x-2PrO01mnNG|mkw_&5xlak@T2dxgw_5J7 zWp47~iUf+#$P8~bfz7wJwo>h<5Uer8ME}jk{g>95ZCP5t8&H1%e*`~Eqa4q&00000 LNkvXXu0mjfjK6$i literal 0 HcmV?d00001 From f6e8f50b47ade18dc36ab0ababd44b058c55a4d9 Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Fri, 8 Jun 2018 14:47:29 +0200 Subject: [PATCH 09/11] [FIX] Correzione estetica file icons.qrc --- src/icons/icons.qrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc index f2d512e8..cd5e3e14 100644 --- a/src/icons/icons.qrc +++ b/src/icons/icons.qrc @@ -60,7 +60,7 @@ page_find.png cross.png page_white_copy.png - page_copy_sql.png + page_copy_sql.png text_replace.png picture_save.png application_side_list.png From 18be12e9e9336ac18e0b97951968e1ff62cb5d29 Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Fri, 8 Jun 2018 15:26:08 +0200 Subject: [PATCH 10/11] [CHG] Removed unnecessary duplicated functions --- src/ExtendedTableWidget.cpp | 13 ++++++++----- src/ExtendedTableWidget.h | 2 -- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index c2167ec3..270acef5 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -209,13 +209,13 @@ ExtendedTableWidget::ExtendedTableWidget(QWidget* parent) : model()->setData(index, QVariant()); }); connect(copyAction, &QAction::triggered, [&]() { - copy(false); + copy(false, false); }); connect(copyWithHeadersAction, &QAction::triggered, [&]() { - copy(true); + copy(true, false); }); connect(copyAsSQLAction, &QAction::triggered, [&]() { - copySQL(); + copy(false, true); }); connect(pasteAction, &QAction::triggered, [&]() { paste(); @@ -564,14 +564,17 @@ void ExtendedTableWidget::keyPressEvent(QKeyEvent* event) // Call a custom copy method when Ctrl-C is pressed if(event->matches(QKeySequence::Copy)) { - copy(false); + copy(false, false); return; } else if(event->matches(QKeySequence::Paste)) { // Call a custom paste method when Ctrl-V is pressed paste(); } else if(event->modifiers().testFlag(Qt::ControlModifier) && event->modifiers().testFlag(Qt::ShiftModifier) && (event->key() == Qt::Key_C)) { // Call copy with headers when Ctrl-Shift-C is pressed - copy(true); + copy(true, false); + } else if(event->modifiers().testFlag(Qt::ControlModifier) && event->modifiers().testFlag(Qt::AltModifier) && (event->key() == Qt::Key_C)) { + // Call copy in SQL format when Ctrl-Alt-C is pressed + copy(false, true); } else if(event->key() == Qt::Key_Tab && hasFocus() && selectedIndexes().count() == 1 && selectedIndexes().at(0).row() == model()->rowCount()-1 && selectedIndexes().at(0).column() == model()->columnCount()-1) { diff --git a/src/ExtendedTableWidget.h b/src/ExtendedTableWidget.h index 0855c6ea..572a18f8 100644 --- a/src/ExtendedTableWidget.h +++ b/src/ExtendedTableWidget.h @@ -51,8 +51,6 @@ signals: private: void copy(const bool withHeaders, const bool inSQL ); - void copy(const bool withHeaders = false) { copy( withHeaders, false); } - void copySQL() { copy( false, true ); } void paste(); QString escapeCopiedData(const QByteArray& data) const; From 3d2e8205a0d94dfe794b38a2662f9cdc3e7ee288 Mon Sep 17 00:00:00 2001 From: Giuseppe Zizza Date: Tue, 12 Jun 2018 13:44:18 +0200 Subject: [PATCH 11/11] [CHG] Converted to HEX from Base64 the image SQL export [CHG] Using sqlb::escapeIdentifier() for table quotation to uniform with the rest of the code --- src/ExtendedTableWidget.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index 270acef5..caab1138 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -322,7 +322,7 @@ void ExtendedTableWidget::copy(const bool withHeaders, const bool inSQL ) const QString fieldSepText = "\t"; const QString rowSepText = "\r\n"; - QString sqlInsertStatement = QString("INSERT INTO \"%1\" ( '").arg(m->currentTableName().toString()); + QString sqlInsertStatement = QString("INSERT INTO %1 ( '").arg(sqlb::escapeIdentifier(m->currentTableName().toString())); // Table headers if (withHeaders || inSQL) { htmlResult.append(""); @@ -377,7 +377,7 @@ void ExtendedTableWidget::copy(const bool withHeaders, const bool inSQL ) htmlResult.append("\"Image\""); } else { QByteArray text; @@ -397,11 +397,11 @@ void ExtendedTableWidget::copy(const bool withHeaders, const bool inSQL ) sqlResult.append("\");"); QMimeData *mimeData = new QMimeData; - mimeData->setHtml(htmlResult + ""); if ( inSQL ) { mimeData->setText(sqlResult); } else { + mimeData->setHtml(htmlResult + ""); mimeData->setText(result); } qApp->clipboard()->setMimeData(mimeData);