Merge pull request #1275 from sqlitebrowser/xml_cell_edit

XML mode for the cell editor
This commit is contained in:
Manuel
2018-01-27 20:09:36 +01:00
committed by GitHub
26 changed files with 6987 additions and 338 deletions

View File

@@ -3,6 +3,7 @@
#include "sqlitedb.h"
#include "Settings.h"
#include "src/qhexedit.h"
#include "docktextedit.h"
#include "FileDialog.h"
#include "Data.h"
@@ -13,6 +14,7 @@
#include <QBuffer>
#include <QModelIndex>
#include <QJsonDocument>
#include <QDomDocument>
#include <QMessageBox>
EditDialog::EditDialog(QWidget* parent)
@@ -21,7 +23,6 @@ EditDialog::EditDialog(QWidget* parent)
currentIndex(QModelIndex()),
dataSource(TextBuffer),
dataType(Null),
textNullSet(false),
isReadOnly(true)
{
ui->setupUi(this);
@@ -34,17 +35,18 @@ EditDialog::EditDialog(QWidget* parent)
hexLayout->addWidget(hexEdit);
hexEdit->setOverwriteMode(false);
QHBoxLayout* jsonLayout = new QHBoxLayout(ui->editorJSON);
jsonEdit = new JsonTextEdit(this);
jsonLayout->addWidget(jsonEdit);
QHBoxLayout* sciLayout = new QHBoxLayout(ui->editorSci);
sciEdit = new DockTextEdit(this);
sciLayout->addWidget(sciEdit);
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()));
connect(sciEdit, SIGNAL(textChanged()), this, SLOT(updateApplyButton()));
connect(sciEdit, SIGNAL(textChanged()), this, SLOT(editTextChanged()));
mustIndentAndCompact = Settings::getValue("databrowser", "indent_compact").toBool();
ui->buttonIndent->setChecked(mustIndentAndCompact);
@@ -99,15 +101,15 @@ void EditDialog::loadData(const QByteArray& data)
// Determine the data type, saving that info in the class variable
dataType = checkDataType(data);
// Get the current editor mode (eg text, hex, or image mode)
int editMode = ui->editorStack->currentIndex();
// Get the current editor mode (eg text, hex, image, json or xml mode)
int editMode = ui->comboMode->currentIndex();
// Data type specific handling
switch (dataType) {
case Null:
// Set enabled any of the text widgets
ui->editorText->setEnabled(true);
jsonEdit->setEnabled(true);
sciEdit->setEnabled(true);
switch (editMode) {
case TextEditor:
// The text widget buffer is now the main data source
@@ -119,15 +121,16 @@ void EditDialog::loadData(const QByteArray& data)
break;
case JsonEditor:
case XmlEditor:
// The JSON widget buffer is now the main data source
dataSource = JsonBuffer;
dataSource = SciBuffer;
// Empty the text editor contents, then enable text editing
jsonEdit->clear();
sciEdit->clear();
break;
case HexEditor:
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
@@ -153,78 +156,27 @@ void EditDialog::loadData(const QByteArray& data)
case Text:
case JSON:
// Set enabled any of the text widgets
ui->editorText->setEnabled(true);
jsonEdit->setEnabled(true);
// Can be stored in any widget, except the ImageViewer
switch (editMode) {
case TextEditor:
{
// The text widget buffer is now the main data source
dataSource = TextBuffer;
// 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();
setDataInBuffer(data, TextBuffer);
break;
}
case JsonEditor:
// The JSON widget buffer is now the main data source
dataSource = JsonBuffer;
{
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(QByteArray(data.constData(), data.size()), &parseError);
if (mustIndentAndCompact && !jsonDoc.isNull()) {
// Load indented JSON into the JSON editor
textData = QString(jsonDoc.toJson(QJsonDocument::Indented));
jsonEdit->setText(textData);
} else {
// Fallback case. The data is not yet valid JSON.
textData = QString::fromUtf8(data.constData(), data.size());
jsonEdit->setText(textData);
}
jsonEdit->clearErrorIndicators();
if (parseError.error != QJsonParseError::NoError)
jsonEdit->setErrorIndicator(parseError.offset-1);
}
case XmlEditor:
setDataInBuffer(data, SciBuffer);
break;
case HexEditor:
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
// Load the text into the hex editor
hexEdit->setData(data);
setDataInBuffer(data, HexBuffer);
break;
case ImageViewer:
// The text widget buffer is now the main data source
dataSource = TextBuffer;
// The image viewer cannot hold data nor display text.
// Clear any image from the image viewing widget
ui->editorImage->setPixmap(QPixmap(0,0));
// Load the text into the text editor
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();
setDataInBuffer(data, TextBuffer);
break;
}
@@ -235,10 +187,7 @@ void EditDialog::loadData(const QByteArray& data)
// stored it in the editorImage widget instead, it would be a pixmap
// and there's no good way to restore that back to the original
// (pristine) image data. eg image metadata would be lost
hexEdit->setData(data);
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
setDataInBuffer(data, HexBuffer);
// Update the display if in text edit or image viewer mode
switch (editMode) {
@@ -250,10 +199,12 @@ void EditDialog::loadData(const QByteArray& data)
ui->editorText->setEnabled(false);
break;
case XmlEditor:
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);
sciEdit->setText(tr("Image data can't be viewed with this editor"));
sciEdit->setEnabled(false);
break;
case ImageViewer:
@@ -264,16 +215,44 @@ void EditDialog::loadData(const QByteArray& data)
break;
}
break;
case SVG:
// Set the XML data in any buffer or update image in image viewer mode
switch (editMode) {
case TextEditor:
setDataInBuffer(data, TextBuffer);
break;
case JsonEditor:
case XmlEditor:
setDataInBuffer(data, SciBuffer);
break;
case HexEditor:
setDataInBuffer(data, HexBuffer);
break;
case ImageViewer:
// Set data in the XML (Sci) Buffer and load the SVG Image
setDataInBuffer(data, SciBuffer);
sciEdit->setLanguage(DockTextEdit::XML);
// Load the image into the image viewing widget
if (img.loadFromData(data)) {
ui->editorImage->setPixmap(QPixmap::fromImage(img));
}
break;
}
break;
default:
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
// The data seems to be general binary data, which is always loaded
// into the hex widget (the only safe place for it)
// Load the data into the hex editor
hexEdit->setData(data);
// Load the data into the hex buffer
setDataInBuffer(data, HexBuffer);
switch (editMode) {
case TextEditor:
@@ -285,9 +264,10 @@ void EditDialog::loadData(const QByteArray& data)
break;
case JsonEditor:
case XmlEditor:
// 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);
sciEdit->setText(QString(tr("Binary data can't be viewed with this editor")));
sciEdit->setEnabled(false);
break;
case ImageViewer:
@@ -310,7 +290,7 @@ void EditDialog::importData()
this,
tr("Choose a file to import")
#ifndef Q_OS_MAC // Filters on OS X are buggy
, tr("Text files(*.txt);;Image files(%1);;JSON files(*.json);;All files(*)").arg(image_formats)
, tr("Text files (*.txt);;Image files (%1);;JSON files (*.json);;XML files (*.xml);;All files (*)").arg(image_formats)
#endif
);
if(QFile::exists(fileName))
@@ -361,11 +341,10 @@ void EditDialog::exportData()
// Data source is the text buffer
file.write(ui->editorText->toPlainText().toUtf8());
break;
case JsonBuffer:
case SciBuffer:
// Data source is the JSON buffer
file.write(jsonEdit->text().toUtf8());
file.write(sciEdit->text().toUtf8());
break;
}
file.close();
}
@@ -377,25 +356,17 @@ void EditDialog::setNull()
ui->editorText->clear();
ui->editorImage->clear();
hexEdit->setData(QByteArray());
jsonEdit->clear();
sciEdit->clear();
dataType = Null;
removedBom.clear();
// Check if in text editor mode
int editMode = ui->editorStack->currentIndex();
if (editMode == TextEditor || editMode == JsonEditor) {
// Setting NULL in the text editor switches the data source to it
dataSource = TextBuffer;
// The text editors don't know the difference between an empty string
// and a NULL, so we need to record NULL outside of that
dataType = Null;
// 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
textNullSet = true;
}
// Ensure the text (plain and Scintilla) editors are enabled
ui->editorText->setEnabled(true);
sciEdit->setEnabled(true);
// Update the cell data info in the bottom left of the Edit Cell
updateCellInfo(hexEdit->data());
@@ -424,47 +395,46 @@ void EditDialog::accept()
if(!currentIndex.isValid())
return;
if (dataType == Null) {
emit recordTextUpdated(currentIndex, hexEdit->data(), true);
return;
}
switch (dataSource) {
case TextBuffer:
// 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 = removedBom + ui->editorText->toPlainText();
if (oldData != newData)
// The data is different, so commit it back to the database
emit recordTextUpdated(currentIndex, removedBom + newData.toUtf8(), false);
}
{
QString oldData = currentIndex.data(Qt::EditRole).toString();
QString newData = removedBom + ui->editorText->toPlainText();
if (oldData != newData)
// The data is different, so commit it back to the database
emit recordTextUpdated(currentIndex, removedBom + newData.toUtf8(), false);
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
}
case SciBuffer:
switch (sciEdit->language()) {
case DockTextEdit::JSON:
{
QString oldData = currentIndex.data(Qt::EditRole).toString();
QString newData;
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonEdit->text().toUtf8(), &parseError);
QJsonDocument jsonDoc = QJsonDocument::fromJson(sciEdit->text().toUtf8(), &parseError);
bool proceed;
jsonEdit->clearErrorIndicators();
sciEdit->clearErrorIndicators();
if (parseError.error != QJsonParseError::NoError)
jsonEdit->setErrorIndicator(parseError.offset-1);
sciEdit->setErrorIndicator(parseError.offset-1);
if (!jsonDoc.isNull()) {
if (mustIndentAndCompact)
// Compact the JSON data before storing
newData = QString(jsonDoc.toJson(QJsonDocument::Compact));
else
newData = jsonEdit->text();
newData = sciEdit->text();
proceed = (oldData != newData);
} else {
newData = jsonEdit->text();
newData = sciEdit->text();
proceed = (oldData != newData && promptInvalidData("JSON", parseError.errorString()));
}
if (proceed)
@@ -472,7 +442,38 @@ void EditDialog::accept()
emit recordTextUpdated(currentIndex, newData.toUtf8(), false);
}
break;
case DockTextEdit::XML:
{
QString oldData = currentIndex.data(Qt::EditRole).toString();
QString newData;
QDomDocument xmlDoc;
QString errorMsg;
int errorLine, errorColumn;
bool isValid = xmlDoc.setContent(sciEdit->text().toUtf8(), true, &errorMsg, &errorLine, &errorColumn);
bool proceed;
sciEdit->clearErrorIndicators();
if (!isValid) {
sciEdit->setErrorIndicator(errorLine-1, errorColumn-1, errorLine, 0);
newData = sciEdit->text();
proceed = (oldData != newData && promptInvalidData("XML", errorMsg));
} else {
if (mustIndentAndCompact)
// Compact the XML data before storing. If indent is -1, no whitespace at all is added.
newData = xmlDoc.toString(-1);
else
newData = sciEdit->text();
proceed = (oldData != newData);
}
if (proceed)
// The data is different, so commit it back to the database
emit recordTextUpdated(currentIndex, newData.toUtf8(), false);
}
break;
}
break;
case HexBuffer:
// The data source is the hex widget buffer, thus binary data
QByteArray oldData = currentIndex.data(Qt::EditRole).toByteArray();
@@ -483,10 +484,94 @@ void EditDialog::accept()
}
}
void EditDialog::setDataInBuffer(const QByteArray& data, DataSources source)
{
dataSource = source;
QString textData;
// 1) Perform validation and text formatting (if applicable).
// 2) Set the text in the corresponding editor widget (the text widget for the Image case).
// 3) Enable the widget.
switch (dataSource) {
case TextBuffer:
{
// 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 (this is useful for simple text data that we usually edit as a whole)
ui->editorText->selectAll();
ui->editorText->setEnabled(true);
break;
}
case SciBuffer:
switch (sciEdit->language()) {
case DockTextEdit::JSON:
{
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(QByteArray(data.constData(), data.size()), &parseError);
if (mustIndentAndCompact && !jsonDoc.isNull()) {
// Load indented JSON into the JSON editor
textData = QString(jsonDoc.toJson(QJsonDocument::Indented));
} else {
// Fallback case. The data is not yet valid JSON or no auto-formatting applied.
textData = QString::fromUtf8(data.constData(), data.size());
}
sciEdit->setText(textData);
sciEdit->clearErrorIndicators();
if (parseError.error != QJsonParseError::NoError)
sciEdit->setErrorIndicator(parseError.offset-1);
sciEdit->setEnabled(true);
}
break;
case DockTextEdit::XML:
{
QString errorMsg;
int errorLine, errorColumn;
QDomDocument xmlDoc;
bool isValid = xmlDoc.setContent(data, true, &errorMsg, &errorLine, &errorColumn);
if (mustIndentAndCompact && isValid) {
// Load indented XML into the XML editor
textData = xmlDoc.toString(Settings::getValue("editor", "tabsize").toInt());
} else {
// Fallback case. The data is not yet valid JSON or no auto-formatting applied.
textData = QString::fromUtf8(data.constData(), data.size());
}
sciEdit->setText(textData);
sciEdit->clearErrorIndicators();
if (!isValid)
// Adjust line and column by one (Scintilla starts at 1 and QDomDocument at 0)
sciEdit->setErrorIndicator(errorLine-1, errorColumn-1, errorLine, 0);
sciEdit->setEnabled(true);
}
break;
}
break;
case HexBuffer:
hexEdit->setData(data);
hexEdit->setEnabled(true);
break;
}
}
// Called when the user manually changes the "Mode" drop down combobox
void EditDialog::editModeChanged(int newMode)
{
ui->buttonIndent->setEnabled(newMode == JsonEditor);
ui->buttonIndent->setEnabled(newMode == JsonEditor || newMode == XmlEditor);
setStackCurrentIndex(newMode);
// * If the dataSource is the text buffer, the data is always text *
switch (dataSource) {
@@ -496,30 +581,16 @@ void EditDialog::editModeChanged(int newMode)
// Nothing to do, as the text is already in the text buffer
break;
case JsonEditor: // Switching to the JSON editor
case JsonEditor: // Switching to one of the Scintilla editor modes
case XmlEditor:
// Convert the text widget buffer for the JSON widget
// * If the dataSource is the TextBuffer, the contents could
// be still compacted so we just pass it to our loadData()
// function to handle, for indenting if necessary *
// 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(ui->editorText->toPlainText().toUtf8());
// jsonEdit->setText(ui->editorText->toPlainText().toUtf8());
// The JSON widget buffer is now the main data source
dataSource = JsonBuffer;
setDataInBuffer(ui->editorText->toPlainText().toUtf8(), SciBuffer);
break;
case HexEditor: // Switching to the hex editor
// Convert the text widget buffer for the hex widget
hexEdit->setData(removedBom + ui->editorText->toPlainText().toUtf8());
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
setDataInBuffer(removedBom + ui->editorText->toPlainText().toUtf8(), HexBuffer);
break;
case ImageViewer:
@@ -527,72 +598,76 @@ void EditDialog::editModeChanged(int newMode)
ui->editorImage->setPixmap(QPixmap(0,0));
break;
}
// 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 *
// Switch to the selected editor first, as loadData() relies on it
// Note that we have already set the editor, 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:
case SciBuffer:
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;
setDataInBuffer(sciEdit->text().toUtf8(), 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;
setDataInBuffer(sciEdit->text().toUtf8(), HexBuffer);
break;
case ImageViewer:
// Clear any image from the image viewing widget
ui->editorImage->setPixmap(QPixmap(0,0));
{
// When SVG format, load the image, else clear it.
QByteArray data = sciEdit->text().toUtf8();
dataType = checkDataType(data);
if (dataType == SVG) {
QImage img;
if (img.loadFromData(data))
ui->editorImage->setPixmap(QPixmap::fromImage(img));
else
// Clear any image from the image viewing widget
ui->editorImage->setPixmap(QPixmap(0,0));
}
}
break;
case JsonEditor: // Switching to the JSON editor
case XmlEditor: // Switching to the XML editor
// The text is already in the Sci buffer but we need to perform the necessary formatting.
setDataInBuffer(sciEdit->text().toUtf8(), SciBuffer);
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 || dataSource == JsonBuffer) {
if (dataSource == TextBuffer || dataSource == SciBuffer) {
// Data has been changed in the text editor, so it can't be a NULL
// any more
textNullSet = false;
// any more. It hasn't been validated yet, so it cannot be JSON nor XML.
if (dataType == Null) {
dataType = Text;
ui->labelType->setText(tr("Type of data currently in cell: Text / Numeric"));
}
// Update the cell info in the bottom left manually. This is because
// updateCellInfo() only works with QByteArray's (for now)
int dataLength;
if (dataSource == TextBuffer)
switch (dataSource) {
case TextBuffer:
dataLength = ui->editorText->toPlainText().length();
else
dataLength = jsonEdit->text().length();
ui->labelType->setText(tr("Type of data currently in cell: Text / Numeric"));
break;
case SciBuffer:
dataLength = sciEdit->text().length();
break;
}
ui->labelSize->setText(tr("%n char(s)", "", dataLength));
}
}
@@ -601,12 +676,11 @@ void EditDialog::setMustIndentAndCompact(bool enable)
{
mustIndentAndCompact = enable;
// Indent or compact if necessary. If data has changed, reload from the widget, else from the table.
if (ui->buttonApply->isEnabled())
loadData(jsonEdit->text().toUtf8());
else
// Indent or compact if necessary. If data has changed (button Apply indicates so), reload from the widget, else from the table.
if (ui->buttonApply->isEnabled()) {
setDataInBuffer(sciEdit->text().toUtf8(), SciBuffer);
} else
setCurrentIndex(currentIndex);
}
// Determine the type of data in the cell
@@ -623,13 +697,14 @@ int EditDialog::checkDataType(const QByteArray& data)
// that returned true, do a more sophisticated test of the data. This way we get both, good performance and proper data checking.
QBuffer imageBuffer(&cellData);
QImageReader readerBuffer(&imageBuffer);
QString imageFormat = readerBuffer.format();
if(readerBuffer.canRead() && !readerBuffer.read().isNull())
return Image;
return imageFormat == "svg" ? SVG : Image;
// Check if it's text only
if(isTextOnly(cellData))
{
QJsonDocument jsonDoc = QJsonDocument::fromJson(QString(cellData).toUtf8());
QJsonDocument jsonDoc = QJsonDocument::fromJson(cellData);
if (!jsonDoc.isNull())
return JSON;
else
@@ -647,7 +722,7 @@ void EditDialog::toggleOverwriteMode()
hexEdit->setOverwriteMode(currentMode);
ui->editorText->setOverwriteMode(currentMode);
jsonEdit->setOverwriteMode(currentMode);
sciEdit->setOverwriteMode(currentMode);
}
void EditDialog::setFocus()
@@ -658,9 +733,25 @@ void EditDialog::setFocus()
// to the dock itself doesn't make much sense as it's just a frame; you'd
// have to tab to the editor which is what you most likely want to use. So
// in order to save the user from doing this we explicitly set the focus
// to the editor.
ui->editorText->setFocus();
ui->editorText->selectAll();
// to the current editor.
int editMode = ui->editorStack->currentIndex();
switch (editMode) {
case TextEditor:
ui->editorText->setFocus();
ui->editorText->selectAll();
break;
case HexEditor:
hexEdit->setFocus();
break;
case SciEditor:
sciEdit->setFocus();
break;
case ImageViewer:
// Nothing to do
break;
}
}
// Enables or disables the Apply, Null, & Import buttons in the Edit Cell dock
@@ -677,7 +768,7 @@ void EditDialog::setReadOnly(bool ro)
ui->editorText->setTextInteractionFlags(textFlags);
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);
sciEdit->setReadOnly(ro);
}
// Update the information labels in the bottom left corner of the dialog
@@ -686,7 +777,7 @@ void EditDialog::updateCellInfo(const QByteArray& data)
QByteArray cellData = data;
// Image data needs special treatment
if (dataType == Image) {
if (dataType == Image || dataType == SVG) {
QBuffer imageBuffer(&cellData);
QImageReader imageReader(&imageBuffer);
@@ -776,6 +867,27 @@ void EditDialog::reloadSettings()
hexFont.setPointSize(Settings::getValue("databrowser", "fontsize").toInt());
hexEdit->setFont(hexFont);
jsonEdit->reloadSettings();
sciEdit->reloadSettings();
}
void EditDialog::setStackCurrentIndex(int editMode)
{
switch (editMode) {
case TextEditor:
case HexEditor:
case ImageViewer:
// General case: switch to the selected editor
ui->editorStack->setCurrentIndex(editMode);
break;
case JsonEditor:
// Scintilla case: switch to the single Scintilla editor and set language
ui->editorStack->setCurrentIndex(SciEditor);
sciEdit->setLanguage(DockTextEdit::JSON);
break;
case XmlEditor:
// Scintilla case: switch to the single Scintilla editor and set language
ui->editorStack->setCurrentIndex(SciEditor);
sciEdit->setLanguage(DockTextEdit::XML);
break;
}
}

View File

@@ -4,9 +4,8 @@
#include <QDialog>
#include <QPersistentModelIndex>
#include "jsontextedit.h"
class QHexEdit;
class DockTextEdit;
namespace Ui {
class EditDialog;
@@ -50,11 +49,10 @@ signals:
private:
Ui::EditDialog* ui;
QHexEdit* hexEdit;
JsonTextEdit* jsonEdit;
DockTextEdit* sciEdit;
QPersistentModelIndex currentIndex;
int dataSource;
int dataType;
bool textNullSet;
bool isReadOnly;
bool mustIndentAndCompact;
QByteArray removedBom;
@@ -62,27 +60,34 @@ private:
enum DataSources {
TextBuffer,
HexBuffer,
JsonBuffer
SciBuffer
};
// SVG is both an Image and an XML document so it is treated separately
enum DataTypes {
Binary,
Image,
Null,
Text,
JSON
JSON,
SVG
};
// Edit modes and editor stack (this must be aligned with the UI)
// Note that JSON and XML share the Scintilla widget.
enum EditModes {
TextEditor = 0,
HexEditor = 1,
JsonEditor = 2,
ImageViewer = 3
ImageViewer = 2,
JsonEditor, SciEditor = 3,
XmlEditor = 4
};
int checkDataType(const QByteArray& data);
QString humanReadableSize(double byteCount) const;
bool promptInvalidData(const QString& dataType, const QString& errorString);
void setDataInBuffer(const QByteArray& data, DataSources source);
void setStackCurrentIndex(int editMode);
};
#endif

View File

@@ -47,6 +47,11 @@
<string>Binary</string>
</property>
</item>
<item>
<property name="text">
<string>Image</string>
</property>
</item>
<item>
<property name="text">
<string>JSON</string>
@@ -54,7 +59,7 @@
</item>
<item>
<property name="text">
<string>Image</string>
<string>XML</string>
</property>
</item>
</widget>
@@ -182,13 +187,6 @@
</layout>
</widget>
<widget class="QWidget" name="editorBinary"/>
<widget class="QWidget" name="editorJSON">
<property name="whatsThis">
<string>This editor mode lets you edit JSON data with syntax highlighting, automatic formatting and validation before saving.
Errors are indicated with a red squiggle underline.</string>
</property>
</widget>
<widget class="QScrollArea" name="editorImageScrollArea">
<property name="widgetResizable">
<bool>true</bool>
@@ -213,6 +211,13 @@ Errors are indicated with a red squiggle underline.</string>
</layout>
</widget>
</widget>
<widget class="QWidget" name="editorSci">
<property name="whatsThis">
<string>This editor mode lets you edit JSON or XML data with syntax highlighting, automatic formatting and validation before saving.
Errors are indicated with a red squiggle underline.</string>
</property>
</widget>
</widget>
</item>
<item>
@@ -305,22 +310,6 @@ Errors are indicated with a red squiggle underline.</string>
</hint>
</hints>
</connection>
<connection>
<sender>editorStack</sender>
<signal>currentChanged(int)</signal>
<receiver>comboMode</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>185</x>
<y>169</y>
</hint>
<hint type="destinationlabel">
<x>149</x>
<y>39</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonApply</sender>
<signal>clicked()</signal>

View File

@@ -87,16 +87,16 @@ void ExtendedScintilla::dropEvent(QDropEvent* e)
f.close();
}
void ExtendedScintilla::setupSyntaxHighlightingFormat(const QString& settings_name, int style)
void ExtendedScintilla::setupSyntaxHighlightingFormat(QsciLexer *lexer, const QString& settings_name, int style)
{
lexer()->setColor(QColor(Settings::getValue("syntaxhighlighter", settings_name + "_colour").toString()), style);
lexer->setColor(QColor(Settings::getValue("syntaxhighlighter", settings_name + "_colour").toString()), style);
QFont font(Settings::getValue("editor", "font").toString());
font.setPointSize(Settings::getValue("editor", "fontsize").toInt());
font.setBold(Settings::getValue("syntaxhighlighter", settings_name + "_bold").toBool());
font.setItalic(Settings::getValue("syntaxhighlighter", settings_name + "_italic").toBool());
font.setUnderline(Settings::getValue("syntaxhighlighter", settings_name + "_underline").toBool());
lexer()->setFont(font, style);
lexer->setFont(font, style);
}
void ExtendedScintilla::reloadKeywords()
@@ -107,23 +107,16 @@ void ExtendedScintilla::reloadKeywords()
void ExtendedScintilla::reloadSettings()
{
// Enable auto completion if it hasn't been disabled
if(Settings::getValue("editor", "auto_completion").toBool())
{
setAutoCompletionThreshold(3);
setAutoCompletionCaseSensitivity(false);
setAutoCompletionShowSingle(true);
setAutoCompletionSource(QsciScintilla::AcsAPIs);
} else {
setAutoCompletionThreshold(0);
}
reloadLexerSettings(lexer());
}
void ExtendedScintilla::reloadLexerSettings(QsciLexer *lexer)
{
// Set syntax highlighting settings
QFont defaultfont(Settings::getValue("editor", "font").toString());
defaultfont.setStyleHint(QFont::TypeWriter);
defaultfont.setPointSize(Settings::getValue("editor", "fontsize").toInt());
lexer()->setDefaultColor(Qt::black);
lexer()->setFont(defaultfont);
lexer->setDefaultColor(Qt::black);
lexer->setFont(defaultfont);
// Set font
QFont font(Settings::getValue("editor", "font").toString());
@@ -145,7 +138,7 @@ void ExtendedScintilla::reloadSettings()
// Set tab width
setTabWidth(Settings::getValue("editor", "tabsize").toInt());
lexer()->refreshProperties();
lexer->refreshProperties();
// Check if error indicators are enabled and clear them if they just got disabled
showErrorIndicators = Settings::getValue("editor", "error_indicators").toBool();

View File

@@ -32,7 +32,8 @@ public slots:
protected:
void dropEvent(QDropEvent* e);
void setupSyntaxHighlightingFormat(const QString& settings_name, int style);
void setupSyntaxHighlightingFormat(QsciLexer *lexer, const QString& settings_name, int style);
void reloadLexerSettings(QsciLexer *lexer);
int errorIndicatorNumber;
bool showErrorIndicators;

View File

@@ -175,7 +175,7 @@ QVariant Settings::getDefaultValue(const QString& group, const QString& name)
return 10;
if(name == "symbol_limit")
return 5000;
if(name == "compact_indent")
if(name == "indent_compact")
return false;
if(name == "null_text")
return "NULL";

79
src/docktextedit.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "docktextedit.h"
#include "Settings.h"
QsciLexerJSON* DockTextEdit::jsonLexer = nullptr;
QsciLexerXML* DockTextEdit::xmlLexer = nullptr;
DockTextEdit::DockTextEdit(QWidget* parent) :
ExtendedScintilla(parent)
{
// Create lexer objects if not done yet
if(jsonLexer == nullptr)
jsonLexer = new QsciLexerJSON(this);
if(xmlLexer == nullptr)
xmlLexer = new QsciLexerXML(this);
// Set the JSON lexer as default
setLexer(jsonLexer);
jsonLexer->setFoldCompact(false);
jsonLexer->setHighlightComments(true);
// Do rest of initialisation
reloadSettings();
}
DockTextEdit::~DockTextEdit()
{
}
void DockTextEdit::reloadSettings()
{
// Set the parent settings for both lexers
reloadLexerSettings(jsonLexer);
reloadLexerSettings(xmlLexer);
setupSyntaxHighlightingFormat(jsonLexer, "comment", QsciLexerJSON::CommentLine);
setupSyntaxHighlightingFormat(jsonLexer, "comment", QsciLexerJSON::CommentBlock);
setupSyntaxHighlightingFormat(jsonLexer, "keyword", QsciLexerJSON::Keyword);
setupSyntaxHighlightingFormat(jsonLexer, "keyword", QsciLexerJSON::KeywordLD);
setupSyntaxHighlightingFormat(jsonLexer, "string", QsciLexerJSON::String);
setupSyntaxHighlightingFormat(jsonLexer, "table", QsciLexerJSON::Number);
setupSyntaxHighlightingFormat(jsonLexer, "identifier", QsciLexerJSON::Property);
// The default style for invalid JSON or unclosed strings uses red
// background and white foreground, but the current line has
// precedence, so it is by default white over gray. We change the
// default to something more readable for the current line at
// invalid JSON.
QColor stringColor = QColor(Settings::getValue("syntaxhighlighter", "string_colour").toString());
jsonLexer->setColor(stringColor, QsciLexerJSON::Error);
jsonLexer->setColor(stringColor, QsciLexerJSON::UnclosedString);
QFont errorFont(Settings::getValue("editor", "font").toString());
errorFont.setPointSize(Settings::getValue("editor", "fontsize").toInt());
errorFont.setItalic(true);
jsonLexer->setFont(errorFont, QsciLexerJSON::Error);
jsonLexer->setFont(errorFont, QsciLexerJSON::UnclosedString);
jsonLexer->setPaper(jsonLexer->defaultPaper(QsciLexerJSON::String), QsciLexerJSON::Error);
jsonLexer->setPaper(jsonLexer->defaultPaper(QsciLexerJSON::String), QsciLexerJSON::UnclosedString);
setupSyntaxHighlightingFormat(xmlLexer, "comment", QsciLexerHTML::HTMLComment);
setupSyntaxHighlightingFormat(xmlLexer, "keyword", QsciLexerHTML::Tag);
setupSyntaxHighlightingFormat(xmlLexer, "string", QsciLexerHTML::HTMLDoubleQuotedString);
setupSyntaxHighlightingFormat(xmlLexer, "string", QsciLexerHTML::HTMLSingleQuotedString);
setupSyntaxHighlightingFormat(xmlLexer, "table", QsciLexerHTML::HTMLNumber);
setupSyntaxHighlightingFormat(xmlLexer, "identifier", QsciLexerHTML::Attribute);
}
void DockTextEdit::setLanguage(Language lang)
{
m_language = lang;
switch (lang) {
case JSON:
setLexer(jsonLexer);
break;
case XML:
setLexer(xmlLexer);
break;
}
}

40
src/docktextedit.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef DOCKTEXTEDIT_H
#define DOCKTEXTEDIT_H
#include "ExtendedScintilla.h"
#include <Qsci/qscilexerjson.h>
#include <Qsci/qscilexerxml.h>
/**
* @brief The DockTextEdit class
* This class is based on our Extended QScintilla widget
*/
class DockTextEdit : public ExtendedScintilla
{
Q_OBJECT
public:
explicit DockTextEdit(QWidget *parent = nullptr);
virtual ~DockTextEdit();
// Enumeration of supported languages
enum Language
{
JSON,
XML
};
void setLanguage(Language lang);
Language language() { return m_language; };
public slots:
void reloadSettings();
protected:
static QsciLexerJSON* jsonLexer;
static QsciLexerXML* xmlLexer;
private:
Language m_language;
};
#endif

View File

@@ -1,54 +0,0 @@
#include "jsontextedit.h"
#include "Settings.h"
QsciLexerJSON* JsonTextEdit::jsonLexer = nullptr;
JsonTextEdit::JsonTextEdit(QWidget* parent) :
ExtendedScintilla(parent)
{
// Create lexer object if not done yet
if(jsonLexer == nullptr)
jsonLexer = new QsciLexerJSON(this);
// Set the JSON lexer
setLexer(jsonLexer);
jsonLexer->setFoldCompact(false);
// Do rest of initialisation
reloadSettings();
}
JsonTextEdit::~JsonTextEdit()
{
}
void JsonTextEdit::reloadSettings()
{
ExtendedScintilla::reloadSettings();
setupSyntaxHighlightingFormat("comment", QsciLexerJSON::CommentLine);
setupSyntaxHighlightingFormat("comment", QsciLexerJSON::CommentBlock);
setupSyntaxHighlightingFormat("keyword", QsciLexerJSON::Keyword);
setupSyntaxHighlightingFormat("keyword", QsciLexerJSON::KeywordLD);
setupSyntaxHighlightingFormat("string", QsciLexerJSON::String);
setupSyntaxHighlightingFormat("table", QsciLexerJSON::Number);
setupSyntaxHighlightingFormat("identifier", QsciLexerJSON::Property);
jsonLexer->setHighlightComments(true);
// The default style for invalid JSON or unclosed strings uses red
// background and white foreground, but the current line has
// precedence, so it is by default white over gray. We change the
// default to something more readable for the current line at
// invalid JSON.
QColor stringColor = QColor(Settings::getValue("syntaxhighlighter", "string_colour").toString());
jsonLexer->setColor(stringColor, QsciLexerJSON::Error);
jsonLexer->setColor(stringColor, QsciLexerJSON::UnclosedString);
QFont errorFont(Settings::getValue("editor", "font").toString());
errorFont.setPointSize(Settings::getValue("editor", "fontsize").toInt());
errorFont.setItalic(true);
jsonLexer->setFont(errorFont, QsciLexerJSON::Error);
jsonLexer->setFont(errorFont, QsciLexerJSON::UnclosedString);
jsonLexer->setPaper(jsonLexer->defaultPaper(QsciLexerJSON::String), QsciLexerJSON::Error);
jsonLexer->setPaper(jsonLexer->defaultPaper(QsciLexerJSON::String), QsciLexerJSON::UnclosedString);
}

View File

@@ -1,25 +0,0 @@
#ifndef JSONTEXTEDIT_H
#define JSONTEXTEDIT_H
#include "ExtendedScintilla.h"
#include <Qsci/qscilexerjson.h>
/**
* @brief The JsonTextEdit class
* This class is based on our Extended QScintilla widget
*/
class JsonTextEdit : public ExtendedScintilla
{
Q_OBJECT
public:
explicit JsonTextEdit(QWidget *parent = nullptr);
virtual ~JsonTextEdit();
static QsciLexerJSON* jsonLexer;
public slots:
void reloadSettings();
};
#endif

View File

@@ -30,16 +30,27 @@ SqlTextEdit::~SqlTextEdit()
void SqlTextEdit::reloadSettings()
{
// Enable auto completion if it hasn't been disabled
if(Settings::getValue("editor", "auto_completion").toBool())
{
setAutoCompletionThreshold(3);
setAutoCompletionCaseSensitivity(true);
setAutoCompletionShowSingle(true);
setAutoCompletionSource(QsciScintilla::AcsAPIs);
} else {
setAutoCompletionThreshold(0);
}
ExtendedScintilla::reloadSettings();
setupSyntaxHighlightingFormat("comment", QsciLexerSQL::Comment);
setupSyntaxHighlightingFormat("comment", QsciLexerSQL::CommentLine);
setupSyntaxHighlightingFormat("comment", QsciLexerSQL::CommentDoc);
setupSyntaxHighlightingFormat("keyword", QsciLexerSQL::Keyword);
setupSyntaxHighlightingFormat("table", QsciLexerSQL::KeywordSet6);
setupSyntaxHighlightingFormat("function", QsciLexerSQL::KeywordSet7);
setupSyntaxHighlightingFormat("string", QsciLexerSQL::DoubleQuotedString);
setupSyntaxHighlightingFormat("string", QsciLexerSQL::SingleQuotedString);
setupSyntaxHighlightingFormat("identifier", QsciLexerSQL::Identifier);
setupSyntaxHighlightingFormat("identifier", QsciLexerSQL::QuotedIdentifier);
setupSyntaxHighlightingFormat(sqlLexer, "comment", QsciLexerSQL::Comment);
setupSyntaxHighlightingFormat(sqlLexer, "comment", QsciLexerSQL::CommentLine);
setupSyntaxHighlightingFormat(sqlLexer, "comment", QsciLexerSQL::CommentDoc);
setupSyntaxHighlightingFormat(sqlLexer, "keyword", QsciLexerSQL::Keyword);
setupSyntaxHighlightingFormat(sqlLexer, "table", QsciLexerSQL::KeywordSet6);
setupSyntaxHighlightingFormat(sqlLexer, "function", QsciLexerSQL::KeywordSet7);
setupSyntaxHighlightingFormat(sqlLexer, "string", QsciLexerSQL::DoubleQuotedString);
setupSyntaxHighlightingFormat(sqlLexer, "string", QsciLexerSQL::SingleQuotedString);
setupSyntaxHighlightingFormat(sqlLexer, "identifier", QsciLexerSQL::Identifier);
setupSyntaxHighlightingFormat(sqlLexer, "identifier", QsciLexerSQL::QuotedIdentifier);
}

View File

@@ -1,6 +1,6 @@
TEMPLATE = app
QT += core gui network widgets printsupport concurrent
QT += core gui network widgets printsupport concurrent xml
macx: QT += opengl
TARGET = sqlitebrowser
@@ -59,7 +59,7 @@ HEADERS += \
RemoteDock.h \
RemoteModel.h \
RemotePushDialog.h \
jsontextedit.h \
docktextedit.h \
FindReplaceDialog.h \
ExtendedScintilla.h \
FileExtensionManager.h \
@@ -100,7 +100,7 @@ SOURCES += \
RemoteDock.cpp \
RemoteModel.cpp \
RemotePushDialog.cpp \
jsontextedit.cpp \
docktextedit.cpp \
FindReplaceDialog.cpp \
ExtendedScintilla.cpp \
FileExtensionManager.cpp \