From c832d8a58c857e309d41516d3918f783f3e848be Mon Sep 17 00:00:00 2001 From: mgrojo Date: Sat, 18 Nov 2017 14:57:36 +0100 Subject: [PATCH] JSON mode for cell editor Support for JSON in the Database Cell editor using the QScintilla library. The lexJSON has been added to the compilation. See issue #1173 --- CMakeLists.txt | 2 + libs/qscintilla/Qt4Qt5/CMakeLists.txt | 3 + libs/qscintilla/Qt4Qt5/qscintilla.pro | 3 + libs/qscintilla/src/Catalogue.cpp | 1 + src/EditDialog.cpp | 164 ++++++++++++++++++++++---- src/EditDialog.h | 9 +- src/EditDialog.ui | 6 + 7 files changed, 165 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a89c7d06..7490196b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,7 @@ set(SQLB_MOC_HDR src/VacuumDialog.h src/sqlitetablemodel.h src/sqltextedit.h + src/jsontextedit.h src/DbStructureModel.h src/Application.h src/CipherDialog.h @@ -139,6 +140,7 @@ set(SQLB_SRC src/sqlitetablemodel.cpp src/sqlitetypes.cpp src/sqltextedit.cpp + src/jsontextedit.cpp src/csvparser.cpp src/DbStructureModel.cpp src/grammar/Sqlite3Lexer.cpp diff --git a/libs/qscintilla/Qt4Qt5/CMakeLists.txt b/libs/qscintilla/Qt4Qt5/CMakeLists.txt index a4bef52a..a90f00b8 100644 --- a/libs/qscintilla/Qt4Qt5/CMakeLists.txt +++ b/libs/qscintilla/Qt4Qt5/CMakeLists.txt @@ -22,6 +22,7 @@ set(QSCINTILLA_SRC qscilexer.cpp qscilexercustom.cpp qscilexersql.cpp + qscilexerjson.cpp qscimacro.cpp qsciprinter.cpp qscistyle.cpp @@ -33,6 +34,7 @@ set(QSCINTILLA_SRC PlatQt.cpp ScintillaQt.cpp ../lexers/LexSQL.cpp + ../lexers/LexJSON.cpp ../lexlib/Accessor.cpp ../lexlib/CharacterCategory.cpp ../lexlib/CharacterSet.cpp @@ -143,6 +145,7 @@ set(QSCINTILLA_MOC_HDR ./Qsci/qscilexer.h ./Qsci/qscilexercustom.h ./Qsci/qscilexersql.h + ./Qsci/qscilexerjson.h ./Qsci/qscimacro.h SciClasses.h ScintillaQt.h diff --git a/libs/qscintilla/Qt4Qt5/qscintilla.pro b/libs/qscintilla/Qt4Qt5/qscintilla.pro index 4b3deef9..127f249a 100644 --- a/libs/qscintilla/Qt4Qt5/qscintilla.pro +++ b/libs/qscintilla/Qt4Qt5/qscintilla.pro @@ -86,6 +86,7 @@ HEADERS = \ ./Qsci/qscilexer.h \ ./Qsci/qscilexercustom.h \ ./Qsci/qscilexersql.h \ + ./Qsci/qscilexerjson.h \ ./Qsci/qscimacro.h \ ./Qsci/qsciprinter.h \ ./Qsci/qscistyle.h \ @@ -158,6 +159,7 @@ SOURCES = \ qscilexer.cpp \ qscilexercustom.cpp \ qscilexersql.cpp \ + qscilexerjson.cpp \ qscimacro.cpp \ qsciprinter.cpp \ qscistyle.cpp \ @@ -169,6 +171,7 @@ SOURCES = \ PlatQt.cpp \ ScintillaQt.cpp \ ../lexers/LexSQL.cpp \ + ../lexers/LexJSON.cpp \ ../lexlib/Accessor.cpp \ ../lexlib/CharacterCategory.cpp \ ../lexlib/CharacterSet.cpp \ diff --git a/libs/qscintilla/src/Catalogue.cpp b/libs/qscintilla/src/Catalogue.cpp index 01c03550..375781d9 100644 --- a/libs/qscintilla/src/Catalogue.cpp +++ b/libs/qscintilla/src/Catalogue.cpp @@ -78,6 +78,7 @@ int Scintilla_LinkLexers() { //++Autogenerated -- run scripts/LexGen.py to regenerate //**\(\tLINK_LEXER(\*);\n\) LINK_LEXER(lmSQL); + LINK_LEXER(lmJSON); //--Autogenerated -- end of automatically generated section diff --git a/src/EditDialog.cpp b/src/EditDialog.cpp index aaf7b7f3..5659fe5b 100644 --- a/src/EditDialog.cpp +++ b/src/EditDialog.cpp @@ -31,11 +31,17 @@ EditDialog::EditDialog(QWidget* parent) hexLayout->addWidget(hexEdit); hexEdit->setOverwriteMode(false); + QHBoxLayout* jsonLayout = new QHBoxLayout(ui->editorJSON); + jsonEdit = new JsonTextEdit(this); + jsonLayout->addWidget(jsonEdit); + QShortcut* ins = new QShortcut(QKeySequence(Qt::Key_Insert), this); connect(ins, SIGNAL(activated()), this, SLOT(toggleOverwriteMode())); connect(ui->editorText, SIGNAL(textChanged()), this, SLOT(updateApplyButton())); connect(hexEdit, SIGNAL(dataChanged()), this, SLOT(updateApplyButton())); + connect(jsonEdit, SIGNAL(textChanged()), this, SLOT(updateApplyButton())); + connect(jsonEdit, SIGNAL(textChanged()), this, SLOT(editTextChanged())); reloadSettings(); } @@ -89,6 +95,9 @@ void EditDialog::loadData(const QByteArray& data) // Data type specific handling switch (dataType) { case Null: + // 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 @@ -96,10 +105,19 @@ void EditDialog::loadData(const QByteArray& data) // Empty the text editor contents, then enable text editing ui->editorText->clear(); - ui->editorText->setEnabled(true); break; + case JsonEditor: + // The JSON widget buffer is now the main data source + dataSource = JsonBuffer; + + // Empty the text editor contents, then enable text editing + jsonEdit->clear(); + + break; + + case HexEditor: // The hex widget buffer is now the main data source dataSource = HexBuffer; @@ -124,6 +142,10 @@ void EditDialog::loadData(const QByteArray& data) break; case Text: + // 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 @@ -133,14 +155,24 @@ void EditDialog::loadData(const QByteArray& data) textData = QString::fromUtf8(data.constData(), data.size()); ui->editorText->setPlainText(textData); - // Enable text editing - ui->editorText->setEnabled(true); - // 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; + + // Load the text into the text editor + textData = QString::fromUtf8(data.constData(), data.size()); + jsonEdit->setText(textData); + + // Select all of the text by default + jsonEdit->selectAll(); + + break; + case HexEditor: // The hex widget buffer is now the main data source dataSource = HexBuffer; @@ -191,6 +223,12 @@ void EditDialog::loadData(const QByteArray& data) ui->editorText->setEnabled(false); break; + case JsonEditor: + // Disable text editing, and use a warning message as the contents + jsonEdit->setText(tr("Image data can't be viewed with the JSON editor")); + jsonEdit->setEnabled(false); + break; + case ImageViewer: // Load the image into the image viewing widget if (img.loadFromData(data)) { @@ -219,6 +257,12 @@ void EditDialog::loadData(const QByteArray& data) ui->editorText->setEnabled(false); break; + case JsonEditor: + // Disable text editing, and use a warning message as the contents + jsonEdit->setText(QString(tr("Binary data can't be viewed with the JSON editor"))); + jsonEdit->setEnabled(false); + break; + case ImageViewer: // Clear any image from the image viewing widget ui->editorImage->setPixmap(QPixmap(0,0)); @@ -281,12 +325,20 @@ void EditDialog::exportData() QFile file(fileName); if(file.open(QIODevice::WriteOnly)) { - if (dataSource == HexBuffer) { + switch (dataSource) { + case HexBuffer: // Data source is the hex buffer file.write(hexEdit->data()); - } else { + break; + case TextBuffer: // Data source is the text buffer file.write(ui->editorText->toPlainText().toUtf8()); + break; + case JsonBuffer: + // Data source is the JSON buffer + file.write(jsonEdit->text().toUtf8()); + break; + } file.close(); } @@ -298,16 +350,19 @@ void EditDialog::setNull() ui->editorText->clear(); ui->editorImage->clear(); hexEdit->setData(QByteArray()); + jsonEdit->clear(); dataType = Null; // Check if in text editor mode int editMode = ui->editorStack->currentIndex(); - if (editMode == TextEditor) { + if (editMode == TextEditor || editMode == JsonEditor) { // Setting NULL in the text editor switches the data source to it dataSource = TextBuffer; // Ensure the text editor is enabled ui->editorText->setEnabled(true); + // Ensure the JSON editor is enabled + jsonEdit->setEnabled(true); // The text editor doesn't know the difference between an empty string // and a NULL, so we need to record NULL outside of that @@ -331,7 +386,8 @@ void EditDialog::accept() if(!currentIndex.isValid()) return; - if (dataSource == TextBuffer) { + switch (dataSource) { + case TextBuffer: // Check if a NULL is set in the text editor if (textNullSet) { emit recordTextUpdated(currentIndex, hexEdit->data(), true); @@ -343,12 +399,28 @@ void EditDialog::accept() // The data is different, so commit it back to the database emit recordTextUpdated(currentIndex, newData.toUtf8(), false); } - } else { + break; + case JsonBuffer: + // Check if a NULL is set in the text editor + if (textNullSet) { + emit recordTextUpdated(currentIndex, hexEdit->data(), true); + } else { + // It's not NULL, so proceed with normal text string checking + QString oldData = currentIndex.data(Qt::EditRole).toString(); + QString newData = jsonEdit->text(); + if (oldData != newData) + // The data is different, so commit it back to the database + emit recordTextUpdated(currentIndex, newData.toUtf8(), false); + } + break; + + case HexBuffer: // The data source is the hex widget buffer, thus binary data QByteArray oldData = currentIndex.data(Qt::EditRole).toByteArray(); QByteArray newData = hexEdit->data(); if (newData != oldData) emit recordTextUpdated(currentIndex, newData, true); + break; } } @@ -356,12 +428,21 @@ void EditDialog::accept() void EditDialog::editModeChanged(int newMode) { // * If the dataSource is the text buffer, the data is always text * - if (dataSource == TextBuffer) { + switch (dataSource) { + case TextBuffer: switch (newMode) { case TextEditor: // Switching to the text editor // Nothing to do, as the text is already in the text buffer break; + case JsonEditor: // Switching to the JSON editor + // Convert the text widget buffer for the JSON widget + jsonEdit->setText(ui->editorText->toPlainText().toUtf8()); + + // The JSON widget buffer is now the main data source + dataSource = JsonBuffer; + break; + case HexEditor: // Switching to the hex editor // Convert the text widget buffer for the hex widget hexEdit->setData(ui->editorText->toPlainText().toUtf8()); @@ -379,24 +460,56 @@ void EditDialog::editModeChanged(int newMode) // Switch to the selected editor ui->editorStack->setCurrentIndex(newMode); return; - } + break; + case HexBuffer: - // * If the dataSource is the hex buffer, the contents could be anything - // so we just pass it to our loadData() function to handle * - if (dataSource == HexBuffer) { + // * If the dataSource is the hex buffer, the contents could be anything + // so we just pass it to our loadData() function to handle * // Switch to the selected editor first, as loadData() relies on it // being current ui->editorStack->setCurrentIndex(newMode); // Load the data into the appropriate widget, as done by loadData() loadData(hexEdit->data()); - } + break; + case JsonBuffer: + switch (newMode) { + case TextEditor: // Switching to the text editor + // Convert the text widget buffer for the JSON widget + ui->editorText->setText(jsonEdit->text()); + + // The Text widget buffer is now the main data source + dataSource = TextBuffer; + break; + + case JsonEditor: // Switching to the JSON editor + // Nothing to do, as the text is already in the JSON buffer + break; + + + case HexEditor: // Switching to the hex editor + // Convert the text widget buffer for the hex widget + hexEdit->setData(jsonEdit->text().toUtf8()); + + // The hex widget buffer is now the main data source + dataSource = HexBuffer; + break; + + case ImageViewer: + // Clear any image from the image viewing widget + ui->editorImage->setPixmap(QPixmap(0,0)); + break; + } + + // Switch to the selected editor + ui->editorStack->setCurrentIndex(newMode); + } } // Called for every keystroke in the text editor (only) void EditDialog::editTextChanged() { - if (dataSource == TextBuffer) { + if (dataSource == TextBuffer || dataSource == JsonBuffer) { // Data has been changed in the text editor, so it can't be a NULL // any more textNullSet = false; @@ -442,6 +555,7 @@ void EditDialog::toggleOverwriteMode() hexEdit->setOverwriteMode(currentMode); ui->editorText->setOverwriteMode(currentMode); + jsonEdit->setOverwriteMode(currentMode); } void EditDialog::setFocus() @@ -467,6 +581,7 @@ void EditDialog::setReadOnly(bool ro) ui->buttonImport->setEnabled(!ro); ui->editorText->setReadOnly(ro); ui->editorBinary->setEnabled(!ro); // We disable the entire hex editor here instead of setting it to read only because it doesn't have a setReadOnly() method + jsonEdit->setReadOnly(ro); } // Update the information labels in the bottom left corner of the dialog @@ -544,9 +659,16 @@ QString EditDialog::humanReadableSize(double byteCount) const void EditDialog::reloadSettings() { - // Set the font for the text and hex editors - QFont editorFont(Settings::getValue("databrowser", "font").toString()); - editorFont.setPointSize(Settings::getValue("databrowser", "fontsize").toInt()); - ui->editorText->setFont(editorFont); - hexEdit->setFont(editorFont); + // Set the databrowser font for the text editor but the (SQL) editor + // font for hex editor, since it needs a Monospace font and the + // databrowser font would be usually of variable width. + QFont textFont(Settings::getValue("databrowser", "font").toString()); + textFont.setPointSize(Settings::getValue("databrowser", "fontsize").toInt()); + ui->editorText->setFont(textFont); + + QFont hexFont(Settings::getValue("editor", "font").toString()); + hexFont.setPointSize(Settings::getValue("databrowser", "fontsize").toInt()); + hexEdit->setFont(hexFont); + + jsonEdit->reloadSettings(); } diff --git a/src/EditDialog.h b/src/EditDialog.h index 399be289..49802133 100644 --- a/src/EditDialog.h +++ b/src/EditDialog.h @@ -4,6 +4,8 @@ #include #include +#include "jsontextedit.h" + class QHexEdit; namespace Ui { @@ -47,6 +49,7 @@ signals: private: Ui::EditDialog* ui; QHexEdit* hexEdit; + JsonTextEdit* jsonEdit; QPersistentModelIndex currentIndex; int dataSource; int dataType; @@ -55,7 +58,8 @@ private: enum DataSources { TextBuffer, - HexBuffer + HexBuffer, + JsonBuffer }; enum DataTypes { @@ -68,7 +72,8 @@ private: enum EditModes { TextEditor = 0, HexEditor = 1, - ImageViewer = 2 + JsonEditor = 2, + ImageViewer = 3 }; int checkDataType(const QByteArray& data); diff --git a/src/EditDialog.ui b/src/EditDialog.ui index 97a1083d..474ddb33 100644 --- a/src/EditDialog.ui +++ b/src/EditDialog.ui @@ -47,6 +47,11 @@ Binary + + + JSON + + Image @@ -151,6 +156,7 @@ + true