mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-20 02:50:46 -06:00
JSON mode for cell editor
Support for JSON in the Database Cell editor using the QScintilla library. The lexJSON lexer has been added to the compilation, including the necessary files from the QScintilla source package. See issue #1173
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
298
libs/qscintilla/Qt4Qt5/qscilexerjson.cpp
Normal file
298
libs/qscintilla/Qt4Qt5/qscilexerjson.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
// This module implements the QsciLexerJSON class.
|
||||
//
|
||||
// Copyright (c) 2017 Riverbank Computing Limited <info@riverbankcomputing.com>
|
||||
//
|
||||
// This file is part of QScintilla.
|
||||
//
|
||||
// This file may be used under the terms of the GNU General Public License
|
||||
// version 3.0 as published by the Free Software Foundation and appearing in
|
||||
// the file LICENSE included in the packaging of this file. Please review the
|
||||
// following information to ensure the GNU General Public License version 3.0
|
||||
// requirements will be met: http://www.gnu.org/copyleft/gpl.html.
|
||||
//
|
||||
// If you do not wish to use this file under the terms of the GPL version 3.0
|
||||
// then you may purchase a commercial license. For more information contact
|
||||
// info@riverbankcomputing.com.
|
||||
//
|
||||
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||||
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
|
||||
#include "Qsci/qscilexerjson.h"
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qfont.h>
|
||||
#include <qsettings.h>
|
||||
|
||||
|
||||
// The ctor.
|
||||
QsciLexerJSON::QsciLexerJSON(QObject *parent)
|
||||
: QsciLexer(parent),
|
||||
allow_comments(true), escape_sequence(true), fold_compact(true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// The dtor.
|
||||
QsciLexerJSON::~QsciLexerJSON()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Returns the language name.
|
||||
const char *QsciLexerJSON::language() const
|
||||
{
|
||||
return "JSON";
|
||||
}
|
||||
|
||||
|
||||
// Returns the lexer name.
|
||||
const char *QsciLexerJSON::lexer() const
|
||||
{
|
||||
return "json";
|
||||
}
|
||||
|
||||
|
||||
// Returns the foreground colour of the text for a style.
|
||||
QColor QsciLexerJSON::defaultColor(int style) const
|
||||
{
|
||||
switch (style)
|
||||
{
|
||||
case UnclosedString:
|
||||
case Error:
|
||||
return QColor(0xff, 0xff, 0xff);
|
||||
|
||||
case Number:
|
||||
return QColor(0x00, 0x7f, 0x7f);
|
||||
|
||||
case String:
|
||||
return QColor(0x7f, 0x00, 0x00);
|
||||
|
||||
case Property:
|
||||
return QColor(0x88, 0x0a, 0xe8);
|
||||
|
||||
case EscapeSequence:
|
||||
return QColor(0x0b, 0x98, 0x2e);
|
||||
|
||||
case CommentLine:
|
||||
case CommentBlock:
|
||||
return QColor(0x05, 0xbb, 0xae);
|
||||
|
||||
case Operator:
|
||||
return QColor(0x18, 0x64, 0x4a);
|
||||
|
||||
case IRI:
|
||||
return QColor(0x00, 0x00, 0xff);
|
||||
|
||||
case IRICompact:
|
||||
return QColor(0xd1, 0x37, 0xc1);
|
||||
|
||||
case Keyword:
|
||||
return QColor(0x0b, 0xce, 0xa7);
|
||||
|
||||
case KeywordLD:
|
||||
return QColor(0xec, 0x28, 0x06);
|
||||
}
|
||||
|
||||
return QsciLexer::defaultColor(style);
|
||||
}
|
||||
|
||||
|
||||
// Returns the end-of-line fill for a style.
|
||||
bool QsciLexerJSON::defaultEolFill(int style) const
|
||||
{
|
||||
switch (style)
|
||||
{
|
||||
case UnclosedString:
|
||||
return true;
|
||||
}
|
||||
|
||||
return QsciLexer::defaultEolFill(style);
|
||||
}
|
||||
|
||||
|
||||
// Returns the font of the text for a style.
|
||||
QFont QsciLexerJSON::defaultFont(int style) const
|
||||
{
|
||||
QFont f;
|
||||
|
||||
switch (style)
|
||||
{
|
||||
case CommentLine:
|
||||
f = QsciLexer::defaultFont(style);
|
||||
f.setItalic(true);
|
||||
break;
|
||||
|
||||
case Keyword:
|
||||
f = QsciLexer::defaultFont(style);
|
||||
f.setBold(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
f = QsciLexer::defaultFont(style);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
// Returns the set of keywords.
|
||||
const char *QsciLexerJSON::keywords(int set) const
|
||||
{
|
||||
if (set == 1)
|
||||
return "false true null";
|
||||
|
||||
if (set == 2)
|
||||
return
|
||||
"@id @context @type @value @language @container @list @set "
|
||||
"@reverse @index @base @vocab @graph";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Returns the user name of a style.
|
||||
QString QsciLexerJSON::description(int style) const
|
||||
{
|
||||
switch (style)
|
||||
{
|
||||
case Default:
|
||||
return tr("Default");
|
||||
|
||||
case Number:
|
||||
return tr("Number");
|
||||
|
||||
case String:
|
||||
return tr("String");
|
||||
|
||||
case UnclosedString:
|
||||
return tr("Unclosed string");
|
||||
|
||||
case Property:
|
||||
return tr("Property");
|
||||
|
||||
case EscapeSequence:
|
||||
return tr("Escape sequence");
|
||||
|
||||
case CommentLine:
|
||||
return tr("Line comment");
|
||||
|
||||
case CommentBlock:
|
||||
return tr("Block comment");
|
||||
|
||||
case Operator:
|
||||
return tr("Operator");
|
||||
|
||||
case IRI:
|
||||
return tr("IRI");
|
||||
|
||||
case IRICompact:
|
||||
return tr("JSON-LD compact IRI");
|
||||
|
||||
case Keyword:
|
||||
return tr("JSON keyword");
|
||||
|
||||
case KeywordLD:
|
||||
return tr("JSON-LD keyword");
|
||||
|
||||
case Error:
|
||||
return tr("Parsing error");
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
// Returns the background colour of the text for a style.
|
||||
QColor QsciLexerJSON::defaultPaper(int style) const
|
||||
{
|
||||
switch (style)
|
||||
{
|
||||
case UnclosedString:
|
||||
case Error:
|
||||
return QColor(0xff, 0x00, 0x00);
|
||||
}
|
||||
|
||||
return QsciLexer::defaultPaper(style);
|
||||
}
|
||||
|
||||
|
||||
// Refresh all properties.
|
||||
void QsciLexerJSON::refreshProperties()
|
||||
{
|
||||
setAllowCommentsProp();
|
||||
setEscapeSequenceProp();
|
||||
setCompactProp();
|
||||
}
|
||||
|
||||
|
||||
// Read properties from the settings.
|
||||
bool QsciLexerJSON::readProperties(QSettings &qs,const QString &prefix)
|
||||
{
|
||||
allow_comments = qs.value(prefix + "allowcomments", true).toBool();
|
||||
escape_sequence = qs.value(prefix + "escapesequence", true).toBool();
|
||||
fold_compact = qs.value(prefix + "foldcompact", true).toBool();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Write properties to the settings.
|
||||
bool QsciLexerJSON::writeProperties(QSettings &qs,const QString &prefix) const
|
||||
{
|
||||
qs.setValue(prefix + "allowcomments", allow_comments);
|
||||
qs.setValue(prefix + "escapesequence", escape_sequence);
|
||||
qs.setValue(prefix + "foldcompact", fold_compact);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Set if comments are highlighted
|
||||
void QsciLexerJSON::setHighlightComments(bool highlight)
|
||||
{
|
||||
allow_comments = highlight;
|
||||
|
||||
setAllowCommentsProp();
|
||||
}
|
||||
|
||||
|
||||
// Set the "lexer.json.allow.comments" property.
|
||||
void QsciLexerJSON::setAllowCommentsProp()
|
||||
{
|
||||
emit propertyChanged("lexer.json.allow.comments",
|
||||
(allow_comments ? "1" : "0"));
|
||||
}
|
||||
|
||||
|
||||
// Set if escape sequences are highlighted.
|
||||
void QsciLexerJSON::setHighlightEscapeSequences(bool highlight)
|
||||
{
|
||||
escape_sequence = highlight;
|
||||
|
||||
setEscapeSequenceProp();
|
||||
}
|
||||
|
||||
|
||||
// Set the "lexer.json.escape.sequence" property.
|
||||
void QsciLexerJSON::setEscapeSequenceProp()
|
||||
{
|
||||
emit propertyChanged("lexer.json.escape.sequence",
|
||||
(escape_sequence ? "1" : "0"));
|
||||
}
|
||||
|
||||
|
||||
// Set if folds are compact.
|
||||
void QsciLexerJSON::setFoldCompact(bool fold)
|
||||
{
|
||||
fold_compact = fold;
|
||||
|
||||
setCompactProp();
|
||||
}
|
||||
|
||||
|
||||
// Set the "fold.compact" property.
|
||||
void QsciLexerJSON::setCompactProp()
|
||||
{
|
||||
emit propertyChanged("fold.compact", (fold_compact ? "1" : "0"));
|
||||
}
|
||||
@@ -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 \
|
||||
|
||||
497
libs/qscintilla/lexers/LexJSON.cpp
Normal file
497
libs/qscintilla/lexers/LexJSON.cpp
Normal file
@@ -0,0 +1,497 @@
|
||||
// Scintilla source code edit control
|
||||
/**
|
||||
* @file LexJSON.cxx
|
||||
* @date February 19, 2016
|
||||
* @brief Lexer for JSON and JSON-LD formats
|
||||
* @author nkmathew
|
||||
*
|
||||
* The License.txt file describes the conditions under which this software may
|
||||
* be distributed.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "ILexer.h"
|
||||
#include "Scintilla.h"
|
||||
#include "SciLexer.h"
|
||||
#include "WordList.h"
|
||||
#include "LexAccessor.h"
|
||||
#include "StyleContext.h"
|
||||
#include "CharacterSet.h"
|
||||
#include "LexerModule.h"
|
||||
#include "OptionSet.h"
|
||||
|
||||
#ifdef SCI_NAMESPACE
|
||||
using namespace Scintilla;
|
||||
#endif
|
||||
|
||||
static const char *const JSONWordListDesc[] = {
|
||||
"JSON Keywords",
|
||||
"JSON-LD Keywords",
|
||||
0
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to detect compact IRI/URLs in JSON-LD without first looking ahead for the
|
||||
* colon separating the prefix and suffix
|
||||
*
|
||||
* https://www.w3.org/TR/json-ld/#dfn-compact-iri
|
||||
*/
|
||||
struct CompactIRI {
|
||||
int colonCount;
|
||||
bool foundInvalidChar;
|
||||
CharacterSet setCompactIRI;
|
||||
CompactIRI() {
|
||||
colonCount = 0;
|
||||
foundInvalidChar = false;
|
||||
setCompactIRI = CharacterSet(CharacterSet::setAlpha, "$_-");
|
||||
}
|
||||
void resetState() {
|
||||
colonCount = 0;
|
||||
foundInvalidChar = false;
|
||||
}
|
||||
void checkChar(int ch) {
|
||||
if (ch == ':') {
|
||||
colonCount++;
|
||||
} else {
|
||||
foundInvalidChar |= !setCompactIRI.Contains(ch);
|
||||
}
|
||||
}
|
||||
bool shouldHighlight() const {
|
||||
return !foundInvalidChar && colonCount == 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Keeps track of escaped characters in strings as per:
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc7159#section-7
|
||||
*/
|
||||
struct EscapeSequence {
|
||||
int digitsLeft;
|
||||
CharacterSet setHexDigits;
|
||||
CharacterSet setEscapeChars;
|
||||
EscapeSequence() {
|
||||
digitsLeft = 0;
|
||||
setHexDigits = CharacterSet(CharacterSet::setDigits, "ABCDEFabcdef");
|
||||
setEscapeChars = CharacterSet(CharacterSet::setNone, "\\\"tnbfru/");
|
||||
}
|
||||
// Returns true if the following character is a valid escaped character
|
||||
bool newSequence(int nextChar) {
|
||||
digitsLeft = 0;
|
||||
if (nextChar == 'u') {
|
||||
digitsLeft = 5;
|
||||
} else if (!setEscapeChars.Contains(nextChar)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool atEscapeEnd() const {
|
||||
return digitsLeft <= 0;
|
||||
}
|
||||
bool isInvalidChar(int currChar) const {
|
||||
return !setHexDigits.Contains(currChar);
|
||||
}
|
||||
};
|
||||
|
||||
struct OptionsJSON {
|
||||
bool foldCompact;
|
||||
bool fold;
|
||||
bool allowComments;
|
||||
bool escapeSequence;
|
||||
OptionsJSON() {
|
||||
foldCompact = false;
|
||||
fold = false;
|
||||
allowComments = false;
|
||||
escapeSequence = false;
|
||||
}
|
||||
};
|
||||
|
||||
struct OptionSetJSON : public OptionSet<OptionsJSON> {
|
||||
OptionSetJSON() {
|
||||
DefineProperty("lexer.json.escape.sequence", &OptionsJSON::escapeSequence,
|
||||
"Set to 1 to enable highlighting of escape sequences in strings");
|
||||
|
||||
DefineProperty("lexer.json.allow.comments", &OptionsJSON::allowComments,
|
||||
"Set to 1 to enable highlighting of line/block comments in JSON");
|
||||
|
||||
DefineProperty("fold.compact", &OptionsJSON::foldCompact);
|
||||
DefineProperty("fold", &OptionsJSON::fold);
|
||||
DefineWordListSets(JSONWordListDesc);
|
||||
}
|
||||
};
|
||||
|
||||
class LexerJSON : public ILexer {
|
||||
OptionsJSON options;
|
||||
OptionSetJSON optSetJSON;
|
||||
EscapeSequence escapeSeq;
|
||||
WordList keywordsJSON;
|
||||
WordList keywordsJSONLD;
|
||||
CharacterSet setOperators;
|
||||
CharacterSet setURL;
|
||||
CharacterSet setKeywordJSONLD;
|
||||
CharacterSet setKeywordJSON;
|
||||
CompactIRI compactIRI;
|
||||
|
||||
static bool IsNextNonWhitespace(LexAccessor &styler, Sci_Position start, char ch) {
|
||||
Sci_Position i = 0;
|
||||
while (i < 50) {
|
||||
i++;
|
||||
char curr = styler.SafeGetCharAt(start+i, '\0');
|
||||
char next = styler.SafeGetCharAt(start+i+1, '\0');
|
||||
bool atEOL = (curr == '\r' && next != '\n') || (curr == '\n');
|
||||
if (curr == ch) {
|
||||
return true;
|
||||
} else if (!isspacechar(curr) || atEOL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for the colon following the end quote
|
||||
*
|
||||
* Assumes property names of lengths no longer than a 100 characters.
|
||||
* The colon is also expected to be less than 50 spaces after the end
|
||||
* quote for the string to be considered a property name
|
||||
*/
|
||||
static bool AtPropertyName(LexAccessor &styler, Sci_Position start) {
|
||||
Sci_Position i = 0;
|
||||
bool escaped = false;
|
||||
while (i < 100) {
|
||||
i++;
|
||||
char curr = styler.SafeGetCharAt(start+i, '\0');
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
escaped = curr == '\\';
|
||||
if (curr == '"') {
|
||||
return IsNextNonWhitespace(styler, start+i, ':');
|
||||
} else if (!curr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsNextWordInList(WordList &keywordList, CharacterSet wordSet,
|
||||
StyleContext &context, LexAccessor &styler) {
|
||||
char word[51];
|
||||
Sci_Position currPos = (Sci_Position) context.currentPos;
|
||||
int i = 0;
|
||||
while (i < 50) {
|
||||
char ch = styler.SafeGetCharAt(currPos + i);
|
||||
if (!wordSet.Contains(ch)) {
|
||||
break;
|
||||
}
|
||||
word[i] = ch;
|
||||
i++;
|
||||
}
|
||||
word[i] = '\0';
|
||||
return keywordList.InList(word);
|
||||
}
|
||||
|
||||
public:
|
||||
LexerJSON() :
|
||||
setOperators(CharacterSet::setNone, "[{}]:,"),
|
||||
setURL(CharacterSet::setAlphaNum, "-._~:/?#[]@!$&'()*+,),="),
|
||||
setKeywordJSONLD(CharacterSet::setAlpha, ":@"),
|
||||
setKeywordJSON(CharacterSet::setAlpha, "$_") {
|
||||
}
|
||||
virtual ~LexerJSON() {}
|
||||
virtual int SCI_METHOD Version() const {
|
||||
return lvOriginal;
|
||||
}
|
||||
virtual void SCI_METHOD Release() {
|
||||
delete this;
|
||||
}
|
||||
virtual const char *SCI_METHOD PropertyNames() {
|
||||
return optSetJSON.PropertyNames();
|
||||
}
|
||||
virtual int SCI_METHOD PropertyType(const char *name) {
|
||||
return optSetJSON.PropertyType(name);
|
||||
}
|
||||
virtual const char *SCI_METHOD DescribeProperty(const char *name) {
|
||||
return optSetJSON.DescribeProperty(name);
|
||||
}
|
||||
virtual Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) {
|
||||
if (optSetJSON.PropertySet(&options, key, val)) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
virtual Sci_Position SCI_METHOD WordListSet(int n, const char *wl) {
|
||||
WordList *wordListN = 0;
|
||||
switch (n) {
|
||||
case 0:
|
||||
wordListN = &keywordsJSON;
|
||||
break;
|
||||
case 1:
|
||||
wordListN = &keywordsJSONLD;
|
||||
break;
|
||||
}
|
||||
Sci_Position firstModification = -1;
|
||||
if (wordListN) {
|
||||
WordList wlNew;
|
||||
wlNew.Set(wl);
|
||||
if (*wordListN != wlNew) {
|
||||
wordListN->Set(wl);
|
||||
firstModification = 0;
|
||||
}
|
||||
}
|
||||
return firstModification;
|
||||
}
|
||||
virtual void *SCI_METHOD PrivateCall(int, void *) {
|
||||
return 0;
|
||||
}
|
||||
static ILexer *LexerFactoryJSON() {
|
||||
return new LexerJSON;
|
||||
}
|
||||
virtual const char *SCI_METHOD DescribeWordListSets() {
|
||||
return optSetJSON.DescribeWordListSets();
|
||||
}
|
||||
virtual void SCI_METHOD Lex(Sci_PositionU startPos,
|
||||
Sci_Position length,
|
||||
int initStyle,
|
||||
IDocument *pAccess);
|
||||
virtual void SCI_METHOD Fold(Sci_PositionU startPos,
|
||||
Sci_Position length,
|
||||
int initStyle,
|
||||
IDocument *pAccess);
|
||||
};
|
||||
|
||||
void SCI_METHOD LexerJSON::Lex(Sci_PositionU startPos,
|
||||
Sci_Position length,
|
||||
int initStyle,
|
||||
IDocument *pAccess) {
|
||||
LexAccessor styler(pAccess);
|
||||
StyleContext context(startPos, length, initStyle, styler);
|
||||
int stringStyleBefore = SCE_JSON_STRING;
|
||||
while (context.More()) {
|
||||
switch (context.state) {
|
||||
case SCE_JSON_BLOCKCOMMENT:
|
||||
if (context.Match("*/")) {
|
||||
context.Forward();
|
||||
context.ForwardSetState(SCE_JSON_DEFAULT);
|
||||
}
|
||||
break;
|
||||
case SCE_JSON_LINECOMMENT:
|
||||
if (context.atLineEnd) {
|
||||
context.SetState(SCE_JSON_DEFAULT);
|
||||
}
|
||||
break;
|
||||
case SCE_JSON_STRINGEOL:
|
||||
if (context.atLineStart) {
|
||||
context.SetState(SCE_JSON_DEFAULT);
|
||||
}
|
||||
break;
|
||||
case SCE_JSON_ESCAPESEQUENCE:
|
||||
escapeSeq.digitsLeft--;
|
||||
if (!escapeSeq.atEscapeEnd()) {
|
||||
if (escapeSeq.isInvalidChar(context.ch)) {
|
||||
context.SetState(SCE_JSON_ERROR);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (context.ch == '"') {
|
||||
context.SetState(stringStyleBefore);
|
||||
context.ForwardSetState(SCE_C_DEFAULT);
|
||||
} else if (context.ch == '\\') {
|
||||
if (!escapeSeq.newSequence(context.chNext)) {
|
||||
context.SetState(SCE_JSON_ERROR);
|
||||
}
|
||||
context.Forward();
|
||||
} else {
|
||||
context.SetState(stringStyleBefore);
|
||||
if (context.atLineEnd) {
|
||||
context.ChangeState(SCE_JSON_STRINGEOL);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SCE_JSON_PROPERTYNAME:
|
||||
case SCE_JSON_STRING:
|
||||
if (context.ch == '"') {
|
||||
if (compactIRI.shouldHighlight()) {
|
||||
context.ChangeState(SCE_JSON_COMPACTIRI);
|
||||
context.ForwardSetState(SCE_JSON_DEFAULT);
|
||||
compactIRI.resetState();
|
||||
} else {
|
||||
context.ForwardSetState(SCE_JSON_DEFAULT);
|
||||
}
|
||||
} else if (context.atLineEnd) {
|
||||
context.ChangeState(SCE_JSON_STRINGEOL);
|
||||
} else if (context.ch == '\\') {
|
||||
stringStyleBefore = context.state;
|
||||
if (options.escapeSequence) {
|
||||
context.SetState(SCE_JSON_ESCAPESEQUENCE);
|
||||
if (!escapeSeq.newSequence(context.chNext)) {
|
||||
context.SetState(SCE_JSON_ERROR);
|
||||
}
|
||||
}
|
||||
context.Forward();
|
||||
} else if (context.Match("https://") ||
|
||||
context.Match("http://") ||
|
||||
context.Match("ssh://") ||
|
||||
context.Match("git://") ||
|
||||
context.Match("svn://") ||
|
||||
context.Match("ftp://") ||
|
||||
context.Match("mailto:")) {
|
||||
// Handle most common URI schemes only
|
||||
stringStyleBefore = context.state;
|
||||
context.SetState(SCE_JSON_URI);
|
||||
} else if (context.ch == '@') {
|
||||
// https://www.w3.org/TR/json-ld/#dfn-keyword
|
||||
if (IsNextWordInList(keywordsJSONLD, setKeywordJSONLD, context, styler)) {
|
||||
stringStyleBefore = context.state;
|
||||
context.SetState(SCE_JSON_LDKEYWORD);
|
||||
}
|
||||
} else {
|
||||
compactIRI.checkChar(context.ch);
|
||||
}
|
||||
break;
|
||||
case SCE_JSON_LDKEYWORD:
|
||||
case SCE_JSON_URI:
|
||||
if ((!setKeywordJSONLD.Contains(context.ch) &&
|
||||
(context.state == SCE_JSON_LDKEYWORD)) ||
|
||||
(!setURL.Contains(context.ch))) {
|
||||
context.SetState(stringStyleBefore);
|
||||
}
|
||||
if (context.ch == '"') {
|
||||
context.ForwardSetState(SCE_JSON_DEFAULT);
|
||||
} else if (context.atLineEnd) {
|
||||
context.ChangeState(SCE_JSON_STRINGEOL);
|
||||
}
|
||||
break;
|
||||
case SCE_JSON_OPERATOR:
|
||||
case SCE_JSON_NUMBER:
|
||||
context.SetState(SCE_JSON_DEFAULT);
|
||||
break;
|
||||
case SCE_JSON_ERROR:
|
||||
if (context.atLineEnd) {
|
||||
context.SetState(SCE_JSON_DEFAULT);
|
||||
}
|
||||
break;
|
||||
case SCE_JSON_KEYWORD:
|
||||
if (!setKeywordJSON.Contains(context.ch)) {
|
||||
context.SetState(SCE_JSON_DEFAULT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (context.state == SCE_JSON_DEFAULT) {
|
||||
if (context.ch == '"') {
|
||||
compactIRI.resetState();
|
||||
context.SetState(SCE_JSON_STRING);
|
||||
Sci_Position currPos = static_cast<Sci_Position>(context.currentPos);
|
||||
if (AtPropertyName(styler, currPos)) {
|
||||
context.SetState(SCE_JSON_PROPERTYNAME);
|
||||
}
|
||||
} else if (setOperators.Contains(context.ch)) {
|
||||
context.SetState(SCE_JSON_OPERATOR);
|
||||
} else if (options.allowComments && context.Match("/*")) {
|
||||
context.SetState(SCE_JSON_BLOCKCOMMENT);
|
||||
context.Forward();
|
||||
} else if (options.allowComments && context.Match("//")) {
|
||||
context.SetState(SCE_JSON_LINECOMMENT);
|
||||
} else if (setKeywordJSON.Contains(context.ch)) {
|
||||
if (IsNextWordInList(keywordsJSON, setKeywordJSON, context, styler)) {
|
||||
context.SetState(SCE_JSON_KEYWORD);
|
||||
}
|
||||
}
|
||||
bool numberStart =
|
||||
IsADigit(context.ch) && (context.chPrev == '+'||
|
||||
context.chPrev == '-' ||
|
||||
context.atLineStart ||
|
||||
IsASpace(context.chPrev) ||
|
||||
setOperators.Contains(context.chPrev));
|
||||
bool exponentPart =
|
||||
tolower(context.ch) == 'e' &&
|
||||
IsADigit(context.chPrev) &&
|
||||
(IsADigit(context.chNext) ||
|
||||
context.chNext == '+' ||
|
||||
context.chNext == '-');
|
||||
bool signPart =
|
||||
(context.ch == '-' || context.ch == '+') &&
|
||||
((tolower(context.chPrev) == 'e' && IsADigit(context.chNext)) ||
|
||||
((IsASpace(context.chPrev) || setOperators.Contains(context.chPrev))
|
||||
&& IsADigit(context.chNext)));
|
||||
bool adjacentDigit =
|
||||
IsADigit(context.ch) && IsADigit(context.chPrev);
|
||||
bool afterExponent = IsADigit(context.ch) && tolower(context.chPrev) == 'e';
|
||||
bool dotPart = context.ch == '.' &&
|
||||
IsADigit(context.chPrev) &&
|
||||
IsADigit(context.chNext);
|
||||
bool afterDot = IsADigit(context.ch) && context.chPrev == '.';
|
||||
if (numberStart ||
|
||||
exponentPart ||
|
||||
signPart ||
|
||||
adjacentDigit ||
|
||||
dotPart ||
|
||||
afterExponent ||
|
||||
afterDot) {
|
||||
context.SetState(SCE_JSON_NUMBER);
|
||||
} else if (context.state == SCE_JSON_DEFAULT && !IsASpace(context.ch)) {
|
||||
context.SetState(SCE_JSON_ERROR);
|
||||
}
|
||||
}
|
||||
context.Forward();
|
||||
}
|
||||
context.Complete();
|
||||
}
|
||||
|
||||
void SCI_METHOD LexerJSON::Fold(Sci_PositionU startPos,
|
||||
Sci_Position length,
|
||||
int,
|
||||
IDocument *pAccess) {
|
||||
if (!options.fold) {
|
||||
return;
|
||||
}
|
||||
LexAccessor styler(pAccess);
|
||||
Sci_PositionU currLine = styler.GetLine(startPos);
|
||||
Sci_PositionU endPos = startPos + length;
|
||||
int currLevel = styler.LevelAt(currLine) & SC_FOLDLEVELNUMBERMASK;
|
||||
int nextLevel = currLevel;
|
||||
int visibleChars = 0;
|
||||
for (Sci_PositionU i = startPos; i < endPos; i++) {
|
||||
char curr = styler.SafeGetCharAt(i);
|
||||
char next = styler.SafeGetCharAt(i+1);
|
||||
bool atEOL = (curr == '\r' && next != '\n') || (curr == '\n');
|
||||
if (styler.StyleAt(i) == SCE_JSON_OPERATOR) {
|
||||
if (curr == '{' || curr == '[') {
|
||||
nextLevel++;
|
||||
} else if (curr == '}' || curr == ']') {
|
||||
nextLevel--;
|
||||
}
|
||||
}
|
||||
if (atEOL || i == (endPos-1)) {
|
||||
int level = currLevel;
|
||||
if (!visibleChars && options.foldCompact) {
|
||||
level |= SC_FOLDLEVELWHITEFLAG;
|
||||
} else if (nextLevel > currLevel) {
|
||||
level |= SC_FOLDLEVELHEADERFLAG;
|
||||
}
|
||||
if (level != styler.LevelAt(currLine)) {
|
||||
styler.SetLevel(currLine, level);
|
||||
}
|
||||
currLine++;
|
||||
currLevel = nextLevel;
|
||||
visibleChars = 0;
|
||||
}
|
||||
if (!isspacechar(curr)) {
|
||||
visibleChars++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LexerModule lmJSON(SCLEX_JSON,
|
||||
LexerJSON::LexerFactoryJSON,
|
||||
"json",
|
||||
JSONWordListDesc);
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <QDialog>
|
||||
#include <QPersistentModelIndex>
|
||||
|
||||
#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);
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
<string>Binary</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>JSON</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Image</string>
|
||||
@@ -151,6 +156,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="editorBinary"/>
|
||||
<widget class="QWidget" name="editorJSON"/>
|
||||
<widget class="QScrollArea" name="editorImageScrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
|
||||
150
src/jsontextedit.cpp
Normal file
150
src/jsontextedit.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "jsontextedit.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDropEvent>
|
||||
#include <QUrl>
|
||||
#include <QMimeData>
|
||||
#include <QDebug>
|
||||
#include <cmath>
|
||||
|
||||
QsciLexerJSON* JsonTextEdit::jsonLexer = nullptr;
|
||||
|
||||
JsonTextEdit::JsonTextEdit(QWidget* parent) :
|
||||
QsciScintilla(parent)
|
||||
{
|
||||
// Create lexer object if not done yet
|
||||
if(jsonLexer == nullptr)
|
||||
jsonLexer = new QsciLexerJSON(this);
|
||||
|
||||
// Set the lexer
|
||||
setLexer(jsonLexer);
|
||||
|
||||
// Enable UTF8
|
||||
setUtf8(true);
|
||||
|
||||
// Enable brace matching
|
||||
setBraceMatching(QsciScintilla::SloppyBraceMatch);
|
||||
|
||||
// Enable auto indentation
|
||||
setAutoIndent(true);
|
||||
|
||||
// Enable folding
|
||||
setFolding(QsciScintilla::BoxedTreeFoldStyle);
|
||||
jsonLexer->setFoldCompact(false);
|
||||
|
||||
// Set a sensible scroll width, so the scroll bar is avoided in
|
||||
// most cases.
|
||||
setScrollWidth(80);
|
||||
|
||||
// Scroll width is adjusted to ensure that all of the lines
|
||||
// currently displayed can be completely scrolled. This mode never
|
||||
// adjusts the scroll width to be narrower.
|
||||
setScrollWidthTracking(true);
|
||||
|
||||
// Do rest of initialisation
|
||||
reloadSettings();
|
||||
|
||||
// Connect signals
|
||||
connect(this, SIGNAL(linesChanged()), this, SLOT(updateLineNumberAreaWidth()));
|
||||
}
|
||||
|
||||
JsonTextEdit::~JsonTextEdit()
|
||||
{
|
||||
}
|
||||
|
||||
void JsonTextEdit::updateLineNumberAreaWidth()
|
||||
{
|
||||
// Calculate number of digits of the current number of lines
|
||||
int digits = std::floor(std::log10(lines())) + 1;
|
||||
|
||||
// Calculate the width of this number if it was all zeros (this is because a 1 might require less space than a 0 and this could
|
||||
// cause some flickering depending on the font) and set the new margin width.
|
||||
QFont font = lexer()->font(QsciLexerJSON::Default);
|
||||
setMarginWidth(0, QFontMetrics(font).width(QString("0").repeated(digits)) + 5);
|
||||
}
|
||||
|
||||
void JsonTextEdit::dropEvent(QDropEvent* e)
|
||||
{
|
||||
QList<QUrl> urls = e->mimeData()->urls();
|
||||
if(urls.isEmpty())
|
||||
return QsciScintilla::dropEvent(e);
|
||||
|
||||
QString file = urls.first().toLocalFile();
|
||||
if(!QFile::exists(file))
|
||||
return;
|
||||
|
||||
QFile f(file);
|
||||
f.open(QIODevice::ReadOnly);
|
||||
setText(f.readAll());
|
||||
f.close();
|
||||
}
|
||||
|
||||
void JsonTextEdit::setupSyntaxHighlightingFormat(const QString& settings_name, int style)
|
||||
{
|
||||
jsonLexer->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());
|
||||
jsonLexer->setFont(font, style);
|
||||
}
|
||||
|
||||
void JsonTextEdit::reloadKeywords()
|
||||
{
|
||||
// Set lexer again to reload the updated keywords list
|
||||
setLexer(lexer());
|
||||
}
|
||||
|
||||
void JsonTextEdit::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);
|
||||
}
|
||||
|
||||
// Set syntax highlighting settings
|
||||
QFont defaultfont(Settings::getValue("editor", "font").toString());
|
||||
defaultfont.setStyleHint(QFont::TypeWriter);
|
||||
defaultfont.setPointSize(Settings::getValue("editor", "fontsize").toInt());
|
||||
jsonLexer->setFont(defaultfont);
|
||||
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);
|
||||
|
||||
// Set font
|
||||
QFont font(Settings::getValue("editor", "font").toString());
|
||||
font.setStyleHint(QFont::TypeWriter);
|
||||
font.setPointSize(Settings::getValue("editor", "fontsize").toInt());
|
||||
setFont(font);
|
||||
|
||||
// Show line numbers
|
||||
QFont marginsfont(QFont(Settings::getValue("editor", "font").toString()));
|
||||
marginsfont.setPointSize(font.pointSize());
|
||||
setMarginsFont(marginsfont);
|
||||
setMarginLineNumbers(0, true);
|
||||
setMarginsBackgroundColor(Qt::lightGray);
|
||||
updateLineNumberAreaWidth();
|
||||
|
||||
// Highlight current line
|
||||
setCaretLineVisible(true);
|
||||
setCaretLineBackgroundColor(QColor(Settings::getValue("syntaxhighlighter", "currentline_colour").toString()));
|
||||
|
||||
// Set tab width
|
||||
setTabWidth(Settings::getValue("editor", "tabsize").toInt());
|
||||
jsonLexer->refreshProperties();
|
||||
|
||||
}
|
||||
35
src/jsontextedit.h
Normal file
35
src/jsontextedit.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef JSONTEXTEDIT_H
|
||||
#define JSONTEXTEDIT_H
|
||||
|
||||
#include "Qsci/qsciscintilla.h"
|
||||
#include <Qsci/qscilexerjson.h>
|
||||
|
||||
/**
|
||||
* @brief The JsonTextEdit class
|
||||
* This class is based on the QScintilla widget
|
||||
*/
|
||||
class JsonTextEdit : public QsciScintilla
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit JsonTextEdit(QWidget *parent = nullptr);
|
||||
virtual ~JsonTextEdit();
|
||||
|
||||
static QsciLexerJSON* jsonLexer;
|
||||
|
||||
public slots:
|
||||
void reloadKeywords();
|
||||
void reloadSettings();
|
||||
|
||||
protected:
|
||||
void dropEvent(QDropEvent* e);
|
||||
|
||||
private:
|
||||
void setupSyntaxHighlightingFormat(const QString& settings_name, int style);
|
||||
|
||||
private slots:
|
||||
void updateLineNumberAreaWidth();
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user