diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cb34bc5..90b48c26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,9 +81,12 @@ This simplifies running Mayo from eg Visual Studio IDE\ " ${Mayo_PostBuildCopyRuntimeDLLs_DefaultValue} ) - endif() +# TODO Make option Mayo_BuildTests dependent of Mayo_BuildApp) +option(Mayo_BuildApp "Build Mayo GUI application" ON) +option(Mayo_BuildConvCli "Build Mayo CLI converter" ON) + # TODO # option(Mayo_BuildPluginGmio "Build plugin to import/export mesh files supported by gmio" OFF) @@ -93,21 +96,28 @@ endif() find_package(QT NAMES Qt6 Qt5 REQUIRED) if(QT_FOUND) - set("Qt${QT_VERSION_MAJOR}_DIR" "${QT_DIR}") - find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets Test) - if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 REQUIRED COMPONENTS WinExtras) - elseif(QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 REQUIRED COMPONENTS OpenGLWidgets) + message(STATUS "Qt version ${QT_VERSION}") + if(QT_VERSION VERSION_LESS 5.14) + message(FATAL_ERROR "Qt >= 5.14 is required but detected version is ${QT_VERSION}") endif() - if(Mayo_PostBuildCopyRuntimeDLLs) - find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Svg) + set("Qt${QT_VERSION_MAJOR}_DIR" "${QT_DIR}") + + if(Mayo_BuildApp OR Mayo_BuildConvCli) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) endif() - message(STATUS "Qt version ${QT_VERSION}") - if(QT_VERSION VERSION_LESS 5.14) - message(FATAL_ERROR "Qt >= 5.14 is required but detected version is ${QT_VERSION}") + if(Mayo_BuildApp) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widgets Test) + if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 REQUIRED COMPONENTS WinExtras) + elseif(QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 REQUIRED COMPONENTS OpenGLWidgets) + endif() + + if(Mayo_PostBuildCopyRuntimeDLLs) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Svg) + endif() endif() endif() @@ -168,30 +178,14 @@ endif() # Linker config ########## -set(Mayo_LinkLibraries) -set(Mayo_LinkDirectories) - -list( - APPEND Mayo_LinkLibraries - Qt${QT_VERSION_MAJOR}::Core - Qt${QT_VERSION_MAJOR}::Gui - Qt${QT_VERSION_MAJOR}::Widgets -) - -if(Mayo_PostBuildCopyRuntimeDLLs) - list(APPEND Mayo_LinkLibraries Qt${QT_VERSION_MAJOR}::Svg) -endif() - -if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) - list(APPEND Mayo_LinkLibraries Qt5::WinExtras) -elseif(QT_VERSION_MAJOR EQUAL 6) - list(APPEND Mayo_LinkLibraries Qt6::OpenGLWidgets) -endif() +set(MayoCore_LinkDirectories) +set(MayoCore_LinkLibraries) +set(MayoIO_LinkLibraries) if(MSVC) - list(APPEND Mayo_LinkLibraries Opengl32 User32) + list(APPEND MayoCore_LinkLibraries Opengl32 User32) elseif(APPLE) - list(APPEND Mayo_LinkLibraries iconv) + list(APPEND MayoCore_LinkLibraries iconv) endif() ########## @@ -200,67 +194,49 @@ endif() set( Mayo_IncludeDirectories - src/app src/3rdparty ${CMAKE_BINARY_DIR} ) ########## -# Source files +# MayoCore+MayoIO source files ########## file( - GLOB Mayo_SourceFiles - ${PROJECT_SOURCE_DIR}/src/app/*.cpp + GLOB MayoCore_SourceFiles ${PROJECT_SOURCE_DIR}/src/base/*.cpp ${PROJECT_SOURCE_DIR}/src/graphics/*.cpp ${PROJECT_SOURCE_DIR}/src/gui/*.cpp + ${PROJECT_SOURCE_DIR}/src/3rdparty/fmt/src/format.cc +) + +file( + GLOB MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_dxf/*.cpp ${PROJECT_SOURCE_DIR}/src/io_image/*.cpp ${PROJECT_SOURCE_DIR}/src/io_occ/*.cpp ${PROJECT_SOURCE_DIR}/src/io_off/*.cpp ${PROJECT_SOURCE_DIR}/src/io_ply/*.cpp - ${PROJECT_SOURCE_DIR}/src/measure/*.cpp - ${PROJECT_SOURCE_DIR}/src/3rdparty/fmt/src/format.cc ) -if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) - list(APPEND Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.cpp) -endif() - -if(MSVC) - set(Mayo_RcIconsWin ${PROJECT_SOURCE_DIR}/images/appicon.rc) -endif() - ########## -# Header files +# MayoCore+MayoIO header files ########## file( - GLOB Mayo_HeaderFiles - ${PROJECT_SOURCE_DIR}/src/app/*.h + GLOB MayoCore_HeaderFiles ${PROJECT_SOURCE_DIR}/src/base/*.h ${PROJECT_SOURCE_DIR}/src/graphics/*.h ${PROJECT_SOURCE_DIR}/src/gui/*.h +) + +file( + GLOB MayoIO_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_dxf/*.h ${PROJECT_SOURCE_DIR}/src/io_image/*.h ${PROJECT_SOURCE_DIR}/src/io_occ/*.h ${PROJECT_SOURCE_DIR}/src/io_off/*.h ${PROJECT_SOURCE_DIR}/src/io_ply/*.h - ${PROJECT_SOURCE_DIR}/src/io_measure/*.h -) - -if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) - list(APPEND Mayo_HeaderFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.h) -endif() - -########## -# UI files -########## - -file( - GLOB Mayo_QtUiFiles - ${PROJECT_SOURCE_DIR}/src/app/*.ui ) ########## @@ -270,11 +246,9 @@ file( if(Mayo_BuildTests) file(GLOB MayoTests_HeaderFiles ${PROJECT_SOURCE_DIR}/tests/*.h) file(GLOB MayoTests_SourceFiles ${PROJECT_SOURCE_DIR}/tests/*.cpp) - list(APPEND Mayo_HeaderFiles ${MayoTests_HeaderFiles}) - list(APPEND Mayo_SourceFiles ${MayoTests_SourceFiles}) set(MAYO_WITH_TESTS 1) - list(APPEND Mayo_LinkLibraries Qt${QT_VERSION_MAJOR}::Test) + list(APPEND MayoTests_LinkLibraries Qt${QT_VERSION_MAJOR}::Test) file(GLOB MayoTests_InputFiles ${PROJECT_SOURCE_DIR}/tests/inputs/*.*) file(COPY ${MayoTests_InputFiles} DESTINATION ${CMAKE_BINARY_DIR}/tests/inputs) @@ -295,14 +269,14 @@ if(WIN32) # Add OpenCASCADE library directories # Note: QtCreator option "Run->Add build library search to PATH" will add to PATH env variable - # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the + # the contents of MayoCore_LinkDirectories variable. For convenience, let's add also the # directories containing DLLs list( - APPEND Mayo_LinkDirectories + APPEND MayoCore_LinkDirectories ${OpenCASCADE_BINARY_DIR} ${OpenCASCADE_LIBRARY_DIR} ${OpenCASCADE_3RDPARTY_BINARY_DIRS} - ) + ) endif() else() find_package(OpenCASCADE REQUIRED) @@ -334,7 +308,7 @@ if(OpenCASCADE_FOUND) # Add OpenCASCADE libraries list( - APPEND Mayo_LinkLibraries + APPEND MayoCore_LinkLibraries # FoundationClasses TKernel TKMath # ModelingData @@ -350,30 +324,30 @@ if(OpenCASCADE_FOUND) ) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) - list(APPEND Mayo_LinkLibraries TKDE) - list(APPEND Mayo_LinkLibraries TKDEIGES) - list(APPEND Mayo_LinkLibraries TKDESTEP) - list(APPEND Mayo_LinkLibraries TKDESTL) - list(APPEND Mayo_LinkLibraries TKDEVRML) + list(APPEND MayoIO_LinkLibraries TKDE) + list(APPEND MayoIO_LinkLibraries TKDEIGES) + list(APPEND MayoIO_LinkLibraries TKDESTEP) + list(APPEND MayoIO_LinkLibraries TKDESTL) + list(APPEND MayoIO_LinkLibraries TKDEVRML) else() - list(APPEND Mayo_LinkLibraries TKIGES TKXDEIGES) - list(APPEND Mayo_LinkLibraries TKSTEP TKSTEP209 TKSTEPAttr TKSTEPBase TKXDESTEP) - list(APPEND Mayo_LinkLibraries TKSTL) - list(APPEND Mayo_LinkLibraries TKVRML) + list(APPEND MayoIO_LinkLibraries TKIGES TKXDEIGES) + list(APPEND MayoIO_LinkLibraries TKSTEP TKSTEP209 TKSTEPAttr TKSTEPBase TKXDESTEP) + list(APPEND MayoIO_LinkLibraries TKSTL) + list(APPEND MayoIO_LinkLibraries TKVRML) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.7.0) - list(APPEND Mayo_LinkLibraries TKXDE) + list(APPEND MayoIO_LinkLibraries TKXDE) endif() endif() # OBJ/glTF support if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.4.0) - list(APPEND Mayo_LinkLibraries TKRWMesh) + list(APPEND MayoIO_LinkLibraries TKRWMesh) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) - list(APPEND Mayo_LinkLibraries TKDEOBJ TKDEGLTF) + list(APPEND MayoIO_LinkLibraries TKDEOBJ TKDEGLTF) endif() else() list( - REMOVE_ITEM Mayo_SourceFiles + REMOVE_ITEM MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_base_mesh.cpp ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_reader.cpp ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_reader.cpp @@ -383,18 +357,18 @@ if(OpenCASCADE_FOUND) endif() if(OpenCASCADE_VERSION VERSION_LESS 7.5.0) - list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_writer.cpp) + list(REMOVE_ITEM MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_writer.cpp) message(STATUS "glTF writer disabled because OpenCascade < v7.5") endif() if(OpenCASCADE_VERSION VERSION_LESS 7.6.0) - list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_writer.cpp) + list(REMOVE_ITEM MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_writer.cpp) message(STATUS "OBJ writer disabled because OpenCascade < v7.6") endif() # VRML support if(OpenCASCADE_VERSION VERSION_LESS 7.7.0) - list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_vrml_reader.cpp) + list(REMOVE_ITEM MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_vrml_reader.cpp) message(STATUS "VRML reader disabled because OpenCascade < v7.7") endif() endif() @@ -420,10 +394,10 @@ if(assimp_FOUND) file(GLOB MayoPluginAssimp_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_assimp/*.h) file(GLOB MayoPluginAssimp_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_assimp/*.cpp) - list(APPEND Mayo_HeaderFiles ${MayoPluginAssimp_HeaderFiles}) - list(APPEND Mayo_SourceFiles ${MayoPluginAssimp_SourceFiles}) + list(APPEND MayoIO_HeaderFiles ${MayoPluginAssimp_HeaderFiles}) + list(APPEND MayoIO_SourceFiles ${MayoPluginAssimp_SourceFiles}) - list(APPEND Mayo_LinkLibraries ${ASSIMP_LIBRARIES}) + list(APPEND MayoIO_LinkLibraries ${ASSIMP_LIBRARIES}) list(GET ASSIMP_INCLUDE_DIRS 0 assimp_IncludeDir) file(READ "${assimp_IncludeDir}/assimp/version.h" assimp_FileVersionH) @@ -445,11 +419,11 @@ if(gmio_FOUND) file(GLOB MayoPluginGmio_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_gmio/*.h) file(GLOB MayoPluginGmio_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_gmio/*.cpp) - list(APPEND Mayo_HeaderFiles ${MayoPluginGmio_HeaderFiles}) - list(APPEND Mayo_SourceFiles ${MayoPluginGmio_SourceFiles}) + list(APPEND MayoIO_HeaderFiles ${MayoPluginGmio_HeaderFiles}) + list(APPEND MayoIO_SourceFiles ${MayoPluginGmio_SourceFiles}) # Needs -L$$GMIO_ROOT/lib -lgmio_static -lzlibstatic - list(APPEND Mayo_LinkLibraries ${GMIO_LIBRARIES}) + list(APPEND MayoIO_LinkLibraries ${GMIO_LIBRARIES}) endif() ########## @@ -460,43 +434,233 @@ configure_file(${PROJECT_SOURCE_DIR}/src/mayo_config.h.cmake common/mayo_config configure_file(${PROJECT_SOURCE_DIR}/src/mayo_version.h.cmake common/mayo_version.h @ONLY) ########## -# Targets +# Icons for Mayo exe targets ########## if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(Mayo_AppIconMacOS images/appicon.icns) - set_source_files_properties(images/appicon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + set_source_files_properties(images/appicon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") endif() -add_executable( - mayo - ${Mayo_SourceFiles} - ${Mayo_HeaderFiles} - ${Mayo_QtUiFiles} - mayo.qrc i18n/qt${QT_VERSION_MAJOR}base.qrc - ${Mayo_RcIconsWin} - ${Mayo_AppIconMacOS} -) +if(MSVC) + set(Mayo_RcIconsWin ${PROJECT_SOURCE_DIR}/images/appicon.rc) +endif() + +########## +# Target: MayoCore(static lib) +########## + +add_library(MayoCore STATIC ${MayoCore_HeaderFiles} ${MayoCore_SourceFiles}) + +target_include_directories(MayoCore PRIVATE ${Mayo_IncludeDirectories}) +target_compile_definitions(MayoCore PRIVATE ${Mayo_CompileDefinitions}) +target_compile_options(MayoCore PRIVATE ${Mayo_CompileOptions}) +target_link_libraries(MayoCore PRIVATE ${MayoCore_LinkLibraries}) + +add_library(MayoCoreLib INTERFACE) +target_link_libraries(MayoCoreLib INTERFACE MayoCore ${MayoCore_LinkLibraries}) +target_link_directories(MayoCoreLib INTERFACE ${MayoCore_LinkDirectories}) +target_include_directories(MayoCoreLib INTERFACE ${Mayo_IncludeDirectories}) + +########## +# Target: MayoIO(static lib) +########## + +add_library(MayoIO STATIC ${MayoIO_HeaderFiles} ${MayoIO_SourceFiles}) + +target_include_directories(MayoIO PRIVATE ${Mayo_IncludeDirectories}) +target_compile_definitions(MayoIO PRIVATE ${Mayo_CompileDefinitions}) +target_compile_options(MayoIO PRIVATE ${Mayo_CompileOptions}) +target_link_libraries(MayoIO PRIVATE ${MayoIO_LinkLibraries}) + +add_library(MayoIOLib INTERFACE) +target_link_libraries(MayoIOLib INTERFACE MayoIO ${MayoIO_LinkLibraries}) +target_include_directories(MayoIOLib INTERFACE ${Mayo_IncludeDirectories}) + +########## +# Target: MayoApp +########## + +set(MayoApp_HeaderFiles) +set(MayoApp_SourceFiles) +set(MayoApp_LinkLibraries) -target_include_directories( - mayo PRIVATE ${Mayo_IncludeDirectories} +# MayoApp headers +file( + GLOB MayoApp_HeaderFiles + ${PROJECT_SOURCE_DIR}/src/app/*.h + ${PROJECT_SOURCE_DIR}/src/measure/*.h + ${PROJECT_SOURCE_DIR}/src/qtbackend/*.h + ${PROJECT_SOURCE_DIR}/src/qtcommon/*.h ) -target_compile_definitions( - mayo PRIVATE ${Mayo_CompileDefinitions} +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + list(APPEND MayoApp_HeaderFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.h) +endif() + +# MayoApp sources +file( + GLOB MayoApp_SourceFiles + ${PROJECT_SOURCE_DIR}/src/app/*.cpp + ${PROJECT_SOURCE_DIR}/src/measure/*.cpp + ${PROJECT_SOURCE_DIR}/src/qtbackend/*.cpp + ${PROJECT_SOURCE_DIR}/src/qtcommon/*.cpp ) -target_compile_options( - mayo PRIVATE ${Mayo_CompileOptions} +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + list(APPEND MayoApp_SourceFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.cpp) +endif() + +# MayoApp UI files +file(GLOB MayoApp_QtUiFiles ${PROJECT_SOURCE_DIR}/src/app/*.ui) + +# MayoApp unit tests +if(Mayo_BuildTests) + list(APPEND MayoApp_HeaderFiles ${MayoTests_HeaderFiles}) + list(APPEND MayoApp_SourceFiles ${MayoTests_SourceFiles}) + list(APPEND MayoApp_LinkLibraries ${MayoTests_LinkLibraries}) +endif() + +# MayoApp libs +list( + APPEND MayoApp_LinkLibraries + MayoCoreLib + MayoIOLib + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets ) -target_link_directories( - mayo PRIVATE ${Mayo_LinkDirectories} +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + list(APPEND MayoApp_LinkLibraries Qt5::WinExtras) +elseif(QT_VERSION_MAJOR EQUAL 6) + list(APPEND MayoApp_LinkLibraries Qt6::OpenGLWidgets) +endif() + +if(Mayo_PostBuildCopyRuntimeDLLs) + list(APPEND MayoApp_LinkLibraries Qt${QT_VERSION_MAJOR}::Svg) +endif() + +if(Mayo_BuildApp) + add_executable( + mayo + ${MayoApp_SourceFiles} + ${MayoApp_HeaderFiles} + ${MayoApp_QtUiFiles} + mayo.qrc i18n/qt${QT_VERSION_MAJOR}base.qrc + ${Mayo_RcIconsWin} + ${Mayo_AppIconMacOS} + ) + + target_compile_definitions(mayo PRIVATE ${Mayo_CompileDefinitions}) + target_compile_options(mayo PRIVATE ${Mayo_CompileOptions}) + target_link_libraries(mayo PRIVATE ${MayoApp_LinkLibraries}) + + set_target_properties( + mayo + PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "mayo" + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE_COPYRIGHT ${Mayo_Copyright} + MACOSX_BUNDLE_ICON_FILE appicon.icns + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE + ) + + if(Mayo_PostBuildCopyRuntimeDLLs) + # Copy required Qt plugins + set(QtPluginsDir "${QT_DIR}/../../../plugins") + file(GLOB QtPluginIconEnginesDLLs "${QtPluginsDir}/iconengines/qsvgicon*.dll") + file(GLOB QtPluginImageFormatsDLLs "${QtPluginsDir}/imageformats/qsvg*.dll") + file(GLOB QtPluginPlatformsDLLs "${QtPluginsDir}/platforms/qwindows*.dll") + set(QtPluginsDLLs ${QtPluginIconEnginesDLLs} ${QtPluginImageFormatsDLLs} ${QtPluginPlatformsDLLs}) + foreach(QtPluginDLL ${QtPluginsDLLs}) + cmake_path(GET QtPluginDLL PARENT_PATH QtPluginDLL_Path) + cmake_path(GET QtPluginDLL_Path FILENAME QtPluginDLL_PathName) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "$/plugins/${QtPluginDLL_PathName}" + ) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${QtPluginDLL}" "$/plugins/${QtPluginDLL_PathName}" + ) + endforeach() + + # Copy OpenCascade 3rd-party DLLs + foreach(Occ3rdDLL ${OpenCASCADE_3RDPARTY_DLLS}) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${Occ3rdDLL}" $ + ) + endforeach() + + # Copy runtime DLLs specified with library IMPORTED_LOCATION property + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ $ + COMMAND_EXPAND_LISTS + ) + endif() # Mayo_PostBuildCopyRuntimeDLLs +endif() # Mayo_BuildApp + +########## +# Target: mayo-conv +########## + +file( + GLOB MayoConv_HeaderFiles + ${PROJECT_SOURCE_DIR}/src/cli/*.h + ${PROJECT_SOURCE_DIR}/src/qtbackend/*.h + ${PROJECT_SOURCE_DIR}/src/qtcommon/*.h +) +list( + APPEND MayoConv_HeaderFiles + ${PROJECT_SOURCE_DIR}/src/app/app_module_properties.h + ${PROJECT_SOURCE_DIR}/src/app/app_module.h + ${PROJECT_SOURCE_DIR}/src/app/recent_files.h ) -target_link_libraries( - mayo PRIVATE ${Mayo_LinkLibraries} +file( + GLOB MayoConv_SourceFiles + ${PROJECT_SOURCE_DIR}/src/cli/*.cpp + ${PROJECT_SOURCE_DIR}/src/qtbackend/*.cpp + ${PROJECT_SOURCE_DIR}/src/qtcommon/*.cpp ) +list( + APPEND MayoConv_SourceFiles + ${PROJECT_SOURCE_DIR}/src/app/app_module_properties.cpp + ${PROJECT_SOURCE_DIR}/src/app/app_module.cpp + ${PROJECT_SOURCE_DIR}/src/app/recent_files.cpp +) + +if(Mayo_BuildConvCli) + add_executable( + mayo-conv + ${MayoConv_HeaderFiles} + ${MayoConv_SourceFiles} + i18n/qt${QT_VERSION_MAJOR}base.qrc + ${Mayo_RcIconsWin} + ${Mayo_AppIconMacOS} + ) + + target_compile_definitions(mayo-conv PRIVATE ${Mayo_CompileDefinitions}) + target_compile_options(mayo-conv PRIVATE ${Mayo_CompileOptions}) + + target_link_libraries( + mayo-conv PRIVATE + MayoCoreLib + MayoIOLib + Qt${QT_VERSION_MAJOR}::Core + ) +endif() # Mayo_BuildConvCli + +########## +# Target: OtherFiles +########## add_custom_target( OtherFiles SOURCES @@ -511,53 +675,3 @@ add_custom_target( src/mayo_version.h.cmake README.md ) - -set_target_properties( - mayo - PROPERTIES - MACOSX_BUNDLE_BUNDLE_NAME "mayo" - MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - MACOSX_BUNDLE_COPYRIGHT ${Mayo_Copyright} - MACOSX_BUNDLE_ICON_FILE appicon.icns - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -if(Mayo_PostBuildCopyRuntimeDLLs) - # Copy required Qt plugins - set(QtPluginsDir "${QT_DIR}/../../../plugins") - file(GLOB QtPluginIconEnginesDLLs "${QtPluginsDir}/iconengines/qsvgicon*.dll") - file(GLOB QtPluginImageFormatsDLLs "${QtPluginsDir}/imageformats/qsvg*.dll") - file(GLOB QtPluginPlatformsDLLs "${QtPluginsDir}/platforms/qwindows*.dll") - set(QtPluginsDLLs ${QtPluginIconEnginesDLLs} ${QtPluginImageFormatsDLLs} ${QtPluginPlatformsDLLs}) - foreach(QtPluginDLL ${QtPluginsDLLs}) - cmake_path(GET QtPluginDLL PARENT_PATH QtPluginDLL_Path) - cmake_path(GET QtPluginDLL_Path FILENAME QtPluginDLL_PathName) - add_custom_command( - TARGET mayo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "$/plugins/${QtPluginDLL_PathName}" - ) - add_custom_command( - TARGET mayo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${QtPluginDLL}" "$/plugins/${QtPluginDLL_PathName}" - ) - endforeach() - - # Copy OpenCascade 3rd-party DLLs - foreach(Occ3rdDLL ${OpenCASCADE_3RDPARTY_DLLS}) - add_custom_command( - TARGET mayo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${Occ3rdDLL}" $ - ) - endforeach() - - # Copy runtime DLLs specified with library IMPORTED_LOCATION property - add_custom_command( - TARGET mayo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ $ - COMMAND_EXPAND_LISTS - ) -endif() diff --git a/cmake/OpenCascadeWin.cmake b/cmake/OpenCascadeWin.cmake index 138783c7..3dc52174 100644 --- a/cmake/OpenCascadeWin.cmake +++ b/cmake/OpenCascadeWin.cmake @@ -120,17 +120,6 @@ if(OpenCASCADE_FOUND) ) endforeach() - # Add OpenCASCADE library directories - # Note: QtCreator option "Run->Add build library search to PATH" will add to PATH env variable - # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the - # directories containing DLLs - list( - APPEND Mayo_LinkDirectories - ${OpenCASCADE_BINARY_DIR} - ${OpenCASCADE_LIBRARY_DIR} - ${OpenCASCADE_3RDPARTY_BINARY_DIRS} - ) - # List all 3rd-party DLLs required by OpenCASCADE set(OpenCASCADE_3RDPARTY_DLLS) foreach(OccBINDIR ${OpenCASCADE_3RDPARTY_BINARY_DIRS}) diff --git a/src/app/app_module.cpp b/src/app/app_module.cpp index efa02e97..60bc3f71 100644 --- a/src/app/app_module.cpp +++ b/src/app/app_module.cpp @@ -15,21 +15,72 @@ #include "../base/settings.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" -#include "qtcore_utils.h" -#include "filepath_conv.h" -#include "qstring_conv.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" +#include "../qtcommon/qtcore_utils.h" #include +#include #include #include -#include #include #include namespace Mayo { +namespace { + +void readRecentFile(QDataStream& stream, RecentFile* recentFile) +{ + QString strFilepath; + stream >> strFilepath; + if (stream.status() != QDataStream::Ok) + return; + + recentFile->filepath = filepathFrom(strFilepath); + stream >> recentFile->thumbnail.imageData; + if (stream.status() != QDataStream::Ok) + return; + + recentFile->thumbnail.imageCacheKey = -1; + // Read thumbnail timestamp + // Warning: qint64 and int64_t may not be the exact same type(eg __int64 and longlong with Windows/MSVC) + qint64 timestamp; + stream >> timestamp; + if (stream.status() != QDataStream::Ok) + return; + + recentFile->thumbnailTimestamp = timestamp; +} + +QuantityLength shapeChordalDeflection(const TopoDS_Shape& shape) +{ + // Excerpted from Prs3d::GetDeflection(...) + constexpr QuantityLength baseDeviation = 1 * Quantity_Millimeter; + + Bnd_Box bndBox; + constexpr bool useTriangulation = true; + BRepBndLib::Add(shape, bndBox, !useTriangulation); + if (bndBox.IsVoid()) + return baseDeviation; + + if (BndUtils::isOpen(bndBox)) { + if (!BndUtils::hasFinitePart(bndBox)) + return baseDeviation; + + bndBox = BndUtils::finitePart(bndBox); + } + + const auto coords = BndBoxCoords::get(bndBox); + const gp_XYZ diag = coords.maxVertex().XYZ() - coords.minVertex().XYZ(); + const double diagMaxComp = std::max({ diag.X(), diag.Y(), diag.Z() }); + return 4 * diagMaxComp * baseDeviation; +} + +} // namespace + AppModule::AppModule() : m_settings(new Settings), m_props(m_settings), @@ -111,7 +162,7 @@ Settings::Variant AppModule::toVariant(const Property& prop) const const auto& filesProp = constRef(prop); QByteArray blob; QDataStream stream(&blob, QIODevice::WriteOnly); - stream << filesProp.value(); + AppModule::writeRecentFiles(stream, filesProp.value()); Variant varBlob(blob.toStdString()); varBlob.setByteArray(true); return varBlob; @@ -124,13 +175,10 @@ Settings::Variant AppModule::toVariant(const Property& prop) const bool AppModule::fromVariant(Property* prop, const Settings::Variant& variant) const { if (isType(prop)) { - if (qobject_cast(QCoreApplication::instance()) == nullptr) - return true; - const QByteArray blob = QtCoreUtils::QByteArray_frowRawData(variant.toConstRefString()); QDataStream stream(blob); RecentFiles recentFiles; - stream >> recentFiles; + AppModule::readRecentFiles(stream, &recentFiles); ptr(prop)->setValue(recentFiles); return stream.status() == QDataStream::Ok; } @@ -201,12 +249,13 @@ void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) const RecentFile* recentFile = this->findRecentFile(guiDoc->document()->filePath()); if (!recentFile) { - qDebug() << fmt::format("RecentFile object is null\n" - " Function: {}\n Document: {}\n RecentFilesCount: {}", - Q_FUNC_INFO, - guiDoc->document()->filePath().u8string(), - m_props.recentFiles.value().size()) - .c_str(); + qDebug() << fmt::format( + "RecentFile object is null\n" + " Function: {}\n Document: {}\n RecentFilesCount: {}", + Q_FUNC_INFO, + guiDoc->document()->filePath().u8string(), + m_props.recentFiles.value().size() + ).c_str(); return; } @@ -214,7 +263,7 @@ void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) return; RecentFile newRecentFile = *recentFile; - const bool okRecord = newRecentFile.recordThumbnail(guiDoc, this->recentFileThumbnailSize()); + const bool okRecord = this->impl_recordRecentFileThumbnail(&newRecentFile, guiDoc); if (!okRecord) return; @@ -238,7 +287,7 @@ void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) continue; // Skip RecentFile newRecentFile = *recentFile; - if (newRecentFile.recordThumbnail(guiDoc, this->recentFileThumbnailSize())) { + if (this->impl_recordRecentFileThumbnail(&newRecentFile, guiDoc)) { auto indexRecentFile = std::distance(&listRecentFile.front(), recentFile); newListRecentFile.at(indexRecentFile) = newRecentFile; } @@ -247,28 +296,51 @@ void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) m_props.recentFiles.setValue(newListRecentFile); } -static QuantityLength shapeChordalDeflection(const TopoDS_Shape& shape) +void AppModule::setRecentFileThumbnailRecorder(std::function fn) { - // Excerpted from Prs3d::GetDeflection(...) - constexpr QuantityLength baseDeviation = 1 * Quantity_Millimeter; + m_fnRecentFileThumbnailRecorder = std::move(fn); +} - Bnd_Box bndBox; - constexpr bool useTriangulation = true; - BRepBndLib::Add(shape, bndBox, !useTriangulation); - if (bndBox.IsVoid()) - return baseDeviation; - if (BndUtils::isOpen(bndBox)) { - if (!BndUtils::hasFinitePart(bndBox)) - return baseDeviation; +void AppModule::readRecentFiles(QDataStream& stream, RecentFiles* recentFiles) +{ + auto fnCheckStreamStatus = [](QDataStream::Status status) { + if (status != QDataStream::Ok) { + qDebug() << fmt::format( + "QDataStream error\n Function: {}\n Status: {}", + Q_FUNC_INFO, MetaEnum::name(status) + ).c_str(); + return false; + } - bndBox = BndUtils::finitePart(bndBox); + return true; + }; + + uint32_t count = 0; + stream >> count; + if (!fnCheckStreamStatus(stream.status())) + return; // Stream extraction error, abort + + recentFiles->clear(); + for (uint32_t i = 0; i < count; ++i) { + RecentFile recent; + readRecentFile(stream, &recent); + if (!fnCheckStreamStatus(stream.status())) + return; // Stream extraction error, abort + + if (!recent.filepath.empty() && recent.thumbnailTimestamp != 0) + recentFiles->push_back(std::move(recent)); } +} - const auto coords = BndBoxCoords::get(bndBox); - const gp_XYZ diag = coords.maxVertex().XYZ() - coords.minVertex().XYZ(); - const double diagMaxComp = std::max({ diag.X(), diag.Y(), diag.Z() }); - return 4 * diagMaxComp * baseDeviation; +void AppModule::writeRecentFiles(QDataStream& stream, const RecentFiles& recentFiles) +{ + stream << uint32_t(recentFiles.size()); + for (const RecentFile& rf : recentFiles) { + stream << filepathTo(rf.filepath); + stream << rf.thumbnail.imageData; + stream << qint64(rf.thumbnailTimestamp); + } } OccBRepMeshParameters AppModule::brepMeshParameters(const TopoDS_Shape& shape) const @@ -347,4 +419,34 @@ AppModule::~AppModule() m_settings = nullptr; } +bool AppModule::impl_recordRecentFileThumbnail(RecentFile* recentFile, GuiDocument* guiDoc) +{ + if (!recentFile) + return false; + + if (!guiDoc) + return false; + + if (!m_fnRecentFileThumbnailRecorder) + return false; + + if (!filepathEquivalent(recentFile->filepath, guiDoc->document()->filePath())) { + qDebug() << fmt::format( + "Filepath mismatch with GUI document\n" + " Function: {}\n Filepath: {}\n Document: {}", + Q_FUNC_INFO, + recentFile->filepath.u8string(), + guiDoc->document()->filePath().u8string() + ).c_str(); + return false; + } + + if (recentFile->thumbnailTimestamp == RecentFile::timestampLastModified(recentFile->filepath)) + return true; + + recentFile->thumbnail = m_fnRecentFileThumbnailRecorder(guiDoc, this->recentFileThumbnailSize()); + recentFile->thumbnailTimestamp = RecentFile::timestampLastModified(recentFile->filepath); + return true; +} + } // namespace Mayo diff --git a/src/app/app_module.h b/src/app/app_module.h index 42e02b67..c9572de8 100644 --- a/src/app/app_module.h +++ b/src/app/app_module.h @@ -18,9 +18,12 @@ #include "../base/settings.h" #include "../base/unit_system.h" +#include + #include #include +class QDataStream; class TDF_Label; class TopoDS_Shape; @@ -83,6 +86,9 @@ class AppModule : void recordRecentFileThumbnail(GuiDocument* guiDoc); void recordRecentFileThumbnails(GuiApplication* guiApp); QSize recentFileThumbnailSize() const { return { 190, 150 }; } + void setRecentFileThumbnailRecorder(std::function fn); + static void readRecentFiles(QDataStream& stream, RecentFiles* recentFiles); + static void writeRecentFiles(QDataStream& stream, const RecentFiles& recentFiles); // Meshing of BRep shapes OccBRepMeshParameters brepMeshParameters(const TopoDS_Shape& shape) const; @@ -113,6 +119,8 @@ class AppModule : AppModule(const AppModule&) = delete; // Not copyable AppModule& operator=(const AppModule&) = delete; // Not copyable + bool impl_recordRecentFileThumbnail(RecentFile* recentFile, GuiDocument* guiDoc); + Settings* m_settings = nullptr; IO::System m_ioSystem; AppModuleProperties m_props; @@ -121,6 +129,7 @@ class AppModule : std::locale m_stdLocale; QLocale m_qtLocale; std::vector> m_vecDocTreeNodePropsProvider; + std::function m_fnRecentFileThumbnailRecorder; }; } // namespace Mayo diff --git a/src/app/app_module_properties.cpp b/src/app/app_module_properties.cpp index e513e7ed..1374fc66 100644 --- a/src/app/app_module_properties.cpp +++ b/src/app/app_module_properties.cpp @@ -91,7 +91,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings) #endif }); settings->addResetFunction(groupId_graphics, [=]{ - this->navigationStyle.setValue(WidgetOccViewController::NavigationStyle::Mayo); + this->navigationStyle.setValue(View3dNavigationStyle::Mayo); this->defaultShowOriginTrihedron.setValue(true); this->instantZoomFactor.setValue(5.); this->turnViewAngleIncrement.setQuantity(5 * Quantity_Degree); diff --git a/src/app/app_module_properties.h b/src/app/app_module_properties.h index d8874672..8440c7f7 100644 --- a/src/app/app_module_properties.h +++ b/src/app/app_module_properties.h @@ -15,7 +15,7 @@ #include "../base/property_enumeration.h" #include "../base/settings.h" #include "../base/unit_system.h" -#include "widget_occ_view_controller.h" +#include "view3d_navigation_style.h" #include #include @@ -60,7 +60,7 @@ class AppModuleProperties : public PropertyGroup { PropertyAngle meshingAngularDeflection{ this, textId("meshingAngularDeflection") }; PropertyBool meshingRelative{ this, textId("meshingRelative") }; // Graphics - PropertyEnum navigationStyle{ this, textId("navigationStyle") }; + PropertyEnum navigationStyle{ this, textId("navigationStyle") }; PropertyBool defaultShowOriginTrihedron{ this, textId("defaultShowOriginTrihedron") }; PropertyDouble instantZoomFactor{ this, textId("instantZoomFactor") }; PropertyAngle turnViewAngleIncrement{ this, textId("turnViewAngleIncrement") }; diff --git a/src/app/command_system_information.cpp b/src/app/command_system_information.cpp index 05017e43..03525e97 100644 --- a/src/app/command_system_information.cpp +++ b/src/app/command_system_information.cpp @@ -8,11 +8,11 @@ #include "app_module.h" #include "command_system_information_occopengl.h" -#include "qstring_conv.h" #include "qtwidgets_utils.h" #include "../base/meta_enum.h" #include "../base/filepath.h" #include "../base/io_system.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/commands_display.cpp b/src/app/commands_display.cpp index 9cdea974..04e99a7b 100644 --- a/src/app/commands_display.cpp +++ b/src/app/commands_display.cpp @@ -12,8 +12,8 @@ #include "../gui/gui_application.h" #include "../gui/gui_document.h" #include "../gui/v3d_view_controller.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "qstring_conv.h" #include "theme.h" #include diff --git a/src/app/commands_file.cpp b/src/app/commands_file.cpp index a4951455..a4ce96ea 100644 --- a/src/app/commands_file.cpp +++ b/src/app/commands_file.cpp @@ -9,9 +9,9 @@ #include "../base/application.h" #include "../base/task_manager.h" #include "../gui/gui_application.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "filepath_conv.h" -#include "qstring_conv.h" #include "recent_files.h" #include "theme.h" diff --git a/src/app/commands_help.cpp b/src/app/commands_help.cpp index 92cb882e..47430bca 100644 --- a/src/app/commands_help.cpp +++ b/src/app/commands_help.cpp @@ -8,7 +8,7 @@ #include "dialog_about.h" #include "qtwidgets_utils.h" -#include "qstring_conv.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/dialog_about.cpp b/src/app/dialog_about.cpp index d117a6ba..3c474d6a 100644 --- a/src/app/dialog_about.cpp +++ b/src/app/dialog_about.cpp @@ -7,7 +7,7 @@ #include "dialog_about.h" #include "ui_dialog_about.h" -#include "qstring_conv.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index 4bdff451..d4c0e85f 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -12,10 +12,10 @@ #include "../base/meta_enum.h" #include "../base/settings.h" #include "../base/tkernel_utils.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "filepath_conv.h" #include "qmeta_tdf_label.h" -#include "qstring_conv.h" #include "qstring_utils.h" #include "qtgui_utils.h" #include "qtwidgets_utils.h" diff --git a/src/app/dialog_options.cpp b/src/app/dialog_options.cpp index 10f147d4..8fd8de92 100644 --- a/src/app/dialog_options.cpp +++ b/src/app/dialog_options.cpp @@ -10,10 +10,10 @@ #include "../base/settings.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" +#include "../qtbackend/qsettings_storage.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" #include "item_view_buttons.h" -#include "qsettings_storage.h" -#include "qstring_conv.h" #include "qtgui_utils.h" #include "qtwidgets_utils.h" #include "theme.h" @@ -372,7 +372,8 @@ void DialogOptions::saveAs() { const QString startDirPath = QString(); const QString filepath = QFileDialog::getSaveFileName( - this, tr("Choose INI file"), startDirPath, tr("INI files(*.ini)")); + this, tr("Choose INI file"), startDirPath, tr("INI files(*.ini)") + ); if (filepath.isEmpty()) return; diff --git a/src/app/dialog_task_manager.cpp b/src/app/dialog_task_manager.cpp index fc195275..6ea2d201 100644 --- a/src/app/dialog_task_manager.cpp +++ b/src/app/dialog_task_manager.cpp @@ -9,9 +9,9 @@ #include "../base/application.h" #include "../base/settings.h" #include "../base/task_manager.h" +#include "../qtcommon/qstring_conv.h" #include "ui_dialog_task_manager.h" #include "app_module.h" -#include "qstring_conv.h" #include "qstring_utils.h" #include "theme.h" diff --git a/src/app/document_property_group.cpp b/src/app/document_property_group.cpp index 24b987b7..98142e03 100644 --- a/src/app/document_property_group.cpp +++ b/src/app/document_property_group.cpp @@ -7,10 +7,10 @@ #include "document_property_group.h" #include "app_module.h" -#include "filepath_conv.h" -#include "qstring_conv.h" #include "qstring_utils.h" #include "../base/application.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/document_tree_node_properties_providers.cpp b/src/app/document_tree_node_properties_providers.cpp index 94db4dcc..70f7950f 100644 --- a/src/app/document_tree_node_properties_providers.cpp +++ b/src/app/document_tree_node_properties_providers.cpp @@ -19,7 +19,7 @@ #include "../graphics/graphics_mesh_object_driver.h" #include "../graphics/graphics_point_cloud_object_driver.h" #include "../graphics/graphics_shape_object_driver.h" -#include "qstring_conv.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/gui_document_list_model.cpp b/src/app/gui_document_list_model.cpp index 7ebb5f9c..87b8b31b 100644 --- a/src/app/gui_document_list_model.cpp +++ b/src/app/gui_document_list_model.cpp @@ -6,12 +6,12 @@ #include "gui_document_list_model.h" -#include "filepath_conv.h" -#include "qstring_conv.h" #include "../base/application.h" #include "../base/document.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" namespace Mayo { diff --git a/src/app/main.cpp b/src/app/main.cpp index ec9d611d..8d10a26d 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -20,28 +20,30 @@ #include "../graphics/graphics_mesh_object_driver.h" #include "../graphics/graphics_point_cloud_object_driver.h" #include "../graphics/graphics_shape_object_driver.h" +#include "../graphics/graphics_utils.h" #include "../gui/gui_application.h" +#include "../qtbackend/qt_app_translator.h" +#include "../qtbackend/qt_signal_thread_helper.h" +#include "../qtbackend/qsettings_storage.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/log_message_handler.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "cli_export.h" #include "commands_help.h" -#include "console.h" #include "document_tree_node_properties_providers.h" -#include "filepath_conv.h" #include "mainwindow.h" -#include "qsettings_storage.h" -#include "qstring_conv.h" #include "qtgui_utils.h" #include "theme.h" #include "widget_model_tree.h" #include "widget_model_tree_builder_mesh.h" #include "widget_model_tree_builder_xde.h" #include "widget_occ_view.h" +#include #include #include #include #include -#include #include #include #include @@ -49,6 +51,7 @@ #include #include #include +#include #include #include @@ -58,10 +61,6 @@ #include #include -#ifdef Q_OS_WIN -# include // For AttachConsole(), etc. -#endif - namespace Mayo { // Declared in graphics/graphics_create_driver.cpp @@ -81,100 +80,10 @@ struct CommandLineArguments { FilePath filepathSettings; FilePath filepathLog; bool includeDebugLogs = true; - std::vector listFilepathToExport; std::vector listFilepathToOpen; - bool cliProgressReport = true; bool showSystemInformation = false; }; -// Provides customization of Qt message handler -class LogMessageHandler { -public: - static LogMessageHandler& instance() - { - static LogMessageHandler object; - return object; - } - - // Corresponds to CommandLineArguments::includeDebugLogs - void enableDebugLogs(bool on) - { - m_enableDebugLogs = on; - } - - // Corresponds to CommandLineArguments::filepathLog - void setOutputFilePath(const FilePath& fp) - { - m_outputFilePath = fp; - if (!fp.empty()) - m_outputFile.open(fp, std::ios::out | std::ios::app); - else - m_outputFile.close(); - } - - std::ostream& outputStream(QtMsgType type) - { - if (!m_outputFilePath.empty() && m_outputFile.is_open()) - return m_outputFile; - - if (type == QtDebugMsg || type == QtInfoMsg) - return std::cout; - - return std::cerr; - } - - // Function called for Qt message handling - static void qtHandler(QtMsgType type, const QMessageLogContext& /*context*/, const QString& msg) - { - const std::string localMsg = consoleToPrintable(msg); - std::ostream& outs = LogMessageHandler::instance().outputStream(type); - switch (type) { - case QtDebugMsg: - if (LogMessageHandler::instance().m_enableDebugLogs) { - outs << "DEBUG: " << localMsg << std::endl; - } - break; - case QtInfoMsg: - outs << "INFO: " << localMsg << std::endl; - break; - case QtWarningMsg: - outs << "WARNING: " << localMsg << std::endl; - break; - case QtCriticalMsg: - outs << "CRITICAL: " << localMsg << std::endl; - break; - case QtFatalMsg: - outs << "FATAL: " << localMsg << std::endl; - break; - } - } - -private: - LogMessageHandler() = default; - - FilePath m_outputFilePath; - std::ofstream m_outputFile; - bool m_enableDebugLogs = true; -}; - -// Provides handling of signal/slot thread mismatch with the help of Qt -// There will be a single QObject created per thread, so it can be used to enqueue slot functions -class QtSignalThreadHelper : public ISignalThreadHelper { -public: - std::any getCurrentThreadContext() override - { - // Note: thread_local implies "static" - // See https://en.cppreference.com/w/cpp/language/storage_duration - thread_local QObject obj; - return &obj; - } - - void execInThread(const std::any& context, const std::function& fn) override - { - QTimer::singleShot(0, std::any_cast(context), fn); - } -}; - } // namespace // Parses command line and process Qt builtin options(basically --version and --help) @@ -204,14 +113,6 @@ static CommandLineArguments processCommandLine() ); cmdParser.addOption(cmdFileSettings); - const QCommandLineOption cmdFileToExport( - QStringList{ "e", "export" }, - Main::tr("Export opened files into an output file, can be repeated for different " - "formats(eg. -e file.stp -e file.igs...)"), - Main::tr("filepath") - ); - cmdParser.addOption(cmdFileToExport); - const QCommandLineOption cmdFileLog( QStringList{ "log-file" }, Main::tr("Writes log messages into output file"), @@ -225,12 +126,6 @@ static CommandLineArguments processCommandLine() ); cmdParser.addOption(cmdDebugLogs); - const QCommandLineOption cmdCliNoProgress( - QStringList{ "no-progress" }, - Main::tr("Disable progress reporting in console output(CLI-mode only)") - ); - cmdParser.addOption(cmdCliNoProgress); - const QCommandLineOption cmdSysInfo( QStringList{ "system-info" }, Main::tr("Show detailed system information and quit") @@ -264,11 +159,6 @@ static CommandLineArguments processCommandLine() if (cmdParser.isSet(cmdFileLog)) args.filepathLog = filepathFrom(cmdParser.value(cmdFileLog)); - if (cmdParser.isSet(cmdFileToExport)) { - for (const QString& strFilepath : cmdParser.values(cmdFileToExport)) - args.listFilepathToExport.push_back(filepathFrom(strFilepath)); - } - for (const QString& posArg : cmdParser.positionalArguments()) args.listFilepathToOpen.push_back(filepathFrom(posArg)); @@ -276,7 +166,6 @@ static CommandLineArguments processCommandLine() // By default this will exclude debug logs in release build args.includeDebugLogs = cmdParser.isSet(cmdDebugLogs); #endif - args.cliProgressReport = !cmdParser.isSet(cmdCliNoProgress); args.showSystemInformation = cmdParser.isSet(cmdSysInfo); return args; @@ -332,25 +221,6 @@ static void initOpenCascadeEnvironment(const FilePath& settingsFilepath) } } -// Function called by the Application i18n system, see Application::addTranslator() -static std::string_view qtTranslate(const TextId& text, int n) -{ - const QString qstr = QCoreApplication::translate(text.trContext.data(), text.key.data(), nullptr, n); - auto qstrHash = qHash(qstr); - static std::unordered_map mapStr; - static QReadWriteLock mapStrLock; - { - QReadLocker locker(&mapStrLock); - auto it = mapStr.find(qstrHash); - if (it != mapStr.cend()) - return it->second; - } - - QWriteLocker locker(&mapStrLock); - auto [it, ok] = mapStr.insert({ qstrHash, to_stdString(qstr) }); - return ok ? it->second : std::string_view{}; -} - // Helper to query the OpenGL version string [[maybe_unused]] static std::string queryGlVersionString() { @@ -388,6 +258,28 @@ static std::string_view qtTranslate(const TextId& text, int n) return QVersionNumber(versionMajor, versionMinor); } +Thumbnail createGuiDocumentThumbnail(GuiDocument* guiDoc, QSize size) +{ + Thumbnail thumbnail; + + IO::ImageWriter::Parameters params; + params.width = size.width(); + params.height = size.height(); + params.backgroundColor = QtGuiUtils::toPreferredColorSpace(mayoTheme()->color(Theme::Color::Palette_Window)); + Handle_Image_AlienPixMap pixmap = IO::ImageWriter::createImage(guiDoc, params); + if (!pixmap) { + qDebug() << "Empty pixmap returned by IO::ImageWriter::createImage()"; + return thumbnail; + } + + GraphicsUtils::ImagePixmap_flipY(*pixmap); + Image_PixMap::SwapRgbaBgra(*pixmap); + const QPixmap qPixmap = QtGuiUtils::toQPixmap(*pixmap); + thumbnail.imageData = QtGuiUtils::toQByteArray(qPixmap); + thumbnail.imageCacheKey = qPixmap.cacheKey(); + return thumbnail; +} + // Initializes "GUI" objects static void initGui(GuiApplication* guiApp) { @@ -459,6 +351,7 @@ static int runApp(QCoreApplication* qtApp) // Initialize AppModule auto appModule = AppModule::get(); appModule->settings()->setStorage(std::make_unique()); + appModule->setRecentFileThumbnailRecorder(&createGuiDocumentThumbnail); { // Load translation files auto fnLoadQmFile = [=](const QString& qmFilePath) { @@ -475,7 +368,7 @@ static int runApp(QCoreApplication* qtApp) // Initialize Base application auto app = Application::instance().get(); - app->addTranslator(&qtTranslate); // Set Qt i18n backend + app->addTranslator(&qtAppTranslate); // Set Qt i18n backend initOpenCascadeEnvironment("opencascade.conf"); // Initialize Gui application @@ -518,23 +411,6 @@ static int runApp(QCoreApplication* qtApp) return qtApp->exec(); } - if (!args.listFilepathToExport.empty()) { - if (args.listFilepathToOpen.empty()) - fnCriticalExit(Main::tr("No input files -> nothing to export")); - - guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed - appModule->settings()->resetAll(); - fnLoadAppSettings(appModule->settings()); - QTimer::singleShot(0, qtApp, [=]{ - CliExportArgs cliArgs; - cliArgs.progressReport = args.cliProgressReport; - cliArgs.filesToOpen = args.listFilepathToOpen; - cliArgs.filesToExport = args.listFilepathToExport; - cli_asyncExportDocuments(app, cliArgs, [=](int retcode) { qtApp->exit(retcode); }); - }); - return qtApp->exec(); - } - // Record recent files when documents are closed guiApp->signalGuiDocumentErased.connectSlot(&AppModule::recordRecentFileThumbnail, AppModule::get()); @@ -594,22 +470,13 @@ int main(int argc, char* argv[]) return false; }; - // If the arguments(argv) contain any of the following option, then Mayo has to run in CLI mode - const bool isAppCliMode = fnArgsContainAnyOf({ "-e", "--export", "-h", "--help", "-v", "--version" }); - // OpenCascade TKOpenGl depends on XLib for Linux(excepting Android) and BSD systems(excepting macOS) // See for example implementation of Aspect_DisplayConnection where XLib is explicitly used // On systems running eg Wayland this would cause problems(see https://github.com/fougue/mayo/issues/178) // As a workaround the Qt platform is forced to xcb #if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || (defined(Q_OS_BSD4) && !defined(Q_OS_MACOS)) - if ( - !isAppCliMode - && !qEnvironmentVariableIsSet("QT_QPA_PLATFORM") - && !fnArgsContainAnyOf({ "-platform" }) - ) - { + if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM") && !fnArgsContainAnyOf({ "-platform" })) qputenv("QT_QPA_PLATFORM", "xcb"); - } #endif // Configure and create Qt application object @@ -621,11 +488,7 @@ int main(int argc, char* argv[]) QCoreApplication::setOrganizationDomain("www.fougue.pro"); QCoreApplication::setApplicationName("Mayo"); QCoreApplication::setApplicationVersion(QString::fromUtf8(Mayo::strVersion)); - std::unique_ptr ptrApp( - isAppCliMode ? new QCoreApplication(argc, argv) : new QApplication(argc, argv) - ); - - //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); // Handle unit tests #ifdef MAYO_WITH_TESTS @@ -633,26 +496,6 @@ int main(int argc, char* argv[]) return Mayo::runTests(argc, argv); #endif - // Configure for CLI mode - if (isAppCliMode) { -#if defined(Q_OS_WIN) && defined(NDEBUG) - qAddPostRoutine(&Mayo::consoleSendEnterKey); - // https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643 - // https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ - if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) { - auto fnRedirectToConsole = [](DWORD hnd, FILE* file, const char* strPath) { - if (GetStdHandle(hnd) != INVALID_HANDLE_VALUE) { - file = freopen(strPath, "w", file); - setvbuf(file, nullptr, _IONBF, 0); - } - }; - fnRedirectToConsole(STD_OUTPUT_HANDLE, stdout, "CONOUT$"); - fnRedirectToConsole(STD_ERROR_HANDLE, stderr, "CONOUT$"); - std::ios::sync_with_stdio(); - } -#endif - } - - // Run Mayo application in CLI or GUI mode - return Mayo::runApp(ptrApp.get()); + // Run Mayo application GUI + return Mayo::runApp(&app); } diff --git a/src/app/property_editor_factory.cpp b/src/app/property_editor_factory.cpp index 57577344..ec48e18a 100644 --- a/src/app/property_editor_factory.cpp +++ b/src/app/property_editor_factory.cpp @@ -10,10 +10,10 @@ #include "../base/property_builtins.h" #include "../base/property_enumeration.h" #include "../base/unit_system.h" +#include "../qtcommon/qstring_conv.h" +#include "../qtcommon/qtcore_utils.h" #include "app_module.h" -#include "qstring_conv.h" #include "qstring_utils.h" -#include "qtcore_utils.h" #include "qtgui_utils.h" #include "qtwidgets_utils.h" diff --git a/src/app/property_item_delegate.cpp b/src/app/property_item_delegate.cpp index 57f3fc78..ad1a9c9b 100644 --- a/src/app/property_item_delegate.cpp +++ b/src/app/property_item_delegate.cpp @@ -9,10 +9,10 @@ #include "../base/property_builtins.h" #include "../base/settings.h" #include "../base/unit_system.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "filepath_conv.h" #include "qmeta_property.h" -#include "qstring_conv.h" #include "qstring_utils.h" #include "qtgui_utils.h" #include "theme.h" diff --git a/src/app/qtgui_utils.cpp b/src/app/qtgui_utils.cpp index 8b3dcb1a..d5690f74 100644 --- a/src/app/qtgui_utils.cpp +++ b/src/app/qtgui_utils.cpp @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -23,12 +24,14 @@ namespace QtGuiUtils { namespace { -constexpr bool isLessOrEqual(double lhs, double rhs) { +constexpr bool isLessOrEqual(double lhs, double rhs) +{ return lhs < rhs || qFuzzyCompare(lhs, rhs); } // Is 'v' inside ['start', 'end'] ? -constexpr bool isInRange(double v, double start, double end) { +constexpr bool isInRange(double v, double start, double end) +{ return isLessOrEqual(start, v) && isLessOrEqual(v, end); } @@ -53,10 +56,10 @@ QColor lerp(const QColor& a, const QColor& b, double t) { Expects(isInRange(t, 0, 1)); return QColor( - MathUtils::lerp(a.red(), b.red(), t), - MathUtils::lerp(a.green(), b.green(), t), - MathUtils::lerp(a.blue(), b.blue(), t), - MathUtils::lerp(a.alpha(), b.alpha(), t) + MathUtils::lerp(a.red(), b.red(), t), + MathUtils::lerp(a.green(), b.green(), t), + MathUtils::lerp(a.blue(), b.blue(), t), + MathUtils::lerp(a.alpha(), b.alpha(), t) ); } @@ -77,8 +80,9 @@ QColor linearColorAt(const QGradient& gradient, double t) ); if (itLower != stops.cbegin() && itLower != stops.cend()) { const int i = (itLower - stops.cbegin()) - 1; - const double tMapped = - MathUtils::mappedValue(t, stops.at(i).first, stops.at(i + 1).first, 0, 1); + const double tMapped = MathUtils::mappedValue( + t, stops.at(i).first, stops.at(i + 1).first, 0, 1 + ); return lerp(stops.at(i).second, stops.at(i + 1).second, tMapped); } @@ -143,20 +147,24 @@ int screenPixelHeight(double screenRatio, const QScreen* screen) QSize screenPixelSize(double widthRatio, double heightRatio, const QScreen* screen) { - return QSize(QtGuiUtils::screenPixelWidth(widthRatio, screen), - QtGuiUtils::screenPixelHeight(heightRatio, screen)); + return QSize( + QtGuiUtils::screenPixelWidth(widthRatio, screen), + QtGuiUtils::screenPixelHeight(heightRatio, screen) + ); } FontChange::FontChange(const QFont& font) : m_font(font) {} -FontChange& FontChange::size(int size) { +FontChange& FontChange::size(int size) +{ m_font.setPixelSize(size); return *this; } -FontChange& FontChange::adjustSize(int offset) { +FontChange& FontChange::adjustSize(int offset) +{ const int pixelSize = m_font.pixelSize() > 0 ? m_font.pixelSize() : 12; m_font.setPixelSize(pixelSize + offset); return *this; @@ -168,12 +176,14 @@ FontChange& FontChange::scalePointSizeF(double f) { return *this; } -FontChange& FontChange::bold(bool on) { +FontChange& FontChange::bold(bool on) +{ m_font.setBold(on); return *this; } -FontChange& FontChange::fixedPitch(bool on) { +FontChange& FontChange::fixedPitch(bool on) +{ m_font.setFixedPitch(on); return *this; } @@ -200,16 +210,34 @@ QPixmap toQPixmap(const Image_PixMap& pixmap) default: return QImage::Format_Invalid; } }; - const QImage img(pixmap.Data(), - int(pixmap.Width()), - int(pixmap.Height()), - int(pixmap.SizeRowBytes()), - fnToQImageFormat(pixmap.Format())); + const QImage img( + pixmap.Data(), + int(pixmap.Width()), + int(pixmap.Height()), + int(pixmap.SizeRowBytes()), + fnToQImageFormat(pixmap.Format()) + ); if (img.isNull()) return {}; return QPixmap::fromImage(img); } +QPixmap toQPixmap(const QByteArray& bytes, Qt::ImageConversionFlags flags) +{ + QPixmap pixmap; + pixmap.loadFromData(bytes, nullptr, flags); + return pixmap; +} + +QByteArray toQByteArray(const QPixmap& pixmap, const char* format) +{ + QByteArray bytes; + QBuffer buffer(&bytes); + buffer.open(QIODevice::WriteOnly); + pixmap.save(&buffer, format); + return bytes; +} + } // namespace QtGuiUtils } // namespace Mayo diff --git a/src/app/qtgui_utils.h b/src/app/qtgui_utils.h index 0a418e4b..810629ea 100644 --- a/src/app/qtgui_utils.h +++ b/src/app/qtgui_utils.h @@ -40,6 +40,13 @@ Quantity_Color toPreferredColorSpace(const QColor& c); // Converts (OCCT)Image_Pixmap -> QPixmap QPixmap toQPixmap(const Image_PixMap& pixmap); +// Loads QPixmap from a QByteArray object +// The loader probes the data in 'bytes' for a header to guess the file format +QPixmap toQPixmap(const QByteArray& bytes, Qt::ImageConversionFlags flags = Qt::AutoColor); + +// Saves QPixmap into a QByteArray object +QByteArray toQByteArray(const QPixmap& pixmap, const char* format = "PNG"); + // Returns linear interpolated color between 'a' and 'b' at parameter 't' QColor lerp(const QColor& a, const QColor& b, double t); diff --git a/src/app/recent_files.cpp b/src/app/recent_files.cpp index 661964e4..e64deb65 100644 --- a/src/app/recent_files.cpp +++ b/src/app/recent_files.cpp @@ -7,52 +7,13 @@ #include "recent_files.h" #include "../base/meta_enum.h" -#include "../graphics/graphics_utils.h" -#include "../gui/gui_document.h" -#include "../io_image/io_image.h" -#include "filepath_conv.h" -#include "qstring_conv.h" -#include "qtgui_utils.h" -#include "theme.h" +#include "../qtcommon/filepath_conv.h" #include #include namespace Mayo { -bool RecentFile::recordThumbnail(GuiDocument* guiDoc, QSize size) -{ - if (!guiDoc) - return false; - - if (!filepathEquivalent(this->filepath, guiDoc->document()->filePath())) { - qDebug() << fmt::format("Filepath mismatch with GUI document\n" - " Function: {}\n Filepath: {}\n Document: {}", - Q_FUNC_INFO, this->filepath.u8string(), guiDoc->document()->filePath().u8string()) - .c_str(); - return false; - } - - if (this->thumbnailTimestamp == RecentFile::timestampLastModified(this->filepath)) - return true; - - IO::ImageWriter::Parameters params; - params.width = size.width(); - params.height = size.height(); - params.backgroundColor = QtGuiUtils::toPreferredColorSpace(mayoTheme()->color(Theme::Color::Palette_Window)); - Handle_Image_AlienPixMap pixmap = IO::ImageWriter::createImage(guiDoc, params); - if (!pixmap) { - qDebug() << "Empty pixmap returned by IO::ImageWriter::createImage()"; - return false; - } - - GraphicsUtils::ImagePixmap_flipY(*pixmap); - Image_PixMap::SwapRgbaBgra(*pixmap); - this->thumbnail = QtGuiUtils::toQPixmap(*pixmap); - this->thumbnailTimestamp = RecentFile::timestampLastModified(this->filepath); - return true; -} - bool RecentFile::isThumbnailOutOfSync() const { return this->thumbnailTimestamp != RecentFile::timestampLastModified(this->filepath); @@ -74,62 +35,21 @@ int64_t RecentFile::timestampLastModified(const FilePath& fp) bool operator==(const RecentFile& lhs, const RecentFile& rhs) { - return lhs.filepath == rhs.filepath - && lhs.thumbnail.cacheKey() == rhs.thumbnail.cacheKey() - && lhs.thumbnailTimestamp == rhs.thumbnailTimestamp; -} - -QDataStream& operator<<(QDataStream& stream, const RecentFile& recentFile) -{ - stream << filepathTo(recentFile.filepath); - stream << recentFile.thumbnail; - stream << qint64(recentFile.thumbnailTimestamp); - return stream; -} - -QDataStream& operator>>(QDataStream& stream, RecentFile& recentFile) -{ - QString strFilepath; - stream >> strFilepath; - recentFile.filepath = filepathFrom(strFilepath); - stream >> recentFile.thumbnail; - // Read thumbnail timestamp - // Warning: qint64 and int64_t may not be the exact same type(eg __int64 and longlong with Windows/MSVC) - qint64 timestamp; - stream >> timestamp; - recentFile.thumbnailTimestamp = timestamp; - return stream; -} - -QDataStream& operator<<(QDataStream& stream, const RecentFiles& recentFiles) -{ - stream << uint32_t(recentFiles.size()); - for (const RecentFile& recent : recentFiles) - stream << recent; - - return stream; -} - -QDataStream& operator>>(QDataStream& stream, RecentFiles& recentFiles) -{ - uint32_t count = 0; - stream >> count; - recentFiles.clear(); - for (uint32_t i = 0; i < count; ++i) { - if (stream.status() != QDataStream::Ok) { - qDebug() << fmt::format("QDataStream error\n Function: {}\n Status: {}", - Q_FUNC_INFO, MetaEnum::name(stream.status())) - .c_str(); - break; // Stream extraction error, abort - } + if (lhs.filepath != rhs.filepath) + return false; - RecentFile recent; - stream >> recent; - if (!recent.filepath.empty() && recent.thumbnailTimestamp != 0) - recentFiles.push_back(std::move(recent)); + if (lhs.thumbnail.imageCacheKey >= 0 && rhs.thumbnail.imageCacheKey >= 0) { + if (lhs.thumbnail.imageCacheKey != rhs.thumbnail.imageCacheKey) + return false; + } + else if (lhs.thumbnail.imageData != rhs.thumbnail.imageData) { + return false; } - return stream; + if (lhs.thumbnailTimestamp != rhs.thumbnailTimestamp) + return false; + + return true; } template<> const char PropertyRecentFiles::TypeName[] = "Mayo::PropertyRecentFiles"; diff --git a/src/app/recent_files.h b/src/app/recent_files.h index 706f1a1c..13f87e27 100644 --- a/src/app/recent_files.h +++ b/src/app/recent_files.h @@ -9,20 +9,24 @@ #include "../base/filepath.h" #include "../base/property_builtins.h" -#include +#include + #include -class QDataStream; namespace Mayo { class GuiDocument; +struct Thumbnail { + QByteArray imageData; // PNG + int64_t imageCacheKey = -1; +}; + // Provides information about a "recently" opened file struct RecentFile { FilePath filepath; - QPixmap thumbnail; + Thumbnail thumbnail; int64_t thumbnailTimestamp = 0; - bool recordThumbnail(GuiDocument* guiDoc, QSize size); bool isThumbnailOutOfSync() const; static int64_t timestampLastModified(const FilePath& fp); }; @@ -36,16 +40,4 @@ using PropertyRecentFiles = GenericProperty; bool operator==(const RecentFile& lhs, const RecentFile& rhs); -// Writes a RecentFile object to QDataStream -QDataStream& operator<<(QDataStream& stream, const RecentFile& recentFile); - -// Writes array of RecentFile objects to QDataStream -QDataStream& operator<<(QDataStream& stream, const RecentFiles& recentFiles); - -// Extracts a RecentFile object from QDataStream -QDataStream& operator>>(QDataStream& stream, RecentFile& recentFile); - -// Extracts array of RecentFile objects from QDataStream -QDataStream& operator>>(QDataStream& stream, RecentFiles& recentFiles); - } // namespace Mayo diff --git a/src/app/view3d_navigation_style.h b/src/app/view3d_navigation_style.h new file mode 100644 index 00000000..5c3d4770 --- /dev/null +++ b/src/app/view3d_navigation_style.h @@ -0,0 +1,15 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +namespace Mayo { + +enum class View3dNavigationStyle { + Mayo, Catia, SolidWorks, Unigraphics, ProEngineer +}; + +} // namespace Mayo diff --git a/src/app/widget_gui_document.cpp b/src/app/widget_gui_document.cpp index 5dde3114..71e56a34 100644 --- a/src/app/widget_gui_document.cpp +++ b/src/app/widget_gui_document.cpp @@ -11,6 +11,7 @@ #include "../graphics/graphics_utils.h" #include "../gui/gui_document.h" #include "../gui/v3d_view_camera_animation.h" +#include "../qtbackend/qt_animation_backend.h" #include "button_flat.h" #include "theme.h" #include "widget_clip_planes.h" @@ -22,8 +23,6 @@ #include "qtwidgets_utils.h" #include -#include -#include #include #include #include @@ -36,58 +35,6 @@ namespace Mayo { namespace { -// Provides implementation of IAnimationBackend based on QAbstractAnimation -class QtAnimationBackend : public IAnimationBackend { -public: - QtAnimationBackend(QEasingCurve::Type easingType = QEasingCurve::Linear) - : m_easingCurve(easingType) - { - } - - void setDuration(QuantityTime t) override { - m_impl.m_duration_ms = UnitSystem::milliseconds(t); - } - - bool isRunning() const override { - return m_impl.state() == QAbstractAnimation::Running; - } - - void start() override { - m_impl.start(QAbstractAnimation::KeepWhenStopped); - } - - void stop() override { - m_impl.stop(); - } - - double valueForProgress(double p) const override { - return m_easingCurve.valueForProgress(p); - } - - void setTimerCallback(std::function fn) override { - m_impl.m_callback = std::move(fn); - } - -private: - class AnimationImpl : public QAbstractAnimation { - public: - double m_duration_ms = 1000.; - std::function m_callback; - - int duration() const override { - return static_cast(m_duration_ms); - } - - protected: - void updateCurrentTime(int currentTime) override { - m_callback(currentTime * Quantity_Millisecond); - } - }; - - AnimationImpl m_impl; - QEasingCurve m_easingCurve; -}; - // Provides an overlay widget to be used within 3D view class PanelView3d : public QWidget { public: diff --git a/src/app/widget_home_files.cpp b/src/app/widget_home_files.cpp index 52b8e4a6..1dd05adb 100644 --- a/src/app/widget_home_files.cpp +++ b/src/app/widget_home_files.cpp @@ -10,9 +10,10 @@ #include "../base/settings.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" +#include "../qtcommon/filepath_conv.h" #include "app_module.h" -#include "filepath_conv.h" #include "qstring_utils.h" +#include "qtgui_utils.h" #include "theme.h" #include @@ -46,7 +47,8 @@ class HomeFilesModel : public ListHelper::Model { HomeFileItem item; item.name = WidgetHomeFiles::tr("New Document"); item.description = WidgetHomeFiles::tr( - "\n\nCreate and add an empty document where you can import files"); + "\n\nCreate and add an empty document where you can import files" + ); item.imageUrl = ImageId_NewDocument; item.type = HomeFileItem::Type::New; item.textWrapMode = QTextOption::WordWrap; @@ -57,7 +59,8 @@ class HomeFilesModel : public ListHelper::Model { HomeFileItem item; item.name = WidgetHomeFiles::tr("Open Document(s)"); item.description = WidgetHomeFiles::tr( - "\n\nSelect files to load and open as distinct documents"); + "\n\nSelect files to load and open as distinct documents" + ); item.imageUrl = ImageId_OpenDocuments; item.type = HomeFileItem::Type::Open; item.textWrapMode = QTextOption::WordWrap; @@ -85,11 +88,14 @@ class HomeFilesModel : public ListHelper::Model { } else { const RecentFile* recentFile = AppModule::get()->findRecentFile(filepathFrom(url)); - pixmap = recentFile ? recentFile->thumbnail : QPixmap(); + if (recentFile) + pixmap = QtGuiUtils::toQPixmap(recentFile->thumbnail.imageData); + if (pixmap.isNull()) { const QIcon icon = m_fileIconProvider.icon(QFileInfo(url)); pixmap = fnPixmap(icon, 64, 64); cachePixmap = false; + } } diff --git a/src/app/widget_main_control.cpp b/src/app/widget_main_control.cpp index 3240e3ae..42704da2 100644 --- a/src/app/widget_main_control.cpp +++ b/src/app/widget_main_control.cpp @@ -10,16 +10,16 @@ #include "../base/application.h" #include "../graphics/graphics_utils.h" #include "../gui/gui_application.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" #include "commands_api.h" #include "commands_file.h" #include "commands_window.h" #include "document_property_group.h" -#include "filepath_conv.h" #include "gui_document_list_model.h" #include "item_view_buttons.h" -#include "qstring_conv.h" #include "theme.h" #include "widget_file_system.h" #include "widget_gui_document.h" diff --git a/src/app/widget_main_home.cpp b/src/app/widget_main_home.cpp index 5b6a45eb..0c1dd4ad 100644 --- a/src/app/widget_main_home.cpp +++ b/src/app/widget_main_home.cpp @@ -7,8 +7,8 @@ #include "widget_main_home.h" #include "ui_widget_main_home.h" +#include "../qtcommon/filepath_conv.h" #include "commands_file.h" -#include "filepath_conv.h" namespace Mayo { diff --git a/src/app/widget_measure.cpp b/src/app/widget_measure.cpp index 237bb184..570af0e4 100644 --- a/src/app/widget_measure.cpp +++ b/src/app/widget_measure.cpp @@ -6,13 +6,13 @@ #include "widget_measure.h" #include "app_module.h" -#include "qstring_conv.h" #include "theme.h" #include "ui_widget_measure.h" #include "../base/unit_system.h" #include "../gui/gui_document.h" #include "../measure/measure_tool_brep.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/widget_model_tree.cpp b/src/app/widget_model_tree.cpp index a2939bbe..0ee000c6 100644 --- a/src/app/widget_model_tree.cpp +++ b/src/app/widget_model_tree.cpp @@ -11,8 +11,8 @@ #include "../base/document.h" #include "../base/settings.h" #include "../gui/gui_application.h" +#include "../qtcommon/qtcore_utils.h" #include "item_view_buttons.h" -#include "qtcore_utils.h" #include "theme.h" #include "widget_model_tree_builder.h" diff --git a/src/app/widget_model_tree_builder.cpp b/src/app/widget_model_tree_builder.cpp index 1f840ac1..7d2b000a 100644 --- a/src/app/widget_model_tree_builder.cpp +++ b/src/app/widget_model_tree_builder.cpp @@ -8,9 +8,8 @@ #include "../base/document.h" #include "../base/caf_utils.h" -#include "../base/filepath.h" -#include "filepath_conv.h" -#include "qstring_conv.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "widget_model_tree.h" #include "theme.h" diff --git a/src/app/widget_model_tree_builder_xde.cpp b/src/app/widget_model_tree_builder_xde.cpp index 01e5277c..0320a763 100644 --- a/src/app/widget_model_tree_builder_xde.cpp +++ b/src/app/widget_model_tree_builder_xde.cpp @@ -8,9 +8,9 @@ #include "../base/xcaf.h" #include "../graphics/graphics_shape_object_driver.h" #include "../gui/gui_application.h" +#include "../qtcommon/qstring_conv.h" +#include "../qtcommon/qtcore_utils.h" #include "app_module.h" -#include "qtcore_utils.h" -#include "qstring_conv.h" #include "theme.h" #include "widget_model_tree.h" diff --git a/src/app/widget_occ_view_controller.cpp b/src/app/widget_occ_view_controller.cpp index c3edeaaf..eb94aa9e 100644 --- a/src/app/widget_occ_view_controller.cpp +++ b/src/app/widget_occ_view_controller.cpp @@ -102,7 +102,7 @@ WidgetOccViewController::WidgetOccViewController(IWidgetOccView* occView) : QObject(occView->widget()), V3dViewController(occView->v3dView()), m_occView(occView), - m_navigStyle(NavigationStyle::Catia), + m_navigStyle(View3dNavigationStyle::Catia), m_actionMatcher(createActionMatcher(m_navigStyle, &m_inputSequence)) { m_occView->widget()->installEventFilter(this); @@ -130,7 +130,7 @@ bool WidgetOccViewController::eventFilter(QObject* watched, QEvent* event) return false; } -void WidgetOccViewController::setNavigationStyle(NavigationStyle style) +void WidgetOccViewController::setNavigationStyle(View3dNavigationStyle style) { m_navigStyle = style; m_inputSequence.clear(); @@ -448,14 +448,14 @@ class WidgetOccViewController::ProEngineer_ActionMatcher : public ActionMatcher }; std::unique_ptr -WidgetOccViewController::createActionMatcher(NavigationStyle style, const InputSequence* seq) +WidgetOccViewController::createActionMatcher(View3dNavigationStyle style, const InputSequence* seq) { switch(style) { - case NavigationStyle::Mayo: return std::make_unique(seq); - case NavigationStyle::Catia: return std::make_unique(seq); - case NavigationStyle::SolidWorks: return std::make_unique(seq); - case NavigationStyle::Unigraphics: return std::make_unique(seq); - case NavigationStyle::ProEngineer: return std::make_unique(seq); + case View3dNavigationStyle::Mayo: return std::make_unique(seq); + case View3dNavigationStyle::Catia: return std::make_unique(seq); + case View3dNavigationStyle::SolidWorks: return std::make_unique(seq); + case View3dNavigationStyle::Unigraphics: return std::make_unique(seq); + case View3dNavigationStyle::ProEngineer: return std::make_unique(seq); } return {}; } diff --git a/src/app/widget_occ_view_controller.h b/src/app/widget_occ_view_controller.h index 7ce388d1..a7311335 100644 --- a/src/app/widget_occ_view_controller.h +++ b/src/app/widget_occ_view_controller.h @@ -6,8 +6,9 @@ #pragma once -#include "../gui/v3d_view_controller.h" #include "../base/span.h" +#include "../gui/v3d_view_controller.h" +#include "view3d_navigation_style.h" #include #include @@ -31,10 +32,7 @@ class WidgetOccViewController : public QObject, public V3dViewController { bool eventFilter(QObject* watched, QEvent* event) override; - enum class NavigationStyle { - Mayo, Catia, SolidWorks, Unigraphics, ProEngineer - }; - void setNavigationStyle(NavigationStyle style); + void setNavigationStyle(View3dNavigationStyle style); protected: void redrawView() override; @@ -110,7 +108,7 @@ class WidgetOccViewController : public QObject, public V3dViewController { }; // Fabrication to create corresponding ActionMatcher from navigation style - static std::unique_ptr createActionMatcher(NavigationStyle style, const InputSequence* seq); + static std::unique_ptr createActionMatcher(View3dNavigationStyle style, const InputSequence* seq); class Mayo_ActionMatcher; class Catia_ActionMatcher; class SolidWorks_ActionMatcher; @@ -121,7 +119,7 @@ class WidgetOccViewController : public QObject, public V3dViewController { IWidgetOccView* m_occView = nullptr; Position m_prevPos; - NavigationStyle m_navigStyle = NavigationStyle::Mayo; + View3dNavigationStyle m_navigStyle = View3dNavigationStyle::Mayo; InputSequence m_inputSequence; std::unique_ptr m_actionMatcher; }; diff --git a/src/app/widget_occ_view_impl.cpp b/src/app/widget_occ_view_impl.cpp index d85711ce..682d7925 100644 --- a/src/app/widget_occ_view_impl.cpp +++ b/src/app/widget_occ_view_impl.cpp @@ -4,7 +4,7 @@ ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ -#include +#include "../graphics/graphics_utils.h" #include #include @@ -19,19 +19,6 @@ namespace Mayo { -namespace { - -Handle_Aspect_DisplayConnection createDisplayConnection() -{ -#if (!defined(Q_OS_WIN) && (!defined(Q_OS_MAC) || defined(MACOSX_USE_GLX))) - return new Aspect_DisplayConnection(std::getenv("DISPLAY")); -#else - return new Aspect_DisplayConnection; -#endif -} - -} // namespace - #if OCC_VERSION_HEX >= 0x070600 namespace { @@ -87,20 +74,13 @@ void QOpenGLWidgetOccView_createOpenGlContext(std::functionChangeOptions().buffersNoSwap = true; // Don't write into alpha channel gfxDriver->ChangeOptions().buffersOpaqueAlpha = true; // Offscreen FBOs should be always used gfxDriver->ChangeOptions().useSystemBuffer = false; -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - Message::SendWarning( - "Warning! Qt 5.10+ is required for sRGB setup.\n" - "Colors in 3D Viewer might look incorrect (Qt " QT_VERSION_STR " is used).\n" - ); - gfxDriver->ChangeOptions().sRGBDisable = true; -#endif return gfxDriver; } @@ -139,7 +119,7 @@ Graphic3d_Vec2i QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(const Han Handle_Graphic3d_GraphicDriver QWidgetOccView_createCompatibleGraphicsDriver() { - return new OpenGl_GraphicDriver(createDisplayConnection()); + return new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create()); } } // namespace Mayo diff --git a/src/app/widget_properties_editor.cpp b/src/app/widget_properties_editor.cpp index 72fe5038..3c43c921 100644 --- a/src/app/widget_properties_editor.cpp +++ b/src/app/widget_properties_editor.cpp @@ -6,8 +6,8 @@ #include "widget_properties_editor.h" #include "qmeta_property.h" -#include "qstring_conv.h" #include "ui_widget_properties_editor.h" +#include "../qtcommon/qstring_conv.h" #include diff --git a/src/app/cli_export.cpp b/src/cli/cli_export.cpp similarity index 99% rename from src/app/cli_export.cpp rename to src/cli/cli_export.cpp index f5d7d428..989267cc 100644 --- a/src/app/cli_export.cpp +++ b/src/cli/cli_export.cpp @@ -6,13 +6,13 @@ #include "cli_export.h" -#include "app_module.h" #include "console.h" -#include "qstring_conv.h" +#include "../app/app_module.h" #include "../base/application.h" #include "../base/io_system.h" #include "../base/messenger.h" #include "../base/task_manager.h" +#include "../qtcommon/qstring_conv.h" #include diff --git a/src/app/cli_export.h b/src/cli/cli_export.h similarity index 100% rename from src/app/cli_export.h rename to src/cli/cli_export.h diff --git a/src/app/console.cpp b/src/cli/console.cpp similarity index 83% rename from src/app/console.cpp rename to src/cli/console.cpp index 183cac75..3e0844bb 100644 --- a/src/app/console.cpp +++ b/src/cli/console.cpp @@ -6,8 +6,9 @@ #include "console.h" -#include -#ifdef Q_OS_WIN +#include "../base/global.h" + +#ifdef MAYO_OS_WINDOWS # include # include #else @@ -16,15 +17,12 @@ # include #endif -#include "../base/global.h" -#include "qstring_conv.h" - namespace Mayo { void consoleSetTextColor(ConsoleColor color) { constexpr bool isBrightText = true; -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS auto fnColorFlags = [](ConsoleColor color) { switch (color) { case ConsoleColor::Black: return 0; @@ -48,7 +46,7 @@ void consoleSetTextColor(ConsoleColor color) const WORD flags = fnColorFlags(color) | brightFlag; SetConsoleTextAttribute(hStdout, flags); } -#elif defined(__EMSCRIPTEN__) +#elif defined(MAYO_OS_WASM) // Terminal capabilities are undefined on this platform. // std::cout could be redirected to HTML page, into terminal or somewhere else. MAYO_UNUSED(color); @@ -77,7 +75,7 @@ void consoleCursorMoveUp(int lines) if (lines == 0) return; -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdout) { CONSOLE_SCREEN_BUFFER_INFO buffInfo; @@ -94,7 +92,7 @@ void consoleCursorMoveUp(int lines) void consoleCursorShow(bool on) { -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdout) { CONSOLE_CURSOR_INFO cursorInfo; @@ -109,7 +107,7 @@ void consoleCursorShow(bool on) std::pair consoleSize() { -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS CONSOLE_SCREEN_BUFFER_INFO buffInfo; int cols, rows; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &buffInfo); @@ -129,36 +127,11 @@ int consoleWidth() { void consoleSendEnterKey() { -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS HWND consoleWnd = GetConsoleWindow(); if (IsWindow(consoleWnd)) PostMessage(consoleWnd, WM_KEYUP, VK_RETURN, 0); #endif } -std::string consoleToPrintable(const QString& str) -{ -#ifdef Q_OS_WIN - const auto codepage = GetConsoleOutputCP(); - const wchar_t* source = reinterpret_cast(str.utf16()); - const int dstSize = WideCharToMultiByte(codepage, 0, source, -1, nullptr, 0, nullptr, nullptr); - std::string dst; - dst.resize(dstSize + 1); - WideCharToMultiByte(codepage, 0, source, -1, dst.data(), dstSize, nullptr, nullptr); - dst.back() = '\0'; - return dst; -#else - return str.toStdString(); // utf8 -#endif -} - -std::string consoleToPrintable(std::string_view str) -{ -#ifdef Q_OS_WIN - return consoleToPrintable(to_QString(str)); -#else - return std::string(str); // utf8 -#endif -} - } // namespace Mayo diff --git a/src/app/console.h b/src/cli/console.h similarity index 82% rename from src/app/console.h rename to src/cli/console.h index f474ba57..19699e56 100644 --- a/src/app/console.h +++ b/src/cli/console.h @@ -6,9 +6,6 @@ #pragma once -#include -#include -#include #include namespace Mayo { @@ -36,8 +33,4 @@ int consoleWidth(); // Useful to release the command prompt on the parent console when using AttachConsole() void consoleSendEnterKey(); -// Returns 'str' converted to a "guaranteed" printable string -std::string consoleToPrintable(const QString& str); -std::string consoleToPrintable(std::string_view str/*utf8*/); - } // namespace Mayo diff --git a/src/cli/main.cpp b/src/cli/main.cpp new file mode 100644 index 00000000..725a9369 --- /dev/null +++ b/src/cli/main.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "../app/app_module.h" +#include "../base/application.h" +#include "../base/io_system.h" +#include "../base/settings.h" +#include "../graphics/graphics_mesh_object_driver.h" +#include "../graphics/graphics_point_cloud_object_driver.h" +#include "../graphics/graphics_shape_object_driver.h" +#include "../graphics/graphics_utils.h" +#include "../gui/gui_application.h" +#include "../io_assimp/io_assimp.h" +#include "../io_dxf/io_dxf.h" +#include "../io_gmio/io_gmio.h" +#include "../io_image/io_image.h" +#include "../io_occ/io_occ.h" +#include "../io_off/io_off_reader.h" +#include "../io_off/io_off_writer.h" +#include "../io_ply/io_ply_reader.h" +#include "../io_ply/io_ply_writer.h" +#include "../qtbackend/qsettings_storage.h" +#include "../qtbackend/qt_app_translator.h" +#include "../qtbackend/qt_signal_thread_helper.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/log_message_handler.h" +#include "../qtcommon/qstring_conv.h" +#include "cli_export.h" +#include "console.h" +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace Mayo { + +// Declared in graphics/graphics_create_driver.cpp +void setFunctionCreateGraphicsDriver(std::function fn); + +// Provides an i18n context for the current file(main.cpp) +class Main { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::Main) + Q_DECLARE_TR_FUNCTIONS(Mayo::Main) +}; + +namespace { + +// Stores arguments(options) passed at command line +struct CommandLineArguments { + FilePath filepathSettings; + FilePath filepathLog; + bool includeDebugLogs = true; + std::vector listFilepathToExport; + std::vector listFilepathToOpen; + bool cliProgressReport = true; + bool showSystemInformation = false; +}; + +} // namespace + +// Parses command line and process Qt builtin options(basically --version and --help) +static CommandLineArguments processCommandLine() +{ + CommandLineArguments args; + + // Configure command-line parser + QCommandLineParser cmdParser; + cmdParser.setApplicationDescription( + Main::tr("Mayo the opensource 3D CAD viewer and converter") + ); + cmdParser.addHelpOption(); + cmdParser.addVersionOption(); + + const QCommandLineOption cmdFileSettings( + QStringList{ "s", "settings" }, + Main::tr("Settings file(INI format) to load at startup"), + Main::tr("filepath") + ); + cmdParser.addOption(cmdFileSettings); + + const QCommandLineOption cmdFileToExport( + QStringList{ "e", "export" }, + Main::tr("Export opened files into an output file, can be repeated for different " + "formats(eg. -e file.stp -e file.igs...)"), + Main::tr("filepath") + ); + cmdParser.addOption(cmdFileToExport); + + const QCommandLineOption cmdFileLog( + QStringList{ "log-file" }, + Main::tr("Writes log messages into output file"), + Main::tr("filepath") + ); + cmdParser.addOption(cmdFileLog); + + const QCommandLineOption cmdDebugLogs( + QStringList{ "debug-logs" }, + Main::tr("Don't filter out debug log messages in release build") + ); + cmdParser.addOption(cmdDebugLogs); + + const QCommandLineOption cmdCliNoProgress( + QStringList{ "no-progress" }, + Main::tr("Disable progress reporting in console output(CLI-mode only)") + ); + cmdParser.addOption(cmdCliNoProgress); + + const QCommandLineOption cmdSysInfo( + QStringList{ "system-info" }, + Main::tr("Show detailed system information and quit") + ); + cmdParser.addOption(cmdSysInfo); + + cmdParser.addPositionalArgument( + Main::tr("files"), + Main::tr("Files to open at startup, optionally"), + Main::tr("[files...]") + ); + + cmdParser.process(QCoreApplication::arguments()); + + // Retrieve arguments + if (cmdParser.isSet(cmdFileSettings)) + args.filepathSettings = filepathFrom(cmdParser.value(cmdFileSettings)); + + if (cmdParser.isSet(cmdFileLog)) + args.filepathLog = filepathFrom(cmdParser.value(cmdFileLog)); + + if (cmdParser.isSet(cmdFileToExport)) { + for (const QString& strFilepath : cmdParser.values(cmdFileToExport)) + args.listFilepathToExport.push_back(filepathFrom(strFilepath)); + } + + for (const QString& posArg : cmdParser.positionalArguments()) + args.listFilepathToOpen.push_back(filepathFrom(posArg)); + +#ifdef NDEBUG + // By default this will exclude debug logs in release build + args.includeDebugLogs = cmdParser.isSet(cmdDebugLogs); +#endif + args.cliProgressReport = !cmdParser.isSet(cmdCliNoProgress); + args.showSystemInformation = cmdParser.isSet(cmdSysInfo); + + return args; +} + +// Set OpenCascade environment variables defined in a settings file(INI format) +static void initOpenCascadeEnvironment(const FilePath& settingsFilepath) +{ + const QString strSettingsFilepath = filepathTo(settingsFilepath); + if (!filepathExists(settingsFilepath) /* TODO Check readable */) { + qDebug().noquote() << Main::tr("OpenCascade settings file doesn't exist or is not readable [path=%1]") + .arg(strSettingsFilepath); + return; + } + + const QSettings occSettings(strSettingsFilepath, QSettings::IniFormat); + if (occSettings.status() != QSettings::NoError) { + qDebug().noquote() << Main::tr("OpenCascade settings file could not be loaded with QSettings [path=%1]") + .arg(strSettingsFilepath); + return; + } + + // Process options + for (const char* varName : Application::envOpenCascadeOptions()) { + const QLatin1String qVarName(varName); + if (occSettings.contains(qVarName)) { + const QString strValue = occSettings.value(qVarName).toString(); + qputenv(varName, strValue.toUtf8()); + qDebug().noquote() << QString("%1 = %2").arg(qVarName).arg(strValue); + } + } + + // Process paths + for (const char* varName : Application::envOpenCascadePaths()) { + const QLatin1String qVarName(varName); + if (occSettings.contains(qVarName)) { + QString strPath = occSettings.value(qVarName).toString(); + if (QFileInfo(strPath).isRelative()) + strPath = QCoreApplication::applicationDirPath() + QDir::separator() + strPath; + + strPath = QDir::toNativeSeparators(strPath); + qputenv(varName, strPath.toUtf8()); + qDebug().noquote() << QString("%1 = %2").arg(qVarName).arg(strPath); + } + } +} + +// Initializes "GUI" objects +static void initGui(GuiApplication* guiApp) +{ + if (!guiApp) + return; + + setFunctionCreateGraphicsDriver([]() -> Handle_Graphic3d_GraphicDriver { + return new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create()); + }); + guiApp->addGraphicsObjectDriver(std::make_unique()); + guiApp->addGraphicsObjectDriver(std::make_unique()); + guiApp->addGraphicsObjectDriver(std::make_unique()); +} + +bool cliExcludingSettingPredicate(const Property& prop) +{ + return AppModule::excludeSettingPredicate(prop) + || prop.dynTypeName() == PropertyRecentFiles::TypeName; +} + +// Initializes and runs Mayo application +static int runApp(QCoreApplication* qtApp) +{ + const CommandLineArguments args = processCommandLine(); + + // Helper function: print critical message and exit application with code failure + auto fnCriticalExit = [](const QString& msg) { + qCritical().noquote() << msg; + std::exit(EXIT_FAILURE); + }; + + // Helper function: load application settings from INI file(if provided) otherwise use the + // application regular storage(eg registry on Windows) + auto fnLoadAppSettings = [&](Settings* appSettings) { + if (args.filepathSettings.empty()) { + appSettings->load(); + } + else { + const QString strFilepathSettings = filepathTo(args.filepathSettings); + if (!filepathIsRegularFile(args.filepathSettings)) + fnCriticalExit(Main::tr("Failed to load application settings file [path=%1]").arg(strFilepathSettings)); + + QSettingsStorage fileSettings(strFilepathSettings, QSettings::IniFormat); + appSettings->loadFrom(fileSettings, &cliExcludingSettingPredicate); + } + }; + + // Signals + setGlobalSignalThreadHelper(std::make_unique()); + + // Message logging + LogMessageHandler::instance().enableDebugLogs(args.includeDebugLogs); + LogMessageHandler::instance().setOutputFilePath(args.filepathLog); + + // Initialize AppModule + auto appModule = AppModule::get(); + appModule->settings()->setStorage(std::make_unique()); + { + // Load translation files + auto fnLoadQmFile = [=](const QString& qmFilePath) { + auto translator = new QTranslator(qtApp); + if (translator->load(qmFilePath)) + qtApp->installTranslator(translator); + else + qWarning() << Main::tr("Failed to load translation file [path=%1]").arg(qmFilePath); + }; + const QString appLangCode = appModule->languageCode(); + fnLoadQmFile(QString(":/i18n/mayo_%1.qm").arg(appLangCode)); + fnLoadQmFile(QString(":/i18n/qtbase_%1.qm").arg(appLangCode)); + } + + // Initialize Base application + auto app = Application::instance().get(); + app->addTranslator(&qtAppTranslate); // Set Qt i18n backend + initOpenCascadeEnvironment("opencascade.conf"); + + // Initialize Gui application + auto guiApp = new GuiApplication(app); + initGui(guiApp); + + // Register I/O objects + IO::System* ioSystem = appModule->ioSystem(); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(IO::AssimpFactoryReader::create()); + ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(IO::GmioFactoryWriter::create()); + ioSystem->addFactoryWriter(std::make_unique(guiApp)); + IO::addPredefinedFormatProbes(ioSystem); + appModule->properties()->IO_bindParameters(ioSystem); + appModule->properties()->retranslate(); + +#if 0 + // Register library infos + CommandSystemInformation::addLibraryInfo( + IO::AssimpLib::strName(), IO::AssimpLib::strVersion(), IO::AssimpLib::strVersionDetails() + ); + CommandSystemInformation::addLibraryInfo( + IO::GmioLib::strName(), IO::GmioLib::strVersion(), IO::GmioLib::strVersionDetails() + ); + + // Process CLI + if (args.showSystemInformation) { + CommandSystemInformation cmdSysInfo(nullptr); + cmdSysInfo.execute(); + return qtApp->exec(); + } +#endif + + if (!args.listFilepathToExport.empty()) { + if (args.listFilepathToOpen.empty()) + fnCriticalExit(Main::tr("No input files -> nothing to export")); + + guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed + appModule->settings()->resetAll(); + fnLoadAppSettings(appModule->settings()); + QTimer::singleShot(0, qtApp, [=]{ + CliExportArgs cliArgs; + cliArgs.progressReport = args.cliProgressReport; + cliArgs.filesToOpen = args.listFilepathToOpen; + cliArgs.filesToExport = args.listFilepathToExport; + cli_asyncExportDocuments(app, cliArgs, [=](int retcode) { qtApp->exit(retcode); }); + }); + return qtApp->exec(); + } + + return 0; +} + +} // namespace Mayo + +int main(int argc, char* argv[]) +{ + qInstallMessageHandler(&Mayo::LogMessageHandler::qtHandler); + + // Configure and create Qt application object + QCoreApplication::setOrganizationName("Fougue Ltd"); + QCoreApplication::setOrganizationDomain("www.fougue.pro"); + QCoreApplication::setApplicationName("MayoConv"); + QCoreApplication::setApplicationVersion(QString::fromUtf8(Mayo::strVersion)); + QCoreApplication app(argc, argv); + // TODO Read settings from Mayo application + return Mayo::runApp(&app); +} diff --git a/src/graphics/graphics_utils.cpp b/src/graphics/graphics_utils.cpp index e33b1adb..f44e89ce 100644 --- a/src/graphics/graphics_utils.cpp +++ b/src/graphics/graphics_utils.cpp @@ -6,6 +6,7 @@ #include "graphics_utils.h" #include "../base/bnd_utils.h" +#include "../base/global.h" #include "../base/math_utils.h" #include "../base/tkernel_utils.h" @@ -18,6 +19,7 @@ #include #include +#include namespace Mayo { @@ -204,6 +206,15 @@ int GraphicsUtils::AspectWindow_height(const Handle_Aspect_Window& wnd) return h; } +Handle_Aspect_DisplayConnection GraphicsUtils::AspectDisplayConnection_create() +{ +#if (!defined(MAYO_OS_WINDOWS) && (!defined(MAYO_OS_MAC) || defined(MACOSX_USE_GLX))) + return new Aspect_DisplayConnection(std::getenv("DISPLAY")); +#else + return new Aspect_DisplayConnection; +#endif +} + void GraphicsUtils::Gfx3dClipPlane_setCappingHatch( const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch) { diff --git a/src/graphics/graphics_utils.h b/src/graphics/graphics_utils.h index 494a3ea8..b0913aff 100644 --- a/src/graphics/graphics_utils.h +++ b/src/graphics/graphics_utils.h @@ -9,6 +9,7 @@ #include "graphics_object_ptr.h" #include #include +#include #include #include #include @@ -50,6 +51,7 @@ struct GraphicsUtils { static int AspectWindow_width(const Handle_Aspect_Window& wnd); static int AspectWindow_height(const Handle_Aspect_Window& wnd); + static Handle_Aspect_DisplayConnection AspectDisplayConnection_create(); static void Gfx3dClipPlane_setCappingHatch( const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch diff --git a/src/app/qsettings_storage.cpp b/src/qtbackend/qsettings_storage.cpp similarity index 97% rename from src/app/qsettings_storage.cpp rename to src/qtbackend/qsettings_storage.cpp index fd345f0d..69c27c4a 100644 --- a/src/app/qsettings_storage.cpp +++ b/src/qtbackend/qsettings_storage.cpp @@ -6,8 +6,8 @@ #include "qsettings_storage.h" -#include "qstring_conv.h" -#include "qtcore_utils.h" +#include "../qtcommon/qstring_conv.h" +#include "../qtcommon/qtcore_utils.h" namespace Mayo { diff --git a/src/app/qsettings_storage.h b/src/qtbackend/qsettings_storage.h similarity index 100% rename from src/app/qsettings_storage.h rename to src/qtbackend/qsettings_storage.h diff --git a/src/qtbackend/qt_animation_backend.cpp b/src/qtbackend/qt_animation_backend.cpp new file mode 100644 index 00000000..5b28d938 --- /dev/null +++ b/src/qtbackend/qt_animation_backend.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "qt_animation_backend.h" + +#include "../base/unit_system.h" + +namespace Mayo { + +QtAnimationBackend::QtAnimationBackend(QEasingCurve::Type easingType) + : m_easingCurve(easingType) +{ +} + +void QtAnimationBackend::setDuration(QuantityTime t) +{ + m_impl.m_duration_ms = UnitSystem::milliseconds(t); +} + +bool QtAnimationBackend::isRunning() const +{ + return m_impl.state() == QAbstractAnimation::Running; +} + +void QtAnimationBackend::start() +{ + m_impl.start(QAbstractAnimation::KeepWhenStopped); +} + +void QtAnimationBackend::stop() +{ + m_impl.stop(); +} + +double QtAnimationBackend::valueForProgress(double p) const +{ + return m_easingCurve.valueForProgress(p); +} + +void QtAnimationBackend::setTimerCallback(std::function fn) +{ + m_impl.m_callback = std::move(fn); +} + +int QtAnimationBackend::AnimationImpl::duration() const +{ + return static_cast(m_duration_ms); +} + +void QtAnimationBackend::AnimationImpl::updateCurrentTime(int currentTime) +{ + m_callback(currentTime * Quantity_Millisecond); +} + +} // namespace Mayo diff --git a/src/qtbackend/qt_animation_backend.h b/src/qtbackend/qt_animation_backend.h new file mode 100644 index 00000000..5e874157 --- /dev/null +++ b/src/qtbackend/qt_animation_backend.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../gui/v3d_view_camera_animation.h" + +#include +#include + +namespace Mayo { + +// Provides implementation of IAnimationBackend based on QAbstractAnimation +class QtAnimationBackend : public IAnimationBackend { +public: + QtAnimationBackend(QEasingCurve::Type easingType = QEasingCurve::Linear); + + void setDuration(QuantityTime t) override; + + bool isRunning() const override; + + void start() override; + void stop() override; + + double valueForProgress(double p) const override; + + void setTimerCallback(std::function fn) override; + +private: + class AnimationImpl : public QAbstractAnimation { + public: + double m_duration_ms = 1000.; + std::function m_callback; + + int duration() const override; + + protected: + void updateCurrentTime(int currentTime) override; + }; + + AnimationImpl m_impl; + QEasingCurve m_easingCurve; +}; + +} // namespace Mayo diff --git a/src/qtbackend/qt_app_translator.cpp b/src/qtbackend/qt_app_translator.cpp new file mode 100644 index 00000000..073c160f --- /dev/null +++ b/src/qtbackend/qt_app_translator.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "qt_app_translator.h" + +#include +#include + +#include + +namespace Mayo { + +// Function called by the Application i18n system, see Application::addTranslator() +std::string_view qtAppTranslate(const TextId& text, int n) +{ + const QString qstr = QCoreApplication::translate(text.trContext.data(), text.key.data(), nullptr, n); + auto qstrHash = qHash(qstr); + static std::unordered_map mapStr; + static QReadWriteLock mapStrLock; + { + QReadLocker locker(&mapStrLock); + auto it = mapStr.find(qstrHash); + if (it != mapStr.cend()) + return it->second; + } + + QWriteLocker locker(&mapStrLock); + auto [it, ok] = mapStr.insert({ qstrHash, qstr.toStdString() }); + return ok ? it->second : std::string_view{}; +} + +} // namespace Mayo diff --git a/src/qtbackend/qt_app_translator.h b/src/qtbackend/qt_app_translator.h new file mode 100644 index 00000000..ef4ff5a3 --- /dev/null +++ b/src/qtbackend/qt_app_translator.h @@ -0,0 +1,18 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/text_id.h" + +#include + +namespace Mayo { + +// Function called by the Application i18n system, see Application::addTranslator() +std::string_view qtAppTranslate(const TextId& text, int n); + +} // namespace Mayo diff --git a/src/qtbackend/qt_signal_thread_helper.cpp b/src/qtbackend/qt_signal_thread_helper.cpp new file mode 100644 index 00000000..06cf28ec --- /dev/null +++ b/src/qtbackend/qt_signal_thread_helper.cpp @@ -0,0 +1,26 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "qt_signal_thread_helper.h" + +#include + +namespace Mayo { + +std::any QtSignalThreadHelper::getCurrentThreadContext() +{ + // Note: thread_local implies "static" + // See https://en.cppreference.com/w/cpp/language/storage_duration + thread_local QObject obj; + return &obj; +} + +void QtSignalThreadHelper::execInThread(const std::any& context, const std::function& fn) +{ + QTimer::singleShot(0, std::any_cast(context), fn); +} + +} // namespace Mayo diff --git a/src/qtbackend/qt_signal_thread_helper.h b/src/qtbackend/qt_signal_thread_helper.h new file mode 100644 index 00000000..dadbbe95 --- /dev/null +++ b/src/qtbackend/qt_signal_thread_helper.h @@ -0,0 +1,21 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/signal.h" + +namespace Mayo { + +// Provides handling of signal/slot thread mismatch with the help of Qt +// There will be a single QObject created per thread, so it can be used to enqueue slot functions +class QtSignalThreadHelper : public ISignalThreadHelper { +public: + std::any getCurrentThreadContext() override; + void execInThread(const std::any& context, const std::function& fn) override; +}; + +} // namespace Mayo diff --git a/src/app/filepath_conv.h b/src/qtcommon/filepath_conv.h similarity index 100% rename from src/app/filepath_conv.h rename to src/qtcommon/filepath_conv.h diff --git a/src/qtcommon/log_message_handler.cpp b/src/qtcommon/log_message_handler.cpp new file mode 100644 index 00000000..e34ad0e6 --- /dev/null +++ b/src/qtcommon/log_message_handler.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "log_message_handler.h" +#include "qstring_conv.h" + +#include + +namespace Mayo { + +LogMessageHandler& LogMessageHandler::instance() +{ + static LogMessageHandler object; + return object; +} + +void LogMessageHandler::enableDebugLogs(bool on) +{ + m_enableDebugLogs = on; +} + +void LogMessageHandler::setOutputFilePath(const FilePath& fp) +{ + m_outputFilePath = fp; + if (!fp.empty()) + m_outputFile.open(fp, std::ios::out | std::ios::app); + else + m_outputFile.close(); +} + +std::ostream& LogMessageHandler::outputStream(QtMsgType type) +{ + if (!m_outputFilePath.empty() && m_outputFile.is_open()) + return m_outputFile; + + if (type == QtDebugMsg || type == QtInfoMsg) + return std::cout; + + return std::cerr; +} + +void LogMessageHandler::qtHandler(QtMsgType type, const QMessageLogContext& /*context*/, const QString& msg) +{ + const std::string localMsg = consoleToPrintable(msg); + std::ostream& outs = LogMessageHandler::instance().outputStream(type); + switch (type) { + case QtDebugMsg: + if (LogMessageHandler::instance().m_enableDebugLogs) { + outs << "DEBUG: " << localMsg << std::endl; + } + break; + case QtInfoMsg: + outs << "INFO: " << localMsg << std::endl; + break; + case QtWarningMsg: + outs << "WARNING: " << localMsg << std::endl; + break; + case QtCriticalMsg: + outs << "CRITICAL: " << localMsg << std::endl; + break; + case QtFatalMsg: + outs << "FATAL: " << localMsg << std::endl; + break; + } +} + +} // namespace Mayo diff --git a/src/qtcommon/log_message_handler.h b/src/qtcommon/log_message_handler.h new file mode 100644 index 00000000..d23bc8c5 --- /dev/null +++ b/src/qtcommon/log_message_handler.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/filepath.h" + +#include +#include + +#include + +namespace Mayo { + +// Provides customization of Qt message handler +class LogMessageHandler { +public: + static LogMessageHandler& instance(); + + void enableDebugLogs(bool on); + void setOutputFilePath(const FilePath& fp); + + std::ostream& outputStream(QtMsgType type); + + // Function called for Qt message handling + static void qtHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); + +private: + LogMessageHandler() = default; + + FilePath m_outputFilePath; + std::ofstream m_outputFile; + bool m_enableDebugLogs = true; +}; +} // namespace Mayo diff --git a/src/qtcommon/qstring_conv.cpp b/src/qtcommon/qstring_conv.cpp new file mode 100644 index 00000000..817c9e95 --- /dev/null +++ b/src/qtcommon/qstring_conv.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** Copyright (c) 2021, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "../base/global.h" +#ifdef MAYO_OS_WINDOWS +# include +#endif + +#include "qstring_conv.h" + +namespace Mayo { + +std::string consoleToPrintable(const QString& str) +{ +#ifdef MAYO_OS_WINDOWS + const auto codepage = GetConsoleOutputCP(); + const wchar_t* source = reinterpret_cast(str.utf16()); + const int dstSize = WideCharToMultiByte(codepage, 0, source, -1, nullptr, 0, nullptr, nullptr); + std::string dst; + dst.resize(dstSize + 1); + WideCharToMultiByte(codepage, 0, source, -1, dst.data(), dstSize, nullptr, nullptr); + dst.back() = '\0'; + return dst; +#else + return str.toStdString(); // utf8 +#endif +} + +std::string consoleToPrintable(std::string_view str) +{ +#ifdef MAYO_OS_WINDOWS + return consoleToPrintable(to_QString(str)); +#else + return std::string(str); // utf8 +#endif +} + +} // namespace Mayo diff --git a/src/app/qstring_conv.h b/src/qtcommon/qstring_conv.h similarity index 96% rename from src/app/qstring_conv.h rename to src/qtcommon/qstring_conv.h index 85ce484a..c3311a7f 100644 --- a/src/app/qstring_conv.h +++ b/src/qtcommon/qstring_conv.h @@ -19,6 +19,10 @@ QString to_QString(const StringType& str) { return string_conv(str); } +// Returns 'str' converted to a "guaranteed" printable string +std::string consoleToPrintable(const QString& str); +std::string consoleToPrintable(std::string_view str/*utf8*/); + // -- // -- X -> QString // -- diff --git a/src/app/qtcore_hfuncs.h b/src/qtcommon/qtcore_hfuncs.h similarity index 100% rename from src/app/qtcore_hfuncs.h rename to src/qtcommon/qtcore_hfuncs.h diff --git a/src/app/qtcore_utils.h b/src/qtcommon/qtcore_utils.h similarity index 100% rename from src/app/qtcore_utils.h rename to src/qtcommon/qtcore_utils.h diff --git a/tests/test_app.cpp b/tests/test_app.cpp index e7c06331..a0e22498 100644 --- a/tests/test_app.cpp +++ b/tests/test_app.cpp @@ -11,15 +11,17 @@ #include "test_app.h" -#include "../src/app/filepath_conv.h" -#include "../src/app/qstring_conv.h" +#include "../src/app/app_module.h" #include "../src/app/qstring_utils.h" #include "../src/app/qtgui_utils.h" #include "../src/app/recent_files.h" #include "../src/app/theme.h" #include "../src/io_occ/io_occ.h" +#include "../src/qtcommon/filepath_conv.h" +#include "../src/qtcommon/qstring_conv.h" #include +#include #include #include #include @@ -29,6 +31,37 @@ namespace Mayo { +namespace { + +QPixmap createColorPixmap(const QColor& color, const QSize& size = QSize(64, 64)) +{ + QPixmap pix(size); + QPainter painter(&pix); + painter.fillRect(0, 0, size.width(), size.height(), color); + return pix; +} + +FilePath temporaryFilePath() +{ + QTemporaryFile file; + file.open(); + return filepathFrom(QFileInfo(file)); +} + +RecentFile createRecentFile(const QPixmap& thumbnail) +{ + QTemporaryFile file; + file.open(); + RecentFile rf; + rf.filepath = filepathFrom(QFileInfo(file)); + rf.thumbnailTimestamp = RecentFile::timestampLastModified(rf.filepath); + rf.thumbnail.imageData = QtGuiUtils::toQByteArray(thumbnail); + rf.thumbnail.imageCacheKey = thumbnail.cacheKey(); + return rf; +} + +} // namespace + void TestApp::FilePathConv_test() { const char strTestPath[] = "../as1-oc-214 - 測試文件.stp"; @@ -121,35 +154,18 @@ void TestApp::QStringUtils_text_test_data() void TestApp::RecentFiles_test() { - auto fnColorPixmap = [](const QColor& color) { - QPixmap pix(64, 64); - QPainter painter(&pix); - painter.fillRect(0, 0, 64, 64, color); - return pix; - }; - - auto fnCreateRecentFile = [](const QPixmap& thumbnail) { - QTemporaryFile file; - file.open(); - RecentFile rf; - rf.filepath = filepathFrom(QFileInfo(file)); - rf.thumbnailTimestamp = RecentFile::timestampLastModified(rf.filepath); - rf.thumbnail = thumbnail; - return rf; - }; - RecentFiles recentFiles; - recentFiles.push_back(fnCreateRecentFile(fnColorPixmap(Qt::blue))); - recentFiles.push_back(fnCreateRecentFile(fnColorPixmap(Qt::white))); - recentFiles.push_back(fnCreateRecentFile(fnColorPixmap(Qt::red))); + recentFiles.push_back(createRecentFile(createColorPixmap(Qt::blue))); + recentFiles.push_back(createRecentFile(createColorPixmap(Qt::white))); + recentFiles.push_back(createRecentFile(createColorPixmap(Qt::red))); RecentFiles recentFiles_read; { QByteArray data; QDataStream wstream(&data, QIODevice::WriteOnly); - wstream << recentFiles; + AppModule::writeRecentFiles(wstream, recentFiles); QDataStream rstream(&data, QIODevice::ReadOnly); - rstream >> recentFiles_read; + AppModule::readRecentFiles(rstream, &recentFiles_read); } QCOMPARE(recentFiles.size(), recentFiles_read.size()); @@ -159,17 +175,38 @@ void TestApp::RecentFiles_test() QCOMPARE(lhs.filepath, rhs.filepath); QVERIFY(lhs.thumbnailTimestamp != -1); QCOMPARE(lhs.thumbnailTimestamp, rhs.thumbnailTimestamp); - QCOMPARE(lhs.thumbnail.size(), rhs.thumbnail.size()); - const QImage lhsImg = lhs.thumbnail.toImage(); - const QImage rhsImg = rhs.thumbnail.toImage(); - for (int i = 0; i < lhs.thumbnail.width(); ++i) { - for (int j = 0; j < lhs.thumbnail.height(); ++j) { + QCOMPARE(lhs.thumbnail.imageData.size(), rhs.thumbnail.imageData.size()); + const QPixmap lhsPix = QtGuiUtils::toQPixmap(lhs.thumbnail.imageData); + const QPixmap rhsPix = QtGuiUtils::toQPixmap(rhs.thumbnail.imageData); + const QImage lhsImg = lhsPix.toImage(); + const QImage rhsImg = rhsPix.toImage(); + for (int i = 0; i < lhsPix.width(); ++i) { + for (int j = 0; j < lhsPix.height(); ++j) { QCOMPARE(lhsImg.pixel(i, j), rhsImg.pixel(i, j)); } } // endfor } } +void TestApp::RecentFiles_QPixmap_test() +{ + QByteArray bytes; + { + QDataStream stream(&bytes, QIODevice::WriteOnly); + stream << uint32_t(1); + stream << filepathTo(temporaryFilePath()); + stream << QPixmap();//createColorPixmap(Qt::blue); + stream << qint64(1); + } + + { + RecentFiles recentFiles; + QDataStream stream(bytes); + // Should not crash + AppModule::readRecentFiles(stream, &recentFiles); + } +} + void TestApp::StringConv_test() { const QString text = "test_éç²µ§_测试_Тест"; diff --git a/tests/test_app.h b/tests/test_app.h index f4cb5353..6ba45d09 100644 --- a/tests/test_app.h +++ b/tests/test_app.h @@ -22,6 +22,7 @@ private slots: void QStringUtils_text_test_data(); void RecentFiles_test(); + void RecentFiles_QPixmap_test(); void StringConv_test();