Convert to CMake project; add object,static,shared targets.

Removed python support for now. Sorry, python users.
This commit is contained in:
S David 2024-05-02 19:16:36 -04:00
parent 65aa28dd7f
commit 95b76b3d74
9 changed files with 403 additions and 447 deletions

173
.gitignore vendored
View File

@ -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
View 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
View 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
View 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 :

View File

@ -22,7 +22,7 @@
* SOFTWARE.
*/
#include "qjsonmodel.h"
#include "QJsonModel.hpp"
#include <QDebug>
#include <QFile>
#include <QFont>

View File

@ -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

View File

@ -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();
}

View File

@ -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_()