mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Add macOS support and CI
This commit is contained in:
71
.github/workflows/build.yml
vendored
71
.github/workflows/build.yml
vendored
@@ -3,7 +3,7 @@ name: Build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- "**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
@@ -22,9 +22,9 @@ jobs:
|
|||||||
- name: Install Qt6 and MinGW
|
- name: Install Qt6 and MinGW
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: '6.8.1'
|
version: "6.8.1"
|
||||||
arch: 'win64_mingw'
|
arch: "win64_mingw"
|
||||||
tools: 'tools_mingw1310,qt.tools.win64_mingw1310'
|
tools: "tools_mingw1310,qt.tools.win64_mingw1310"
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
- name: Install Qt6
|
- name: Install Qt6
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: '6.8.1'
|
version: "6.8.1"
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -141,9 +141,66 @@ jobs:
|
|||||||
name: Reclass-linux64-qt6
|
name: Reclass-linux64-qt6
|
||||||
path: Reclass-linux64-qt6.AppImage
|
path: Reclass-linux64-qt6.AppImage
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: macos-15
|
||||||
|
qt_arch: clang_arm64
|
||||||
|
artifact_name: Reclass-macos-arm64-qt6
|
||||||
|
zip_name: Reclass-macos-arm64-qt6.zip
|
||||||
|
- os: macos-15-intel
|
||||||
|
qt_arch: clang_64
|
||||||
|
artifact_name: Reclass-macos-x86_64-qt6
|
||||||
|
zip_name: Reclass-macos-x86_64-qt6.zip
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
brew update
|
||||||
|
brew install cmake ninja qt
|
||||||
|
|
||||||
|
- name: Configure Qt paths
|
||||||
|
run: |
|
||||||
|
QT_PREFIX="$(brew --prefix qt)"
|
||||||
|
echo "QT_PREFIX=$QT_PREFIX" >> "$GITHUB_ENV"
|
||||||
|
echo "PATH=$QT_PREFIX/bin:$PATH" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_UI_TESTS=OFF -DCMAKE_PREFIX_PATH="$QT_PREFIX"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build build
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: ctest --test-dir build --output-on-failure
|
||||||
|
|
||||||
|
- name: Package app zip
|
||||||
|
run: |
|
||||||
|
MACDEPLOYQT_BIN="$QT_PREFIX/bin/macdeployqt"
|
||||||
|
if [ ! -x "$MACDEPLOYQT_BIN" ]; then
|
||||||
|
MACDEPLOYQT_BIN=$(which macdeployqt 2>/dev/null || find "$RUNNER_WORKSPACE" -name macdeployqt -path "*/bin/*" | head -1)
|
||||||
|
fi
|
||||||
|
echo "Found macdeployqt at: $MACDEPLOYQT_BIN"
|
||||||
|
"$MACDEPLOYQT_BIN" build/Reclass.app -always-overwrite
|
||||||
|
codesign --force --deep --sign - build/Reclass.app
|
||||||
|
ditto -c -k --sequesterRsrc --keepParent build/Reclass.app "${{ matrix.zip_name }}"
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.artifact_name }}
|
||||||
|
path: ${{ matrix.zip_name }}
|
||||||
|
|
||||||
release:
|
release:
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
needs: [windows, linux]
|
needs: [windows, linux, macos]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -168,5 +225,7 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
artifacts/Reclass-win64-qt6/Reclass-win64-qt6.zip
|
artifacts/Reclass-win64-qt6/Reclass-win64-qt6.zip
|
||||||
artifacts/Reclass-linux64-qt6/Reclass-linux64-qt6.AppImage
|
artifacts/Reclass-linux64-qt6/Reclass-linux64-qt6.AppImage
|
||||||
|
artifacts/Reclass-macos-arm64-qt6/Reclass-macos-arm64-qt6.zip
|
||||||
|
artifacts/Reclass-macos-x86_64-qt6/Reclass-macos-x86_64-qt6.zip
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ CMakeUserPresets.json
|
|||||||
plugins/RcNetPluginCompatLayer/bridge/obj
|
plugins/RcNetPluginCompatLayer/bridge/obj
|
||||||
plugins/RcNetPluginCompatLayer/bridge/bin
|
plugins/RcNetPluginCompatLayer/bridge/bin
|
||||||
.cache
|
.cache
|
||||||
|
*.DS_Store
|
||||||
|
|||||||
210
CMakeLists.txt
210
CMakeLists.txt
@@ -113,6 +113,8 @@ add_executable(Reclass
|
|||||||
src/optionsdialog.cpp
|
src/optionsdialog.cpp
|
||||||
src/titlebar.h
|
src/titlebar.h
|
||||||
src/titlebar.cpp
|
src/titlebar.cpp
|
||||||
|
src/macos_titlebar.h
|
||||||
|
$<$<PLATFORM_ID:Darwin>:src/macos_titlebar.mm>
|
||||||
src/mcp/mcp_bridge.h
|
src/mcp/mcp_bridge.h
|
||||||
src/mcp/mcp_bridge.cpp
|
src/mcp/mcp_bridge.cpp
|
||||||
src/addressparser.h
|
src/addressparser.h
|
||||||
@@ -124,6 +126,16 @@ add_executable(Reclass
|
|||||||
$<$<PLATFORM_ID:Windows>:src/app.rc>
|
$<$<PLATFORM_ID:Windows>:src/app.rc>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
set_target_properties(Reclass PROPERTIES
|
||||||
|
MACOSX_BUNDLE TRUE
|
||||||
|
MACOSX_BUNDLE_ICON_FILE "class.icns"
|
||||||
|
)
|
||||||
|
target_sources(Reclass PRIVATE src/icons/class.icns)
|
||||||
|
set_source_files_properties(src/icons/class.icns
|
||||||
|
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(Reclass PRIVATE src third_party/fadec)
|
target_include_directories(Reclass PRIVATE src third_party/fadec)
|
||||||
|
|
||||||
target_link_libraries(Reclass PRIVATE
|
target_link_libraries(Reclass PRIVATE
|
||||||
@@ -150,6 +162,12 @@ foreach(_tf ${_theme_files})
|
|||||||
configure_file(${_tf} "${CMAKE_BINARY_DIR}/themes/${_name}" COPYONLY)
|
configure_file(${_tf} "${CMAKE_BINARY_DIR}/themes/${_name}" COPYONLY)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
target_sources(Reclass PRIVATE ${_theme_files})
|
||||||
|
set_source_files_properties(${_theme_files}
|
||||||
|
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/themes")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Copy example .rcx files to build directory
|
# Copy example .rcx files to build directory
|
||||||
file(GLOB _example_files "${CMAKE_SOURCE_DIR}/src/examples/*.rcx")
|
file(GLOB _example_files "${CMAKE_SOURCE_DIR}/src/examples/*.rcx")
|
||||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/examples")
|
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/examples")
|
||||||
@@ -158,6 +176,12 @@ foreach(_ef ${_example_files})
|
|||||||
configure_file(${_ef} "${CMAKE_BINARY_DIR}/examples/${_name}" COPYONLY)
|
configure_file(${_ef} "${CMAKE_BINARY_DIR}/examples/${_name}" COPYONLY)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
target_sources(Reclass PRIVATE ${_example_files})
|
||||||
|
set_source_files_properties(${_example_files}
|
||||||
|
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/examples")
|
||||||
|
endif()
|
||||||
|
|
||||||
include(deploy)
|
include(deploy)
|
||||||
|
|
||||||
|
|
||||||
@@ -298,178 +322,178 @@ if(BUILD_TESTING)
|
|||||||
option(BUILD_UI_TESTS "Build tests that require a display (Qt Widgets)" ON)
|
option(BUILD_UI_TESTS "Build tests that require a display (Qt Widgets)" ON)
|
||||||
if(BUILD_UI_TESTS)
|
if(BUILD_UI_TESTS)
|
||||||
|
|
||||||
add_executable(test_controller tests/test_controller.cpp
|
add_executable(test_controller tests/test_controller.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
src/typeselectorpopup.cpp
|
src/typeselectorpopup.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(test_controller PRIVATE src third_party/fadec)
|
target_include_directories(test_controller PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_controller PRIVATE
|
target_link_libraries(test_controller PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(test_controller PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(test_controller PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME test_controller COMMAND test_controller)
|
add_test(NAME test_controller COMMAND test_controller)
|
||||||
|
|
||||||
add_executable(test_validation tests/test_validation.cpp
|
add_executable(test_validation tests/test_validation.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
src/typeselectorpopup.cpp
|
src/typeselectorpopup.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(test_validation PRIVATE src third_party/fadec)
|
target_include_directories(test_validation PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_validation PRIVATE
|
target_link_libraries(test_validation PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(test_validation PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(test_validation PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME test_validation COMMAND test_validation)
|
add_test(NAME test_validation COMMAND test_validation)
|
||||||
|
|
||||||
add_executable(test_context_menu tests/test_context_menu.cpp
|
add_executable(test_context_menu tests/test_context_menu.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
src/typeselectorpopup.cpp
|
src/typeselectorpopup.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(test_context_menu PRIVATE src third_party/fadec)
|
target_include_directories(test_context_menu PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_context_menu PRIVATE
|
target_link_libraries(test_context_menu PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(test_context_menu PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(test_context_menu PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME test_context_menu COMMAND test_context_menu)
|
add_test(NAME test_context_menu COMMAND test_context_menu)
|
||||||
|
|
||||||
add_executable(test_source_management tests/test_source_management.cpp
|
add_executable(test_source_management tests/test_source_management.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
src/typeselectorpopup.cpp
|
src/typeselectorpopup.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(test_source_management PRIVATE src third_party/fadec)
|
target_include_directories(test_source_management PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_source_management PRIVATE
|
target_link_libraries(test_source_management PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(test_source_management PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(test_source_management PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME test_source_management COMMAND test_source_management)
|
add_test(NAME test_source_management COMMAND test_source_management)
|
||||||
|
|
||||||
add_executable(test_editor tests/test_editor.cpp
|
add_executable(test_editor tests/test_editor.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp
|
||||||
src/providerregistry.cpp
|
src/providerregistry.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(test_editor PRIVATE src third_party/fadec)
|
target_include_directories(test_editor PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_editor PRIVATE
|
target_link_libraries(test_editor PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
add_test(NAME test_editor COMMAND test_editor)
|
add_test(NAME test_editor COMMAND test_editor)
|
||||||
|
|
||||||
add_executable(test_rendered_view tests/test_rendered_view.cpp
|
add_executable(test_rendered_view tests/test_rendered_view.cpp
|
||||||
src/generator.cpp src/compose.cpp src/format.cpp src/addressparser.cpp)
|
src/generator.cpp src/compose.cpp src/format.cpp src/addressparser.cpp)
|
||||||
target_include_directories(test_rendered_view PRIVATE src)
|
target_include_directories(test_rendered_view PRIVATE src)
|
||||||
target_link_libraries(test_rendered_view PRIVATE
|
target_link_libraries(test_rendered_view PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
add_test(NAME test_rendered_view COMMAND test_rendered_view)
|
add_test(NAME test_rendered_view COMMAND test_rendered_view)
|
||||||
|
|
||||||
add_executable(test_new_features tests/test_new_features.cpp
|
add_executable(test_new_features tests/test_new_features.cpp
|
||||||
src/generator.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/generator.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/editor.cpp src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/editor.cpp src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
src/typeselectorpopup.cpp
|
src/typeselectorpopup.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(test_new_features PRIVATE src third_party/fadec)
|
target_include_directories(test_new_features PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_new_features PRIVATE
|
target_link_libraries(test_new_features PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(test_new_features PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(test_new_features PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME test_new_features COMMAND test_new_features)
|
add_test(NAME test_new_features COMMAND test_new_features)
|
||||||
|
|
||||||
add_executable(test_type_selector tests/test_type_selector.cpp
|
add_executable(test_type_selector tests/test_type_selector.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
src/typeselectorpopup.cpp
|
src/typeselectorpopup.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(test_type_selector PRIVATE src third_party/fadec)
|
target_include_directories(test_type_selector PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_type_selector PRIVATE
|
target_link_libraries(test_type_selector PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(test_type_selector PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(test_type_selector PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME test_type_selector COMMAND test_type_selector)
|
add_test(NAME test_type_selector COMMAND test_type_selector)
|
||||||
|
|
||||||
add_executable(test_type_visibility tests/test_type_visibility.cpp
|
add_executable(test_type_visibility tests/test_type_visibility.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
src/typeselectorpopup.cpp
|
src/typeselectorpopup.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(test_type_visibility PRIVATE src third_party/fadec)
|
target_include_directories(test_type_visibility PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_type_visibility PRIVATE
|
target_link_libraries(test_type_visibility PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(test_type_visibility PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(test_type_visibility PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME test_type_visibility COMMAND test_type_visibility)
|
add_test(NAME test_type_visibility COMMAND test_type_visibility)
|
||||||
|
|
||||||
add_executable(test_options_dialog tests/test_options_dialog.cpp
|
add_executable(test_options_dialog tests/test_options_dialog.cpp
|
||||||
src/optionsdialog.cpp src/themes/theme.cpp src/themes/thememanager.cpp)
|
src/optionsdialog.cpp src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||||
target_include_directories(test_options_dialog PRIVATE src)
|
target_include_directories(test_options_dialog PRIVATE src)
|
||||||
target_link_libraries(test_options_dialog PRIVATE ${QT}::Widgets ${QT}::Test)
|
target_link_libraries(test_options_dialog PRIVATE ${QT}::Widgets ${QT}::Test)
|
||||||
add_test(NAME test_options_dialog COMMAND test_options_dialog)
|
add_test(NAME test_options_dialog COMMAND test_options_dialog)
|
||||||
|
|
||||||
add_executable(test_source_provider tests/test_source_provider.cpp
|
add_executable(test_source_provider tests/test_source_provider.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
src/typeselectorpopup.cpp
|
src/typeselectorpopup.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS}
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS}
|
||||||
src/resources.qrc)
|
src/resources.qrc)
|
||||||
target_include_directories(test_source_provider PRIVATE src third_party/fadec)
|
target_include_directories(test_source_provider PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(test_source_provider PRIVATE
|
target_link_libraries(test_source_provider PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test ${QT}::Svg
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test ${QT}::Svg
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(test_source_provider PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(test_source_provider PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME test_source_provider COMMAND test_source_provider)
|
add_test(NAME test_source_provider COMMAND test_source_provider)
|
||||||
|
|
||||||
add_executable(test_scanner_ui tests/test_scanner_ui.cpp
|
add_executable(test_scanner_ui tests/test_scanner_ui.cpp
|
||||||
src/scanner.cpp src/scannerpanel.cpp src/addressparser.cpp
|
src/scanner.cpp src/scannerpanel.cpp src/addressparser.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp)
|
src/themes/theme.cpp src/themes/thememanager.cpp)
|
||||||
target_include_directories(test_scanner_ui PRIVATE src)
|
target_include_directories(test_scanner_ui PRIVATE src)
|
||||||
target_link_libraries(test_scanner_ui PRIVATE
|
target_link_libraries(test_scanner_ui PRIVATE
|
||||||
${QT}::Widgets ${QT}::Concurrent ${QT}::Test)
|
${QT}::Widgets ${QT}::Concurrent ${QT}::Test)
|
||||||
add_test(NAME test_scanner_ui COMMAND test_scanner_ui)
|
add_test(NAME test_scanner_ui COMMAND test_scanner_ui)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_executable(test_windbg_provider tests/test_windbg_provider.cpp
|
add_executable(test_windbg_provider tests/test_windbg_provider.cpp
|
||||||
plugins/WinDbgMemory/WinDbgMemoryPlugin.cpp
|
plugins/WinDbgMemory/WinDbgMemoryPlugin.cpp
|
||||||
src/scanner.cpp)
|
src/scanner.cpp)
|
||||||
target_include_directories(test_windbg_provider PRIVATE src plugins/WinDbgMemory)
|
target_include_directories(test_windbg_provider PRIVATE src plugins/WinDbgMemory)
|
||||||
target_link_libraries(test_windbg_provider PRIVATE
|
target_link_libraries(test_windbg_provider PRIVATE
|
||||||
${QT}::Widgets ${QT}::Concurrent ${QT}::Test dbgeng ole32)
|
${QT}::Widgets ${QT}::Concurrent ${QT}::Test dbgeng ole32)
|
||||||
add_test(NAME test_windbg_provider COMMAND test_windbg_provider)
|
add_test(NAME test_windbg_provider COMMAND test_windbg_provider)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(bench_large_class tests/bench_large_class.cpp
|
add_executable(bench_large_class tests/bench_large_class.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp
|
||||||
src/providerregistry.cpp
|
src/providerregistry.cpp
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||||
target_include_directories(bench_large_class PRIVATE src third_party/fadec)
|
target_include_directories(bench_large_class PRIVATE src third_party/fadec)
|
||||||
target_link_libraries(bench_large_class PRIVATE
|
target_link_libraries(bench_large_class PRIVATE
|
||||||
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
${QT}::Widgets ${QT}::PrintSupport ${QT}::Concurrent ${QT}::Test
|
||||||
QScintilla::QScintilla)
|
QScintilla::QScintilla)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(bench_large_class PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
target_link_libraries(bench_large_class PRIVATE dbghelp psapi ${_QT_WINEXTRAS})
|
||||||
endif()
|
endif()
|
||||||
add_test(NAME bench_large_class COMMAND bench_large_class)
|
add_test(NAME bench_large_class COMMAND bench_large_class)
|
||||||
|
|
||||||
# Deploy Qt runtime DLLs for tests (run windeployqt on a representative test exe
|
# Deploy Qt runtime DLLs for tests (run windeployqt on a representative test exe
|
||||||
# that links the broadest set of Qt modules; all test exes share the same output dir)
|
# that links the broadest set of Qt modules; all test exes share the same output dir)
|
||||||
if(TARGET ${QT}::windeployqt)
|
if(TARGET ${QT}::windeployqt)
|
||||||
add_custom_target(deploy_tests ALL
|
add_custom_target(deploy_tests ALL
|
||||||
COMMAND $<TARGET_FILE:${QT}::windeployqt>
|
COMMAND $<TARGET_FILE:${QT}::windeployqt>
|
||||||
--no-compiler-runtime --no-translations
|
--no-compiler-runtime --no-translations
|
||||||
--no-opengl-sw --no-system-d3d-compiler
|
--no-opengl-sw --no-system-d3d-compiler
|
||||||
@@ -477,12 +501,14 @@ if(BUILD_TESTING)
|
|||||||
DEPENDS test_controller
|
DEPENDS test_controller
|
||||||
COMMENT "Deploying Qt runtime DLLs for tests..."
|
COMMENT "Deploying Qt runtime DLLs for tests..."
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
endif() # BUILD_UI_TESTS
|
endif() # BUILD_UI_TESTS
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(plugins/ProcessMemory)
|
if(NOT APPLE)
|
||||||
add_subdirectory(plugins/RemoteProcessMemory)
|
add_subdirectory(plugins/ProcessMemory)
|
||||||
|
add_subdirectory(plugins/RemoteProcessMemory)
|
||||||
|
endif()
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_subdirectory(plugins/WinDbgMemory)
|
add_subdirectory(plugins/WinDbgMemory)
|
||||||
add_subdirectory(plugins/RcNetPluginCompatLayer)
|
add_subdirectory(plugins/RcNetPluginCompatLayer)
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -49,7 +49,7 @@ Built with C++17, Qt 6 (Qt 5 also supported), and QScintilla. The entire editor
|
|||||||
- [ ] Safe mode
|
- [ ] Safe mode
|
||||||
- [ ] File import for other Reclass instances
|
- [ ] File import for other Reclass instances
|
||||||
- [ ] Expose UI functionality to plugins
|
- [ ] Expose UI functionality to plugins
|
||||||
- [ ] iOS/macOS support
|
- [ ] iOS support
|
||||||
- [ ] Display RTTI information
|
- [ ] Display RTTI information
|
||||||
|
|
||||||
## Data Sources
|
## Data Sources
|
||||||
@@ -92,7 +92,7 @@ Built-in [Model Context Protocol](https://modelcontextprotocol.io/) bridge via `
|
|||||||
|
|
||||||
### Quick Build
|
### Quick Build
|
||||||
|
|
||||||
```bash
|
```/dev/null/commands.sh#L1-4
|
||||||
git clone --recurse-submodules https://github.com/IChooseYou/Reclass.git
|
git clone --recurse-submodules https://github.com/IChooseYou/Reclass.git
|
||||||
cd Reclass
|
cd Reclass
|
||||||
.\scripts\build_qscintilla.ps1
|
.\scripts\build_qscintilla.ps1
|
||||||
@@ -101,6 +101,16 @@ cd Reclass
|
|||||||
|
|
||||||
The build script auto-detects your Qt install location.
|
The build script auto-detects your Qt install location.
|
||||||
|
|
||||||
|
### macOS Build
|
||||||
|
|
||||||
|
```/dev/null/commands.sh#L1-2
|
||||||
|
./scripts/build_macos.sh --qt-dir /opt/homebrew/opt/qt --build-type Release --package
|
||||||
|
```
|
||||||
|
|
||||||
|
If you installed Qt via Homebrew, `--qt-dir /opt/homebrew/opt/qt` is typical on Apple Silicon. You can also set `QTDIR` or `Qt6_DIR` instead of passing `--qt-dir`.
|
||||||
|
|
||||||
|
Note: macOS Gatekeeper may block unsigned apps. If the app won’t open, go to **System Settings → Privacy & Security** and click **Open Anyway**.
|
||||||
|
|
||||||
### Manual Build (MinGW)
|
### Manual Build (MinGW)
|
||||||
|
|
||||||
1. Clone with `--recurse-submodules` (or run `git submodule update --init --recursive` after cloning)
|
1. Clone with `--recurse-submodules` (or run `git submodule update --init --recursive` after cloning)
|
||||||
|
|||||||
168
scripts/build_macos.sh
Executable file
168
scripts/build_macos.sh
Executable file
@@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
print_help() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Reclass macOS Build Script
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
./scripts/build_macos.sh [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--qt-dir <path> Qt installation prefix (e.g. /opt/homebrew/opt/qt)
|
||||||
|
--build-type <type> Release | Debug | RelWithDebInfo | MinSizeRel (default: Release)
|
||||||
|
--build-dir <path> Build directory (default: <repo>/build)
|
||||||
|
--generator <name> CMake generator (default: Ninja if available)
|
||||||
|
--clean Remove build directory before configuring
|
||||||
|
--rebuild Clean then build
|
||||||
|
--package Run macdeployqt and create a zip
|
||||||
|
--tests Run ctest after build
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- You can set QTDIR or Qt6_DIR in your environment instead of --qt-dir.
|
||||||
|
- If Qt is installed via Homebrew, the script will try to detect it.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
project_root="$(cd "${script_dir}/.." && pwd)"
|
||||||
|
|
||||||
|
qt_dir=""
|
||||||
|
build_type="Release"
|
||||||
|
build_dir="${project_root}/build"
|
||||||
|
generator=""
|
||||||
|
do_clean="false"
|
||||||
|
do_package="false"
|
||||||
|
do_tests="false"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--qt-dir)
|
||||||
|
qt_dir="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--build-type)
|
||||||
|
build_type="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--build-dir)
|
||||||
|
build_dir="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--generator)
|
||||||
|
generator="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--clean)
|
||||||
|
do_clean="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--rebuild)
|
||||||
|
do_clean="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--package)
|
||||||
|
do_package="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--tests)
|
||||||
|
do_tests="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
print_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $1" >&2
|
||||||
|
print_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "${qt_dir}" ]]; then
|
||||||
|
if [[ -n "${QTDIR:-}" ]]; then
|
||||||
|
qt_dir="${QTDIR}"
|
||||||
|
elif [[ -n "${Qt6_DIR:-}" ]]; then
|
||||||
|
qt_dir="${Qt6_DIR}"
|
||||||
|
elif command -v brew >/dev/null 2>&1; then
|
||||||
|
if brew --prefix qt >/dev/null 2>&1; then
|
||||||
|
qt_dir="$(brew --prefix qt)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v cmake >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: cmake not found. Install CMake and try again." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${generator}" ]]; then
|
||||||
|
if command -v ninja >/dev/null 2>&1; then
|
||||||
|
generator="Ninja"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${do_clean}" == "true" && -d "${build_dir}" ]]; then
|
||||||
|
echo "Cleaning build directory: ${build_dir}"
|
||||||
|
rm -rf "${build_dir}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${build_dir}"
|
||||||
|
|
||||||
|
cmake_args=(
|
||||||
|
-S "${project_root}"
|
||||||
|
-B "${build_dir}"
|
||||||
|
-DCMAKE_BUILD_TYPE="${build_type}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "${generator}" ]]; then
|
||||||
|
cmake_args+=(-G "${generator}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${qt_dir}" ]]; then
|
||||||
|
export PATH="${qt_dir}/bin:${PATH}"
|
||||||
|
cmake_args+=(-DCMAKE_PREFIX_PATH="${qt_dir}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Configuring..."
|
||||||
|
cmake "${cmake_args[@]}"
|
||||||
|
|
||||||
|
echo "Building..."
|
||||||
|
cmake --build "${build_dir}" --config "${build_type}"
|
||||||
|
|
||||||
|
if [[ "${do_tests}" == "true" ]]; then
|
||||||
|
echo "Running tests..."
|
||||||
|
ctest --test-dir "${build_dir}" --output-on-failure -C "${build_type}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${do_package}" == "true" ]]; then
|
||||||
|
app_path="${build_dir}/Reclass.app"
|
||||||
|
if [[ ! -d "${app_path}" ]]; then
|
||||||
|
echo "ERROR: ${app_path} not found. Build may have failed." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
macdeployqt_bin=""
|
||||||
|
if [[ -n "${qt_dir}" && -x "${qt_dir}/bin/macdeployqt" ]]; then
|
||||||
|
macdeployqt_bin="${qt_dir}/bin/macdeployqt"
|
||||||
|
elif command -v macdeployqt >/dev/null 2>&1; then
|
||||||
|
macdeployqt_bin="$(command -v macdeployqt)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${macdeployqt_bin}" ]]; then
|
||||||
|
echo "ERROR: macdeployqt not found. Ensure Qt is installed and in PATH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Running macdeployqt..."
|
||||||
|
"${macdeployqt_bin}" "${app_path}" -always-overwrite
|
||||||
|
|
||||||
|
arch="$(uname -m)"
|
||||||
|
zip_name="Reclass-macos-${arch}-qt6.zip"
|
||||||
|
echo "Creating zip: ${zip_name}"
|
||||||
|
ditto -c -k --sequesterRsrc --keepParent "${app_path}" "${build_dir}/${zip_name}"
|
||||||
|
echo "Packaged: ${build_dir}/${zip_name}"
|
||||||
|
fi
|
||||||
BIN
src/icons/class.icns
Normal file
BIN
src/icons/class.icns
Normal file
Binary file not shown.
13
src/macos_titlebar.h
Normal file
13
src/macos_titlebar.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
struct Theme;
|
||||||
|
|
||||||
|
// Apply macOS native title bar color to match the theme.
|
||||||
|
// No-op on non-macOS platforms (implementation is platform-specific).
|
||||||
|
void applyMacTitleBarTheme(QWidget* window, const Theme& theme);
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
43
src/macos_titlebar.mm
Normal file
43
src/macos_titlebar.mm
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include "macos_titlebar.h"
|
||||||
|
#include "themes/theme.h"
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
static NSColor* toNSColor(const QColor& color) {
|
||||||
|
return [NSColor colorWithCalibratedRed:color.redF()
|
||||||
|
green:color.greenF()
|
||||||
|
blue:color.blueF()
|
||||||
|
alpha:color.alphaF()];
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyMacTitleBarTheme(QWidget* window, const Theme& theme) {
|
||||||
|
if (!window) return;
|
||||||
|
|
||||||
|
// Ensure native window is created.
|
||||||
|
window->winId();
|
||||||
|
|
||||||
|
auto* nsView = reinterpret_cast<NSView*>(window->winId());
|
||||||
|
if (!nsView) return;
|
||||||
|
|
||||||
|
NSWindow* nsWindow = [nsView window];
|
||||||
|
if (!nsWindow) return;
|
||||||
|
|
||||||
|
// Keep native traffic lights while tinting the title bar to the theme.
|
||||||
|
// Match the title text contrast by selecting the appropriate system appearance.
|
||||||
|
const qreal luminance =
|
||||||
|
0.2126 * theme.background.redF() +
|
||||||
|
0.7152 * theme.background.greenF() +
|
||||||
|
0.0722 * theme.background.blueF();
|
||||||
|
const bool isLight = luminance >= 0.5;
|
||||||
|
[nsWindow setAppearance:[NSAppearance appearanceNamed:
|
||||||
|
(isLight ? NSAppearanceNameAqua : NSAppearanceNameDarkAqua)]];
|
||||||
|
[nsWindow setTitlebarAppearsTransparent:YES];
|
||||||
|
[nsWindow setTitleVisibility:NSWindowTitleVisible];
|
||||||
|
[nsWindow setBackgroundColor:toNSColor(theme.background)];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
94
src/main.cpp
94
src/main.cpp
@@ -53,7 +53,6 @@
|
|||||||
#include "themes/thememanager.h"
|
#include "themes/thememanager.h"
|
||||||
#include "themes/themeeditor.h"
|
#include "themes/themeeditor.h"
|
||||||
#include "optionsdialog.h"
|
#include "optionsdialog.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <windowsx.h>
|
#include <windowsx.h>
|
||||||
@@ -389,13 +388,18 @@ public:
|
|||||||
|
|
||||||
namespace rcx {
|
namespace rcx {
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
void applyMacTitleBarTheme(QWidget* window, const Theme& theme);
|
||||||
|
#endif
|
||||||
|
|
||||||
// MainWindow class declaration is in mainwindow.h
|
// MainWindow class declaration is in mainwindow.h
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
||||||
setWindowTitle("Reclass");
|
setWindowTitle("Reclass");
|
||||||
resize(1200, 800);
|
resize(1200, 800);
|
||||||
|
|
||||||
// Frameless window with system menu (Alt+Space) and min/max/close support
|
#ifndef __APPLE__
|
||||||
|
// Frameless window with system menu (Alt+Space) and min/max/close support.
|
||||||
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint
|
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint
|
||||||
| Qt::WindowMinMaxButtonsHint);
|
| Qt::WindowMinMaxButtonsHint);
|
||||||
|
|
||||||
@@ -403,6 +407,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
m_titleBar = new TitleBarWidget(this);
|
m_titleBar = new TitleBarWidget(this);
|
||||||
m_titleBar->applyTheme(ThemeManager::instance().current());
|
m_titleBar->applyTheme(ThemeManager::instance().current());
|
||||||
setMenuWidget(m_titleBar);
|
setMenuWidget(m_titleBar);
|
||||||
|
m_menuBar = m_titleBar->menuBar();
|
||||||
|
#else
|
||||||
|
setWindowTitle(QStringLiteral("Reclass"));
|
||||||
|
setUnifiedTitleAndToolBarOnMac(true);
|
||||||
|
m_menuBar = menuBar();
|
||||||
|
m_menuBar->setNativeMenuBar(true);
|
||||||
|
applyMacTitleBarTheme(this, ThemeManager::instance().current());
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// 1px top margin preserves DWM drop shadow on the frameless window
|
// 1px top margin preserves DWM drop shadow on the frameless window
|
||||||
@@ -454,8 +466,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
// Restore menu bar title case setting (after menus are created)
|
// Restore menu bar title case setting (after menus are created)
|
||||||
{
|
{
|
||||||
QSettings s("Reclass", "Reclass");
|
QSettings s("Reclass", "Reclass");
|
||||||
m_titleBar->setMenuBarTitleCase(s.value("menuBarTitleCase", false).toBool());
|
m_menuBarTitleCase = s.value("menuBarTitleCase", false).toBool();
|
||||||
if (s.value("showIcon", false).toBool())
|
applyMenuBarTitleCase(m_menuBarTitleCase);
|
||||||
|
if (m_titleBar && s.value("showIcon", false).toBool())
|
||||||
m_titleBar->setShowIcon(true);
|
m_titleBar->setShowIcon(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,9 +520,42 @@ inline QAction* Qt5Qt6AddAction(QMenu* menu, const QString &text, const QKeySequ
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::applyMenuBarTitleCase(bool titleCase) {
|
||||||
|
m_menuBarTitleCase = titleCase;
|
||||||
|
if (m_titleBar) {
|
||||||
|
m_titleBar->setMenuBarTitleCase(titleCase);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_menuBar) return;
|
||||||
|
|
||||||
|
for (QAction* action : m_menuBar->actions()) {
|
||||||
|
QString text = action->text();
|
||||||
|
QString clean = text;
|
||||||
|
clean.remove('&');
|
||||||
|
|
||||||
|
if (titleCase) {
|
||||||
|
action->setText("&" + clean.toUpper());
|
||||||
|
} else {
|
||||||
|
QString result;
|
||||||
|
bool capitalizeNext = true;
|
||||||
|
for (int i = 0; i < clean.length(); ++i) {
|
||||||
|
QChar ch = clean[i];
|
||||||
|
if (ch.isLetter()) {
|
||||||
|
result += capitalizeNext ? ch.toUpper() : ch.toLower();
|
||||||
|
capitalizeNext = false;
|
||||||
|
} else {
|
||||||
|
result += ch;
|
||||||
|
if (ch.isSpace()) capitalizeNext = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action->setText("&" + result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::createMenus() {
|
void MainWindow::createMenus() {
|
||||||
// File
|
// File
|
||||||
auto* file = m_titleBar->menuBar()->addMenu("&File");
|
auto* file = m_menuBar->addMenu("&File");
|
||||||
Qt5Qt6AddAction(file, "New &Class", QKeySequence::New, QIcon(), this, &MainWindow::newClass);
|
Qt5Qt6AddAction(file, "New &Class", QKeySequence::New, QIcon(), this, &MainWindow::newClass);
|
||||||
Qt5Qt6AddAction(file, "New &Struct", QKeySequence(Qt::CTRL | Qt::Key_T), QIcon(), this, &MainWindow::newStruct);
|
Qt5Qt6AddAction(file, "New &Struct", QKeySequence(Qt::CTRL | Qt::Key_T), QIcon(), this, &MainWindow::newStruct);
|
||||||
Qt5Qt6AddAction(file, "New &Enum", QKeySequence(Qt::CTRL | Qt::Key_E), QIcon(), this, &MainWindow::newEnum);
|
Qt5Qt6AddAction(file, "New &Enum", QKeySequence(Qt::CTRL | Qt::Key_E), QIcon(), this, &MainWindow::newEnum);
|
||||||
@@ -529,7 +575,11 @@ void MainWindow::createMenus() {
|
|||||||
Qt5Qt6AddAction(exportMenu, "ReClass &XML...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportReclassXmlAction);
|
Qt5Qt6AddAction(exportMenu, "ReClass &XML...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportReclassXmlAction);
|
||||||
// Examples submenu — scan once at init
|
// Examples submenu — scan once at init
|
||||||
{
|
{
|
||||||
|
#ifdef __APPLE__
|
||||||
|
QDir exDir(QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/examples"));
|
||||||
|
#else
|
||||||
QDir exDir(QCoreApplication::applicationDirPath() + "/examples");
|
QDir exDir(QCoreApplication::applicationDirPath() + "/examples");
|
||||||
|
#endif
|
||||||
QStringList rcxFiles = exDir.entryList({"*.rcx"}, QDir::Files, QDir::Name);
|
QStringList rcxFiles = exDir.entryList({"*.rcx"}, QDir::Files, QDir::Name);
|
||||||
if (!rcxFiles.isEmpty()) {
|
if (!rcxFiles.isEmpty()) {
|
||||||
auto* examples = file->addMenu("E&xamples");
|
auto* examples = file->addMenu("E&xamples");
|
||||||
@@ -545,12 +595,12 @@ void MainWindow::createMenus() {
|
|||||||
Qt5Qt6AddAction(file, "E&xit", QKeySequence(Qt::Key_Close), makeIcon(":/vsicons/close.svg"), this, &QMainWindow::close);
|
Qt5Qt6AddAction(file, "E&xit", QKeySequence(Qt::Key_Close), makeIcon(":/vsicons/close.svg"), this, &QMainWindow::close);
|
||||||
|
|
||||||
// Edit
|
// Edit
|
||||||
auto* edit = m_titleBar->menuBar()->addMenu("&Edit");
|
auto* edit = m_menuBar->addMenu("&Edit");
|
||||||
Qt5Qt6AddAction(edit, "&Undo", QKeySequence::Undo, makeIcon(":/vsicons/arrow-left.svg"), this, &MainWindow::undo);
|
Qt5Qt6AddAction(edit, "&Undo", QKeySequence::Undo, makeIcon(":/vsicons/arrow-left.svg"), this, &MainWindow::undo);
|
||||||
Qt5Qt6AddAction(edit, "&Redo", QKeySequence::Redo, makeIcon(":/vsicons/arrow-right.svg"), this, &MainWindow::redo);
|
Qt5Qt6AddAction(edit, "&Redo", QKeySequence::Redo, makeIcon(":/vsicons/arrow-right.svg"), this, &MainWindow::redo);
|
||||||
|
|
||||||
// View
|
// View
|
||||||
auto* view = m_titleBar->menuBar()->addMenu("&View");
|
auto* view = m_menuBar->addMenu("&View");
|
||||||
Qt5Qt6AddAction(view, "Split &Horizontal", QKeySequence::UnknownKey, makeIcon(":/vsicons/split-horizontal.svg"), this, &MainWindow::splitView);
|
Qt5Qt6AddAction(view, "Split &Horizontal", QKeySequence::UnknownKey, makeIcon(":/vsicons/split-horizontal.svg"), this, &MainWindow::splitView);
|
||||||
m_removeSplitAction = Qt5Qt6AddAction(view, "&Remove Split", QKeySequence::UnknownKey, makeIcon(":/vsicons/chrome-close.svg"), this, &MainWindow::unsplitView);
|
m_removeSplitAction = Qt5Qt6AddAction(view, "&Remove Split", QKeySequence::UnknownKey, makeIcon(":/vsicons/chrome-close.svg"), this, &MainWindow::unsplitView);
|
||||||
m_removeSplitAction->setVisible(false);
|
m_removeSplitAction->setVisible(false);
|
||||||
@@ -626,7 +676,7 @@ void MainWindow::createMenus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
auto* tools = m_titleBar->menuBar()->addMenu("&Tools");
|
auto* tools = m_menuBar->addMenu("&Tools");
|
||||||
Qt5Qt6AddAction(tools, "&Type Aliases...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::showTypeAliasesDialog);
|
Qt5Qt6AddAction(tools, "&Type Aliases...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::showTypeAliasesDialog);
|
||||||
tools->addSeparator();
|
tools->addSeparator();
|
||||||
const auto mcpName = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool() ? "Stop &MCP Server" : "Start &MCP Server";
|
const auto mcpName = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool() ? "Stop &MCP Server" : "Start &MCP Server";
|
||||||
@@ -635,11 +685,11 @@ void MainWindow::createMenus() {
|
|||||||
Qt5Qt6AddAction(tools, "&Options...", QKeySequence::UnknownKey, makeIcon(":/vsicons/settings-gear.svg"), this, &MainWindow::showOptionsDialog);
|
Qt5Qt6AddAction(tools, "&Options...", QKeySequence::UnknownKey, makeIcon(":/vsicons/settings-gear.svg"), this, &MainWindow::showOptionsDialog);
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
auto* plugins = m_titleBar->menuBar()->addMenu("&Plugins");
|
auto* plugins = m_menuBar->addMenu("&Plugins");
|
||||||
Qt5Qt6AddAction(plugins, "&Manage Plugins...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::showPluginsDialog);
|
Qt5Qt6AddAction(plugins, "&Manage Plugins...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::showPluginsDialog);
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
auto* help = m_titleBar->menuBar()->addMenu("&Help");
|
auto* help = m_menuBar->addMenu("&Help");
|
||||||
Qt5Qt6AddAction(help, "&About Reclass", QKeySequence::UnknownKey, makeIcon(":/vsicons/question.svg"), this, &MainWindow::about);
|
Qt5Qt6AddAction(help, "&About Reclass", QKeySequence::UnknownKey, makeIcon(":/vsicons/question.svg"), this, &MainWindow::about);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1671,10 +1721,15 @@ void MainWindow::toggleMcp() {
|
|||||||
void MainWindow::applyTheme(const Theme& theme) {
|
void MainWindow::applyTheme(const Theme& theme) {
|
||||||
applyGlobalTheme(theme);
|
applyGlobalTheme(theme);
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
applyMacTitleBarTheme(this, theme);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Dock separator is 1px via PM_DockWidgetSeparatorExtent in MenuBarStyle
|
// Dock separator is 1px via PM_DockWidgetSeparatorExtent in MenuBarStyle
|
||||||
|
|
||||||
// Custom title bar
|
// Custom title bar
|
||||||
m_titleBar->applyTheme(theme);
|
if (m_titleBar)
|
||||||
|
m_titleBar->applyTheme(theme);
|
||||||
|
|
||||||
// Update border overlay color
|
// Update border overlay color
|
||||||
updateBorderColor(isActiveWindow() ? theme.borderFocused : theme.border);
|
updateBorderColor(isActiveWindow() ? theme.borderFocused : theme.border);
|
||||||
@@ -1831,8 +1886,10 @@ void MainWindow::showOptionsDialog() {
|
|||||||
OptionsResult current;
|
OptionsResult current;
|
||||||
current.themeIndex = tm.currentIndex();
|
current.themeIndex = tm.currentIndex();
|
||||||
current.fontName = QSettings("Reclass", "Reclass").value("font", "JetBrains Mono").toString();
|
current.fontName = QSettings("Reclass", "Reclass").value("font", "JetBrains Mono").toString();
|
||||||
current.menuBarTitleCase = m_titleBar->menuBarTitleCase();
|
current.menuBarTitleCase = m_menuBarTitleCase;
|
||||||
current.showIcon = QSettings("Reclass", "Reclass").value("showIcon", false).toBool();
|
current.showIcon = m_titleBar
|
||||||
|
? QSettings("Reclass", "Reclass").value("showIcon", false).toBool()
|
||||||
|
: false;
|
||||||
current.safeMode = QSettings("Reclass", "Reclass").value("safeMode", false).toBool();
|
current.safeMode = QSettings("Reclass", "Reclass").value("safeMode", false).toBool();
|
||||||
current.autoStartMcp = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool();
|
current.autoStartMcp = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool();
|
||||||
current.refreshMs = QSettings("Reclass", "Reclass").value("refreshMs", 660).toInt();
|
current.refreshMs = QSettings("Reclass", "Reclass").value("refreshMs", 660).toInt();
|
||||||
@@ -1850,12 +1907,13 @@ void MainWindow::showOptionsDialog() {
|
|||||||
setEditorFont(r.fontName);
|
setEditorFont(r.fontName);
|
||||||
|
|
||||||
if (r.menuBarTitleCase != current.menuBarTitleCase) {
|
if (r.menuBarTitleCase != current.menuBarTitleCase) {
|
||||||
m_titleBar->setMenuBarTitleCase(r.menuBarTitleCase);
|
applyMenuBarTitleCase(r.menuBarTitleCase);
|
||||||
QSettings("Reclass", "Reclass").setValue("menuBarTitleCase", r.menuBarTitleCase);
|
QSettings("Reclass", "Reclass").setValue("menuBarTitleCase", r.menuBarTitleCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.showIcon != current.showIcon) {
|
if (r.showIcon != current.showIcon) {
|
||||||
m_titleBar->setShowIcon(r.showIcon);
|
if (m_titleBar)
|
||||||
|
m_titleBar->setShowIcon(r.showIcon);
|
||||||
QSettings("Reclass", "Reclass").setValue("showIcon", r.showIcon);
|
QSettings("Reclass", "Reclass").setValue("showIcon", r.showIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1932,6 +1990,9 @@ MainWindow::TabState* MainWindow::tabByIndex(int index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateWindowTitle() {
|
void MainWindow::updateWindowTitle() {
|
||||||
|
#ifdef __APPLE__
|
||||||
|
setWindowTitle(QStringLiteral("Reclass"));
|
||||||
|
#else
|
||||||
QString title;
|
QString title;
|
||||||
auto* sub = m_mdiArea->activeSubWindow();
|
auto* sub = m_mdiArea->activeSubWindow();
|
||||||
if (sub && m_tabs.contains(sub)) {
|
if (sub && m_tabs.contains(sub)) {
|
||||||
@@ -1945,6 +2006,7 @@ void MainWindow::updateWindowTitle() {
|
|||||||
title = "Reclass";
|
title = "Reclass";
|
||||||
}
|
}
|
||||||
setWindowTitle(title);
|
setWindowTitle(title);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Rendered view setup ──
|
// ── Rendered view setup ──
|
||||||
@@ -3110,7 +3172,7 @@ void MainWindow::changeEvent(QEvent* event) {
|
|||||||
const auto& t = ThemeManager::instance().current();
|
const auto& t = ThemeManager::instance().current();
|
||||||
updateBorderColor(isActiveWindow() ? t.borderFocused : t.border);
|
updateBorderColor(isActiveWindow() ? t.borderFocused : t.border);
|
||||||
}
|
}
|
||||||
if (event->type() == QEvent::WindowStateChange)
|
if (event->type() == QEvent::WindowStateChange && m_titleBar)
|
||||||
m_titleBar->updateMaximizeIcon();
|
m_titleBar->updateMaximizeIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ private:
|
|||||||
QPushButton* m_btnReclass = nullptr;
|
QPushButton* m_btnReclass = nullptr;
|
||||||
QPushButton* m_btnRendered = nullptr;
|
QPushButton* m_btnRendered = nullptr;
|
||||||
TitleBarWidget* m_titleBar = nullptr;
|
TitleBarWidget* m_titleBar = nullptr;
|
||||||
|
QMenuBar* m_menuBar = nullptr;
|
||||||
|
bool m_menuBarTitleCase = false;
|
||||||
QWidget* m_borderOverlay = nullptr;
|
QWidget* m_borderOverlay = nullptr;
|
||||||
PluginManager m_pluginManager;
|
PluginManager m_pluginManager;
|
||||||
McpBridge* m_mcp = nullptr;
|
McpBridge* m_mcp = nullptr;
|
||||||
@@ -118,6 +120,7 @@ private:
|
|||||||
void rebuildAllDocs();
|
void rebuildAllDocs();
|
||||||
|
|
||||||
void createMenus();
|
void createMenus();
|
||||||
|
void applyMenuBarTitleCase(bool titleCase);
|
||||||
void createStatusBar();
|
void createStatusBar();
|
||||||
void showPluginsDialog();
|
void showPluginsDialog();
|
||||||
void populateSourceMenu();
|
void populateSourceMenu();
|
||||||
|
|||||||
@@ -33,7 +33,12 @@ ThemeManager::ThemeManager() {
|
|||||||
// ── Load built-in themes from JSON files next to the executable ──
|
// ── Load built-in themes from JSON files next to the executable ──
|
||||||
|
|
||||||
QString ThemeManager::builtInDir() const {
|
QString ThemeManager::builtInDir() const {
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
// In a macOS .app bundle, resources live in Contents/Resources, not Contents/MacOS
|
||||||
|
return QCoreApplication::applicationDirPath() + "/../Resources/themes";
|
||||||
|
#else
|
||||||
return QCoreApplication::applicationDirPath() + "/themes";
|
return QCoreApplication::applicationDirPath() + "/themes";
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThemeManager::loadBuiltInThemes() {
|
void ThemeManager::loadBuiltInThemes() {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ void TitleBarWidget::applyTheme(const Theme& theme) {
|
|||||||
// App label
|
// App label
|
||||||
m_appLabel->setStyleSheet(
|
m_appLabel->setStyleSheet(
|
||||||
QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }")
|
QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }")
|
||||||
.arg(theme.textDim.name()));
|
.arg(theme.text.name()));
|
||||||
|
|
||||||
// Menu bar palette — hover/bg handled by MenuBarStyle QProxyStyle.
|
// Menu bar palette — hover/bg handled by MenuBarStyle QProxyStyle.
|
||||||
// Set Window + Button to background so Fusion never paints a foreign color.
|
// Set Window + Button to background so Fusion never paints a foreign color.
|
||||||
@@ -82,7 +82,7 @@ void TitleBarWidget::applyTheme(const Theme& theme) {
|
|||||||
QPalette mbPal = m_menuBar->palette();
|
QPalette mbPal = m_menuBar->palette();
|
||||||
mbPal.setColor(QPalette::Window, theme.background);
|
mbPal.setColor(QPalette::Window, theme.background);
|
||||||
mbPal.setColor(QPalette::Button, theme.background);
|
mbPal.setColor(QPalette::Button, theme.background);
|
||||||
mbPal.setColor(QPalette::ButtonText, theme.textDim);
|
mbPal.setColor(QPalette::ButtonText, theme.text);
|
||||||
m_menuBar->setPalette(mbPal);
|
m_menuBar->setPalette(mbPal);
|
||||||
m_menuBar->setAutoFillBackground(false);
|
m_menuBar->setAutoFillBackground(false);
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ void TitleBarWidget::setShowIcon(bool show) {
|
|||||||
m_appLabel->setText(QStringLiteral("Reclass"));
|
m_appLabel->setText(QStringLiteral("Reclass"));
|
||||||
m_appLabel->setStyleSheet(
|
m_appLabel->setStyleSheet(
|
||||||
QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }")
|
QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }")
|
||||||
.arg(m_theme.textDim.name()));
|
.arg(m_theme.text.name()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user