library/forms/jsonview.cpp (1,386 lines of code) (raw):

/* * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <set> #include <sstream> #include <cctype> #include <future> #ifdef __APPLE__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcomma" #endif #include <boost/date_time.hpp> #ifdef __APPLE__ #pragma GCC diagnostic pop #endif #include "mforms/jsonview.h" #include "mforms/panel.h" #include "mforms/treeview.h" #include "mforms/code_editor.h" #include "mforms/tabview.h" #include "mforms/menubar.h" #include "mforms/button.h" #include "mforms/label.h" #include "mforms/textentry.h" #include "base/string_utilities.h" #undef min #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" #include "rapidjson/prettywriter.h" using namespace mforms; using namespace rapidjson; namespace ph = std::placeholders; namespace bt = boost::posix_time; // JSON Data structures implementation //-------------------------------------------------------------------------------------------------- // JSON Control Implementation static void findNode(TreeNodeRef parent, const std::string &text, JsonTreeBaseView::TreeNodeVectorMap &found) { if (parent.is_valid()) { auto node = parent; if (base::contains_string(node->get_string(1), text, false)) found[text].push_back(node); int count = node->count(); for (int i = 0; i < count; ++i) { TreeNodeRef child(node->get_child(i)); if (child) findNode(child, text, found); } } } //-------------------------------------------------------------------------------------------------- static std::string getParseErrorText(ParseErrorCode code) { std::string text = "No error."; switch (code) { case kParseErrorDocumentEmpty: text = "The document is empty."; break; case kParseErrorDocumentRootNotSingular: text = "The document root must not follow by other values."; break; case kParseErrorValueInvalid: text = "Invalid value."; break; case kParseErrorObjectMissName: text = "Missing a name for object member."; break; case kParseErrorObjectMissColon: text = "Missing a colon after a name of object member."; break; case kParseErrorObjectMissCommaOrCurlyBracket: text = "Missing a comma or '}' after an object member."; break; case kParseErrorArrayMissCommaOrSquareBracket: text = "Missing a comma or ']' after an array element."; break; case kParseErrorStringUnicodeEscapeInvalidHex: text = "Incorrect hex digit after \\u escape in string."; break; case kParseErrorStringUnicodeSurrogateInvalid: text = "The surrogate pair in string is invalid."; break; case kParseErrorStringEscapeInvalid: text = "Invalid escape character in string."; break; case kParseErrorStringMissQuotationMark: text = "Missing a closing quotation mark in string."; break; case kParseErrorStringInvalidEncoding: text = "Invalid encoding in string."; break; case kParseErrorNumberTooBig: text = "Number too big to be stored in double."; break; case kParseErrorNumberMissFraction: text = "Miss fraction part in number."; break; case kParseErrorNumberMissExponent: text = "Miss exponent in number."; break; case kParseErrorTermination: text = "Parsing was terminated."; break; case kParseErrorUnspecificSyntaxError: text = "Unspecific syntax error."; break; case kParseErrorNone: default: text = "No error"; } return text; } //-------------------------------------------------------------------------------------------------- JsonInputDlg::JsonInputDlg(mforms::Form *owner, bool showTextEntry) : mforms::Form(owner, mforms::FormResizable), _textEditor(manage(new CodeEditor())), _save(nullptr), _cancel(nullptr), _textEntry(nullptr), _validated(false) { setup(showTextEntry); } //-------------------------------------------------------------------------------------------------- JsonInputDlg::~JsonInputDlg() { } //-------------------------------------------------------------------------------------------------- std::string JsonInputDlg::objectName() const { return (_textEntry != NULL) ? _textEntry->get_string_value() : ""; } //-------------------------------------------------------------------------------------------------- const std::string &JsonInputDlg::text() const { return _text; } //-------------------------------------------------------------------------------------------------- const rapidjson::Value &JsonInputDlg::data() const { return _value; } //-------------------------------------------------------------------------------------------------- bool JsonInputDlg::run() { return run_modal(NULL, _cancel); } //-------------------------------------------------------------------------------------------------- void JsonInputDlg::setup(bool showTextEntry) { Box *box = manage(new Box(false)); Box *hbox = manage(new Box(true)); Button *check = manage(new Button()); if (showTextEntry) { Box *textEntryBox = manage(new Box(true)); textEntryBox->set_padding(12); textEntryBox->set_spacing(12); Label *nameDescription = manage(new Label("Object name:")); _textEntry = manage(new TextEntry()); textEntryBox->add(nameDescription, false, false); textEntryBox->add(_textEntry, true, true); box->add(textEntryBox, false, true); } _cancel = manage(new Button()); _save = manage(new Button()); set_title("JSON Editor"); set_content(box); box->set_padding(12); box->set_spacing(12); _textEditor->set_language(mforms::LanguageJson); _textEditor->set_features(mforms::FeatureWrapText, false); _textEditor->set_features(mforms::FeatureReadOnly, false); box->add(_textEditor, true, true); box->add(hbox, false, true); hbox->add_end(_cancel, false, true); hbox->add_end(_save, false, true); hbox->add_end(check, false, true); hbox->set_spacing(12); check->set_text("Validate"); _save->set_text("Save"); _save->set_enabled(false); _cancel->set_text("Cancel"); scoped_connect(check->signal_clicked(), std::bind(&JsonInputDlg::validate, this)); scoped_connect(_save->signal_clicked(), std::bind(&JsonInputDlg::save, this)); scoped_connect(_textEditor->signal_changed(), std::bind(&JsonInputDlg::editorContentChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); set_size(800, 500); center(); } //-------------------------------------------------------------------------------------------------- void JsonInputDlg::save() { if (_textEntry) { auto text = _textEntry->get_string_value(); if (text.empty() && _textEntry->is_enabled()) { mforms::Utilities::show_error("JSON Editor.", "The field 'name' can not be empty", "Ok"); return; } } end_modal(true); } //-------------------------------------------------------------------------------------------------- void JsonInputDlg::setText(const std::string &text, bool readonly) { if (_textEntry) { _textEntry->set_value(text); _textEntry->set_enabled(!readonly); } } //-------------------------------------------------------------------------------------------------- void JsonInputDlg::setJson(const Value &json) { Document d; d.CopyFrom(json, d.GetAllocator()); StringBuffer buffer; Writer<StringBuffer> writer(buffer); d.Accept(writer); _textEditor->set_text(buffer.GetString()); } //-------------------------------------------------------------------------------------------------- void JsonInputDlg::validate() { auto text = _textEditor->get_text(false); if (text.empty()) return; _document.Parse(text); _save->set_enabled(true); _validated = true; _value.CopyFrom(_document, _document.GetAllocator()); _text = _textEditor->get_string_value(); if (_document.HasParseError()) mforms::Utilities::show_error( "JSON check.", base::strfmt("Validation failed: '%s'", getParseErrorText(_document.GetParseError()).c_str()), "Ok"); } //-------------------------------------------------------------------------------------------------- void JsonInputDlg::editorContentChanged(Sci_Position /*position*/, Sci_Position /*length*/, Sci_Position /*numberOfLines*/, bool /*inserted*/) { _save->set_enabled(false); _validated = false; _text = ""; _value = Value(); } //-------------------------------------------------------------------------------------------------- JsonBaseView::JsonBaseView(Document &doc) : Panel(TransparentPanel), _document(doc) { } //-------------------------------------------------------------------------------------------------- bool JsonBaseView::isDateTime(const std::string &text) { static std::string validChars = "0123456789-.: "; if (text.find_first_not_of(validChars) != std::string::npos) return false; bt::time_input_facet *isoFacet = new bt::time_input_facet(); isoFacet->set_iso_format(); bt::time_input_facet *extendedIsoFacet = new bt::time_input_facet(); isoFacet->set_iso_extended_format(); static const std::locale formats[] = { std::locale(std::locale::classic(), isoFacet), std::locale(std::locale::classic(), extendedIsoFacet), std::locale(std::locale::classic(), new bt::time_input_facet("%Y-%m-%d %H:%M:%S")), std::locale(std::locale::classic(), new bt::time_input_facet("%Y/%m/%d %H:%M:%S")), std::locale(std::locale::classic(), new bt::time_input_facet("%d.%m.%Y %H:%M:%S")), std::locale(std::locale::classic(), new bt::time_input_facet("%Y-%m-%d")) }; static const size_t formatCounts = sizeof(formats) / sizeof(formats[0]); bt::ptime pt; bool ret = false; for (size_t i = 0; i < formatCounts; ++i) { std::istringstream is(text); is.imbue(formats[i]); is >> pt; if (pt != bt::ptime()) { ret = true; break; } } return ret; } //-------------------------------------------------------------------------------------------------- JsonBaseView::~JsonBaseView() { } //-------------------------------------------------------------------------------------------------- boost::signals2::signal<void(bool)> *JsonBaseView::dataChanged() { return &_dataChanged; } //-------------------------------------------------------------------------------------------------- void JsonBaseView::clear() { } //-------------------------------------------------------------------------------------------------- JsonTreeBaseView::JsonTreeBaseView(rapidjson::Document &doc) : JsonBaseView(doc), _useFilter(false), _searchIdx(0) { _contextMenu = mforms::manage(new mforms::ContextMenu()); _contextMenu->signal_will_show()->connect(std::bind(&JsonTreeBaseView::prepareMenu, this)); } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::prepareMenu() { if (_contextMenu) { _contextMenu->remove_all(); auto node = _treeView->get_selected_node(); if (!node.is_valid()) return; auto *data = dynamic_cast<JsonValueNodeData *>(node->get_data()); if (data != NULL) { auto &jv = data->getData(); bool showAddModify = true; if (!jv.IsObject() && !jv.IsArray()) showAddModify = false; auto *item = mforms::manage(new mforms::MenuItem("Add new value")); item->set_name("Add New Document"); item->setInternalName("add_new_doc"); item->signal_clicked()->connect(std::bind(&JsonTreeBaseView::handleMenuCommand, this, item->getInternalName())); item->set_enabled(showAddModify); _contextMenu->add_item(item); item = mforms::manage(new mforms::MenuItem("Delete JSON")); item->set_name("Delete Document"); item->setInternalName("delete_doc"); item->signal_clicked()->connect(std::bind(&JsonTreeBaseView::handleMenuCommand, this, item->getInternalName())); _contextMenu->add_item(item); item = mforms::manage(new mforms::MenuItem("Modify JSON")); item->set_name("Modify Document"); item->setInternalName("modify_doc"); item->signal_clicked()->connect(std::bind(&JsonTreeBaseView::handleMenuCommand, this, item->getInternalName())); item->set_enabled(showAddModify); _contextMenu->add_item(item); } } } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::handleMenuCommand(const std::string &command) { auto node = _treeView->get_selected_node(); if (command == "add_new_doc") { openInputJsonWindow(node); return; } if (command == "delete_doc") { auto data = dynamic_cast<JsonValueNodeData *>(node->get_data()); if (data != nullptr) { auto &jv = data->getData(); auto parent = node->get_parent(); if (parent != nullptr) { auto parentData = dynamic_cast<JsonValueNodeData *>(parent->get_data()); if (parentData != nullptr) { auto &value = parentData->getData(); if (value.IsArray()) { for (auto &item : value.GetArray()) { if (item == jv) { value.Erase(&item); break; } } } else if (value.IsObject()) { for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { if (it->value == jv) { value.RemoveMember(it); break; } } } } } node->set_data(nullptr); // This will explicitly delete the data. } node->remove_from_parent(); _dataChanged(false); return; } if (command == "modify_doc") { openInputJsonWindow(node, true); } } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::openInputJsonWindow(TreeNodeRef node, bool updateMode /*= false*/) { auto data = dynamic_cast<JsonValueNodeData *>(node->get_data()); if (data != nullptr) { auto &jv = data->getData(); JsonInputDlg dlg(_treeView->get_parent_form(), jv.IsObject()); if (updateMode) { if (jv.IsObject()) { auto tag = node->get_tag(); dlg.setText(tag, true); } dlg.setJson(jv); } if (dlg.run()) { Value value; value.CopyFrom(dlg.data(), _document.GetAllocator()); auto objectName = dlg.objectName(); switch (jv.GetType()) { case kObjectType: { jv.AddMember(Value(objectName, _document.GetAllocator()), value, _document.GetAllocator()); if (updateMode) { node->remove_children(); } auto newNode = (updateMode) ? node : node->add_child(); generateTree(objectName.empty() ? jv : jv[objectName], 0, newNode); newNode->set_string(0, objectName + "{" + std::to_string(jv.MemberCount()) + "}"); newNode->set_tag(objectName); _dataChanged(false); break; } case kArrayType: { if (updateMode) { jv.Clear(); node->remove_children(); jv.CopyFrom(value, _document.GetAllocator()); } else { jv.PushBack(value, _document.GetAllocator()); } auto newNode = (updateMode) ? node : node->add_child(); generateTree((updateMode) ? jv : *(jv.End()-1), 0, newNode); newNode->set_string(0, objectName + "[" + std::to_string(jv.Size()) + "]"); _dataChanged(false); break; } default: break; } } } } //-------------------------------------------------------------------------------------------------- JsonTreeBaseView::~JsonTreeBaseView() { } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::generateStringInTree(rapidjson::Value &value, int columnId, TreeNodeRef node) { auto text = value.GetString(); setStringData(columnId, node, text); node->set_data(new JsonTreeBaseView::JsonValueNodeData(value)); node->expand(); } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::highlightMatchNode(const std::string &text, bool backward) { if (_textToFind != text) { _textToFind = text; _searchIdx = 0; } bool needSearch = false; auto it = _viewFindResult.find(text); if (it != _viewFindResult.end()) { if (_searchIdx >= it->second.size()) _searchIdx = 0; auto node = it->second[_searchIdx]; if (base::contains_string(node->get_string(1), text, false)) { _treeView->select_node(node); _treeView->scrollToNode(node); _searchIdx++; } else { _viewFindResult.erase(text); needSearch = true; } } else needSearch = true; if (needSearch) { _searchIdx = 0; auto node = _treeView->get_selected_node(); if (!node.is_valid()) node = _treeView->root_node(); findNode(node, text, _viewFindResult); auto it = _viewFindResult.find(text); if (it != _viewFindResult.end()) { auto node = it->second[_searchIdx]; _treeView->select_node(node); _treeView->scrollToNode(node); _treeView->focus(); } } } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::collectParents(TreeNodeRef node, std::list<TreeNodeRef> &parents) { auto parent = node->get_parent(); if (parent->is_valid()) { parents.push_back(parent); collectParents(parent, parents); } } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::reCreateTree(Value &value) { _useFilter = false; _treeView->clear(); auto node = _treeView->root_node()->add_child(); _treeView->BeginUpdate(); Value o(kObjectType); o.CopyFrom(value, _document.GetAllocator()); generateTree(value, 0, node); _treeView->EndUpdate(); } //-------------------------------------------------------------------------------------------------- bool JsonTreeBaseView::filterView(const std::string &text, rapidjson::Value &value) { auto selectedNode = _treeView->get_selected_node(); if (!selectedNode.is_valid()) selectedNode = _treeView->root_node(); TreeNodeVectorMap viewFilterResult; findNode(selectedNode, text, viewFilterResult); auto it = viewFilterResult.find(text); if (it != viewFilterResult.end()) { std::shared_ptr<TreeNodeList> branch(new TreeNodeList); for (auto node : it->second) { branch->push_back(node); collectParents(node, *branch); } _filterGuard.clear(); auto actualNode = _treeView->root_node(); while (!branch->empty()) { auto node = branch->back(); branch->pop_back(); auto data = dynamic_cast<JsonValueNodeData *>(node->get_data()); if (data != nullptr) { auto &jv = data->getData(); if (_filterGuard.count(&jv)) continue; _filterGuard.insert(&jv); } } _useFilter = true; _treeView->clear(); generateTree(value, 0, _treeView->root_node()); } return _useFilter; } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::generateTree(rapidjson::Value &value, int columnId, TreeNodeRef node, bool addNew) { switch (value.GetType()) { case kNumberType: generateNumberInTree(value, columnId, node); break; case kFalseType: case kTrueType: generateBoolInTree(value, columnId, node); break; case kStringType: generateStringInTree(value, columnId, node); break; case kObjectType: generateObjectInTree(value, columnId, node, addNew); break; case kArrayType: generateArrayInTree(value, columnId, node); break; case kNullType: generateNullInTree(value, columnId, node); break; default: break; } } //-------------------------------------------------------------------------------------------------- void JsonTreeBaseView::setCellValue(mforms::TreeNodeRef node, int column, const std::string &value) { auto data = dynamic_cast<JsonValueNodeData *>(node->get_data()); bool setData = false; if (data != nullptr) { std::stringstream buffer; double number = 0; auto &storedValue = data->getData(); switch (storedValue.GetType()) { case kNumberType: if (!base::is_number(value)) break; buffer << value; buffer >> number; storedValue = Value(number).Move(); setData = true; break; case kTrueType: storedValue = Value(true).Move(); setData = true; break; case kFalseType: storedValue = Value(false).Move(); setData = true; break; case kStringType: storedValue = Value(value, _document.GetAllocator()).Move(); setStringData(column, node, value); setData = true; break; default: break; } } if (setData) { node->set_string(column, value); _dataChanged(false); } } //-------------------------------------------------------------------------------------------------- JsonTextView::JsonTextView(Document &doc) : JsonBaseView(doc), _textEditor(manage(new CodeEditor())), _modified(false), _position(0) { init(); } //-------------------------------------------------------------------------------------------------- void JsonTextView::setText(const std::string &jsonText, bool validateJson /*= true*/) { _textEditor->set_value(jsonText.c_str()); if (validateJson) validate(); _text = jsonText; } //-------------------------------------------------------------------------------------------------- const Value &JsonTextView::getJson() const { return _json; } //-------------------------------------------------------------------------------------------------- const std::string &JsonTextView::getText() const { return _text; } //-------------------------------------------------------------------------------------------------- JsonTextView::~JsonTextView() { } //-------------------------------------------------------------------------------------------------- void JsonTextView::clear() { _textEditor->set_value(""); } //-------------------------------------------------------------------------------------------------- void JsonTextView::init() { assert(_textEditor != NULL); _textEditor->set_language(mforms::LanguageJson); _textEditor->set_features(mforms::FeatureWrapText, false); _textEditor->set_features(mforms::FeatureReadOnly, false); scoped_connect(_textEditor->signal_changed(), [this](Sci_Position position, Sci_Position length, Sci_Position numberOfLines, bool inserted) { editorContentChanged(position, length, numberOfLines, inserted); }); scoped_connect(_textEditor->signal_dwell(), [this](bool started, size_t position, int x, int y) { dwellEvent(started, position, x, y); }); Box *box = manage(new Box(false)); box->set_padding(5); box->set_spacing(5); box->add(_textEditor, true, true); add(box); } //-------------------------------------------------------------------------------------------------- void JsonTextView::editorContentChanged(Sci_Position position, Sci_Position length, Sci_Position numberOfLines, bool inserted) { if (_stopTextProcessing) _stopTextProcessing(); _modified = true; _position = position; _text = _textEditor->get_text(false); if (_startTextProcessing) { _startTextProcessing([this]() -> bool { _dataChanged(true); return false; }); } else _dataChanged(true); } //-------------------------------------------------------------------------------------------------- bool JsonTextView::validate() { bool ret = true; if (_modified) { std::future<std::string> validateFuture = std::async(std::launch::async, [&, this]() -> std::string { _document.Parse(_text); if (_document.HasParseError()) { return getParseErrorText(_document.GetParseError()); } else { _json.CopyFrom(_document, _document.GetAllocator()); } return ""; }); validateFuture.wait(); auto text = validateFuture.get(); if (text.empty()) { _textEditor->remove_markup(LineMarkupAll, -1); _textEditor->remove_indicator(mforms::RangeIndicatorError, 0, _textEditor->text_length()); _errorEntry.clear(); _modified = false; ret = true; } else { int line = (int)_textEditor->line_from_position(_position); _textEditor->show_markup(LineMarkupError, line); std::size_t posBegin = _textEditor->position_from_line(line); posBegin = _text.find_first_not_of(" \t\r\n", posBegin); std::size_t posEnd = _text.find_first_of("\n\r", posBegin + 1); _textEditor->show_indicator(mforms::RangeIndicatorError, posBegin, posEnd - posBegin); _errorEntry.push_back(JsonErrorEntry{ text, posBegin, posEnd - posBegin }); ret = false; } } return ret; } //-------------------------------------------------------------------------------------------------- void JsonTextView::dwellEvent(bool started, size_t position, int x, int y) { if (started) { if (_textEditor->indicator_at(position) == mforms::RangeIndicatorError) { auto end = _errorEntry.cend(); auto it = std::find_if(_errorEntry.cbegin(), end, [&](const JsonErrorEntry &value) { return value.pos <= position && position <= value.pos + value.length; }); if (it != end) _textEditor->show_calltip(true, position, it->text); } } else _textEditor->show_calltip(false, 0, ""); } //-------------------------------------------------------------------------------------------------- void JsonTextView::findAndHighlightText(const std::string &text, bool backward /*= false*/) { _textEditor->find_and_highlight_text(text, mforms::FindDefault, true, backward); } //-------------------------------------------------------------------------------------------------- JsonTreeView::JsonTreeView(Document &doc) : JsonTreeBaseView(doc) { _treeView = manage(new mforms::TreeView(mforms::TreeAltRowColors | mforms::TreeShowRowLines | mforms::TreeShowColumnLines | mforms::TreeNoBorder)); _treeView->add_column(IconStringColumnType, "Key", 150, false, true); _treeView->add_column(StringLTColumnType, "Value", 200, true, true); _treeView->add_column(StringLTColumnType, "Type", 200, false, true); _treeView->end_columns(); _treeView->set_cell_edit_handler(std::bind(&JsonTreeBaseView::setCellValue, this, ph::_1, ph::_2, ph::_3)); _treeView->set_selection_mode(TreeSelectSingle); _treeView->set_context_menu(_contextMenu); init(); } //-------------------------------------------------------------------------------------------------- void JsonTreeView::init() { assert(_treeView != nullptr); add(_treeView); } //-------------------------------------------------------------------------------------------------- JsonTreeView::~JsonTreeView() { _treeView->clear(); } //-------------------------------------------------------------------------------------------------- void JsonTreeView::clear() { _treeView->clear(); _viewFindResult.clear(); _textToFind = ""; _searchIdx = 0; _useFilter = false; } //-------------------------------------------------------------------------------------------------- void JsonTreeView::setJson(rapidjson::Value &value) { clear(); auto node = _treeView->root_node()->add_child(); generateTree(value, 0, node); } //-------------------------------------------------------------------------------------------------- void JsonTreeView::appendJson(rapidjson::Value &value) { TreeNodeRef node = _treeView->root_node(); _viewFindResult.clear(); _textToFind = ""; _searchIdx = 0; generateTree(value, 0, node); } //-------------------------------------------------------------------------------------------------- void JsonTreeView::generateObjectInTree(rapidjson::Value &value, int /*columnId*/, TreeNodeRef node, bool addNew) { if (_useFilter && _filterGuard.count(&value) == 0) return; size_t size = 0; node->set_data(new JsonTreeBaseView::JsonValueNodeData(value)); for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { std::string text = it->name.GetString(); std::stringstream textSize; switch (it->value.GetType()) { case kArrayType: { auto &arrayVal = it->value; size = arrayVal.Size(); node->set_tag(text); textSize << size; text += "["; text += textSize.str(); text += "]"; break; } case kObjectType: { auto &objectVal = it->value; size = objectVal.MemberCount(); textSize << size; text += "{"; text += textSize.str(); text += "}"; break; } default: break; } auto node2 = (addNew) ? node->add_child() : node; if (addNew) { node->set_icon_path(0, "JS_Datatype_Object.png"); std::string name = node->get_string(0); if (name.empty()) node->set_string(0, "<unnamed>"); node->set_string(1, ""); node->set_string(2, "Object"); } node2->set_string(0, text); node2->set_tag(text); generateTree(it->value, 1, node2); node2->expand(); } } //-------------------------------------------------------------------------------------------------- void JsonTreeView::generateArrayInTree(rapidjson::Value &value, int /*columnId*/, TreeNodeRef node) { if (_useFilter && _filterGuard.count(&value) == 0) return; node->set_icon_path(0, "JS_Datatype_Array.png"); std::string name = node->get_string(0); if (name.empty()) node->set_string(0, "<unnamed>"); node->set_string(1, ""); node->set_string(2, "Array"); std::string tagName = node->get_tag(); node->set_data(new JsonTreeBaseView::JsonValueNodeData(value)); int index = 0; for (auto &v : value.GetArray()) { if (_useFilter && _filterGuard.count(&v) == 0) continue; auto arrrayNode = node->add_child(); bool addNew = false; if (v.GetType() == kArrayType || v.GetType() == kObjectType) addNew = true; std::string keyName = tagName.empty() ? "key[%d]" : tagName + "[%d]"; arrrayNode->set_string(0, base::strfmt(keyName.c_str(), index)); arrrayNode->set_string(1, ""); generateTree(v, 1, arrrayNode, addNew); index++; } node->expand(); } //-------------------------------------------------------------------------------------------------- void JsonTreeView::generateBoolInTree(rapidjson::Value &value, int /*columnId*/, TreeNodeRef node) { node->set_icon_path(0, "JS_Datatype_Bool.png"); node->set_attributes(1, mforms::TextAttributes("#4b4a4c", false, false)); node->set_bool(1, value.GetBool()); node->set_string(2, "Boolean"); node->set_data(new JsonTreeBaseView::JsonValueNodeData(value)); node->expand(); } //-------------------------------------------------------------------------------------------------- void JsonTreeView::generateNumberInTree(rapidjson::Value &value, int /*columnId*/, TreeNodeRef node) { node->set_icon_path(0, "JS_Datatype_Number.png"); node->set_attributes(1, mforms::TextAttributes("#4b4a4c", false, false)); if (value.IsDouble()) { node->set_string(1, std::to_string(value.GetDouble())); node->set_string(2, "Double"); } else if (value.IsInt64()) { node->set_string(1, std::to_string(value.GetInt64())); node->set_string(2, "Long Integer"); } else if (value.IsUint64()) { node->set_string(1, std::to_string(value.GetUint64())); node->set_string(2, "Unsigned Long Integer"); } node->set_data(new JsonTreeBaseView::JsonValueNodeData(value)); node->expand(); } //-------------------------------------------------------------------------------------------------- void JsonTreeView::generateNullInTree(rapidjson::Value &value, int /*columnId*/, TreeNodeRef node) { node->set_icon_path(0, "JS_Datatype_Null.png"); node->set_string(0, "null"); node->set_string(1, ""); node->set_string(2, "Null"); node->set_data(new JsonTreeBaseView::JsonValueNodeData(value)); node->expand(); } //-------------------------------------------------------------------------------------------------- void JsonTreeView::setStringData(int /*columnId*/, TreeNodeRef node, const std::string &text) { /*if (isDateTime(text)) { node->set_icon_path(0, "JS_Datatype_Date.png"); node->set_string(2, "Date/Time"); } else*/ { node->set_icon_path(0, "JS_Datatype_String.png"); node->set_string(2, "String"); } node->set_attributes(1, mforms::TextAttributes("#4b4a4c", false, false)); node->set_string(1, text.c_str()); } //-------------------------------------------------------------------------------------------------- JsonGridView::JsonGridView(Document &doc) : JsonTreeBaseView(doc), _level(0), _headerAdded(false), _noNameColId(-1), _columnIndex(0), _rowNum(1), _actualParent(20) { init(); } //-------------------------------------------------------------------------------------------------- /** * @brief Init flat grid view * * Based of readed JSON data control function initialize mforms control TreNodeView */ void JsonGridView::init() { _treeView = manage(new mforms::TreeView(mforms::TreeAltRowColors | mforms::TreeShowRowLines | mforms::TreeShowColumnLines | mforms::TreeNoBorder)); assert(_treeView != nullptr); _treeView->add_column(StringLTColumnType, "", 30, false, false); _treeView->set_cell_edit_handler( std::bind(&JsonGridView::setCellValue, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); _treeView->signal_node_activated()->connect( std::bind(&JsonGridView::nodeActivated, this, std::placeholders::_1, std::placeholders::_2)); _treeView->set_selection_mode(TreeSelectSingle); _treeView->set_context_menu(_contextMenu); _goUpButton = manage(new Button()); _goUpButton->set_text("Back <<<"); _goUpButton->set_enabled(false); scoped_connect(_goUpButton->signal_clicked(), std::bind(&JsonGridView::goUp, this)); _content = manage(new Box(false)); _content->add(_treeView, true, true); Box *hbox = manage(new Box(true)); hbox->add_end(_goUpButton, false, false); hbox->set_size(-1, 30); _content->add(hbox, false, false); add(_content); } //-------------------------------------------------------------------------------------------------- void JsonGridView::goUp() { if (_level <= 0 || _actualParent.empty()) return; rapidjson::Value *value = _actualParent.at(_level - 1); if (value == NULL) return; setJson(*value); if (--_level <= 0) _goUpButton->set_enabled(false); } //-------------------------------------------------------------------------------------------------- JsonGridView::~JsonGridView() { _treeView->clear(); } //-------------------------------------------------------------------------------------------------- void JsonGridView::clear() { _treeView->clear(); _viewFindResult.clear(); _textToFind = ""; _searchIdx = 0; _useFilter = false; } //-------------------------------------------------------------------------------------------------- void JsonGridView::setJson(rapidjson::Value &value) { clear(); _rowNum = 1; if (!_headerAdded) { _columnIndex = 1; _level = 0; _noNameColId = -1; generateColumnNames(value); _treeView->end_columns(); _headerAdded = true; } if (_level >= (int)_actualParent.size()) _actualParent.resize(_actualParent.size() * 2); _actualParent[_level] = &value; TreeNodeRef node = _treeView->root_node(); generateTree(value, 0, node); } //-------------------------------------------------------------------------------------------------- void JsonGridView::reCreateTree(rapidjson::Value &value) { remove(_content); init(); _headerAdded = false; _colNameToColId.clear(); setJson(value); } //-------------------------------------------------------------------------------------------------- void JsonGridView::addColumn(int size, Type type, Value *value, const std::string &name) { switch (type) { case kArrayType: case kObjectType: _treeView->add_column(IconStringColumnType, name, size, false, true); break; case kNumberType: { if (value != nullptr && (value->IsFloat() || value->IsDouble())) { _treeView->add_column(FloatColumnType, name, size, true, true); } else { _treeView->add_column(LongIntegerColumnType, name, size, true, true); } break; } case kTrueType: case kFalseType: case kStringType: case kNullType: default: _treeView->add_column(IconStringColumnType, name, size, true, true); break; } } //-------------------------------------------------------------------------------------------------- void JsonGridView::generateColumnNames(rapidjson::Value &value) { if (_level != 0) return; switch (value.GetType()) { case kObjectType: { for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { if (_colNameToColId.count(it->name.GetString()) == 1) continue; addColumn(100, it->value.GetType(), &value, it->name.GetString()); _colNameToColId[it->name.GetString()] = _columnIndex++; if (it->value.GetType() == kObjectType || it->value.GetType() == kArrayType) generateColumnNames(it->value); } break; } case kArrayType: { for (auto &item : value.GetArray()) { if (item.GetType() == kObjectType) { Value &obj = item; for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { if (_colNameToColId.count(it->name.GetString()) == 1) continue; addColumn(100, it->value.GetType(), &value, it->name.GetString()); _colNameToColId[it->name.GetString()] = _columnIndex++; if (it->value.GetType() == kObjectType || it->value.GetType() == kArrayType) generateColumnNames(it->value); } } else { if (_noNameColId > 0) continue; addColumn(100, kStringType, nullptr, ""); _noNameColId = _columnIndex++; } if (item.GetType() == kObjectType || item.GetType() == kArrayType) generateColumnNames(item); } break; } default: break; } } //-------------------------------------------------------------------------------------------------- void JsonGridView::setCellValue(mforms::TreeNodeRef node, int column, const std::string &value) { JsonValueNodeData *data = dynamic_cast<JsonValueNodeData *>(node->get_data()); if (data == NULL) return; const std::map<std::string, int>::const_iterator it = std::find_if(_colNameToColId.begin(), _colNameToColId.end(), [&column](const std::pair<std::string, int> &elem) { return column == elem.second; }); if (it == _colNameToColId.end() || it->first.empty()) return; std::string key = it->first; rapidjson::Value &valData = data->getData(); if (valData.FindMember(it->first) == valData.MemberEnd()) return; rapidjson::Value &storedValue = valData[key]; if (data != NULL) { std::stringstream buffer; double number = 0; int64_t number2 = 0; uint64_t number3 = 0; long number4 = 0; bool retBool = false; switch (storedValue.GetType()) { case kNumberType: { if (!base::is_number(value)) break; if (storedValue.IsDouble()) { buffer << value; buffer >> number; storedValue = number; node->set_float(column, number); } else if (storedValue.IsInt64() || storedValue.IsInt()) { buffer << value; buffer >> number2; storedValue.SetInt64(number2); node->set_long(column, number2); } else if (storedValue.IsUint64()) { buffer << value; buffer >> number3; storedValue.SetUint64(number3); node->set_float(column, (double)number3); } else { buffer << value; buffer >> number4; storedValue.SetInt(number4); node->set_long(column, number4); } break; } case kTrueType: case kFalseType: if (!base::isBool(value)) break; buffer << value; buffer >> std::boolalpha >> retBool; storedValue.SetBool(retBool); node->set_bool(column, retBool); _dataChanged(false); break; case kStringType: storedValue.SetString(value, _document.GetAllocator()); setStringData(column, node, value); node->set_string(column, value); _dataChanged(false); break; default: break; } } } //-------------------------------------------------------------------------------------------------- void JsonGridView::openInputJsonWindow(rapidjson::Value &value) { JsonInputDlg dlg(_treeView->get_parent_form(), false); dlg.setJson(value); if (dlg.run()) { value.CopyFrom(dlg.data(), _document.GetAllocator()); _actualParent[_level] = &value; reCreateTree(*_actualParent.at(0)); setJson(*_actualParent.at(_level)); _dataChanged(false); } } //-------------------------------------------------------------------------------------------------- void JsonGridView::handleMenuCommand(const std::string &command) { rapidjson::Value *parent = _actualParent.at(_level); if (parent == nullptr) return; if (command == "add_new_doc" || command == "modify_doc") { openInputJsonWindow(*parent); return; } if (command == "delete_doc") { TreeNodeRef node = _treeView->get_selected_node(); if (!node.is_valid()) return; auto *data = dynamic_cast<JsonValueNodeData *>(node->get_data()); if (data != nullptr) { rapidjson::Value &jv = data->getData(); if (parent->IsArray()) { for (auto &item : parent->GetArray()) { if (item == jv) { parent->Erase(&item); break; } } } else if (parent->IsObject()) { parent->RemoveAllMembers(); } node->set_data(nullptr); // This will explicitly delete the data. } node->remove_from_parent(); _dataChanged(false); } } //-------------------------------------------------------------------------------------------------- void JsonGridView::appendJson(rapidjson::Value & /*value*/) { } //-------------------------------------------------------------------------------------------------- void JsonGridView::generateObjectInTree(rapidjson::Value &value, int columnId, TreeNodeRef node, bool addNew) { auto child = node; if (addNew) child = node->add_child(); size_t size = 0; std::stringstream textSize; child->set_data(new JsonTreeBaseView::JsonValueNodeData(value)); node->set_string(0, std::to_string(_rowNum++)); for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { std::string text = it->name.GetString(); if (_colNameToColId.count(text) == 0) continue; int index = _colNameToColId[text]; switch (it->value.GetType()) { case kArrayType: { auto const& arrayVal = it->value.GetArray(); size = arrayVal.Size(); textSize << size; text = "Array ["; text += textSize.str(); text += "]"; child->set_icon_path(index, "JS_Datatype_Array.png"); child->set_string(index, text); break; } case kObjectType: { auto &objectVal = it->value; size = objectVal.MemberCount(); textSize << size; text = "Object {"; text += textSize.str(); text += "}"; child->set_icon_path(index, "JS_Datatype_Object.png"); child->set_string(index, text); break; } case kNumberType: generateNumberInTree(it->value, index, child); break; case kTrueType: case kFalseType: generateBoolInTree(it->value, index, child); break; case kStringType: setStringData(index, child, it->value.GetString()); break; case kNullType: generateNullInTree(it->value, index, child); break; default: break; } } } //-------------------------------------------------------------------------------------------------- void JsonGridView::generateArrayInTree(rapidjson::Value &value, int /*columnId*/, TreeNodeRef /*node*/) { auto const& arrayType = value.GetArray(); for (auto &item : arrayType) { mforms::TreeNodeRef arrrayNode = _treeView->root_node()->add_child(); arrrayNode->set_string(0, std::to_string(_rowNum++)); switch (item.GetType()) { case kArrayType: { auto const& arrayVal = item.GetArray(); size_t size = arrayVal.Size(); std::stringstream textSize; textSize << size; std::string text = "Array ["; text += textSize.str(); text += "]"; arrrayNode->set_icon_path(_noNameColId, "JS_Datatype_Array.png"); arrrayNode->set_string(_noNameColId, text); arrrayNode->set_data(new JsonTreeBaseView::JsonValueNodeData(item)); break; } case kObjectType: _rowNum--; generateObjectInTree(item, 0, arrrayNode, false); break; case kNumberType: generateNumberInTree(item, _noNameColId, arrrayNode); arrrayNode->set_data(new JsonTreeBaseView::JsonValueNodeData(item)); break; case kTrueType: case kFalseType: generateBoolInTree(item, _noNameColId, arrrayNode); arrrayNode->set_data(new JsonTreeBaseView::JsonValueNodeData(item)); break; case kStringType: setStringData(_noNameColId, arrrayNode, item.GetString()); arrrayNode->set_data(new JsonTreeBaseView::JsonValueNodeData(item)); break; case kNullType: generateNullInTree(item, _noNameColId, arrrayNode); arrrayNode->set_data(new JsonTreeBaseView::JsonValueNodeData(item)); break; default: break; } } } //-------------------------------------------------------------------------------------------------- void JsonGridView::nodeActivated(TreeNodeRef node, int column) { if (column > 0) { JsonValueNodeData *data = dynamic_cast<JsonValueNodeData *>(node->get_data()); if (!data) return; rapidjson::Value &storedValue = data->getData(); if (storedValue.GetType() == kObjectType) { const std::map<std::string, int>::const_iterator it = std::find_if(_colNameToColId.begin(), _colNameToColId.end(), [&column](const std::pair<std::string, int> &elem) { return column == elem.second; }); if (it != _colNameToColId.end() && storedValue.FindMember(it->first) != storedValue.MemberEnd()) { rapidjson::Value &clickedValue = storedValue[it->first]; if (clickedValue.GetType() != kObjectType && clickedValue.GetType() != kArrayType) return; _level++; setJson(clickedValue); _goUpButton->set_enabled(true); } } if (storedValue.GetType() == kArrayType) { _level++; setJson(storedValue); _goUpButton->set_enabled(true); } } } //-------------------------------------------------------------------------------------------------- void JsonGridView::generateBoolInTree(rapidjson::Value &value, int columnId, TreeNodeRef node) { node->set_bool(columnId, value.GetBool()); } //-------------------------------------------------------------------------------------------------- void JsonGridView::generateNumberInTree(rapidjson::Value &value, int columnId, TreeNodeRef node) { if (value.IsDouble()) { node->set_float(columnId, value.GetDouble()); } else if (value.IsInt64()) { node->set_long(columnId, value.GetInt64()); } else if (value.IsUint64()) { node->set_long(columnId, value.GetUint64()); } else if (value.IsNumber()) { node->set_long(columnId, value.GetInt()); } } //-------------------------------------------------------------------------------------------------- void JsonGridView::generateNullInTree(rapidjson::Value &value, int columnId, TreeNodeRef node) { node->set_string(columnId, "null"); } //-------------------------------------------------------------------------------------------------- void JsonGridView::setStringData(int columnId, TreeNodeRef node, const std::string &text) { if (isDateTime(text)) node->set_icon_path(0, "JS_Datatype_Date.png"); node->set_attributes(columnId, mforms::TextAttributes("#4b4a4c", false, false)); node->set_string(columnId, text.c_str()); } //-------------------------------------------------------------------------------------------------- void JsonTabView::Setup() { assert(_tabView != NULL); _tabView->set_name("JSON Editor"); _tabId.textTabId = _tabView->add_page(_textView, "Text"); _tabId.treeViewTabId = _tabView->add_page(_treeView, "Tree"); _tabId.gridViewTabId = _tabView->add_page(_gridView, "Grid"); add(_tabView); scoped_connect(_textView->dataChanged(), std::bind(&JsonTabView::dataChanged, this, std::placeholders::_1)); scoped_connect(_treeView->dataChanged(), std::bind(&JsonTabView::dataChanged, this, std::placeholders::_1)); scoped_connect(_gridView->dataChanged(), std::bind(&JsonTabView::dataChanged, this, std::placeholders::_1)); scoped_connect(_tabView->signal_tab_changed(), std::bind(&JsonTabView::tabChanged, this)); } //-------------------------------------------------------------------------------------------------- JsonTabView::JsonTabView(bool tabLess, JsonTabViewType defaultView) : Panel(TransparentPanel), _textView(manage(new JsonTextView(_document))), _treeView(manage(new JsonTreeView(_document))), _gridView(manage(new JsonGridView(_document))), _tabView(manage(new TabView(tabLess ? TabViewTabless : TabViewPalette))), _updating(false), _defaultView(defaultView) { Setup(); } //-------------------------------------------------------------------------------------------------- JsonTabView::~JsonTabView() { } //-------------------------------------------------------------------------------------------------- void JsonTabView::setJson(const rapidjson::Value &value) { Document d; _json.CopyFrom(value, d.GetAllocator()); _ident = 0; _updating = true; d.CopyFrom(_json, d.GetAllocator()); StringBuffer buffer; PrettyWriter<StringBuffer> writer(buffer); d.Accept(writer); _jsonText = buffer.GetString(); _updateView = { true, true, true }; switch (_defaultView) { case JsonTabView::TabText: _textView->setText(_jsonText, false); _updateView.textViewUpdate = false; break; case JsonTabView::TabTree: _treeView->setJson(_json); _updateView.treeViewUpdate = false; break; case JsonTabView::TabGrid: _gridView->setJson(_json); _updateView.gridViewUpdate = false; break; } switchTab(_defaultView); _updating = false; } //-------------------------------------------------------------------------------------------------- void JsonTabView::setText(const std::string &text, bool validate) { _jsonText = text; _textView->setText(text, validate); _updateView.textViewUpdate = false; } //-------------------------------------------------------------------------------------------------- void JsonTabView::tabChanged() { int tabId = _tabView->get_active_tab(); if (tabId == _tabId.textTabId && _updateView.textViewUpdate) { _updating = true; _textView->setText(_jsonText); _updateView.textViewUpdate = false; _updating = false; _dataChanged(_jsonText); } else if (tabId == _tabId.treeViewTabId && _updateView.treeViewUpdate) { _treeView->reCreateTree(_json); _updateView.treeViewUpdate = false; _dataChanged(_jsonText); } else if (tabId == _tabId.gridViewTabId && _updateView.gridViewUpdate) { _gridView->reCreateTree(_json); _updateView.gridViewUpdate = false; _dataChanged(_jsonText); } } //-------------------------------------------------------------------------------------------------- void JsonTabView::dataChanged(bool forceUpdate) { if (_updating) return; int tabId = _tabView->get_active_tab(); if (tabId != _tabId.textTabId) { _document.CopyFrom(_json, _document.GetAllocator()); StringBuffer buffer; Writer<StringBuffer> writer(buffer); _document.Accept(writer); _jsonText = buffer.GetString(); } else { if (_textView->validate()) { _jsonText = _textView->getText(); _json.CopyFrom(_textView->getJson(), _document.GetAllocator()); } else return; } _updateView.textViewUpdate = tabId != _tabId.textTabId; _updateView.treeViewUpdate = tabId != _tabId.treeViewTabId; _updateView.gridViewUpdate = tabId != _tabId.gridViewTabId; _dataChanged(_jsonText); } //-------------------------------------------------------------------------------------------------- boost::signals2::signal<void(const std::string &text)> *JsonTabView::editorDataChanged() { return &_dataChanged; } //-------------------------------------------------------------------------------------------------- void JsonTabView::clear() { _jsonText.clear(); _textView->clear(); _treeView->clear(); _gridView->clear(); } //-------------------------------------------------------------------------------------------------- void JsonTabView::highlightMatch(const std::string &text) { _matchText = text; int tabId = _tabView->get_active_tab(); if (tabId == _tabId.textTabId) { _textView->findAndHighlightText(text); } else if (tabId == _tabId.treeViewTabId) { _treeView->highlightMatchNode(text); } else if (tabId == _tabId.gridViewTabId) { _gridView->highlightMatchNode(text); } } //-------------------------------------------------------------------------------------------------- void JsonTabView::highlightNextMatch() { int tabId = _tabView->get_active_tab(); if (tabId == _tabId.textTabId && !_matchText.empty()) { _textView->findAndHighlightText(_matchText); } else if (tabId == _tabId.treeViewTabId && !_matchText.empty()) { _treeView->highlightMatchNode(_matchText); } else if (tabId == _tabId.gridViewTabId && !_matchText.empty()) { _gridView->highlightMatchNode(_matchText); } } //-------------------------------------------------------------------------------------------------- void JsonTabView::highlightPreviousMatch() { int tabId = _tabView->get_active_tab(); if (tabId == _tabId.textTabId && !_matchText.empty()) { _textView->findAndHighlightText(_matchText, true); } else if (tabId == _tabId.treeViewTabId && !_matchText.empty()) { _treeView->highlightMatchNode(_matchText, true); } else if (tabId == _tabId.gridViewTabId && !_matchText.empty()) { _gridView->highlightMatchNode(_matchText, true); } } //-------------------------------------------------------------------------------------------------- bool JsonTabView::filterView(const std::string &text) { int tabId = _tabView->get_active_tab(); bool ret = false; if (tabId == _tabId.textTabId) { return false; // no filtering for text view } else if (tabId == _tabId.treeViewTabId) { ret = _treeView->filterView(text, _json); } else if (tabId == _tabId.gridViewTabId) { ret = _gridView->filterView(text, _json); } return ret; } //-------------------------------------------------------------------------------------------------- void JsonTabView::restoreOrginalResult() { int tabId = _tabView->get_active_tab(); if (tabId == _tabId.textTabId) { return; } else if (tabId == _tabId.treeViewTabId) { _treeView->reCreateTree(_json); } else if (tabId == _tabId.gridViewTabId) { _gridView->reCreateTree(_json); } } //-------------------------------------------------------------------------------------------------- void JsonTabView::switchTab(JsonTabViewType tab) const { switch (tab) { case JsonTabViewType::TabText: _tabView->set_active_tab(_tabId.textTabId); break; case JsonTabViewType::TabTree: _tabView->set_active_tab(_tabId.treeViewTabId); break; case JsonTabViewType::TabGrid: _tabView->set_active_tab(_tabId.gridViewTabId); break; default: _tabView->set_active_tab(_tabId.textTabId); } } //-------------------------------------------------------------------------------------------------- JsonTabView::JsonTabViewType JsonTabView::getActiveTab() const { int tabId = _tabView->get_active_tab(); if (tabId == _tabId.textTabId) return JsonTabViewType::TabText; else if (tabId == _tabId.treeViewTabId) return JsonTabViewType::TabTree; else return JsonTabViewType::TabGrid; } //-------------------------------------------------------------------------------------------------- const std::string &JsonTabView::text() const { return _jsonText; } //-------------------------------------------------------------------------------------------------- const rapidjson::Value &JsonTabView::json() const { return _json; } //-------------------------------------------------------------------------------------------------- void JsonTabView::setTextProcessingStartHandler(std::function<void(std::function<bool()>)> callback) { if (_textView) _textView->_startTextProcessing = callback; } //-------------------------------------------------------------------------------------------------- void JsonTabView::setTextProcessingStopHandler(std::function<void()> callabck) { if (_textView) _textView->_stopTextProcessing = callabck; } //--------------------------------------------------------------------------------------------------