Convert to CMake project; add object,static,shared targets.
Removed python support for now. Sorry, python users.
This commit is contained in:
parent
65aa28dd7f
commit
95b76b3d74
173
.gitignore
vendored
173
.gitignore
vendored
@ -1,32 +1,155 @@
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file commonly created by Windows Explorer
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares and remote volumes
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# Thumbnail cache files created by Windows
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
|
||||
# Windows Desktop Search
|
||||
*.pst
|
||||
*.ost
|
||||
*.log
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.lo
|
||||
*.la
|
||||
*.a
|
||||
*.class
|
||||
*.so
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.dll
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Qt
|
||||
*.pro.user
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
|
||||
# Java
|
||||
*.class
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# IntelliJ
|
||||
.idea/
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
.vscodium/
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints/
|
||||
|
||||
# Thumbnails
|
||||
Thumbs/
|
||||
Thumbs.db
|
||||
|
||||
# macOS metadata
|
||||
._*
|
||||
|
||||
# TextMate
|
||||
*.tmproj
|
||||
*.tmproject
|
||||
.tmtags
|
||||
|
||||
# Sublime Text
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
|
||||
# VS Code directories
|
||||
.vscode/
|
||||
|
||||
# CodeKit
|
||||
.codekit-config.json
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Compiled files
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
|
||||
# Logs and databases
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
*.xml
|
||||
|
||||
# Binary and source packages
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.tar
|
||||
*.zip
|
||||
*.rar
|
||||
*.bin
|
||||
*.war
|
||||
*.ear
|
||||
*.sar
|
||||
*.bbl
|
||||
*.pdf
|
||||
*.xls
|
||||
*.xlsx
|
||||
*.ppt
|
||||
*.pptx
|
||||
|
||||
# Virtual environment
|
||||
venv/
|
||||
env/
|
||||
|
||||
### Manually Entered
|
||||
vim-debug/
|
||||
**/out/bin
|
||||
**/out/lib
|
||||
**/out/share
|
||||
_deps
|
||||
.cache/
|
||||
compile_commands.json
|
||||
*.bak
|
||||
docs/
|
||||
*.old
|
||||
|
||||
# clangd cache
|
||||
.cache/
|
||||
.vim/
|
||||
build/
|
||||
debug/
|
||||
realease/
|
||||
Release/
|
||||
Debug
|
||||
|
114
CMake/BuildProperties.cmake
Normal file
114
CMake/BuildProperties.cmake
Normal file
@ -0,0 +1,114 @@
|
||||
# BuildProperties.cmake
|
||||
# Copyright (c) 2024 Saul D Beniquez
|
||||
# License: MIT
|
||||
#
|
||||
# This module defines a function prevent_in_source_build() that prevents in-source builds
|
||||
# and sets a policy for CMake version 3.24.0 and above.
|
||||
|
||||
function(prevent_in_source_build)
|
||||
# Prevent in-source builds
|
||||
if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
message(FATAL_ERROR "Source and build directories cannot be the same.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(disable_deprecated_features)
|
||||
# Use new timestamp behavior when extracting files archives
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(git_setup_submodules)
|
||||
find_package(Git QUIET)
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
option(GIT_SUBMODULE "Check submodules during build" ON)
|
||||
if(GIT_SUBMODULE)
|
||||
message(STATUS "Git submodule update")
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
RESULT_VARIABLE GIT_SUBMOD_RESULT)
|
||||
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
|
||||
message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(set_artifact_dir path)
|
||||
# Set local variable, not necessary to be parent scope since it's not used outside this function
|
||||
set(ARTIFACT_DIR "${path}")
|
||||
|
||||
# Set project-specific artifact directory in parent scope
|
||||
set(${PROJECT_NAME}_ARTIFACT_DIR "${path}" PARENT_SCOPE)
|
||||
|
||||
# Set output directories in parent scope using the provided path directly
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${path}/lib" PARENT_SCOPE)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${path}/lib" PARENT_SCOPE)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${path}/bin" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(disable_tests_if_subproject)
|
||||
if (NOT DEFINED PROJECT_NAME)
|
||||
option(BUILD_TESTING "Build and run unit tests" ON)
|
||||
else()
|
||||
option(BUILD_TESTING "Build and run unit tests" OFF)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(use_ccache)
|
||||
option(USE_CCACHE
|
||||
[=[Use ccache compiler cache to speed up builds.
|
||||
Enabled by default if ccache is found]=]
|
||||
ON
|
||||
)
|
||||
# Search for the code caching compiler wrapper, ccache and enable it
|
||||
# if found. This will speed up repeated builds.
|
||||
if (USE_CCACHE)
|
||||
message(CHECK_START "Detecting cacche")
|
||||
|
||||
find_program(CCACHE_PATH ccache)
|
||||
if(CCACHE_PATH)
|
||||
message(CHECK_PASS("found"))
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PATH})
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PATH})
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MESSAGE_INDENT " ")
|
||||
message(STATUS "(set -DUSE_CCACHE=Off to disable)")
|
||||
list(POP_BACK CMAKE_MESSAGE_INDENT)
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
function(detect_linkers)
|
||||
option(USE_MOLD "Use the mold/sold parallel linker for faster builds" OFF)
|
||||
if(USE_MOLD)
|
||||
# Determine if the compiler is GCC or Clang
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||
message(STATUS "Detected GCC/Clang, checking for mold/sold linker...")
|
||||
|
||||
# Check for mold linker on general systems and ld64.mold on macOS
|
||||
if(APPLE)
|
||||
find_program(MOLD_LINKER ld64.mold)
|
||||
set(CMAKE_LINKER_TYPE SOLD)
|
||||
else()
|
||||
find_program(MOLD_LINKER mold)
|
||||
set(CMAKE_LINKER_TYPE MOLD)
|
||||
endif()
|
||||
|
||||
if(MOLD_LINKER)
|
||||
message(STATUS "LINKER_TYPE set to ${CMAKE_LINKER_TYPE} for faster builds")
|
||||
list(APPEND CMAKE_MESSAGE_INDENT " ")
|
||||
message(STATUS "(set -DUSE_MOLD=OFF to disable)")
|
||||
list(POP_BACK CMAKE_MESSAGE_INDENT)
|
||||
else()
|
||||
message(STATUS " -- No suitable mold linker found. Using default linker.")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "Compiler is neither GCC nor Clang. Skipping mold linker check.")
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# vim: ts=4 sts=4 sw=4 noet foldmethod=indent :
|
24
CMake/CPM.cmake
Normal file
24
CMake/CPM.cmake
Normal file
@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
|
||||
|
||||
set(CPM_DOWNLOAD_VERSION 0.38.7)
|
||||
set(CPM_HASH_SUM "83e5eb71b2bbb8b1f2ad38f1950287a057624e385c238f6087f94cdfc44af9c5")
|
||||
|
||||
if(CPM_SOURCE_CACHE)
|
||||
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
|
||||
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
else()
|
||||
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
endif()
|
||||
|
||||
# Expand relative path. This is important if the provided path contains a tilde (~)
|
||||
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
|
||||
|
||||
file(DOWNLOAD
|
||||
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
|
||||
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
|
||||
)
|
||||
|
||||
include(${CPM_DOWNLOAD_LOCATION})
|
116
CMakeLists.txt
Normal file
116
CMakeLists.txt
Normal file
@ -0,0 +1,116 @@
|
||||
cmake_minimum_required(VERSION 3.26)
|
||||
|
||||
# Additional paths to search for custom and third-party CMake modules
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake)
|
||||
|
||||
include(BuildProperties)
|
||||
|
||||
prevent_in_source_build()
|
||||
disable_deprecated_features()
|
||||
|
||||
# When this package is included as a subproject, there's no need to
|
||||
# build and run the unit-tests.
|
||||
disable_tests_if_subproject()
|
||||
#git_setup_submodules()
|
||||
|
||||
project(Qt6JsonModel
|
||||
VERSION 0.0.1
|
||||
LANGUAGES C CXX
|
||||
# Save this for later:
|
||||
# HOMEPAGE_URL <URL>
|
||||
DESCRIPTION "QJsonModel is a json tree model class for Qt6/C++17 based on QAbstractItemModel. MIT License."
|
||||
)
|
||||
|
||||
detect_linkers()
|
||||
|
||||
include(CPM)
|
||||
|
||||
SET(QJsonModel_TOP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# enable compile_commands.json generation for clangd
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS On)
|
||||
|
||||
IF(NOT CMAKE_BUILD_TYPE)
|
||||
SET( CMAKE_BUILD_TYPE Debug )
|
||||
ENDIF()
|
||||
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
# Disable GNU compiler extensions
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
|
||||
find_package(Qt6 COMPONENTS
|
||||
Core REQUIRED
|
||||
Widgets REQUIRED
|
||||
Gui REQUIRED
|
||||
)
|
||||
qt_standard_project_setup()
|
||||
|
||||
# Set output directories for build targets
|
||||
#set_artifact_dir(${CMAKE_BINARY_DIR}/out)
|
||||
|
||||
include(CheckIncludeFile)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_compile_definitions(-DDEBUG=1 -DQDEBUG=1)
|
||||
endif()
|
||||
|
||||
qt_add_library(QJsonModel
|
||||
OBJECT
|
||||
QJsonModel.cpp
|
||||
)
|
||||
set_target_properties(QJsonModel PROPERTIES EXPORT_NAME JsonModel)
|
||||
add_library(Qt6::JsonModel ALIAS QJsonModel)
|
||||
|
||||
# Since headers are in the include/ directory, going to handle MOC invocation
|
||||
# manually.
|
||||
set_target_properties(QJsonModel PROPERTIES
|
||||
AUTOMOC OFF
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
set(QJsonModel_PUBLIC_INCLUDE_DIR ${CMAKE_BINARY_DIR}/include)
|
||||
#file(MAKE_DIRECTORY ${Qt6JsonModel_TOP_SOURCE_DIR})
|
||||
|
||||
add_custom_target(QJsonModel_include_files COMMAND ${CMAKE_COMMAND} -E copy_directory ${QJsonModel_TOP_SOURCE_DIR}/include ${QJsonModel_PUBLIC_INCLUDE_DIR}
|
||||
DEPENDS ${QJsonModel_TOP_SOURCE_DIR}/include
|
||||
COMMENT "Copying include files to ${IOCore_PUBLIC_INCLUDE_DIR}"
|
||||
)
|
||||
|
||||
target_include_directories(QJsonModel
|
||||
SYSTEM BEFORE
|
||||
PUBLIC ${QJsonModel_PUBLIC_INCLUDE_DIR}
|
||||
)
|
||||
add_dependencies(QJsonModel QJsonModel_include_files)
|
||||
|
||||
# Manually call moc on all header files
|
||||
file(GLOB Qt6JsonModel_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp)
|
||||
qt_wrap_cpp(Qt6JsonModel_MOC_SOURCES ${Qt6JsonModel_HEADER_FILES}
|
||||
TARGET QJsonModel
|
||||
)
|
||||
|
||||
# Append the MOC files to the source list
|
||||
target_sources(QJsonModel PRIVATE ${Qt6JsonModel_MOC_SOURCES})
|
||||
|
||||
add_library(QJsonModelStatic STATIC)
|
||||
add_library(Qt6::QJsonModelStatic ALIAS QJsonModelStatic )
|
||||
set_target_properties(QJsonModelStatic PROPERTIES OUTPUT_NAME "QJsonModel")
|
||||
|
||||
add_library(QJsonModelShared SHARED )
|
||||
add_library(Qt6::QJsonModelShared ALIAS QJsonModelShared )
|
||||
set_target_properties(QJsonModelShared PROPERTIES OUTPUT_NAME "QJsonModel")
|
||||
|
||||
target_link_libraries(QJsonModelStatic PRIVATE QJsonModel)
|
||||
target_link_libraries(QJsonModelShared PRIVATE QJsonModel)
|
||||
|
||||
target_link_libraries(QJsonModel
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
# vim: ts=2 sw=2 noet foldmethod=indent :
|
@ -22,7 +22,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qjsonmodel.h"
|
||||
#include "QJsonModel.hpp"
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QFont>
|
@ -1,23 +0,0 @@
|
||||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2015-01-22T08:20:52
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += core gui widgets
|
||||
CONFIG += c++11
|
||||
lessThan(QT_MAJOR_VERSION, 5): error("requires Qt 5")
|
||||
|
||||
TARGET = QJsonModel
|
||||
TEMPLATE = app
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
qjsonmodel.cpp
|
||||
|
||||
HEADERS += \
|
||||
qjsonmodel.h
|
||||
|
||||
|
||||
|
||||
|
70
main.cpp
70
main.cpp
@ -1,70 +0,0 @@
|
||||
/***********************************************
|
||||
Copyright (C) 2014 Schutz Sacha
|
||||
This file is part of QJsonModel (https://github.com/dridk/QJsonmodel).
|
||||
|
||||
QJsonModel is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QJsonModel 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 for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QJsonModel. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
**********************************************/
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QTreeView>
|
||||
#include <string>
|
||||
#include "qjsonmodel.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
QTreeView *view = new QTreeView;
|
||||
QJsonModel *model = new QJsonModel;
|
||||
|
||||
view->setModel(model);
|
||||
|
||||
std::string json = R"({
|
||||
"firstName": "John",
|
||||
"lastName": "Smith",
|
||||
"age": 25,
|
||||
"far-sighted": true,
|
||||
"address":
|
||||
{
|
||||
"streetAddress": "21 2nd Street",
|
||||
"city": "New York",
|
||||
"state": "NY",
|
||||
"postalCode": "10021"
|
||||
},
|
||||
"phoneNumber":
|
||||
[
|
||||
{
|
||||
"comment": "This is just a comment!",
|
||||
"type": "home",
|
||||
"number": "212 555-1234"
|
||||
},
|
||||
{
|
||||
"comment1": "Another comment!",
|
||||
"type": "fax",
|
||||
"number": "646 555-4567"
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
model->addException({"comment"});
|
||||
model->loadJson(QByteArray::fromStdString(json));
|
||||
view->show();
|
||||
|
||||
QByteArray mjson = model->json();
|
||||
qDebug() << mjson;
|
||||
|
||||
return a.exec();
|
||||
}
|
328
qjsonmodel.py
328
qjsonmodel.py
@ -1,328 +0,0 @@
|
||||
"""Python adaptation of https://github.com/dridk/QJsonModel
|
||||
|
||||
Supports Python 2 and 3 with PySide, PySide2, PyQt4 or PyQt5.
|
||||
Requires https://github.com/mottosso/Qt.py
|
||||
|
||||
Usage:
|
||||
Use it like you would the C++ version.
|
||||
|
||||
>>> import qjsonmodel
|
||||
>>> model = qjsonmodel.QJsonModel()
|
||||
>>> model.load({"key": "value"})
|
||||
|
||||
Test:
|
||||
Run the provided example to sanity check your Python,
|
||||
dependencies and Qt binding.
|
||||
|
||||
$ python qjsonmodel.py
|
||||
|
||||
Changes:
|
||||
This module differs from the C++ version in the following ways.
|
||||
|
||||
1. Setters and getters are replaced by Python properties
|
||||
2. Objects are sorted by default, disabled via load(sort=False)
|
||||
3. load() takes a Python dictionary as opposed to
|
||||
a string or file handle.
|
||||
|
||||
- To load from a string, use built-in `json.loads()`
|
||||
>>> import json
|
||||
>>> document = json.loads("{'key': 'value'}")
|
||||
>>> model.load(document)
|
||||
|
||||
- To load from a file, use `with open(fname)`
|
||||
>>> import json
|
||||
>>> with open("file.json") as f:
|
||||
... document = json.load(f)
|
||||
... model.load(document)
|
||||
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from Qt import QtWidgets, QtCore, __binding__
|
||||
|
||||
|
||||
class QJsonTreeItem(object):
|
||||
def __init__(self, parent=None):
|
||||
self._parent = parent
|
||||
|
||||
self._key = ""
|
||||
self._value = ""
|
||||
self._type = None
|
||||
self._children = list()
|
||||
|
||||
def appendChild(self, item):
|
||||
self._children.append(item)
|
||||
|
||||
def child(self, row):
|
||||
return self._children[row]
|
||||
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
def childCount(self):
|
||||
return len(self._children)
|
||||
|
||||
def row(self):
|
||||
return (
|
||||
self._parent._children.index(self)
|
||||
if self._parent else 0
|
||||
)
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self._key
|
||||
|
||||
@key.setter
|
||||
def key(self, key):
|
||||
self._key = key
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@type.setter
|
||||
def type(self, typ):
|
||||
self._type = typ
|
||||
|
||||
@classmethod
|
||||
def load(self, value, parent=None, sort=True):
|
||||
rootItem = QJsonTreeItem(parent)
|
||||
rootItem.key = "root"
|
||||
|
||||
if isinstance(value, dict):
|
||||
items = (
|
||||
sorted(value.items())
|
||||
if sort else value.items()
|
||||
)
|
||||
|
||||
for key, value in items:
|
||||
child = self.load(value, rootItem)
|
||||
child.key = key
|
||||
child.type = type(value)
|
||||
rootItem.appendChild(child)
|
||||
|
||||
elif isinstance(value, list):
|
||||
for index, value in enumerate(value):
|
||||
child = self.load(value, rootItem)
|
||||
child.key = index
|
||||
child.type = type(value)
|
||||
rootItem.appendChild(child)
|
||||
|
||||
else:
|
||||
rootItem.value = value
|
||||
rootItem.type = type(value)
|
||||
|
||||
return rootItem
|
||||
|
||||
|
||||
class QJsonModel(QtCore.QAbstractItemModel):
|
||||
def __init__(self, parent=None):
|
||||
super(QJsonModel, self).__init__(parent)
|
||||
|
||||
self._rootItem = QJsonTreeItem()
|
||||
self._headers = ("key", "value")
|
||||
|
||||
def clear(self):
|
||||
self.load({})
|
||||
|
||||
def load(self, document):
|
||||
"""Load from dictionary
|
||||
|
||||
Arguments:
|
||||
document (dict): JSON-compatible dictionary
|
||||
|
||||
"""
|
||||
|
||||
assert isinstance(document, (dict, list, tuple)), (
|
||||
"`document` must be of dict, list or tuple, "
|
||||
"not %s" % type(document)
|
||||
)
|
||||
|
||||
self.beginResetModel()
|
||||
|
||||
self._rootItem = QJsonTreeItem.load(document)
|
||||
self._rootItem.type = type(document)
|
||||
|
||||
self.endResetModel()
|
||||
|
||||
return True
|
||||
|
||||
def json(self, root=None):
|
||||
"""Serialise model as JSON-compliant dictionary
|
||||
|
||||
Arguments:
|
||||
root (QJsonTreeItem, optional): Serialise from here
|
||||
defaults to the the top-level item
|
||||
|
||||
Returns:
|
||||
model as dict
|
||||
|
||||
"""
|
||||
|
||||
root = root or self._rootItem
|
||||
return self.genJson(root)
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
item = index.internalPointer()
|
||||
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if index.column() == 0:
|
||||
return item.key
|
||||
|
||||
if index.column() == 1:
|
||||
return item.value
|
||||
|
||||
elif role == QtCore.Qt.EditRole:
|
||||
if index.column() == 1:
|
||||
return item.value
|
||||
|
||||
def setData(self, index, value, role):
|
||||
if role == QtCore.Qt.EditRole:
|
||||
if index.column() == 1:
|
||||
item = index.internalPointer()
|
||||
item.value = str(value)
|
||||
|
||||
if __binding__ in ("PySide", "PyQt4"):
|
||||
self.dataChanged.emit(index, index)
|
||||
else:
|
||||
self.dataChanged.emit(index, index, [QtCore.Qt.EditRole])
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role != QtCore.Qt.DisplayRole:
|
||||
return None
|
||||
|
||||
if orientation == QtCore.Qt.Horizontal:
|
||||
return self._headers[section]
|
||||
|
||||
def index(self, row, column, parent=QtCore.QModelIndex()):
|
||||
if not self.hasIndex(row, column, parent):
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
if not parent.isValid():
|
||||
parentItem = self._rootItem
|
||||
else:
|
||||
parentItem = parent.internalPointer()
|
||||
|
||||
childItem = parentItem.child(row)
|
||||
if childItem:
|
||||
return self.createIndex(row, column, childItem)
|
||||
else:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
def parent(self, index):
|
||||
if not index.isValid():
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
childItem = index.internalPointer()
|
||||
parentItem = childItem.parent()
|
||||
|
||||
if parentItem == self._rootItem:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
return self.createIndex(parentItem.row(), 0, parentItem)
|
||||
|
||||
def rowCount(self, parent=QtCore.QModelIndex()):
|
||||
if parent.column() > 0:
|
||||
return 0
|
||||
|
||||
if not parent.isValid():
|
||||
parentItem = self._rootItem
|
||||
else:
|
||||
parentItem = parent.internalPointer()
|
||||
|
||||
return parentItem.childCount()
|
||||
|
||||
def columnCount(self, parent=QtCore.QModelIndex()):
|
||||
return 2
|
||||
|
||||
def flags(self, index):
|
||||
flags = super(QJsonModel, self).flags(index)
|
||||
|
||||
if index.column() == 1:
|
||||
return QtCore.Qt.ItemIsEditable | flags
|
||||
else:
|
||||
return flags
|
||||
|
||||
def genJson(self, item):
|
||||
nchild = item.childCount()
|
||||
|
||||
if item.type is dict:
|
||||
document = {}
|
||||
for i in range(nchild):
|
||||
ch = item.child(i)
|
||||
document[ch.key] = self.genJson(ch)
|
||||
return document
|
||||
|
||||
elif item.type == list:
|
||||
document = []
|
||||
for i in range(nchild):
|
||||
ch = item.child(i)
|
||||
document.append(self.genJson(ch))
|
||||
return document
|
||||
|
||||
else:
|
||||
return item.value
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
view = QtWidgets.QTreeView()
|
||||
model = QJsonModel()
|
||||
|
||||
view.setModel(model)
|
||||
|
||||
document = json.loads("""\
|
||||
{
|
||||
"firstName": "John",
|
||||
"lastName": "Smith",
|
||||
"age": 25,
|
||||
"address": {
|
||||
"streetAddress": "21 2nd Street",
|
||||
"city": "New York",
|
||||
"state": "NY",
|
||||
"postalCode": "10021"
|
||||
},
|
||||
"phoneNumber": [
|
||||
{
|
||||
"type": "home",
|
||||
"number": "212 555-1234"
|
||||
},
|
||||
{
|
||||
"type": "fax",
|
||||
"number": "646 555-4567"
|
||||
}
|
||||
]
|
||||
}
|
||||
""")
|
||||
|
||||
model.load(document)
|
||||
model.clear()
|
||||
model.load(document)
|
||||
|
||||
# Sanity check
|
||||
assert (
|
||||
json.dumps(model.json(), sort_keys=True) ==
|
||||
json.dumps(document, sort_keys=True)
|
||||
)
|
||||
|
||||
view.show()
|
||||
view.resize(500, 300)
|
||||
app.exec_()
|
Loading…
Reference in New Issue
Block a user