1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-26 06:40:22 +00:00
This commit is contained in:
kuyondo 2021-10-04 08:12:56 +08:00
commit 071793eeb3
558 changed files with 49415 additions and 8388 deletions

50
.github/workflows/cmake.yml vendored Normal file
View File

@ -0,0 +1,50 @@
name: CMake
on:
pull_request:
branches: [ master ]
env:
BUILD_TYPE: RelWithDebInfo
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Add OpenMW PPA Dependancies
run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update
- name: Install Building Dependancies
run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic
- name: Prime ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ matrix.os }}-${{ env.BUILD_TYPE }}
max-size: 1000M
- name: Configure
run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3
- name: Install
shell: bash
run: cmake --install .
- name: Create Artifact
shell: bash
working-directory: install
run: |
ls -laR
7z a ../build_artifact.7z .
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
path: ./build_artifact.7z
name: build_artifact.7z

View File

@ -31,6 +31,23 @@ stages:
paths: paths:
- build/install/ - build/install/
Clang_Tidy:
extends: .Debian_Image
stage: build
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
before_script:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic clang-tidy clang
script:
- CI/before_script.linux.sh
- cd build
- cmake --build . -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter
variables:
CC: clang
CXX: clang++
CI_CLANG_TIDY: 1
timeout: 8h
Coverity: Coverity:
extends: .Debian_Image extends: .Debian_Image
stage: build stage: build
@ -42,8 +59,8 @@ Coverity:
- tar xfz /tmp/cov-analysis-linux64.tgz - tar xfz /tmp/cov-analysis-linux64.tgz
script: script:
- CI/before_script.linux.sh - CI/before_script.linux.sh
# Add more than just `openmw` once we can build everything under 3h # Remove the specific targets and build everything once we can do it under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter
after_script: after_script:
- tar cfz cov-int.tar.gz cov-int - tar cfz cov-int.tar.gz cov-int
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
@ -53,7 +70,7 @@ Coverity:
variables: variables:
CC: gcc CC: gcc
CXX: g++ CXX: g++
timeout: 8h artifacts:
Debian_GCC: Debian_GCC:
extends: .Debian extends: .Debian
@ -76,6 +93,15 @@ Debian_GCC_tests:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
Debian_GCC_tests_Debug:
extends: Debian_GCC
cache:
key: Debian_GCC_tests_Debug.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
Debian_GCC_Static_Deps: Debian_GCC_Static_Deps:
extends: Debian_GCC extends: Debian_GCC
cache: cache:
@ -88,6 +114,7 @@ Debian_GCC_Static_Deps:
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static - CI/install_debian_deps.sh gcc openmw-deps openmw-deps-static
variables: variables:
CI_OPENMW_USE_STATIC_DEPS: 1 CI_OPENMW_USE_STATIC_DEPS: 1
timeout: 3h
Debian_GCC_Static_Deps_tests: Debian_GCC_Static_Deps_tests:
extends: Debian_GCC_Static_Deps extends: Debian_GCC_Static_Deps
@ -118,6 +145,15 @@ Debian_Clang_tests:
CCACHE_SIZE: 1G CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1 BUILD_TESTS_ONLY: 1
Debian_Clang_tests_Debug:
extends: Debian_Clang
cache:
key: Debian_Clang_tests_Debug.v1
variables:
CCACHE_SIZE: 1G
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
.MacOS: .MacOS:
image: macos-11-xcode-12 image: macos-11-xcode-12
tags: tags:
@ -187,6 +223,18 @@ variables: &tests-targets
- choco install ninja -y - choco install ninja -y
- choco install python -y - choco install python -y
- refreshenv - refreshenv
- |
function Make-SafeFileName {
param(
[Parameter(Mandatory=$true)]
[String]
$FileName
)
[IO.Path]::GetInvalidFileNameChars() | ForEach-Object {
$FileName = $FileName.Replace($_, '_')
}
return $FileName
}
stage: build stage: build
script: script:
- $time = (Get-Date -Format "HH:mm:ss") - $time = (Get-Date -Format "HH:mm:ss")
@ -198,12 +246,13 @@ variables: &tests-targets
- cmake --build . --config $config --target ($targets.Split(',')) - cmake --build . --config $config --target ($targets.Split(','))
- cd $config - cd $config
- echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt
- Get-ChildItem -Recurse *.ilk | Remove-Item
- | - |
if (Get-ChildItem -Recurse *.pdb) { if (Get-ChildItem -Recurse *.pdb) {
7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
Get-ChildItem -Recurse *.pdb | Remove-Item Get-ChildItem -Recurse *.pdb | Remove-Item
} }
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*'
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script: after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
@ -289,6 +338,18 @@ Windows_Ninja_Tests_RelWithDebInfo:
- choco install vswhere -y - choco install vswhere -y
- choco install python -y - choco install python -y
- refreshenv - refreshenv
- |
function Make-SafeFileName {
param(
[Parameter(Mandatory=$true)]
[String]
$FileName
)
[IO.Path]::GetInvalidFileNameChars() | ForEach-Object {
$FileName = $FileName.Replace($_, '_')
}
return $FileName
}
stage: build stage: build
script: script:
- $time = (Get-Date -Format "HH:mm:ss") - $time = (Get-Date -Format "HH:mm:ss")
@ -299,12 +360,13 @@ Windows_Ninja_Tests_RelWithDebInfo:
- cmake --build . --config $config --target ($targets.Split(',')) - cmake --build . --config $config --target ($targets.Split(','))
- cd $config - cd $config
- echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt
- Get-ChildItem -Recurse *.ilk | Remove-Item
- | - |
if (Get-ChildItem -Recurse *.pdb) { if (Get-ChildItem -Recurse *.pdb) {
7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt
Get-ChildItem -Recurse *.pdb | Remove-Item Get-ChildItem -Recurse *.pdb | Remove-Item
} }
- 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*'
- if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } }
after_script: after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
@ -412,3 +474,4 @@ Debian_AndroidNDK_arm64-v8a:
- build/install/ - build/install/
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
timeout: 1h30m timeout: 1h30m

View File

@ -44,6 +44,7 @@ Programmers
Austin Salgat (Salgat) Austin Salgat (Salgat)
Ben Shealy (bentsherman) Ben Shealy (bentsherman)
Berulacks Berulacks
Bo Svensson
Britt Mathis (galdor557) Britt Mathis (galdor557)
Capostrophic Capostrophic
Carl Maxwell Carl Maxwell
@ -79,6 +80,7 @@ Programmers
Federico Guerra (FedeWar) Federico Guerra (FedeWar)
Fil Krynicki (filkry) Fil Krynicki (filkry)
Finbar Crago(finbar-crago) Finbar Crago(finbar-crago)
Florent Teppe (Tetramir)
Florian Weber (Florianjw) Florian Weber (Florianjw)
Frédéric Chardon (fr3dz10) Frédéric Chardon (fr3dz10)
Gaëtan Dezeiraud (Brouilles) Gaëtan Dezeiraud (Brouilles)
@ -99,6 +101,7 @@ Programmers
James Stephens (james-h-stephens) James Stephens (james-h-stephens)
Jan-Peter Nilsson (peppe) Jan-Peter Nilsson (peppe)
Jan Borsodi (am0s) Jan Borsodi (am0s)
JanuarySnow
Jason Hooks (jhooks) Jason Hooks (jhooks)
jeaye jeaye
jefetienne jefetienne
@ -159,6 +162,7 @@ Programmers
Nikolay Kasyanov (corristo) Nikolay Kasyanov (corristo)
nobrakal nobrakal
Nolan Poe (nopoe) Nolan Poe (nopoe)
Nurivan Gomez (Nuri-G)
Oleg Chkan (mrcheko) Oleg Chkan (mrcheko)
Paul Cercueil (pcercuei) Paul Cercueil (pcercuei)
Paul McElroy (Greendogo) Paul McElroy (Greendogo)
@ -225,6 +229,7 @@ Programmers
Yuri Krupenin Yuri Krupenin
zelurker zelurker
Noah Gooder Noah Gooder
Andrew Appuhamy (andrew-app)
Documentation Documentation
------------- -------------

View File

@ -1,28 +1,80 @@
0.48.0 0.48.0
------ ------
Bug #1751: Birthsign abilities increase modified attribute values instead of base ones
Bug #3246: ESSImporter: Most NPCs are dead on save load
Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear
Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions)
Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes
Bug #3905: Great House Dagoth issues Bug #3905: Great House Dagoth issues
Bug #4203: Resurrecting an actor should close the loot GUI
Bug #4602: Robert's Bodies: crash inside createInstance()
Bug #4700: Editor: Incorrect command implementation
Bug #4744: Invisible particles must still be processed
Bug #5100: Persuasion doesn't always clamp the resulting disposition
Bug #5120: Scripted object spawning updates physics system Bug #5120: Scripted object spawning updates physics system
Bug #5207: Loose summons can be present in scene
Bug #5379: Wandering NPCs falling through cantons Bug #5379: Wandering NPCs falling through cantons
Bug #5453: Magic effect VFX are offset for creatures Bug #5453: Magic effect VFX are offset for creatures
Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5483: AutoCalc flag is not used to calculate spells cost
Bug #5508: Engine binary links to Qt without using it
Bug #5596: Effects in constant spells should not be merged
Bug #5621: Drained stats cannot be restored
Bug #5755: Active grid object paging - disappearing textures
Bug #5766: Active grid object paging - disappearing textures
Bug #5788: Texture editing parses the selected indexes wrongly
Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention
Bug #5842: GetDisposition adds temporary disposition change from different actors
Bug #5863: GetEffect should return true after the player has teleported
Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher
Bug #6051: NaN water height in ESM file is not handled gracefully
Bug #6066: addtopic "return" does not work from within script. No errors thrown Bug #6066: addtopic "return" does not work from within script. No errors thrown
Bug #6067: esp loader fails in for certain subrecord orders Bug #6067: esp loader fails in for certain subrecord orders
Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends
Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime
Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed
Bug #6115: Showmap overzealous matching Bug #6115: Showmap overzealous matching
Bug #6118: Creature landing sound counts as a footstep
Bug #6123: NPC with broken script freezes the game on hello Bug #6123: NPC with broken script freezes the game on hello
Bug #6129: Player avatar not displayed correctly for large window sizes when GUI scaling active Bug #6129: Player avatar not displayed correctly for large window sizes when GUI scaling active
Bug #6131: Item selection in the avatar window not working correctly for large window sizes Bug #6131: Item selection in the avatar window not working correctly for large window sizes
Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player
Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive
Bug #6165: Paralyzed player character can pickup items when the inventory is open
Bug #6172: Some creatures can't open doors
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
Bug #6197: Infinite Casting Loop
Bug #6223: Some Constant Effect Bound Items inconsistencies
Bug #6273: Respawning NPCs rotation is inconsistent
Bug #6282: Laura craft doesn't follow the player character
Bug #6283: Avis Dorsey follows you after her death
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
Bug #6302: Teleporting disabled actor breaks its disabled state
Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken
Feature #890: OpenMW-CS: Column filtering
Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record
Feature #2780: A way to see current OpenMW version in the console Feature #2780: A way to see current OpenMW version in the console
Feature #3616: Allow Zoom levels on the World Map
Feature #4297: Implement APPLIED_ONCE flag for magic effects
Feature #4414: Handle duration of EXTRA SPELL magic effect
Feature #4595: Unique object identifier
Feature #4737: Handle instance move from one cell to another
Feature #5198: Implement "Magic effect expired" event
Feature #5454: Clear active spells from actor when he disappears from scene
Feature #5489: MCP: Telekinesis fix for activators Feature #5489: MCP: Telekinesis fix for activators
Feature #5737: Handle instance move from one cell to another
Feature #5996: Support Lua scripts in OpenMW Feature #5996: Support Lua scripts in OpenMW
Feature #6017: Separate persistent and temporary cell references when saving Feature #6017: Separate persistent and temporary cell references when saving
Feature #6032: Reverse-z depth buffer
Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
Feature #6199: Support FBO Rendering
Feature #6249: Alpha testing support for Collada
Feature #6251: OpenMW-CS: Set instance movement based on camera zoom
Feature #6288: Preserve the "blocked" record flag for referenceable objects.
Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings
Task #6264: Remove the old classes in animation.cpp
0.47.0 0.47.0
------ ------
@ -1930,6 +1982,7 @@
Bug #2025: Missing mouse-over text for non affordable items Bug #2025: Missing mouse-over text for non affordable items
Bug #2028: [MOD: Tamriel Rebuilt] Crashing when trying to enter interior cell "Ruinous Keep, Great Hall" Bug #2028: [MOD: Tamriel Rebuilt] Crashing when trying to enter interior cell "Ruinous Keep, Great Hall"
Bug #2029: Ienith Brothers Thiev's Guild quest journal entry not adding Bug #2029: Ienith Brothers Thiev's Guild quest journal entry not adding
Bug #3066: Editor doesn't check if IDs and other strings are longer than their hardcoded field length
Feature #471: Editor: Special case implementation for top-level window with single sub-window Feature #471: Editor: Special case implementation for top-level window with single sub-window
Feature #472: Editor: Sub-Window re-use settings Feature #472: Editor: Sub-Window re-use settings
Feature #704: Font colors import from fallback settings Feature #704: Font colors import from fallback settings

View File

@ -1,11 +1,15 @@
#!/bin/sh -ex #!/bin/sh -ex
export HOMEBREW_NO_EMOJI=1
brew update --quiet
# workaround python issue on travis # workaround python issue on travis
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true [ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies python@3.8 || true
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true [ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies python@3.9 || true
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true [ -z "${TRAVIS}" ] && brew uninstall --ignore-dependencies qt@6 || true
# Some of these tools can come from places other than brew, so check before installing # Some of these tools can come from places other than brew, so check before installing
[ -z "${TRAVIS}" ] && brew install fontconfig
command -v ccache >/dev/null 2>&1 || brew install ccache command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake command -v cmake >/dev/null 2>&1 || brew install cmake
command -v qmake >/dev/null 2>&1 || brew install qt@5 command -v qmake >/dev/null 2>&1 || brew install qt@5
@ -15,10 +19,6 @@ ccache --version
cmake --version cmake --version
qmake --version qmake --version
brew install lua curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20210716.zip -o ~/openmw-deps.zip
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20210617.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
# additional libraries
[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfig

View File

@ -14,6 +14,12 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then
BUILD_BENCHMARKS=ON BUILD_BENCHMARKS=ON
fi fi
CXX_FLAGS='-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy'
if [[ "${CXX}" == 'clang++' ]]; then
CXX_FLAGS="${CXX_FLAGS} -Wno-error=unused-lambda-capture -Wno-error=gnu-zero-variadic-macro-arguments"
fi
declare -a CMAKE_CONF_OPTS=( declare -a CMAKE_CONF_OPTS=(
-DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}" -DCMAKE_C_COMPILER="${CC:-/usr/bin/cc}"
-DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}" -DCMAKE_CXX_COMPILER="${CXX:-/usr/bin/c++}"
@ -24,6 +30,8 @@ declare -a CMAKE_CONF_OPTS=(
-DBUILD_SHARED_LIBS=OFF -DBUILD_SHARED_LIBS=OFF
-DUSE_SYSTEM_TINYXML=ON -DUSE_SYSTEM_TINYXML=ON
-DCMAKE_INSTALL_PREFIX=install -DCMAKE_INSTALL_PREFIX=install
-DCMAKE_C_FLAGS='-Werror'
-DCMAKE_CXX_FLAGS="${CXX_FLAGS}"
) )
if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then
@ -34,6 +42,19 @@ if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then
) )
fi fi
if [[ $CI_CLANG_TIDY ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_CXX_CLANG_TIDY='clang-tidy;-checks=-*,boost-*,clang-analyzer-*,concurrency-*,performance-*,-header-filter=.*,bugprone-*,misc-definitions-in-headers,misc-misplaced-const,misc-redundant-expression'
)
fi
if [[ "${CMAKE_BUILD_TYPE}" ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
)
fi
mkdir -p build mkdir -p build
cd build cd build

View File

@ -25,6 +25,5 @@ cmake \
-D BUILD_BSATOOL=TRUE \ -D BUILD_BSATOOL=TRUE \
-D BUILD_ESSIMPORTER=TRUE \ -D BUILD_ESSIMPORTER=TRUE \
-D BUILD_NIFTEST=TRUE \ -D BUILD_NIFTEST=TRUE \
-D USE_LUAJIT=FALSE \
-G"Unix Makefiles" \ -G"Unix Makefiles" \
.. ..

View File

@ -29,6 +29,7 @@ declare -rA GROUPED_DEPS=(
# These dependencies can alternatively be built and linked statically. # These dependencies can alternatively be built and linked statically.
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"
[coverity]="curl" [coverity]="curl"
[clang-tidy]="clang-tidy"
# Pre-requisites for building MyGUI and OSG for static linking. # Pre-requisites for building MyGUI and OSG for static linking.
# #

View File

@ -13,6 +13,11 @@ if(POLICY CMP0083)
cmake_policy(SET CMP0083 NEW) cmake_policy(SET CMP0083 NEW)
endif() endif()
# to link with freetype library
if(POLICY CMP0079)
cmake_policy(SET CMP0079 NEW)
endif()
option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF) option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF)
if(OPENMW_GL4ES_MANUAL_INIT) if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
@ -202,8 +207,6 @@ if (USE_QT)
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets REQUIRED)
find_package(Qt5Network REQUIRED) find_package(Qt5Network REQUIRED)
find_package(Qt5OpenGL REQUIRED) find_package(Qt5OpenGL REQUIRED)
# Instruct CMake to run moc automatically when needed.
#set(CMAKE_AUTOMOC ON)
endif() endif()
set(USED_OSG_COMPONENTS set(USED_OSG_COMPONENTS
@ -404,11 +407,8 @@ else(USE_LUAJIT)
add_compile_definitions(NO_LUAJIT) add_compile_definitions(NO_LUAJIT)
endif(USE_LUAJIT) endif(USE_LUAJIT)
# Download sol - C++ library binding to Lua # C++ library binding to Lua
file(DOWNLOAD set(SOL_INCLUDE_DIRS ${OpenMW_SOURCE_DIR}/extern/sol3.2.2 ${OpenMW_SOURCE_DIR}/extern/sol_config)
"https://github.com/ThePhD/sol2/releases/download/v3.2.2/sol.hpp" "${OpenMW_BINARY_DIR}/extern/sol3.2.2/sol/sol.hpp"
EXPECTED_MD5 ba113cf458f60672917108e69bb4d958)
set(SOL_INCLUDE_DIRS ${OpenMW_BINARY_DIR}/extern/sol3.2.2 ${OpenMW_SOURCE_DIR}/extern/sol_config)
include_directories( include_directories(
BEFORE SYSTEM BEFORE SYSTEM
@ -515,7 +515,7 @@ endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long") set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wundef -Wno-unused-parameter -pedantic -Wno-long-long ${CMAKE_CXX_FLAGS}")
add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON ) add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON )
if (APPLE) if (APPLE)
@ -545,7 +545,6 @@ endif()
# Components # Components
add_subdirectory (components) add_subdirectory (components)
target_compile_definitions(components PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}")
# Apps and tools # Apps and tools
if (BUILD_OPENMW) if (BUILD_OPENMW)
@ -681,7 +680,7 @@ if (WIN32)
if (BUILD_OPENMW) if (BUILD_OPENMW)
# \bigobj is required: # \bigobj is required:
# 1) for OPENMW_UNITY_BUILD; # 1) for OPENMW_UNITY_BUILD;
# 2) to compile lua binginds, because sol3 is heavily templated. # 2) to compile lua bindings, because sol3 is heavily templated.
set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj")
endif() endif()

View File

@ -1,6 +1,7 @@
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <components/detournavigator/navmeshtilescache.hpp> #include <components/detournavigator/navmeshtilescache.hpp>
#include <components/esm/loadland.hpp>
#include <algorithm> #include <algorithm>
#include <random> #include <random>
@ -15,13 +16,12 @@ namespace
osg::Vec3f mAgentHalfExtents; osg::Vec3f mAgentHalfExtents;
TilePosition mTilePosition; TilePosition mTilePosition;
RecastMesh mRecastMesh; RecastMesh mRecastMesh;
std::vector<OffMeshConnection> mOffMeshConnections;
}; };
struct Item struct Item
{ {
Key mKey; Key mKey;
NavMeshData mValue; PreparedNavMeshData mValue;
}; };
template <typename Random> template <typename Random>
@ -31,6 +31,14 @@ namespace
return TilePosition(distribution(random), distribution(random)); return TilePosition(distribution(random), distribution(random));
} }
template <typename Random>
TileBounds generateTileBounds(Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
const osg::Vec2f min(distribution(random), distribution(random));
return TileBounds {min, min + osg::Vec2f(1.0, 1.0)};
}
template <typename Random> template <typename Random>
osg::Vec3f generateAgentHalfExtents(float min, float max, Random& random) osg::Vec3f generateAgentHalfExtents(float min, float max, Random& random)
{ {
@ -81,22 +89,55 @@ namespace
template <typename OutputIterator, typename Random> template <typename OutputIterator, typename Random>
void generateWater(OutputIterator out, std::size_t count, Random& random) void generateWater(OutputIterator out, std::size_t count, Random& random)
{ {
std::uniform_real_distribution<btScalar> distribution(0.0, 1.0); std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, count, [&] { std::generate_n(out, count, [&] {
const btVector3 shift(distribution(random), distribution(random), distribution(random)); const osg::Vec3f shift(distribution(random), distribution(random), distribution(random));
return RecastMesh::Water {1, btTransform(btMatrix3x3::getIdentity(), shift)}; return Cell {1, shift};
}); });
} }
template <typename OutputIterator, typename Random> template <class Random>
void generateOffMeshConnection(OutputIterator out, std::size_t count, Random& random) Mesh generateMesh(std::size_t triangles, Random& random)
{ {
std::uniform_real_distribution<btScalar> distribution(0.0, 1.0); std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, count, [&] { std::vector<float> vertices;
const osg::Vec3f start(distribution(random), distribution(random), distribution(random)); std::vector<int> indices;
const osg::Vec3f end(distribution(random), distribution(random), distribution(random)); std::vector<AreaType> areaTypes;
return OffMeshConnection {start, end, generateAreaType(random)}; if (distribution(random) < 0.939)
{
generateVertices(std::back_inserter(vertices), triangles * 2.467, random);
generateIndices(std::back_inserter(indices), static_cast<int>(vertices.size() / 3) - 1, vertices.size() * 1.279, random);
generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random);
}
return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes));
}
template <class Random>
Heightfield generateHeightfield(Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
Heightfield result;
result.mBounds = generateTileBounds(random);
result.mMinHeight = distribution(random);
result.mMaxHeight = result.mMinHeight + 1.0;
result.mShift = osg::Vec3f(distribution(random), distribution(random), distribution(random));
result.mScale = distribution(random);
result.mLength = static_cast<std::uint8_t>(ESM::Land::LAND_SIZE);
std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&]
{
return distribution(random);
}); });
return result;
}
template <class Random>
FlatHeightfield generateFlatHeightfield(Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
FlatHeightfield result;
result.mBounds = generateTileBounds(random);
result.mHeight = distribution(random);
return result;
} }
template <class Random> template <class Random>
@ -106,22 +147,15 @@ namespace
const TilePosition tilePosition = generateTilePosition(10000, random); const TilePosition tilePosition = generateTilePosition(10000, random);
const std::size_t generation = std::uniform_int_distribution<std::size_t>(0, 100)(random); const std::size_t generation = std::uniform_int_distribution<std::size_t>(0, 100)(random);
const std::size_t revision = std::uniform_int_distribution<std::size_t>(0, 10000)(random); const std::size_t revision = std::uniform_int_distribution<std::size_t>(0, 10000)(random);
std::vector<float> vertices; Mesh mesh = generateMesh(triangles, random);
generateVertices(std::back_inserter(vertices), triangles * 1.98, random); std::vector<Cell> water;
std::vector<int> indices; generateWater(std::back_inserter(water), 1, random);
generateIndices(std::back_inserter(indices), static_cast<int>(vertices.size() / 3) - 1, vertices.size() * 1.53, random); RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water),
std::vector<AreaType> areaTypes; {generateHeightfield(random)}, {generateFlatHeightfield(random)});
generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random); return Key {agentHalfExtents, tilePosition, std::move(recastMesh)};
std::vector<RecastMesh::Water> water;
generateWater(std::back_inserter(water), 2, random);
RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices),
std::move(areaTypes), std::move(water));
std::vector<OffMeshConnection> offMeshConnections;
generateOffMeshConnection(std::back_inserter(offMeshConnections), 300, random);
return Key {agentHalfExtents, tilePosition, std::move(recastMesh), std::move(offMeshConnections)};
} }
constexpr std::size_t trianglesPerTile = 310; constexpr std::size_t trianglesPerTile = 239;
template <typename OutputIterator, typename Random> template <typename OutputIterator, typename Random>
void generateKeys(OutputIterator out, std::size_t count, Random& random) void generateKeys(OutputIterator out, std::size_t count, Random& random)
@ -137,7 +171,8 @@ namespace
while (true) while (true)
{ {
Key key = generateKey(trianglesPerTile, random); Key key = generateKey(trianglesPerTile, random);
cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh,
std::make_unique<PreparedNavMeshData>());
*out++ = std::move(key); *out++ = std::move(key);
const std::size_t newSize = cache.getStats().mNavMeshCacheSize; const std::size_t newSize = cache.getStats().mNavMeshCacheSize;
if (size >= newSize) if (size >= newSize)
@ -159,7 +194,7 @@ namespace
while (state.KeepRunning()) while (state.KeepRunning())
{ {
const auto& key = keys[n++ % keys.size()]; const auto& key = keys[n++ % keys.size()];
const auto result = cache.get(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections); const auto result = cache.get(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh);
benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(result);
} }
} }
@ -187,7 +222,8 @@ namespace
while (state.KeepRunning()) while (state.KeepRunning())
{ {
const auto& key = keys[n++ % keys.size()]; const auto& key = keys[n++ % keys.size()];
const auto result = cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); const auto result = cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh,
std::make_unique<PreparedNavMeshData>());
benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(result);
} }
} }

View File

@ -236,7 +236,9 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
if(!quiet) std::cout << " References:\n"; if(!quiet) std::cout << " References:\n";
bool deleted = false; bool deleted = false;
while(cell.getNextRef(esm, ref, deleted)) ESM::MovedCellRef movedCellRef;
bool moved = false;
while(cell.getNextRef(esm, ref, deleted, movedCellRef, moved))
{ {
if (save) { if (save) {
info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted));
@ -244,7 +246,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
if(quiet) continue; if(quiet) continue;
std::cout << " Refnum: " << ref.mRefNum.mIndex << '\n'; std::cout << " - Refnum: " << ref.mRefNum.mIndex << '\n';
std::cout << " ID: " << ref.mRefID << '\n'; std::cout << " ID: " << ref.mRefID << '\n';
std::cout << " Position: (" << ref.mPos.pos[0] << ", " << ref.mPos.pos[1] << ", " << ref.mPos.pos[2] << ")\n"; std::cout << " Position: (" << ref.mPos.pos[0] << ", " << ref.mPos.pos[1] << ", " << ref.mPos.pos[2] << ")\n";
if (ref.mScale != 1.f) if (ref.mScale != 1.f)
@ -276,6 +278,13 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
if (!ref.mDestCell.empty()) if (!ref.mDestCell.empty())
std::cout << " Destination cell: " << ref.mDestCell << '\n'; std::cout << " Destination cell: " << ref.mDestCell << '\n';
} }
std::cout << " Moved: " << std::boolalpha << moved << std::noboolalpha << '\n';
if (moved)
{
std::cout << " Moved refnum: " << movedCellRef.mRefNum.mIndex << '\n';
std::cout << " Moved content file: " << movedCellRef.mRefNum.mContentFile << '\n';
std::cout << " Target: " << movedCellRef.mTarget[0] << ", " << movedCellRef.mTarget[1] << '\n';
}
} }
} }

View File

@ -2,6 +2,7 @@
#include <stdexcept> #include <stdexcept>
#include <algorithm> #include <algorithm>
#include <climits> // INT_MIN
#include <osgDB/WriteFile> #include <osgDB/WriteFile>
@ -357,7 +358,7 @@ namespace ESSImport
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find( auto npccIt = mContext->mNpcChanges.find(
std::make_pair(refIndex, out.mRefID)); std::make_pair(refIndex, out.mRefID));
if (npccIt != mContext->mNpcChanges.end()) if (npccIt != mContext->mNpcChanges.end())
{ {
@ -369,6 +370,8 @@ namespace ESSImport
// from the ESM with default values // from the ESM with default values
if (cellref.mHasACDT) if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats); convertACDT(cellref.mACDT, objstate.mCreatureStats);
else
objstate.mCreatureStats.mGoldPool = INT_MIN; // HACK: indicates no ACDT
if (cellref.mHasACSC) if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats); convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertNpcData(cellref, objstate.mNpcStats); convertNpcData(cellref, objstate.mNpcStats);
@ -383,7 +386,7 @@ namespace ESSImport
continue; continue;
} }
std::map<std::pair<int, std::string>, CNTC>::const_iterator cntcIt = mContext->mContainerChanges.find( auto cntcIt = mContext->mContainerChanges.find(
std::make_pair(refIndex, out.mRefID)); std::make_pair(refIndex, out.mRefID));
if (cntcIt != mContext->mContainerChanges.end()) if (cntcIt != mContext->mContainerChanges.end())
{ {
@ -398,7 +401,7 @@ namespace ESSImport
continue; continue;
} }
std::map<std::pair<int, std::string>, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find( auto crecIt = mContext->mCreatureChanges.find(
std::make_pair(refIndex, out.mRefID)); std::make_pair(refIndex, out.mRefID));
if (crecIt != mContext->mCreatureChanges.end()) if (crecIt != mContext->mCreatureChanges.end())
{ {
@ -410,6 +413,8 @@ namespace ESSImport
// from the ESM with default values // from the ESM with default values
if (cellref.mHasACDT) if (cellref.mHasACDT)
convertACDT(cellref.mACDT, objstate.mCreatureStats); convertACDT(cellref.mACDT, objstate.mCreatureStats);
else
objstate.mCreatureStats.mGoldPool = INT_MIN; // HACK: indicates no ACDT
if (cellref.mHasACSC) if (cellref.mHasACSC)
convertACSC(cellref.mACSC, objstate.mCreatureStats); convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertCREC(crecIt->second, objstate); convertCREC(crecIt->second, objstate);

View File

@ -124,11 +124,9 @@ public:
{ {
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel; mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel;
mContext->mPlayerBase = npc; mContext->mPlayerBase = npc;
ESM::SpellState::SpellParams empty;
// FIXME: player start spells and birthsign spells aren't listed here, // FIXME: player start spells and birthsign spells aren't listed here,
// need to fix openmw to account for this // need to fix openmw to account for this
for (const auto & spell : npc.mSpells.mList) mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList;
mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty;
// Clear the list now that we've written it, this prevents issues cropping up with // Clear the list now that we've written it, this prevents issues cropping up with
// ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal. // ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal.
@ -374,7 +372,7 @@ public:
void write(ESM::ESMWriter &esm) override void write(ESM::ESMWriter &esm) override
{ {
esm.startRecord(ESM::REC_DCOU); esm.startRecord(ESM::REC_DCOU);
for (std::map<std::string, int>::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it) for (auto it = mKillCounter.begin(); it != mKillCounter.end(); ++it)
{ {
esm.writeHNString("ID__", it->first); esm.writeHNString("ID__", it->first);
esm.writeHNT ("COUN", it->second); esm.writeHNT ("COUN", it->second);
@ -397,7 +395,7 @@ public:
faction.load(esm, isDeleted); faction.load(esm, isDeleted);
std::string id = Misc::StringUtils::lowerCase(faction.mId); std::string id = Misc::StringUtils::lowerCase(faction.mId);
for (std::map<std::string, int>::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) for (auto it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it)
{ {
std::string faction2 = Misc::StringUtils::lowerCase(it->first); std::string faction2 = Misc::StringUtils::lowerCase(it->first);
mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second)); mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second));
@ -431,7 +429,7 @@ public:
void write(ESM::ESMWriter &esm) override void write(ESM::ESMWriter &esm) override
{ {
ESM::StolenItems items; ESM::StolenItems items;
for (std::map<std::string, std::set<Owner> >::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it) for (auto it = mStolenItems.begin(); it != mStolenItems.end(); ++it)
{ {
std::map<std::pair<std::string, bool>, int> owners; std::map<std::pair<std::string, bool>, int> owners;
for (const auto & ownerIt : it->second) for (const auto & ownerIt : it->second)
@ -487,7 +485,7 @@ public:
} }
void write(ESM::ESMWriter &esm) override void write(ESM::ESMWriter &esm) override
{ {
for (std::map<std::string, DIAL>::const_iterator it = mDials.begin(); it != mDials.end(); ++it) for (auto it = mDials.begin(); it != mDials.end(); ++it)
{ {
esm.startRecord(ESM::REC_QUES); esm.startRecord(ESM::REC_QUES);
ESM::QuestState state; ESM::QuestState state;

View File

@ -385,7 +385,7 @@ namespace ESSImport
// Writing order should be Dynamic Store -> Cells -> Player, // Writing order should be Dynamic Store -> Cells -> Player,
// so that references to dynamic records can be recognized when loading // so that references to dynamic records can be recognized when loading
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin(); for (auto it = converters.begin();
it != converters.end(); ++it) it != converters.end(); ++it)
{ {
if (it->second->getStage() != 0) if (it->second->getStage() != 0)
@ -398,7 +398,7 @@ namespace ESSImport
context.mPlayerBase.save(writer); context.mPlayerBase.save(writer);
writer.endRecord(ESM::REC_NPC_); writer.endRecord(ESM::REC_NPC_);
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin(); for (auto it = converters.begin();
it != converters.end(); ++it) it != converters.end(); ++it)
{ {
if (it->second->getStage() != 1) if (it->second->getStage() != 1)
@ -423,7 +423,7 @@ namespace ESSImport
writer.endRecord(ESM::REC_ACTC); writer.endRecord(ESM::REC_ACTC);
// Stage 2 requires cell references to be written / actors IDs assigned // Stage 2 requires cell references to be written / actors IDs assigned
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin(); for (auto it = converters.begin();
it != converters.end(); ++it) it != converters.end(); ++it)
{ {
if (it->second->getStage() != 2) if (it->second->getStage() != 2)

View File

@ -36,23 +36,6 @@ set(LAUNCHER_HEADER
) )
# Headers that must be pre-processed # Headers that must be pre-processed
set(LAUNCHER_HEADER_MOC
datafilespage.hpp
graphicspage.hpp
maindialog.hpp
playpage.hpp
textslotmsgbox.hpp
settingspage.hpp
advancedpage.hpp
utils/cellnameloader.hpp
utils/textinputdialog.hpp
utils/profilescombobox.hpp
utils/lineedit.hpp
utils/openalutil.hpp
)
set(LAUNCHER_UI set(LAUNCHER_UI
${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
@ -74,7 +57,6 @@ if(WIN32)
endif(WIN32) endif(WIN32)
QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR})
@ -99,7 +81,7 @@ endif (WIN32)
target_link_libraries(openmw-launcher target_link_libraries(openmw-launcher
${SDL2_LIBRARY_ONLY} ${SDL2_LIBRARY_ONLY}
${OPENAL_LIBRARY} ${OPENAL_LIBRARY}
components components_qt
) )
target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core) target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core)
@ -109,4 +91,7 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(openmw-launcher gcov) target_link_libraries(openmw-launcher gcov)
endif() endif()
if(USE_QT)
set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON)
endif(USE_QT)

View File

@ -192,6 +192,7 @@ bool Launcher::AdvancedPage::loadSettings()
if (showOwnedIndex >= 0 && showOwnedIndex <= 3) if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
showOwnedComboBox->setCurrentIndex(showOwnedIndex); showOwnedComboBox->setCurrentIndex(showOwnedIndex);
loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
loadSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map");
loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "GUI")); scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "GUI"));
} }
@ -352,6 +353,7 @@ void Launcher::AdvancedPage::saveSettings()
if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game")) if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game"))
Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex); Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex);
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map");
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
float uiScalingFactor = scalingSpinBox->value(); float uiScalingFactor = scalingSpinBox->value();
if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI")) if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI"))

View File

@ -17,7 +17,6 @@
#include <components/config/gamesettings.hpp> #include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp> #include <components/config/launchersettings.hpp>
#include <iostream>
#include "utils/textinputdialog.hpp" #include "utils/textinputdialog.hpp"
#include "utils/profilescombobox.hpp" #include "utils/profilescombobox.hpp"

View File

@ -14,8 +14,7 @@
#include <SDL_video.h> #include <SDL_video.h>
#include <numeric> #include <numeric>
#include <array>
#include <components/files/configurationmanager.hpp>
QString getAspect(int x, int y) QString getAspect(int x, int y)
{ {

View File

@ -6,7 +6,6 @@
#include <QDate> #include <QDate>
#include <QMessageBox> #include <QMessageBox>
#include <QFontDatabase> #include <QFontDatabase>
#include <QInputDialog>
#include <QFileDialog> #include <QFileDialog>
#include <QCloseEvent> #include <QCloseEvent>
#include <QTextCodec> #include <QTextCodec>
@ -52,8 +51,8 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
iconWidget->setCurrentRow(0); iconWidget->setCurrentRow(0);
iconWidget->setFlow(QListView::LeftToRight); iconWidget->setFlow(QListView::LeftToRight);
QPushButton *helpButton = new QPushButton(tr("Help")); auto *helpButton = new QPushButton(tr("Help"));
QPushButton *playButton = new QPushButton(tr("Play")); auto *playButton = new QPushButton(tr("Play"));
buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close")); buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close"));
buttonBox->addButton(helpButton, QDialogButtonBox::HelpRole); buttonBox->addButton(helpButton, QDialogButtonBox::HelpRole);
buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole); buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole);
@ -79,31 +78,31 @@ void Launcher::MainDialog::createIcons()
if (!QIcon::hasThemeIcon("document-new")) if (!QIcon::hasThemeIcon("document-new"))
QIcon::setThemeName("tango"); QIcon::setThemeName("tango");
QListWidgetItem *playButton = new QListWidgetItem(iconWidget); auto *playButton = new QListWidgetItem(iconWidget);
playButton->setIcon(QIcon(":/images/openmw.png")); playButton->setIcon(QIcon(":/images/openmw.png"));
playButton->setText(tr("Play")); playButton->setText(tr("Play"));
playButton->setTextAlignment(Qt::AlignCenter); playButton->setTextAlignment(Qt::AlignCenter);
playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *dataFilesButton = new QListWidgetItem(iconWidget); auto *dataFilesButton = new QListWidgetItem(iconWidget);
dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png")); dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png"));
dataFilesButton->setText(tr("Data Files")); dataFilesButton->setText(tr("Data Files"));
dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); auto *graphicsButton = new QListWidgetItem(iconWidget);
graphicsButton->setIcon(QIcon(":/images/preferences-video.png")); graphicsButton->setIcon(QIcon(":/images/preferences-video.png"));
graphicsButton->setText(tr("Graphics")); graphicsButton->setText(tr("Graphics"));
graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute);
graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget); auto *settingsButton = new QListWidgetItem(iconWidget);
settingsButton->setIcon(QIcon(":/images/preferences.png")); settingsButton->setIcon(QIcon(":/images/preferences.png"));
settingsButton->setText(tr("Settings")); settingsButton->setText(tr("Settings"));
settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget); auto *advancedButton = new QListWidgetItem(iconWidget);
advancedButton->setIcon(QIcon(":/images/preferences-advanced.png")); advancedButton->setIcon(QIcon(":/images/preferences-advanced.png"));
advancedButton->setText(tr("Advanced")); advancedButton->setText(tr("Advanced"));
advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);

View File

@ -4,11 +4,6 @@
#include <QMessageBox> #include <QMessageBox>
#include <QDir> #include <QDir>
#include <components/files/configurationmanager.hpp>
#include <components/config/gamesettings.hpp>
#include <components/config/launchersettings.hpp>
#include "utils/textinputdialog.hpp" #include "utils/textinputdialog.hpp"
#include "datafilespage.hpp" #include "datafilespage.hpp"

View File

@ -1,6 +1,5 @@
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include <memory>
#include <apps/openmw/mwsound/alext.h> #include <apps/openmw/mwsound/alext.h>

View File

@ -29,7 +29,7 @@ void ProfilesComboBox::setEditEnabled(bool editable)
setEditable(true); setEditable(true);
setValidator(mValidator); setValidator(mValidator);
ComboBoxLineEdit *edit = new ComboBoxLineEdit(this); auto *edit = new ComboBoxLineEdit(this);
setLineEdit(edit); setLineEdit(edit);
setCompleter(nullptr); setCompleter(nullptr);

View File

@ -16,7 +16,7 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString &
mButtonBox->addButton(QDialogButtonBox::Cancel); mButtonBox->addButton(QDialogButtonBox::Cancel);
mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false);
QLabel *label = new QLabel(this); auto *label = new QLabel(this);
label->setText(text); label->setText(text);
// Line edit // Line edit
@ -25,7 +25,7 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString &
mLineEdit->setValidator(validator); mLineEdit->setValidator(validator);
mLineEdit->setCompleter(nullptr); mLineEdit->setCompleter(nullptr);
QVBoxLayout *dialogLayout = new QVBoxLayout(this); auto *dialogLayout = new QVBoxLayout(this);
dialogLayout->addWidget(label); dialogLayout->addWidget(label);
dialogLayout->addWidget(mLineEdit); dialogLayout->addWidget(mLineEdit);
dialogLayout->addWidget(mButtonBox); dialogLayout->addWidget(mButtonBox);

View File

@ -778,7 +778,7 @@ void MwIniImporter::mergeFallback(multistrmap &cfg, const multistrmap &ini) cons
} }
void MwIniImporter::insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value) { void MwIniImporter::insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value) {
const multistrmap::const_iterator it = cfg.find(key); const auto it = cfg.find(key);
if(it == cfg.end()) { if(it == cfg.end()) {
cfg.insert(std::make_pair (key, std::vector<std::string>() )); cfg.insert(std::make_pair (key, std::vector<std::string>() ));
} }
@ -791,7 +791,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con
std::string archive; std::string archive;
// Search archives listed in ini file // Search archives listed in ini file
multistrmap::const_iterator it = ini.begin(); auto it = ini.begin();
for(int i=0; it != ini.end(); i++) { for(int i=0; it != ini.end(); i++) {
archive = baseArchive; archive = baseArchive;
archive.append(std::to_string(i)); archive.append(std::to_string(i));
@ -813,7 +813,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con
// does not appears in the ini file // does not appears in the ini file
cfg["fallback-archive"].push_back("Morrowind.bsa"); cfg["fallback-archive"].push_back("Morrowind.bsa");
for(std::vector<std::string>::const_iterator iter=archives.begin(); iter!=archives.end(); ++iter) { for(auto iter=archives.begin(); iter!=archives.end(); ++iter) {
cfg["fallback-archive"].push_back(*iter); cfg["fallback-archive"].push_back(*iter);
} }
} }
@ -886,7 +886,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co
dataPaths.push_back(iniFilename.parent_path() /= "Data Files"); dataPaths.push_back(iniFilename.parent_path() /= "Data Files");
multistrmap::const_iterator it = ini.begin(); auto it = ini.begin();
for (int i=0; it != ini.end(); i++) for (int i=0; it != ini.end(); i++)
{ {
std::string gameFile = baseGameFile; std::string gameFile = baseGameFile;
@ -969,7 +969,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co
void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) { void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) {
for(multistrmap::const_iterator it=cfg.begin(); it != cfg.end(); ++it) { for(multistrmap::const_iterator it=cfg.begin(); it != cfg.end(); ++it) {
for(std::vector<std::string>::const_iterator entry=it->second.begin(); entry != it->second.end(); ++entry) { for(auto entry=it->second.begin(); entry != it->second.end(); ++entry) {
out << (it->first) << "=" << (*entry) << std::endl; out << (it->first) << "=" << (*entry) << std::endl;
} }
} }

View File

@ -52,11 +52,8 @@ void readVFS(VFS::Archive* anArchive,std::string archivePath = "")
myManager.addArchive(anArchive); myManager.addArchive(anArchive);
myManager.buildIndex(); myManager.buildIndex();
std::map<std::string, VFS::File*> files=myManager.getIndex(); for(const auto& name : myManager.getRecursiveDirectoryIterator(""))
for(std::map<std::string, VFS::File*>::const_iterator it=files.begin(); it!=files.end(); ++it)
{ {
std::string name = it->first;
try{ try{
if(isNIF(name)) if(isNIF(name))
{ {
@ -134,7 +131,7 @@ int main(int argc, char **argv)
Nif::NIFFile::setLoadUnsupportedFiles(true); Nif::NIFFile::setLoadUnsupportedFiles(true);
// std::cout << "Reading Files" << std::endl; // std::cout << "Reading Files" << std::endl;
for(std::vector<std::string>::const_iterator it=files.begin(); it!=files.end(); ++it) for(auto it=files.begin(); it!=files.end(); ++it)
{ {
std::string name = *it; std::string name = *it;

View File

@ -8,29 +8,29 @@ opencs_units (model/doc
document operation saving documentmanager loader runner operationholder document operation saving documentmanager loader runner operationholder
) )
opencs_units_noqt (model/doc opencs_units (model/doc
stage savingstate savingstages blacklist messages stage savingstate savingstages blacklist messages
) )
opencs_hdrs_noqt (model/doc opencs_hdrs (model/doc
state state
) )
opencs_units (model/world opencs_units (model/world
idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel
actoradapter actoradapter idcollection
) )
opencs_units_noqt (model/world opencs_units (model/world
universalid record commands columnbase columnimp scriptcontext cell refidcollection universalid record commands columnbase columnimp scriptcontext cell refidcollection
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro
) )
opencs_hdrs_noqt (model/world opencs_hdrs (model/world
columnimp idcollection collection info subcellcollection columnimp idcollection collection info subcellcollection
) )
@ -39,14 +39,14 @@ opencs_units (model/tools
tools reportmodel mergeoperation tools reportmodel mergeoperation
) )
opencs_units_noqt (model/tools opencs_units (model/tools
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck
) )
opencs_hdrs_noqt (model/tools opencs_hdrs (model/tools
mergestate mergestate
) )
@ -57,11 +57,11 @@ opencs_units (view/doc
) )
opencs_units_noqt (view/doc opencs_units (view/doc
subviewfactory subviewfactory
) )
opencs_hdrs_noqt (view/doc opencs_hdrs (view/doc
subviewfactoryimp subviewfactoryimp
) )
@ -71,10 +71,10 @@ opencs_units (view/world
cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator
bodypartcreator landtexturecreator landcreator bodypartcreator landtexturecreator landcreator tableheadermouseeventhandler
) )
opencs_units_noqt (view/world opencs_units (view/world
subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate
scripthighlighter idvalidator dialoguecreator idcompletiondelegate scripthighlighter idvalidator dialoguecreator idcompletiondelegate
colordelegate dragdroputils colordelegate dragdroputils
@ -92,12 +92,12 @@ opencs_units (view/render
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands
) )
opencs_units_noqt (view/render opencs_units (view/render
lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase
cellarrow cellmarker cellborder pathgrid cellarrow cellmarker cellborder pathgrid
) )
opencs_hdrs_noqt (view/render opencs_hdrs (view/render
mask mask
) )
@ -106,7 +106,7 @@ opencs_units (view/tools
reportsubview reporttable searchsubview searchbox merge reportsubview reporttable searchsubview searchbox merge
) )
opencs_units_noqt (view/tools opencs_units (view/tools
subviews subviews
) )
@ -119,11 +119,11 @@ opencs_units (model/prefs
shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting
) )
opencs_units_noqt (model/prefs opencs_units (model/prefs
category category
) )
opencs_units_noqt (model/filter opencs_units (model/filter
node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode
) )
@ -150,7 +150,6 @@ if(WIN32)
endif(WIN32) endif(WIN32)
qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT})
qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
# for compiled .ui files # for compiled .ui files
@ -229,34 +228,9 @@ target_link_libraries(openmw-cs
${Boost_SYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY}
components components_qt
) )
if(OSG_STATIC)
unset(_osg_plugins_static_files)
add_library(openmw_cs_osg_plugins INTERFACE)
foreach(_plugin ${USED_OSG_PLUGINS})
string(TOUPPER ${_plugin} _plugin_uc)
if(OPENMW_USE_SYSTEM_OSG)
list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY})
else()
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>)
target_link_libraries(openmw_cs_osg_plugins INTERFACE $<TARGET_PROPERTY:${${_plugin_uc}_LIBRARY},LINK_LIBRARIES>)
add_dependencies(openmw_cs_osg_plugins ${${_plugin_uc}_LIBRARY})
endif()
endforeach()
# We use --whole-archive because OSG plugins use registration.
get_whole_archive_options(_opts ${_osg_plugins_static_files})
target_link_options(openmw_cs_osg_plugins INTERFACE ${_opts})
target_link_libraries(openmw-cs openmw_cs_osg_plugins)
if(OPENMW_USE_SYSTEM_OSG)
# OSG plugin pkgconfig files are missing these dependencies.
# https://github.com/openscenegraph/OpenSceneGraph/issues/1052
target_link_libraries(openmw freetype jpeg png)
endif()
endif(OSG_STATIC)
target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL)
if (WIN32) if (WIN32)
@ -284,3 +258,7 @@ endif (MSVC)
if(APPLE) if(APPLE)
INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle) INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle)
endif() endif()
if(USE_QT)
set_property(TARGET openmw-cs PROPERTY AUTOMOC ON)
endif(USE_QT)

View File

@ -1,6 +1,7 @@
#include "document.hpp" #include "document.hpp"
#include <cassert> #include <cassert>
#include <memory>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
@ -115,10 +116,10 @@ void CSMDoc::Document::addOptionalGmst (const ESM::GameSetting& gmst)
{ {
if (getData().getGmsts().searchId (gmst.mId)==-1) if (getData().getGmsts().searchId (gmst.mId)==-1)
{ {
CSMWorld::Record<ESM::GameSetting> record; std::unique_ptr<CSMWorld::Record<ESM::GameSetting> > record(new CSMWorld::Record<ESM::GameSetting>);
record.mBase = gmst; record->mBase = gmst;
record.mState = CSMWorld::RecordBase::State_BaseOnly; record->mState = CSMWorld::RecordBase::State_BaseOnly;
getData().getGmsts().appendRecord (record); getData().getGmsts().appendRecord (std::move(record));
} }
} }
@ -126,10 +127,10 @@ void CSMDoc::Document::addOptionalGlobal (const ESM::Global& global)
{ {
if (getData().getGlobals().searchId (global.mId)==-1) if (getData().getGlobals().searchId (global.mId)==-1)
{ {
CSMWorld::Record<ESM::Global> record; std::unique_ptr<CSMWorld::Record<ESM::Global> > record(new CSMWorld::Record<ESM::Global>);
record.mBase = global; record->mBase = global;
record.mState = CSMWorld::RecordBase::State_BaseOnly; record->mState = CSMWorld::RecordBase::State_BaseOnly;
getData().getGlobals().appendRecord (record); getData().getGlobals().appendRecord (std::move(record));
} }
} }
@ -137,10 +138,10 @@ void CSMDoc::Document::addOptionalMagicEffect (const ESM::MagicEffect& magicEffe
{ {
if (getData().getMagicEffects().searchId (magicEffect.mId)==-1) if (getData().getMagicEffects().searchId (magicEffect.mId)==-1)
{ {
CSMWorld::Record<ESM::MagicEffect> record; std::unique_ptr<CSMWorld::Record<ESM::MagicEffect> > record(new CSMWorld::Record<ESM::MagicEffect>);
record.mBase = magicEffect; record->mBase = magicEffect;
record.mState = CSMWorld::RecordBase::State_BaseOnly; record->mState = CSMWorld::RecordBase::State_BaseOnly;
getData().getMagicEffects().appendRecord (record); getData().getMagicEffects().appendRecord (std::move(record));
} }
} }

View File

@ -85,19 +85,19 @@ void CSMDoc::Loader::load()
return; return;
} }
if (iter->second.mFile<size) if (iter->second.mFile<size) // start loading the files
{ {
boost::filesystem::path path = document->getContentFiles()[iter->second.mFile]; boost::filesystem::path path = document->getContentFiles()[iter->second.mFile];
int steps = document->getData().startLoading (path, iter->second.mFile!=editedIndex, false); int steps = document->getData().startLoading (path, iter->second.mFile!=editedIndex, /*project*/false);
iter->second.mRecordsLeft = true; iter->second.mRecordsLeft = true;
iter->second.mRecordsLoaded = 0; iter->second.mRecordsLoaded = 0;
emit nextStage (document, path.filename().string(), steps); emit nextStage (document, path.filename().string(), steps);
} }
else if (iter->second.mFile==size) else if (iter->second.mFile==size) // start loading the last (project) file
{ {
int steps = document->getData().startLoading (document->getProjectPath(), false, true); int steps = document->getData().startLoading (document->getProjectPath(), /*base*/false, true);
iter->second.mRecordsLeft = true; iter->second.mRecordsLeft = true;
iter->second.mRecordsLoaded = 0; iter->second.mRecordsLoaded = 0;

View File

@ -83,6 +83,8 @@ void CSMDoc::Runner::start (bool delayed)
arguments << arguments <<
QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str()); QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str());
arguments << "--replace=content";
for (std::vector<std::string>::const_iterator iter (mContentFiles.begin()); for (std::vector<std::string>::const_iterator iter (mContentFiles.begin());
iter!=mContentFiles.end(); ++iter) iter!=mContentFiles.end(); ++iter)
{ {

View File

@ -114,7 +114,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message
for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter)
{ {
if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) if ((*iter)->isModified() || (*iter)->mState == CSMWorld::RecordBase::State_Deleted)
{ {
infoModified = true; infoModified = true;
break; break;
@ -140,9 +140,9 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message
// write modified selected info records // write modified selected info records
for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter)
{ {
if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) if ((*iter)->isModified() || (*iter)->mState == CSMWorld::RecordBase::State_Deleted)
{ {
ESM::DialInfo info = iter->get(); ESM::DialInfo info = (*iter)->get();
info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1);
info.mPrev = ""; info.mPrev = "";
@ -151,7 +151,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message
CSMWorld::InfoCollection::RecordConstIterator prev = iter; CSMWorld::InfoCollection::RecordConstIterator prev = iter;
--prev; --prev;
info.mPrev = prev->get().mId.substr (prev->get().mId.find_last_of ('#')+1); info.mPrev = (*prev)->get().mId.substr ((*prev)->get().mId.find_last_of ('#')+1);
} }
CSMWorld::InfoCollection::RecordConstIterator next = iter; CSMWorld::InfoCollection::RecordConstIterator next = iter;
@ -160,11 +160,11 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message
info.mNext = ""; info.mNext = "";
if (next!=range.second) if (next!=range.second)
{ {
info.mNext = next->get().mId.substr (next->get().mId.find_last_of ('#')+1); info.mNext = (*next)->get().mId.substr ((*next)->get().mId.find_last_of ('#')+1);
} }
writer.startRecord (info.sRecordId); writer.startRecord (info.sRecordId);
info.save (writer, iter->mState == CSMWorld::RecordBase::State_Deleted); info.save (writer, (*iter)->mState == CSMWorld::RecordBase::State_Deleted);
writer.endRecord (info.sRecordId); writer.endRecord (info.sRecordId);
} }
} }

View File

@ -108,7 +108,7 @@ namespace CSMDoc
state == CSMWorld::RecordBase::State_ModifiedOnly || state == CSMWorld::RecordBase::State_ModifiedOnly ||
state == CSMWorld::RecordBase::State_Deleted) state == CSMWorld::RecordBase::State_Deleted)
{ {
writer.startRecord (record.sRecordId); writer.startRecord (record.sRecordId, record.mRecordFlags);
record.save (writer, state == CSMWorld::RecordBase::State_Deleted); record.save (writer, state == CSMWorld::RecordBase::State_Deleted);
writer.endRecord (record.sRecordId); writer.endRecord (record.sRecordId);
} }

View File

@ -35,7 +35,7 @@ void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages)
for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it) for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it)
{ {
const CSMWorld::Record<CSMWorld::Info> infoRecord = (*it); const CSMWorld::Record<CSMWorld::Info> infoRecord = (*it->get());
if (infoRecord.isDeleted()) if (infoRecord.isDeleted())
continue; continue;

View File

@ -103,10 +103,9 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa
ref.mRefNum.mContentFile = 0; ref.mRefNum.mContentFile = 0;
ref.mNew = false; ref.mNew = false;
CSMWorld::Record<CSMWorld::CellRef> newRecord ( mState.mTarget->getData().getReferences().appendRecord (
CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &ref); std::make_unique<CSMWorld::Record<CSMWorld::CellRef> >(
CSMWorld::Record<CSMWorld::CellRef>(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &ref)));
mState.mTarget->getData().getReferences().appendRecord (newRecord);
} }
} }
@ -128,7 +127,9 @@ void CSMTools::PopulateLandTexturesMergeStage::perform (int stage, CSMDoc::Messa
if (!record.isDeleted()) if (!record.isDeleted())
{ {
mState.mTarget->getData().getLandTextures().appendRecord(record); mState.mTarget->getData().getLandTextures().appendRecord(
std::make_unique<CSMWorld::Record<CSMWorld::LandTexture> >(
CSMWorld::Record<CSMWorld::LandTexture>(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get())));
} }
} }
@ -150,7 +151,9 @@ void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages)
if (!record.isDeleted()) if (!record.isDeleted())
{ {
mState.mTarget->getData().getLand().appendRecord (record); mState.mTarget->getData().getLand().appendRecord (
std::make_unique<CSMWorld::Record<CSMWorld::Land> >(
CSMWorld::Record<CSMWorld::Land>(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get())));
} }
} }
@ -185,10 +188,9 @@ void CSMTools::FixLandsAndLandTexturesMergeStage::perform (int stage, CSMDoc::Me
const CSMWorld::Record<CSMWorld::Land>& oldRecord = const CSMWorld::Record<CSMWorld::Land>& oldRecord =
mState.mTarget->getData().getLand().getRecord (stage); mState.mTarget->getData().getLand().getRecord (stage);
CSMWorld::Record<CSMWorld::Land> newRecord(CSMWorld::RecordBase::State_ModifiedOnly, mState.mTarget->getData().getLand().setRecord (stage,
nullptr, &oldRecord.get()); std::make_unique<CSMWorld::Record<CSMWorld::Land> >(
CSMWorld::Record<CSMWorld::Land>(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &oldRecord.get())));
mState.mTarget->getData().getLand().setRecord(stage, newRecord);
} }
} }

View File

@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include <map> #include <map>
#include <memory>
#include <components/to_utf8/to_utf8.hpp> #include <components/to_utf8/to_utf8.hpp>
@ -82,7 +83,8 @@ namespace CSMTools
const CSMWorld::Record<RecordType>& record = source.getRecord (stage); const CSMWorld::Record<RecordType>& record = source.getRecord (stage);
if (!record.isDeleted()) if (!record.isDeleted())
target.appendRecord (CSMWorld::Record<RecordType> (CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get())); target.appendRecord (std::make_unique<CSMWorld::Record<RecordType> >(
CSMWorld::Record<RecordType>(CSMWorld::RecordBase::State_ModifiedOnly, nullptr, &record.get())));
} }
class MergeRefIdsStage : public CSMDoc::Stage class MergeRefIdsStage : public CSMDoc::Stage

View File

@ -9,6 +9,9 @@
#include "data.hpp" #include "data.hpp"
#include <string>
#include <string_view>
namespace CSMWorld namespace CSMWorld
{ {
const std::string& ActorAdapter::RaceData::getId() const const std::string& ActorAdapter::RaceData::getId() const
@ -121,7 +124,7 @@ namespace CSMWorld
return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf);
} }
const std::string ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const std::string_view ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const
{ {
auto it = mParts.find(index); auto it = mParts.find(index);
if (it == mParts.end()) if (it == mParts.end())
@ -131,7 +134,7 @@ namespace CSMWorld
if (mFemale) if (mFemale)
{ {
// Note: we should use male parts for females as fallback // Note: we should use male parts for females as fallback
const std::string femalePart = mRaceData->getFemalePart(index); const std::string& femalePart = mRaceData->getFemalePart(index);
if (!femalePart.empty()) if (!femalePart.empty())
return femalePart; return femalePart;
} }
@ -139,11 +142,10 @@ namespace CSMWorld
return mRaceData->getMalePart(index); return mRaceData->getMalePart(index);
} }
return ""; return {};
} }
const std::string& partName = it->second.first; return it->second.first;
return partName;
} }
bool ActorAdapter::ActorData::hasDependency(const std::string& id) const bool ActorAdapter::ActorData::hasDependency(const std::string& id) const

View File

@ -4,6 +4,8 @@
#include <array> #include <array>
#include <map> #include <map>
#include <unordered_set> #include <unordered_set>
#include <string>
#include <string_view>
#include <QObject> #include <QObject>
#include <QModelIndex> #include <QModelIndex>
@ -93,7 +95,7 @@ namespace CSMWorld
/// Returns the skeleton the actor should use for attaching parts to /// Returns the skeleton the actor should use for attaching parts to
std::string getSkeleton() const; std::string getSkeleton() const;
/// Retrieves the associated actor part /// Retrieves the associated actor part
const std::string getPart(ESM::PartReferenceType index) const; std::string_view getPart(ESM::PartReferenceType index) const;
/// Checks if the actor has a data dependency /// Checks if the actor has a data dependency
bool hasDependency(const std::string& id) const; bool hasDependency(const std::string& id) const;

View File

@ -8,6 +8,8 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <functional> #include <functional>
#include <memory>
#include <string_view>
#include <QVariant> #include <QVariant>
@ -84,7 +86,7 @@ namespace CSMWorld
private: private:
std::vector<Record<ESXRecordT> > mRecords; std::vector<std::unique_ptr<Record<ESXRecordT> > > mRecords;
std::map<std::string, int> mIndex; std::map<std::string, int> mIndex;
std::vector<Column<ESXRecordT> *> mColumns; std::vector<Column<ESXRecordT> *> mColumns;
@ -94,9 +96,7 @@ namespace CSMWorld
protected: protected:
const std::map<std::string, int>& getIdMap() const; const std::vector<std::unique_ptr<Record<ESXRecordT> > >& getRecords() const;
const std::vector<Record<ESXRecordT> >& getRecords() const;
bool reorderRowsImp (int baseIndex, const std::vector<int>& newOrder); bool reorderRowsImp (int baseIndex, const std::vector<int>& newOrder);
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
@ -154,16 +154,16 @@ namespace CSMWorld
///< Change the state of a record from base to modified, if it is not already. ///< Change the state of a record from base to modified, if it is not already.
/// \return True if the record was changed. /// \return True if the record was changed.
int searchId (const std::string& id) const override; int searchId(std::string_view id) const override;
////< Search record with \a id. ////< Search record with \a id.
/// \return index of record (if found) or -1 (not found) /// \return index of record (if found) or -1 (not found)
void replace (int index, const RecordBase& record) override; void replace (int index, std::unique_ptr<RecordBase> record) override;
///< If the record type does not match, an exception is thrown. ///< If the record type does not match, an exception is thrown.
/// ///
/// \attention \a record must not change the ID. /// \attention \a record must not change the ID.
void appendRecord (const RecordBase& record, void appendRecord (std::unique_ptr<RecordBase> record,
UniversalId::Type type = UniversalId::Type_None) override; UniversalId::Type type = UniversalId::Type_None) override;
///< If the record type does not match, an exception is thrown. ///< If the record type does not match, an exception is thrown.
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
@ -181,7 +181,7 @@ namespace CSMWorld
/// ///
/// \param listDeleted include deleted record in the list /// \param listDeleted include deleted record in the list
virtual void insertRecord (const RecordBase& record, int index, virtual void insertRecord (std::unique_ptr<RecordBase> record, int index,
UniversalId::Type type = UniversalId::Type_None); UniversalId::Type type = UniversalId::Type_None);
///< Insert record before index. ///< Insert record before index.
/// ///
@ -198,20 +198,14 @@ namespace CSMWorld
void addColumn (Column<ESXRecordT> *column); void addColumn (Column<ESXRecordT> *column);
void setRecord (int index, const Record<ESXRecordT>& record); void setRecord (int index, std::unique_ptr<Record<ESXRecordT> > record);
///< \attention This function must not change the ID. ///< \attention This function must not change the ID.
NestableColumn *getNestableColumn (int column) const; NestableColumn *getNestableColumn (int column) const;
}; };
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
const std::map<std::string, int>& Collection<ESXRecordT, IdAccessorT>::getIdMap() const const std::vector<std::unique_ptr<Record<ESXRecordT> > >& Collection<ESXRecordT, IdAccessorT>::getRecords() const
{
return mIndex;
}
template<typename ESXRecordT, typename IdAccessorT>
const std::vector<Record<ESXRecordT> >& Collection<ESXRecordT, IdAccessorT>::getRecords() const
{ {
return mRecords; return mRecords;
} }
@ -231,15 +225,15 @@ namespace CSMWorld
return false; return false;
// reorder records // reorder records
std::vector<Record<ESXRecordT> > buffer (size); std::vector<std::unique_ptr<Record<ESXRecordT> > > buffer (size);
for (int i=0; i<size; ++i) for (int i=0; i<size; ++i)
{ {
buffer[newOrder[i]] = mRecords [baseIndex+i]; buffer[newOrder[i]] = std::move(mRecords [baseIndex+i]);
buffer[newOrder[i]].setModified (buffer[newOrder[i]].get()); buffer[newOrder[i]]->setModified (buffer[newOrder[i]]->get());
} }
std::copy (buffer.begin(), buffer.end(), mRecords.begin()+baseIndex); std::move (buffer.begin(), buffer.end(), mRecords.begin()+baseIndex);
// adjust index // adjust index
for (std::map<std::string, int>::iterator iter (mIndex.begin()); iter!=mIndex.end(); for (std::map<std::string, int>::iterator iter (mIndex.begin()); iter!=mIndex.end();
@ -255,18 +249,18 @@ namespace CSMWorld
int Collection<ESXRecordT, IdAccessorT>::cloneRecordImp(const std::string& origin, int Collection<ESXRecordT, IdAccessorT>::cloneRecordImp(const std::string& origin,
const std::string& destination, UniversalId::Type type) const std::string& destination, UniversalId::Type type)
{ {
Record<ESXRecordT> copy; std::unique_ptr<Record<ESXRecordT> > copy(new Record<ESXRecordT>);
copy.mModified = getRecord(origin).get(); copy->mModified = getRecord(origin).get();
copy.mState = RecordBase::State_ModifiedOnly; copy->mState = RecordBase::State_ModifiedOnly;
IdAccessorT().setId(copy.get(), destination); IdAccessorT().setId(copy->get(), destination);
if (type == UniversalId::Type_Reference) { if (type == UniversalId::Type_Reference) {
CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) &copy.mModified; CSMWorld::CellRef* ptr = (CSMWorld::CellRef*) &copy->mModified;
ptr->mRefNum.mIndex = 0; ptr->mRefNum.mIndex = 0;
} }
int index = getAppendIndex(destination, type); int index = getAppendIndex(destination, type);
insertRecord(copy, getAppendIndex(destination, type)); insertRecord(std::move(copy), getAppendIndex(destination, type));
return index; return index;
} }
@ -275,7 +269,7 @@ namespace CSMWorld
int Collection<ESXRecordT, IdAccessorT>::touchRecordImp(const std::string& id) int Collection<ESXRecordT, IdAccessorT>::touchRecordImp(const std::string& id)
{ {
int index = getIndex(id); int index = getIndex(id);
Record<ESXRecordT>& record = mRecords.at(index); Record<ESXRecordT>& record = *mRecords.at(index);
if (record.isDeleted()) if (record.isDeleted())
{ {
throw std::runtime_error("attempt to touch deleted record"); throw std::runtime_error("attempt to touch deleted record");
@ -302,7 +296,7 @@ namespace CSMWorld
const std::string& destination, const UniversalId::Type type) const std::string& destination, const UniversalId::Type type)
{ {
int index = cloneRecordImp(origin, destination, type); int index = cloneRecordImp(origin, destination, type);
mRecords.at(index).get().mPlugin = 0; mRecords.at(index)->get().mPlugin = 0;
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
@ -317,7 +311,7 @@ namespace CSMWorld
int index = touchRecordImp(id); int index = touchRecordImp(id);
if (index >= 0) if (index >= 0)
{ {
mRecords.at(index).get().mPlugin = 0; mRecords.at(index)->get().mPlugin = 0;
return true; return true;
} }
@ -344,15 +338,15 @@ namespace CSMWorld
if (iter==mIndex.end()) if (iter==mIndex.end())
{ {
Record<ESXRecordT> record2; std::unique_ptr<Record<ESXRecordT> > record2(new Record<ESXRecordT>);
record2.mState = Record<ESXRecordT>::State_ModifiedOnly; record2->mState = Record<ESXRecordT>::State_ModifiedOnly;
record2.mModified = record; record2->mModified = record;
insertRecord (record2, getAppendIndex (id)); insertRecord (std::move(record2), getAppendIndex (id));
} }
else else
{ {
mRecords[iter->second].setModified (record); mRecords[iter->second]->setModified (record);
} }
} }
@ -365,7 +359,7 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
std::string Collection<ESXRecordT, IdAccessorT>::getId (int index) const std::string Collection<ESXRecordT, IdAccessorT>::getId (int index) const
{ {
return IdAccessorT().getId (mRecords.at (index).get()); return IdAccessorT().getId (mRecords.at (index)->get());
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
@ -388,13 +382,13 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
QVariant Collection<ESXRecordT, IdAccessorT>::getData (int index, int column) const QVariant Collection<ESXRecordT, IdAccessorT>::getData (int index, int column) const
{ {
return mColumns.at (column)->get (mRecords.at (index)); return mColumns.at (column)->get (*mRecords.at (index));
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::setData (int index, int column, const QVariant& data) void Collection<ESXRecordT, IdAccessorT>::setData (int index, int column, const QVariant& data)
{ {
return mColumns.at (column)->set (mRecords.at (index), data); return mColumns.at (column)->set (*mRecords.at (index), data);
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
@ -421,8 +415,8 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::merge() void Collection<ESXRecordT, IdAccessorT>::merge()
{ {
for (typename std::vector<Record<ESXRecordT> >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter) for (typename std::vector<std::unique_ptr<Record<ESXRecordT> > >::iterator iter (mRecords.begin()); iter!=mRecords.end(); ++iter)
iter->merge(); (*iter)->merge();
purge(); purge();
} }
@ -434,7 +428,7 @@ namespace CSMWorld
while (i<static_cast<int> (mRecords.size())) while (i<static_cast<int> (mRecords.size()))
{ {
if (mRecords[i].isErased()) if (mRecords[i]->isErased())
removeRows (i, 1); removeRows (i, 1);
else else
++i; ++i;
@ -475,15 +469,15 @@ namespace CSMWorld
IdAccessorT().setId(record, id); IdAccessorT().setId(record, id);
record.blank(); record.blank();
Record<ESXRecordT> record2; std::unique_ptr<Record<ESXRecordT> > record2(new Record<ESXRecordT>);
record2.mState = Record<ESXRecordT>::State_ModifiedOnly; record2->mState = Record<ESXRecordT>::State_ModifiedOnly;
record2.mModified = record; record2->mModified = record;
insertRecord (record2, getAppendIndex (id, type), type); insertRecord (std::move(record2), getAppendIndex (id, type), type);
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
int Collection<ESXRecordT, IdAccessorT>::searchId (const std::string& id) const int Collection<ESXRecordT, IdAccessorT>::searchId(std::string_view id) const
{ {
std::string id2 = Misc::StringUtils::lowerCase(id); std::string id2 = Misc::StringUtils::lowerCase(id);
@ -496,18 +490,19 @@ namespace CSMWorld
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::replace (int index, const RecordBase& record) void Collection<ESXRecordT, IdAccessorT>::replace (int index, std::unique_ptr<RecordBase> record)
{ {
mRecords.at (index) = dynamic_cast<const Record<ESXRecordT>&> (record); std::unique_ptr<Record<ESXRecordT> > tmp(static_cast<Record<ESXRecordT>*>(record.release()));
mRecords.at (index) = std::move(tmp);
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::appendRecord (const RecordBase& record, void Collection<ESXRecordT, IdAccessorT>::appendRecord (std::unique_ptr<RecordBase> record,
UniversalId::Type type) UniversalId::Type type)
{ {
insertRecord (record, int index =
getAppendIndex (IdAccessorT().getId ( getAppendIndex(IdAccessorT().getId(static_cast<Record<ESXRecordT>*>(record.get())->get()), type);
dynamic_cast<const Record<ESXRecordT>&> (record).get()), type), type); insertRecord (std::move(record), index, type);
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
@ -525,8 +520,8 @@ namespace CSMWorld
for (typename std::map<std::string, int>::const_iterator iter = mIndex.begin(); for (typename std::map<std::string, int>::const_iterator iter = mIndex.begin();
iter!=mIndex.end(); ++iter) iter!=mIndex.end(); ++iter)
{ {
if (listDeleted || !mRecords[iter->second].isDeleted()) if (listDeleted || !mRecords[iter->second]->isDeleted())
ids.push_back (IdAccessorT().getId (mRecords[iter->second].get())); ids.push_back (IdAccessorT().getId (mRecords[iter->second]->get()));
} }
return ids; return ids;
@ -536,46 +531,52 @@ namespace CSMWorld
const Record<ESXRecordT>& Collection<ESXRecordT, IdAccessorT>::getRecord (const std::string& id) const const Record<ESXRecordT>& Collection<ESXRecordT, IdAccessorT>::getRecord (const std::string& id) const
{ {
int index = getIndex (id); int index = getIndex (id);
return mRecords.at (index); return *mRecords.at (index);
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
const Record<ESXRecordT>& Collection<ESXRecordT, IdAccessorT>::getRecord (int index) const const Record<ESXRecordT>& Collection<ESXRecordT, IdAccessorT>::getRecord (int index) const
{ {
return mRecords.at (index); return *mRecords.at (index);
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::insertRecord (const RecordBase& record, int index, void Collection<ESXRecordT, IdAccessorT>::insertRecord (std::unique_ptr<RecordBase> record, int index,
UniversalId::Type type) UniversalId::Type type)
{ {
if (index<0 || index>static_cast<int> (mRecords.size())) int size = static_cast<int>(mRecords.size());
if (index < 0 || index > size)
throw std::runtime_error ("index out of range"); throw std::runtime_error ("index out of range");
const Record<ESXRecordT>& record2 = dynamic_cast<const Record<ESXRecordT>&> (record); std::unique_ptr<Record<ESXRecordT> > record2(static_cast<Record<ESXRecordT>*>(record.release()));
std::string lowerId = Misc::StringUtils::lowerCase(IdAccessorT().getId(record2->get()));
mRecords.insert (mRecords.begin()+index, record2); if (index == size)
mRecords.push_back (std::move(record2));
else
mRecords.insert (mRecords.begin()+index, std::move(record2));
if (index<static_cast<int> (mRecords.size())-1) if (index < size-1)
{ {
for (std::map<std::string, int>::iterator iter (mIndex.begin()); iter!=mIndex.end(); for (std::map<std::string, int>::iterator iter (mIndex.begin()); iter!=mIndex.end(); ++iter)
++iter) {
if (iter->second>=index) if (iter->second >= index)
++(iter->second); ++(iter->second);
}
} }
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( mIndex.insert (std::make_pair (lowerId, index));
record2.get())), index));
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::setRecord (int index, const Record<ESXRecordT>& record) void Collection<ESXRecordT, IdAccessorT>::setRecord (int index,
std::unique_ptr<Record<ESXRecordT> > record)
{ {
if (Misc::StringUtils::lowerCase (IdAccessorT().getId (mRecords.at (index).get()))!= if (Misc::StringUtils::lowerCase (IdAccessorT().getId (mRecords.at (index)->get())) !=
Misc::StringUtils::lowerCase (IdAccessorT().getId (record.get()))) Misc::StringUtils::lowerCase (IdAccessorT().getId (record->get())))
throw std::runtime_error ("attempt to change the ID of a record"); throw std::runtime_error ("attempt to change the ID of a record");
mRecords.at (index) = record; mRecords.at (index) = std::move(record);
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>

View File

@ -8,6 +8,11 @@ CSMWorld::CollectionBase::CollectionBase() {}
CSMWorld::CollectionBase::~CollectionBase() {} CSMWorld::CollectionBase::~CollectionBase() {}
int CSMWorld::CollectionBase::getInsertIndex (const std::string& id, UniversalId::Type type, RecordBase *record) const
{
return getAppendIndex(id, type);
}
int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const
{ {
int columns = getColumns(); int columns = getColumns();

View File

@ -3,6 +3,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
#include <string_view>
#include "universalid.hpp" #include "universalid.hpp"
#include "columns.hpp" #include "columns.hpp"
@ -60,17 +62,17 @@ namespace CSMWorld
UniversalId::Type type = UniversalId::Type_None) = 0; UniversalId::Type type = UniversalId::Type_None) = 0;
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
virtual int searchId (const std::string& id) const = 0; virtual int searchId(std::string_view id) const = 0;
////< Search record with \a id. ////< Search record with \a id.
/// \return index of record (if found) or -1 (not found) /// \return index of record (if found) or -1 (not found)
virtual void replace (int index, const RecordBase& record) = 0; virtual void replace (int index, std::unique_ptr<RecordBase> record) = 0;
///< If the record type does not match, an exception is thrown. ///< If the record type does not match, an exception is thrown.
/// ///
/// \attention \a record must not change the ID. /// \attention \a record must not change the ID.
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
virtual void appendRecord (const RecordBase& record, virtual void appendRecord (std::unique_ptr<RecordBase> record,
UniversalId::Type type = UniversalId::Type_None) = 0; UniversalId::Type type = UniversalId::Type_None) = 0;
///< If the record type does not match, an exception is thrown. ///< If the record type does not match, an exception is thrown.
@ -99,6 +101,12 @@ namespace CSMWorld
/// ///
/// \return Success? /// \return Success?
virtual int getInsertIndex (const std::string& id,
UniversalId::Type type = UniversalId::Type_None,
RecordBase *record = nullptr) const;
///< Works like getAppendIndex unless an overloaded method uses the record pointer
/// to get additional info about the record that results in an alternative index.
int searchColumnIndex (Columns::ColumnId id) const; int searchColumnIndex (Columns::ColumnId id) const;
///< Return index of column with the given \a id. If no such column exists, -1 is returned. ///< Return index of column with the given \a id. If no such column exists, -1 is returned.

View File

@ -104,7 +104,8 @@ bool CSMWorld::ColumnBase::isId (Display display)
bool CSMWorld::ColumnBase::isText (Display display) bool CSMWorld::ColumnBase::isText (Display display)
{ {
return display==Display_String || display==Display_LongString || return display==Display_String || display==Display_LongString ||
display==Display_String32 || display==Display_LongString256; display==Display_String32 || display==Display_String64 ||
display==Display_LongString256;
} }
bool CSMWorld::ColumnBase::isScript (Display display) bool CSMWorld::ColumnBase::isScript (Display display)

View File

@ -135,6 +135,7 @@ namespace CSMWorld
Display_InfoCondVar, Display_InfoCondVar,
Display_InfoCondComp, Display_InfoCondComp,
Display_String32, Display_String32,
Display_String64,
Display_LongString256, Display_LongString256,
Display_BookType, Display_BookType,
Display_BloodType, Display_BloodType,

View File

@ -334,7 +334,8 @@ namespace CSMWorld
template<typename ESXRecordT> template<typename ESXRecordT>
struct NameColumn : public Column<ESXRecordT> struct NameColumn : public Column<ESXRecordT>
{ {
NameColumn() : Column<ESXRecordT> (Columns::ColumnId_Name, ColumnBase::Display_String) {} NameColumn(ColumnBase::Display display = ColumnBase::Display_String)
: Column<ESXRecordT> (Columns::ColumnId_Name, display) {}
QVariant get (const Record<ESXRecordT>& record) const override QVariant get (const Record<ESXRecordT>& record) const override
{ {

View File

@ -371,6 +371,7 @@ namespace CSMWorld
{ ColumnId_Skill7, "Skill 7" }, { ColumnId_Skill7, "Skill 7" },
{ ColumnId_Persistent, "Persistent" }, { ColumnId_Persistent, "Persistent" },
{ ColumnId_Blocked, "Blocked" },
{ -1, 0 } // end marker { -1, 0 } // end marker
}; };
@ -391,7 +392,7 @@ int CSMWorld::Columns::getId (const std::string& name)
std::string name2 = Misc::StringUtils::lowerCase (name); std::string name2 = Misc::StringUtils::lowerCase (name);
for (int i=0; sNames[i].mName; ++i) for (int i=0; sNames[i].mName; ++i)
if (Misc::StringUtils::ciEqual(sNames[i].mName, name2)) if (Misc::StringUtils::ciEqual(std::string_view(sNames[i].mName), name2))
return sNames[i].mId; return sNames[i].mId;
return -1; return -1;

View File

@ -344,6 +344,7 @@ namespace CSMWorld
ColumnId_FactionAttrib2 = 312, ColumnId_FactionAttrib2 = 312,
ColumnId_Persistent = 313, ColumnId_Persistent = 313,
ColumnId_Blocked = 314,
// Allocated to a separate value range, so we don't get a collision should we ever need // Allocated to a separate value range, so we don't get a collision should we ever need
// to extend the number of use values. // to extend the number of use values.

View File

@ -141,7 +141,6 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
std::unique_ptr<CSMWorld::UpdateCellCommand> modifyCell; std::unique_ptr<CSMWorld::UpdateCellCommand> modifyCell;
std::unique_ptr<CSMWorld::ModifyCommand> modifyDataRefNum;
int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt();
@ -170,14 +169,8 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
if (cellId.find ('#')!=std::string::npos) if (cellId.find ('#')!=std::string::npos)
{ {
// Need to recalculate the cell and (if necessary) clear the instance's refNum // Need to recalculate the cell
modifyCell.reset (new UpdateCellCommand (model2, row)); modifyCell.reset (new UpdateCellCommand (model2, row));
// Not sure which model this should be applied to
int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum);
if (refNumColumn!=-1)
modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0));
} }
} }
} }
@ -191,8 +184,6 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons
CommandMacro macro (mDocument.getUndoStack()); CommandMacro macro (mDocument.getUndoStack());
macro.push (modifyData.release()); macro.push (modifyData.release());
macro.push (modifyCell.release()); macro.push (modifyCell.release());
if (modifyDataRefNum.get())
macro.push (modifyDataRefNum.release());
} }
else else
mDocument.getUndoStack().push (modifyData.release()); mDocument.getUndoStack().push (modifyData.release());

View File

@ -24,11 +24,11 @@ CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUnd
, mChanged(false) , mChanged(false)
{ {
setText(("Touch " + mId).c_str()); setText(("Touch " + mId).c_str());
mOld.reset(mTable.getRecord(mId).clone());
} }
void CSMWorld::TouchCommand::redo() void CSMWorld::TouchCommand::redo()
{ {
mOld.reset(mTable.getRecord(mId).clone().get());
mChanged = mTable.touchRecord(mId); mChanged = mTable.touchRecord(mId);
} }
@ -36,7 +36,7 @@ void CSMWorld::TouchCommand::undo()
{ {
if (mChanged) if (mChanged)
{ {
mTable.setRecord(mId, *mOld); mTable.setRecord(mId, std::move(mOld));
mChanged = false; mChanged = false;
} }
} }
@ -159,7 +159,6 @@ CSMWorld::TouchLandCommand::TouchLandCommand(IdTable& landTable, IdTable& ltexTa
, mChanged(false) , mChanged(false)
{ {
setText(("Touch " + mId).c_str()); setText(("Touch " + mId).c_str());
mOld.reset(mLands.getRecord(mId).clone());
} }
const std::string& CSMWorld::TouchLandCommand::getOriginId() const const std::string& CSMWorld::TouchLandCommand::getOriginId() const
@ -175,13 +174,14 @@ const std::string& CSMWorld::TouchLandCommand::getDestinationId() const
void CSMWorld::TouchLandCommand::onRedo() void CSMWorld::TouchLandCommand::onRedo()
{ {
mChanged = mLands.touchRecord(mId); mChanged = mLands.touchRecord(mId);
if (mChanged) mOld.reset(mLands.getRecord(mId).clone().get());
} }
void CSMWorld::TouchLandCommand::onUndo() void CSMWorld::TouchLandCommand::onUndo()
{ {
if (mChanged) if (mChanged)
{ {
mLands.setRecord(mId, *mOld); mLands.setRecord(mId, std::move(mOld));
mChanged = false; mChanged = false;
} }
} }
@ -190,13 +190,16 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI
const QVariant& new_, QUndoCommand* parent) const QVariant& new_, QUndoCommand* parent)
: QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly)
{ {
if (QAbstractProxyModel *proxy = dynamic_cast<QAbstractProxyModel *> (&model)) if (QAbstractProxyModel *proxy = dynamic_cast<QAbstractProxyModel *> (mModel))
{ {
// Replace proxy with actual model // Replace proxy with actual model
mIndex = proxy->mapToSource (index); mIndex = proxy->mapToSource (mIndex);
mModel = proxy->sourceModel(); mModel = proxy->sourceModel();
} }
}
void CSMWorld::ModifyCommand::redo()
{
if (mIndex.parent().isValid()) if (mIndex.parent().isValid())
{ {
CSMWorld::IdTree* tree = &dynamic_cast<CSMWorld::IdTree&>(*mModel); CSMWorld::IdTree* tree = &dynamic_cast<CSMWorld::IdTree&>(*mModel);
@ -223,10 +226,7 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI
mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mRecordStateIndex = table->index(rowIndex, stateColumnIndex);
mOldRecordState = static_cast<CSMWorld::RecordBase::State>(table->data(mRecordStateIndex).toInt()); mOldRecordState = static_cast<CSMWorld::RecordBase::State>(table->data(mRecordStateIndex).toInt());
} }
}
void CSMWorld::ModifyCommand::redo()
{
mOld = mModel->data (mIndex, Qt::EditRole); mOld = mModel->data (mIndex, Qt::EditRole);
mModel->setData (mIndex, mNew); mModel->setData (mIndex, mNew);
} }
@ -291,20 +291,19 @@ void CSMWorld::CreateCommand::undo()
} }
CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent) CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent)
: QUndoCommand (parent), mModel (model), mId (id), mOld (nullptr) : QUndoCommand (parent), mModel (model), mId (id), mOld(nullptr)
{ {
setText (("Revert record " + id).c_str()); setText (("Revert record " + id).c_str());
mOld = model.getRecord (id).clone();
} }
CSMWorld::RevertCommand::~RevertCommand() CSMWorld::RevertCommand::~RevertCommand()
{ {
delete mOld;
} }
void CSMWorld::RevertCommand::redo() void CSMWorld::RevertCommand::redo()
{ {
mOld = mModel.getRecord (mId).clone();
int column = mModel.findColumnIndex (Columns::ColumnId_Modification); int column = mModel.findColumnIndex (Columns::ColumnId_Modification);
QModelIndex index = mModel.getModelIndex (mId, column); QModelIndex index = mModel.getModelIndex (mId, column);
@ -322,25 +321,24 @@ void CSMWorld::RevertCommand::redo()
void CSMWorld::RevertCommand::undo() void CSMWorld::RevertCommand::undo()
{ {
mModel.setRecord (mId, *mOld); mModel.setRecord (mId, std::move(mOld));
} }
CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, CSMWorld::DeleteCommand::DeleteCommand (IdTable& model,
const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent) const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent)
: QUndoCommand (parent), mModel (model), mId (id), mOld (nullptr), mType(type) : QUndoCommand (parent), mModel (model), mId (id), mOld(nullptr), mType(type)
{ {
setText (("Delete record " + id).c_str()); setText (("Delete record " + id).c_str());
mOld = model.getRecord (id).clone();
} }
CSMWorld::DeleteCommand::~DeleteCommand() CSMWorld::DeleteCommand::~DeleteCommand()
{ {
delete mOld;
} }
void CSMWorld::DeleteCommand::redo() void CSMWorld::DeleteCommand::redo()
{ {
mOld = mModel.getRecord (mId).clone();
int column = mModel.findColumnIndex (Columns::ColumnId_Modification); int column = mModel.findColumnIndex (Columns::ColumnId_Modification);
QModelIndex index = mModel.getModelIndex (mId, column); QModelIndex index = mModel.getModelIndex (mId, column);
@ -358,7 +356,7 @@ void CSMWorld::DeleteCommand::redo()
void CSMWorld::DeleteCommand::undo() void CSMWorld::DeleteCommand::undo()
{ {
mModel.setRecord (mId, *mOld, mType); mModel.setRecord (mId, std::move(mOld), mType);
} }
@ -424,18 +422,19 @@ void CSMWorld::CreatePathgridCommand::redo()
{ {
CreateCommand::redo(); CreateCommand::redo();
Record<Pathgrid> record = static_cast<const Record<Pathgrid>& >(mModel.getRecord(mId)); std::unique_ptr<Record<Pathgrid> > record
record.get().blank(); = std::make_unique<Record<Pathgrid> >(static_cast<const Record<Pathgrid>& >(mModel.getRecord(mId)));
record.get().mCell = mId; record->get().blank();
record->get().mCell = mId;
std::pair<CellCoordinates, bool> coords = CellCoordinates::fromId(mId); std::pair<CellCoordinates, bool> coords = CellCoordinates::fromId(mId);
if (coords.second) if (coords.second)
{ {
record.get().mData.mX = coords.first.getX(); record->get().mData.mX = coords.first.getX();
record.get().mData.mY = coords.first.getY(); record->get().mData.mY = coords.first.getY();
} }
mModel.setRecord(mId, record, mType); mModel.setRecord(mId, std::move(record), mType);
} }
CSMWorld::UpdateCellCommand::UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent) CSMWorld::UpdateCellCommand::UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent)
@ -499,8 +498,8 @@ CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model,
void CSMWorld::DeleteNestedCommand::redo() void CSMWorld::DeleteNestedCommand::redo()
{ {
QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn);
mModel.removeRows (mNestedRow, 1, parentIndex);
mModifyParentCommand->redo(); mModifyParentCommand->redo();
mModel.removeRows (mNestedRow, 1, parentIndex);
} }
@ -530,8 +529,8 @@ CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& i
void CSMWorld::AddNestedCommand::redo() void CSMWorld::AddNestedCommand::redo()
{ {
QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn);
mModel.addNestedRow (parentIndex, mNewRow);
mModifyParentCommand->redo(); mModifyParentCommand->redo();
mModel.addNestedRow (parentIndex, mNewRow);
} }
void CSMWorld::AddNestedCommand::undo() void CSMWorld::AddNestedCommand::undo()

View File

@ -203,7 +203,7 @@ namespace CSMWorld
{ {
IdTable& mModel; IdTable& mModel;
std::string mId; std::string mId;
RecordBase *mOld; std::unique_ptr<RecordBase> mOld;
// not implemented // not implemented
RevertCommand (const RevertCommand&); RevertCommand (const RevertCommand&);
@ -224,7 +224,7 @@ namespace CSMWorld
{ {
IdTable& mModel; IdTable& mModel;
std::string mId; std::string mId;
RecordBase *mOld; std::unique_ptr<RecordBase> mOld;
UniversalId::Type mType; UniversalId::Type mType;
// not implemented // not implemented

View File

@ -84,6 +84,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind
defines["radialFog"] = "0"; defines["radialFog"] = "0";
defines["lightingModel"] = "0"; defines["lightingModel"] = "0";
defines["reverseZ"] = "0";
for (const auto& define : shadowDefines) for (const auto& define : shadowDefines)
defines[define.first] = define.second; defines[define.first] = define.second;
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);
@ -130,7 +131,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
mFactions.addColumn (new StringIdColumn<ESM::Faction>); mFactions.addColumn (new StringIdColumn<ESM::Faction>);
mFactions.addColumn (new RecordStateColumn<ESM::Faction>); mFactions.addColumn (new RecordStateColumn<ESM::Faction>);
mFactions.addColumn (new FixedRecordTypeColumn<ESM::Faction> (UniversalId::Type_Faction)); mFactions.addColumn (new FixedRecordTypeColumn<ESM::Faction> (UniversalId::Type_Faction));
mFactions.addColumn (new NameColumn<ESM::Faction>); mFactions.addColumn (new NameColumn<ESM::Faction>(ColumnBase::Display_String32));
mFactions.addColumn (new AttributesColumn<ESM::Faction> (0)); mFactions.addColumn (new AttributesColumn<ESM::Faction> (0));
mFactions.addColumn (new AttributesColumn<ESM::Faction> (1)); mFactions.addColumn (new AttributesColumn<ESM::Faction> (1));
mFactions.addColumn (new HiddenColumn<ESM::Faction>); mFactions.addColumn (new HiddenColumn<ESM::Faction>);
@ -339,7 +340,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
mCells.addColumn (new StringIdColumn<Cell>); mCells.addColumn (new StringIdColumn<Cell>);
mCells.addColumn (new RecordStateColumn<Cell>); mCells.addColumn (new RecordStateColumn<Cell>);
mCells.addColumn (new FixedRecordTypeColumn<Cell> (UniversalId::Type_Cell)); mCells.addColumn (new FixedRecordTypeColumn<Cell> (UniversalId::Type_Cell));
mCells.addColumn (new NameColumn<Cell>); mCells.addColumn (new NameColumn<Cell>(ColumnBase::Display_String64));
mCells.addColumn (new FlagColumn<Cell> (Columns::ColumnId_SleepForbidden, ESM::Cell::NoSleep)); mCells.addColumn (new FlagColumn<Cell> (Columns::ColumnId_SleepForbidden, ESM::Cell::NoSleep));
mCells.addColumn (new FlagColumn<Cell> (Columns::ColumnId_InteriorWater, ESM::Cell::HasWater, mCells.addColumn (new FlagColumn<Cell> (Columns::ColumnId_InteriorWater, ESM::Cell::HasWater,
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh));
@ -917,8 +918,8 @@ const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const
void CSMWorld::Data::setMetaData (const MetaData& metaData) void CSMWorld::Data::setMetaData (const MetaData& metaData)
{ {
Record<MetaData> record (RecordBase::State_ModifiedOnly, nullptr, &metaData); mMetaData.setRecord (0, std::make_unique<Record<MetaData> >(
mMetaData.setRecord (0, record); Record<MetaData>(RecordBase::State_ModifiedOnly, nullptr, &metaData)));
} }
QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id)
@ -958,6 +959,25 @@ void CSMWorld::Data::merge()
mGlobals.merge(); mGlobals.merge();
} }
int CSMWorld::Data::getTotalRecords (const std::vector<boost::filesystem::path>& files)
{
int records = 0;
std::unique_ptr<ESM::ESMReader> reader = std::unique_ptr<ESM::ESMReader>(new ESM::ESMReader);
for (unsigned int i = 0; i < files.size(); ++i)
{
if (!boost::filesystem::exists(files[i]))
continue;
reader->open(files[i].string());
records += reader->getRecordCount();
reader->close();
}
return records;
}
int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base, bool project) int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base, bool project)
{ {
// Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading
@ -983,7 +1003,8 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base
metaData.mId = "sys::meta"; metaData.mId = "sys::meta";
metaData.load (*mReader); metaData.load (*mReader);
mMetaData.setRecord (0, Record<MetaData> (RecordBase::State_ModifiedOnly, nullptr, &metaData)); mMetaData.setRecord (0, std::make_unique<Record<MetaData> >(
Record<MetaData> (RecordBase::State_ModifiedOnly, nullptr, &metaData)));
} }
return mReader->getRecordCount(); return mReader->getRecordCount();
@ -1011,10 +1032,10 @@ void CSMWorld::Data::loadFallbackEntries()
ESM::Static newMarker; ESM::Static newMarker;
newMarker.mId = marker.first; newMarker.mId = marker.first;
newMarker.mModel = marker.second; newMarker.mModel = marker.second;
CSMWorld::Record<ESM::Static> record; std::unique_ptr<CSMWorld::Record<ESM::Static> > record(new CSMWorld::Record<ESM::Static>);
record.mBase = newMarker; record->mBase = newMarker;
record.mState = CSMWorld::RecordBase::State_BaseOnly; record->mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static); mReferenceables.appendRecord (std::move(record), CSMWorld::UniversalId::Type_Static);
} }
} }
@ -1025,10 +1046,10 @@ void CSMWorld::Data::loadFallbackEntries()
ESM::Door newMarker; ESM::Door newMarker;
newMarker.mId = marker.first; newMarker.mId = marker.first;
newMarker.mModel = marker.second; newMarker.mModel = marker.second;
CSMWorld::Record<ESM::Door> record; std::unique_ptr<CSMWorld::Record<ESM::Door> > record(new CSMWorld::Record<ESM::Door>);
record.mBase = newMarker; record->mBase = newMarker;
record.mState = CSMWorld::RecordBase::State_BaseOnly; record->mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Door); mReferenceables.appendRecord (std::move(record), CSMWorld::UniversalId::Type_Door);
} }
} }
} }

View File

@ -118,7 +118,7 @@ namespace CSMWorld
const ESM::Dialogue *mDialogue; // last loaded dialogue const ESM::Dialogue *mDialogue; // last loaded dialogue
bool mBase; bool mBase;
bool mProject; bool mProject;
std::map<std::string, std::map<ESM::RefNum, std::string> > mRefLoadCache; std::map<std::string, std::map<unsigned int, unsigned int> > mRefLoadCache;
int mReaderIndex; int mReaderIndex;
bool mFsStrict; bool mFsStrict;
@ -292,6 +292,8 @@ namespace CSMWorld
void merge(); void merge();
///< Merge modified into base. ///< Merge modified into base.
int getTotalRecords (const std::vector<boost::filesystem::path>& files); // for better loading bar
int startLoading (const boost::filesystem::path& path, bool base, bool project); int startLoading (const boost::filesystem::path& path, bool base, bool project);
///< Begin merging content of a file into base or modified. ///< Begin merging content of a file into base or modified.
/// ///

View File

@ -0,0 +1,43 @@
#include "idcollection.hpp"
namespace CSMWorld
{
template<>
int IdCollection<Pathgrid, IdAccessor<Pathgrid> >::load (ESM::ESMReader& reader, bool base)
{
Pathgrid record;
bool isDeleted = false;
loadRecord (record, reader, isDeleted);
std::string id = IdAccessor<Pathgrid>().getId (record);
int index = this->searchId (id);
if (record.mPoints.empty() || record.mEdges.empty())
isDeleted = true;
if (isDeleted)
{
if (index==-1)
{
// deleting a record that does not exist
// ignore it for now
/// \todo report the problem to the user
return -1;
}
if (base)
{
this->removeRows (index, 1);
return -1;
}
std::unique_ptr<Record<Pathgrid> > baseRecord(new Record<Pathgrid>(this->getRecord(index)));
baseRecord->mState = RecordBase::State_Deleted;
this->setRecord(index, std::move(baseRecord));
return index;
}
return load (record, base, index);
}
}

View File

@ -5,6 +5,7 @@
#include "collection.hpp" #include "collection.hpp"
#include "land.hpp" #include "land.hpp"
#include "pathgrid.hpp"
namespace CSMWorld namespace CSMWorld
{ {
@ -83,9 +84,9 @@ namespace CSMWorld
return -1; return -1;
} }
Record<ESXRecordT> baseRecord = this->getRecord (index); std::unique_ptr<Record<ESXRecordT> > baseRecord(new Record<ESXRecordT>(this->getRecord(index)));
baseRecord.mState = RecordBase::State_Deleted; baseRecord->mState = RecordBase::State_Deleted;
this->setRecord (index, baseRecord); this->setRecord(index, std::move(baseRecord));
return index; return index;
} }
@ -96,30 +97,31 @@ namespace CSMWorld
int IdCollection<ESXRecordT, IdAccessorT>::load (const ESXRecordT& record, bool base, int IdCollection<ESXRecordT, IdAccessorT>::load (const ESXRecordT& record, bool base,
int index) int index)
{ {
if (index==-2) if (index==-2) // index unknown
index = this->searchId (IdAccessorT().getId (record)); index = this->searchId (IdAccessorT().getId (record));
if (index==-1) if (index==-1)
{ {
// new record // new record
Record<ESXRecordT> record2; std::unique_ptr<Record<ESXRecordT> > record2(new Record<ESXRecordT>);
record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record2.mBase : record2.mModified) = record; (base ? record2->mBase : record2->mModified) = record;
index = this->getSize(); index = this->getSize();
this->appendRecord (record2); this->appendRecord(std::move(record2));
} }
else else
{ {
// old record // old record
Record<ESXRecordT> record2 = Collection<ESXRecordT, IdAccessorT>::getRecord (index); std::unique_ptr<Record<ESXRecordT> > record2(
new Record<ESXRecordT>(Collection<ESXRecordT, IdAccessorT>::getRecord(index)));
if (base) if (base)
record2.mBase = record; record2->mBase = record;
else else
record2.setModified (record); record2->setModified(record);
this->setRecord (index, record2); this->setRecord(index, std::move(record2));
} }
return index; return index;
@ -133,7 +135,7 @@ namespace CSMWorld
if (index==-1) if (index==-1)
return false; return false;
Record<ESXRecordT> record = Collection<ESXRecordT, IdAccessorT>::getRecord (index); const Record<ESXRecordT>& record = Collection<ESXRecordT, IdAccessorT>::getRecord (index);
if (record.isDeleted()) if (record.isDeleted())
return false; return false;
@ -144,12 +146,17 @@ namespace CSMWorld
} }
else else
{ {
record.mState = RecordBase::State_Deleted; std::unique_ptr<Record<ESXRecordT> > record2(
this->setRecord (index, record); new Record<ESXRecordT>(Collection<ESXRecordT, IdAccessorT>::getRecord(index)));
record2->mState = RecordBase::State_Deleted;
this->setRecord(index, std::move(record2));
} }
return true; return true;
} }
template<>
int IdCollection<Pathgrid, IdAccessor<Pathgrid> >::load(ESM::ESMReader& reader, bool base);
} }
#endif #endif

View File

@ -123,6 +123,14 @@ Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const
if (mIdCollection->getColumn (index.column()).isUserEditable()) if (mIdCollection->getColumn (index.column()).isUserEditable())
flags |= Qt::ItemIsEditable; flags |= Qt::ItemIsEditable;
int blockedColumn = searchColumnIndex(Columns::ColumnId_Blocked);
if (blockedColumn != -1 && blockedColumn != index.column())
{
bool isBlocked = mIdCollection->getData(index.row(), blockedColumn).toInt();
if (isBlocked)
flags = Qt::ItemIsSelectable; // not enabled (to grey out)
}
return flags; return flags;
} }
@ -191,7 +199,7 @@ void CSMWorld::IdTable::cloneRecord(const std::string& origin,
const std::string& destination, const std::string& destination,
CSMWorld::UniversalId::Type type) CSMWorld::UniversalId::Type type)
{ {
int index = mIdCollection->getAppendIndex (destination); int index = mIdCollection->getAppendIndex (destination, type);
beginInsertRows (QModelIndex(), index, index); beginInsertRows (QModelIndex(), index, index);
mIdCollection->cloneRecord(origin, destination, type); mIdCollection->cloneRecord(origin, destination, type);
@ -228,23 +236,30 @@ QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column)
return QModelIndex(); return QModelIndex();
} }
void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& record, CSMWorld::UniversalId::Type type) void CSMWorld::IdTable::setRecord (const std::string& id,
std::unique_ptr<RecordBase> record, CSMWorld::UniversalId::Type type)
{ {
int index = mIdCollection->searchId (id); int index = mIdCollection->searchId (id);
if (index==-1) if (index==-1)
{ {
index = mIdCollection->getAppendIndex (id, type); // For info records, appendRecord may use a different index than the one returned by
// getAppendIndex (because of prev/next links). This can result in the display not
// updating correctly after an undo
//
// Use an alternative method to get the correct index. For non-Info records the
// record pointer is ignored and internally calls getAppendIndex.
int index2 = mIdCollection->getInsertIndex (id, type, record.get());
beginInsertRows (QModelIndex(), index, index); beginInsertRows (QModelIndex(), index2, index2);
mIdCollection->appendRecord (record, type); mIdCollection->appendRecord (std::move(record), type);
endInsertRows(); endInsertRows();
} }
else else
{ {
mIdCollection->replace (index, record); mIdCollection->replace (index, std::move(record));
emit dataChanged (CSMWorld::IdTable::index (index, 0), emit dataChanged (CSMWorld::IdTable::index (index, 0),
CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1)); CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1));
} }

View File

@ -2,6 +2,7 @@
#define CSM_WOLRD_IDTABLE_H #define CSM_WOLRD_IDTABLE_H
#include <vector> #include <vector>
#include <memory>
#include "idtablebase.hpp" #include "idtablebase.hpp"
#include "universalid.hpp" #include "universalid.hpp"
@ -67,7 +68,7 @@ namespace CSMWorld
QModelIndex getModelIndex (const std::string& id, int column) const override; QModelIndex getModelIndex (const std::string& id, int column) const override;
void setRecord (const std::string& id, const RecordBase& record, void setRecord (const std::string& id, std::unique_ptr<RecordBase> record,
UniversalId::Type type = UniversalId::Type_None); UniversalId::Type type = UniversalId::Type_None);
///< Add record or overwrite existing record. ///< Add record or overwrite existing record.

View File

@ -121,8 +121,11 @@ QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const
void CSMWorld::IdTableProxyModel::refreshFilter() void CSMWorld::IdTableProxyModel::refreshFilter()
{ {
updateColumnMap(); if (mFilter)
invalidateFilter(); {
updateColumnMap();
invalidateFilter();
}
} }
void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end)

View File

@ -2,12 +2,74 @@
#include <stdexcept> #include <stdexcept>
#include <iterator> #include <iterator>
#include <cassert>
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
#include <components/esm/loaddial.hpp> #include <components/esm/loaddial.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
namespace CSMWorld
{
template<>
void Collection<Info, IdAccessor<Info> >::removeRows (int index, int count)
{
mRecords.erase(mRecords.begin()+index, mRecords.begin()+index+count);
// index map is updated in InfoCollection::removeRows()
}
template<>
void Collection<Info, IdAccessor<Info> >::insertRecord (std::unique_ptr<RecordBase> record,
int index, UniversalId::Type type)
{
int size = static_cast<int>(mRecords.size());
if (index < 0 || index > size)
throw std::runtime_error("index out of range");
std::unique_ptr<Record<Info> > record2(static_cast<Record<Info>*>(record.release()));
if (index == size)
mRecords.push_back(std::move(record2));
else
mRecords.insert(mRecords.begin()+index, std::move(record2));
// index map is updated in InfoCollection::insertRecord()
}
template<>
bool Collection<Info, IdAccessor<Info> >::reorderRowsImp (int baseIndex,
const std::vector<int>& newOrder)
{
if (!newOrder.empty())
{
int size = static_cast<int>(newOrder.size());
// check that all indices are present
std::vector<int> test(newOrder);
std::sort(test.begin(), test.end());
if (*test.begin() != 0 || *--test.end() != size-1)
return false;
// reorder records
std::vector<std::unique_ptr<Record<Info> > > buffer(size);
// FIXME: BUG: undo does not remove modified flag
for (int i = 0; i < size; ++i)
{
buffer[newOrder[i]] = std::move(mRecords[baseIndex+i]);
buffer[newOrder[i]]->setModified(buffer[newOrder[i]]->get());
}
std::move(buffer.begin(), buffer.end(), mRecords.begin()+baseIndex);
// index map is updated in InfoCollection::reorderRows()
}
return true;
}
}
void CSMWorld::InfoCollection::load (const Info& record, bool base) void CSMWorld::InfoCollection::load (const Info& record, bool base)
{ {
int index = searchId (record.mId); int index = searchId (record.mId);
@ -15,74 +77,96 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base)
if (index==-1) if (index==-1)
{ {
// new record // new record
Record<Info> record2; std::unique_ptr<Record<Info> > record2(new Record<Info>);
record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; record2->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record2.mBase : record2.mModified) = record; (base ? record2->mBase : record2->mModified) = record;
std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId); appendRecord(std::move(record2));
if (!record2.get().mPrev.empty())
{
index = getInfoIndex (record2.get().mPrev, topic);
if (index!=-1)
++index;
}
if (index==-1 && !record2.get().mNext.empty())
{
index = getInfoIndex (record2.get().mNext, topic);
}
if (index==-1)
{
Range range = getTopicRange (topic);
index = std::distance (getRecords().begin(), range.second);
}
insertRecord (record2, index);
} }
else else
{ {
// old record // old record
Record<Info> record2 = getRecord (index); std::unique_ptr<Record<Info> > record2(new Record<Info>(getRecord(index)));
if (base) if (base)
record2.mBase = record; record2->mBase = record;
else else
record2.setModified (record); record2->setModified (record);
setRecord (index, record2); setRecord (index, std::move(record2));
} }
} }
int CSMWorld::InfoCollection::getInfoIndex (const std::string& id, const std::string& topic) const int CSMWorld::InfoCollection::getInfoIndex(std::string_view id, std::string_view topic) const
{ {
std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id; // find the topic first
std::unordered_map<std::string, std::vector<std::pair<std::string, int> > >::const_iterator iter
= mInfoIndex.find(Misc::StringUtils::lowerCase(topic));
std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange (topic); if (iter == mInfoIndex.end())
return -1;
for (; range.first!=range.second; ++range.first) // brute force loop
if (Misc::StringUtils::ciEqual(range.first->get().mId, fullId)) for (std::vector<std::pair<std::string, int> >::const_iterator it = iter->second.begin();
return std::distance (getRecords().begin(), range.first); it != iter->second.end(); ++it)
{
if (Misc::StringUtils::ciEqual(it->first, id))
return it->second;
}
return -1; return -1;
} }
int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const // Calling insertRecord() using index from getInsertIndex() needs to take into account of
// prev/next records; an example is deleting a record then undo
int CSMWorld::InfoCollection::getInsertIndex (const std::string& id,
UniversalId::Type type, RecordBase *record) const
{ {
std::string::size_type separator = id.find_last_of ('#'); if (record == nullptr)
{
std::string::size_type separator = id.find_last_of('#');
if (separator==std::string::npos) if (separator == std::string::npos)
throw std::runtime_error ("invalid info ID: " + id); throw std::runtime_error("invalid info ID: " + id);
std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange (id.substr (0, separator)); std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange(id.substr(0, separator));
if (range.first==range.second) if (range.first == range.second)
return Collection<Info, IdAccessor<Info> >::getAppendIndex (id, type); return Collection<Info, IdAccessor<Info> >::getAppendIndex(id, type);
return std::distance (getRecords().begin(), range.second); return std::distance(getRecords().begin(), range.second);
}
int index = -1;
const Info& info = static_cast<Record<Info>*>(record)->get();
std::string topic = info.mTopicId;
// if the record has a prev, find its index value
if (!info.mPrev.empty())
{
index = getInfoIndex(info.mPrev, topic);
if (index != -1)
++index; // if prev exists, set current index to one above prev
}
// if prev doesn't exist or not found and the record has a next, find its index value
if (index == -1 && !info.mNext.empty())
{
// if next exists, use its index as the current index
index = getInfoIndex(info.mNext, topic);
}
// if next doesn't exist or not found (i.e. neither exist yet) then start a new one
if (index == -1)
{
Range range = getTopicRange(topic); // getTopicRange converts topic to lower case first
index = std::distance(getRecords().begin(), range.second);
}
return index;
} }
bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector<int>& newOrder) bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector<int>& newOrder)
@ -99,7 +183,17 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector<int
return false; return false;
// reorder // reorder
return reorderRowsImp (baseIndex, newOrder); if (!Collection<Info, IdAccessor<Info> >::reorderRowsImp(baseIndex, newOrder))
return false;
// adjust index
int size = static_cast<int>(newOrder.size());
for (auto& [hash, infos] : mInfoIndex)
for (auto& [a, b] : infos)
if (b >= baseIndex && b < baseIndex + size)
b = newOrder.at(b - baseIndex) + baseIndex;
return true;
} }
void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue)
@ -126,9 +220,9 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES
} }
else else
{ {
Record<Info> record = getRecord (index); std::unique_ptr<Record<Info> > record(new Record<Info>(getRecord(index)));
record.mState = RecordBase::State_Deleted; record->mState = RecordBase::State_Deleted;
setRecord (index, record); setRecord (index, std::move(record));
} }
} }
else else
@ -142,73 +236,54 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES
CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic) CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic)
const const
{ {
std::string topic2 = Misc::StringUtils::lowerCase (topic); std::string lowerTopic = Misc::StringUtils::lowerCase (topic);
std::map<std::string, int>::const_iterator iter = getIdMap().lower_bound (topic2); // find the topic
std::unordered_map<std::string, std::vector<std::pair<std::string, int> > >::const_iterator iter
= mInfoIndex.find(lowerTopic);
// Skip invalid records: The beginning of a topic string could be identical to another topic if (iter == mInfoIndex.end())
// string.
for (; iter!=getIdMap().end(); ++iter)
{
std::string testTopicId =
Misc::StringUtils::lowerCase (getRecord (iter->second).get().mTopicId);
if (testTopicId==topic2)
break;
std::size_t size = topic2.size();
if (testTopicId.size()<size || testTopicId.substr (0, size)!=topic2)
return Range (getRecords().end(), getRecords().end());
}
if (iter==getIdMap().end())
return Range (getRecords().end(), getRecords().end()); return Range (getRecords().end(), getRecords().end());
RecordConstIterator begin = getRecords().begin()+iter->second; // topic found, find the starting index
int low = INT_MAX;
while (begin != getRecords().begin()) for (std::vector<std::pair<std::string, int> >::const_iterator it = iter->second.begin();
it != iter->second.end(); ++it)
{ {
if (!Misc::StringUtils::ciEqual(begin->get().mTopicId, topic2)) low = std::min(low, it->second);
{
// we've gone one too far, go back
++begin;
break;
}
--begin;
} }
// Find end RecordConstIterator begin = getRecords().begin() + low;
RecordConstIterator end = begin;
for (; end!=getRecords().end(); ++end) // Find end (one past the range)
if (!Misc::StringUtils::ciEqual(end->get().mTopicId, topic2)) RecordConstIterator end = begin + iter->second.size();
break;
assert(static_cast<size_t>(std::distance(begin, end)) == iter->second.size());
return Range (begin, end); return Range (begin, end);
} }
void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId) void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId)
{ {
std::string id = Misc::StringUtils::lowerCase(dialogueId);
std::vector<int> erasedRecords; std::vector<int> erasedRecords;
std::map<std::string, int>::const_iterator current = getIdMap().lower_bound(id); Range range = getTopicRange(dialogueId); // getTopicRange converts dialogueId to lower case first
std::map<std::string, int>::const_iterator end = getIdMap().end();
for (; current != end; ++current) for (; range.first != range.second; ++range.first)
{ {
Record<Info> record = getRecord(current->second); const Record<Info>& record = **range.first;
if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId))
{ {
if (record.mState == RecordBase::State_ModifiedOnly) if (record.mState == RecordBase::State_ModifiedOnly)
{ {
erasedRecords.push_back(current->second); erasedRecords.push_back(range.first - getRecords().begin());
} }
else else
{ {
record.mState = RecordBase::State_Deleted; std::unique_ptr<Record<Info> > record2(new Record<Info>(record));
setRecord(current->second, record); record2->mState = RecordBase::State_Deleted;
setRecord(range.first - getRecords().begin(), std::move(record2));
} }
} }
else else
@ -223,3 +298,105 @@ void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId
erasedRecords.pop_back(); erasedRecords.pop_back();
} }
} }
// FIXME: removing a record should adjust prev/next and mark those records as modified
// accordingly (also consider undo)
void CSMWorld::InfoCollection::removeRows (int index, int count)
{
Collection<Info, IdAccessor<Info> >::removeRows(index, count); // erase records only
for (std::unordered_map<std::string, std::vector<std::pair<std::string, int> > >::iterator iter
= mInfoIndex.begin(); iter != mInfoIndex.end();)
{
for (std::vector<std::pair<std::string, int> >::iterator it = iter->second.begin();
it != iter->second.end();)
{
if (it->second >= index)
{
if (it->second >= index+count)
{
it->second -= count;
++it;
}
else
iter->second.erase(it);
}
else
++it;
}
// check for an empty vector
if (iter->second.empty())
mInfoIndex.erase(iter++);
else
++iter;
}
}
void CSMWorld::InfoCollection::appendBlankRecord (const std::string& id, UniversalId::Type type)
{
std::unique_ptr<Record<Info> > record2(new Record<Info>);
record2->mState = Record<Info>::State_ModifiedOnly;
record2->mModified.blank();
record2->get().mId = id;
insertRecord(std::move(record2), getInsertIndex(id, type, nullptr), type); // call InfoCollection::insertRecord()
}
int CSMWorld::InfoCollection::searchId(std::string_view id) const
{
std::string::size_type separator = id.find_last_of('#');
if (separator == std::string::npos)
throw std::runtime_error("invalid info ID: " + std::string(id));
return getInfoIndex(id.substr(separator+1), id.substr(0, separator));
}
void CSMWorld::InfoCollection::appendRecord (std::unique_ptr<RecordBase> record, UniversalId::Type type)
{
int index = getInsertIndex(static_cast<Record<Info>*>(record.get())->get().mId, type, record.get());
insertRecord(std::move(record), index, type);
}
void CSMWorld::InfoCollection::insertRecord (std::unique_ptr<RecordBase> record, int index,
UniversalId::Type type)
{
int size = static_cast<int>(getRecords().size());
std::string id = static_cast<Record<Info>*>(record.get())->get().mId;
std::string::size_type separator = id.find_last_of('#');
if (separator == std::string::npos)
throw std::runtime_error("invalid info ID: " + id);
Collection<Info, IdAccessor<Info> >::insertRecord(std::move(record), index, type); // add records only
// adjust index
if (index < size-1)
{
for (std::unordered_map<std::string, std::vector<std::pair<std::string, int> > >::iterator iter
= mInfoIndex.begin(); iter != mInfoIndex.end(); ++iter)
{
for (std::vector<std::pair<std::string, int> >::iterator it = iter->second.begin();
it != iter->second.end(); ++it)
{
if (it->second >= index)
++(it->second);
}
}
}
// get iterator for existing topic or a new topic
std::string lowerId = Misc::StringUtils::lowerCase(id);
std::pair<std::unordered_map<std::string, std::vector<std::pair<std::string, int> > >::iterator, bool> res
= mInfoIndex.insert(
std::make_pair(lowerId.substr(0, separator),
std::vector<std::pair<std::string, int> >())); // empty vector
// insert info and index
res.first->second.push_back(std::make_pair(lowerId.substr(separator+1), index));
}

View File

@ -1,6 +1,9 @@
#ifndef CSM_WOLRD_INFOCOLLECTION_H #ifndef CSM_WOLRD_INFOCOLLECTION_H
#define CSM_WOLRD_INFOCOLLECTION_H #define CSM_WOLRD_INFOCOLLECTION_H
#include <unordered_map>
#include <string_view>
#include "collection.hpp" #include "collection.hpp"
#include "info.hpp" #include "info.hpp"
@ -11,27 +14,52 @@ namespace ESM
namespace CSMWorld namespace CSMWorld
{ {
template<>
void Collection<Info, IdAccessor<Info> >::removeRows (int index, int count);
template<>
void Collection<Info, IdAccessor<Info> >::insertRecord (std::unique_ptr<RecordBase> record,
int index, UniversalId::Type type);
template<>
bool Collection<Info, IdAccessor<Info> >::reorderRowsImp (int baseIndex,
const std::vector<int>& newOrder);
class InfoCollection : public Collection<Info, IdAccessor<Info> > class InfoCollection : public Collection<Info, IdAccessor<Info> >
{ {
public: public:
typedef std::vector<Record<Info> >::const_iterator RecordConstIterator; typedef std::vector<std::unique_ptr<Record<Info> > >::const_iterator RecordConstIterator;
typedef std::pair<RecordConstIterator, RecordConstIterator> Range; typedef std::pair<RecordConstIterator, RecordConstIterator> Range;
private: private:
// The general strategy is to keep the records in Collection kept in order (within
// a topic group) while the index lookup maps are not ordered. It is assumed that
// each topic has a small number of infos, which allows the use of vectors for
// iterating through them without too much penalty.
//
// NOTE: topic string as well as id string are stored in lower case.
std::unordered_map<std::string, std::vector<std::pair<std::string, int> > > mInfoIndex;
void load (const Info& record, bool base); void load (const Info& record, bool base);
int getInfoIndex (const std::string& id, const std::string& topic) const; int getInfoIndex(std::string_view id, std::string_view topic) const;
///< Return index for record \a id or -1 (if not present; deleted records are considered) ///< Return index for record \a id or -1 (if not present; deleted records are considered)
/// ///
/// \param id info ID without topic prefix /// \param id info ID without topic prefix
//
/// \attention id and topic are assumed to be in lower case
public: public:
int getAppendIndex (const std::string& id, int getInsertIndex (const std::string& id,
UniversalId::Type type = UniversalId::Type_None) const override; UniversalId::Type type = UniversalId::Type_None,
RecordBase *record = nullptr) const override;
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
///
/// Works like getAppendIndex unless an overloaded method uses the record pointer
/// to get additional info about the record that results in an alternative index.
bool reorderRows (int baseIndex, const std::vector<int>& newOrder) override; bool reorderRows (int baseIndex, const std::vector<int>& newOrder) override;
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
@ -46,6 +74,20 @@ namespace CSMWorld
/// the given topic. /// the given topic.
void removeDialogueInfos(const std::string& dialogueId); void removeDialogueInfos(const std::string& dialogueId);
void removeRows (int index, int count) override;
void appendBlankRecord (const std::string& id,
UniversalId::Type type = UniversalId::Type_None) override;
int searchId(std::string_view id) const override;
void appendRecord (std::unique_ptr<RecordBase> record,
UniversalId::Type type = UniversalId::Type_None) override;
void insertRecord (std::unique_ptr<RecordBase> record,
int index,
UniversalId::Type type = UniversalId::Type_None) override;
}; };
} }

View File

@ -90,23 +90,23 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void NestedIdCollection<ESXRecordT, IdAccessorT>::addNestedRow(int row, int column, int position) void NestedIdCollection<ESXRecordT, IdAccessorT>::addNestedRow(int row, int column, int position)
{ {
Record<ESXRecordT> record; std::unique_ptr<Record<ESXRecordT> > record(new Record<ESXRecordT>);
record.assign(Collection<ESXRecordT, IdAccessorT>::getRecord(row)); record->assign(Collection<ESXRecordT, IdAccessorT>::getRecord(row));
getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).addRow(record, position); getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).addRow(*record, position);
Collection<ESXRecordT, IdAccessorT>::setRecord(row, record); Collection<ESXRecordT, IdAccessorT>::setRecord(row, std::move(record));
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void NestedIdCollection<ESXRecordT, IdAccessorT>::removeNestedRows(int row, int column, int subRow) void NestedIdCollection<ESXRecordT, IdAccessorT>::removeNestedRows(int row, int column, int subRow)
{ {
Record<ESXRecordT> record; std::unique_ptr<Record<ESXRecordT> > record(new Record<ESXRecordT>);
record.assign(Collection<ESXRecordT, IdAccessorT>::getRecord(row)); record->assign(Collection<ESXRecordT, IdAccessorT>::getRecord(row));
getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).removeRow(record, subRow); getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).removeRow(*record, subRow);
Collection<ESXRecordT, IdAccessorT>::setRecord(row, record); Collection<ESXRecordT, IdAccessorT>::setRecord(row, std::move(record));
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
@ -121,13 +121,13 @@ namespace CSMWorld
void NestedIdCollection<ESXRecordT, IdAccessorT>::setNestedData(int row, void NestedIdCollection<ESXRecordT, IdAccessorT>::setNestedData(int row,
int column, const QVariant& data, int subRow, int subColumn) int column, const QVariant& data, int subRow, int subColumn)
{ {
Record<ESXRecordT> record; std::unique_ptr<Record<ESXRecordT> > record(new Record<ESXRecordT>);
record.assign(Collection<ESXRecordT, IdAccessorT>::getRecord(row)); record->assign(Collection<ESXRecordT, IdAccessorT>::getRecord(row));
getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).setData( getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).setData(
record, data, subRow, subColumn); *record, data, subRow, subColumn);
Collection<ESXRecordT, IdAccessorT>::setRecord(row, record); Collection<ESXRecordT, IdAccessorT>::setRecord(row, std::move(record));
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
@ -142,13 +142,13 @@ namespace CSMWorld
void NestedIdCollection<ESXRecordT, IdAccessorT>::setNestedTable(int row, void NestedIdCollection<ESXRecordT, IdAccessorT>::setNestedTable(int row,
int column, const CSMWorld::NestedTableWrapperBase& nestedTable) int column, const CSMWorld::NestedTableWrapperBase& nestedTable)
{ {
Record<ESXRecordT> record; std::unique_ptr<Record<ESXRecordT> > record(new Record<ESXRecordT>);
record.assign(Collection<ESXRecordT, IdAccessorT>::getRecord(row)); record->assign(Collection<ESXRecordT, IdAccessorT>::getRecord(row));
getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).setTable( getAdapter(Collection<ESXRecordT, IdAccessorT>::getColumn(column)).setTable(
record, nestedTable); *record, nestedTable);
Collection<ESXRecordT, IdAccessorT>::setRecord(row, record); Collection<ESXRecordT, IdAccessorT>::setRecord(row, std::move(record));
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>

View File

@ -35,22 +35,22 @@ namespace CSMWorld
void NestedInfoCollection::addNestedRow(int row, int column, int position) void NestedInfoCollection::addNestedRow(int row, int column, int position)
{ {
Record<Info> record; std::unique_ptr<Record<Info> > record(new Record<Info>);
record.assign(Collection<Info, IdAccessor<Info> >::getRecord(row)); record->assign(Collection<Info, IdAccessor<Info> >::getRecord(row));
getAdapter(Collection<Info, IdAccessor<Info> >::getColumn(column)).addRow(record, position); getAdapter(Collection<Info, IdAccessor<Info> >::getColumn(column)).addRow(*record, position);
Collection<Info, IdAccessor<Info> >::setRecord(row, record); Collection<Info, IdAccessor<Info> >::setRecord(row, std::move(record));
} }
void NestedInfoCollection::removeNestedRows(int row, int column, int subRow) void NestedInfoCollection::removeNestedRows(int row, int column, int subRow)
{ {
Record<Info> record; std::unique_ptr<Record<Info> > record(new Record<Info>);
record.assign(Collection<Info, IdAccessor<Info> >::getRecord(row)); record->assign(Collection<Info, IdAccessor<Info> >::getRecord(row));
getAdapter(Collection<Info, IdAccessor<Info> >::getColumn(column)).removeRow(record, subRow); getAdapter(Collection<Info, IdAccessor<Info> >::getColumn(column)).removeRow(*record, subRow);
Collection<Info, IdAccessor<Info> >::setRecord(row, record); Collection<Info, IdAccessor<Info> >::setRecord(row, std::move(record));
} }
QVariant NestedInfoCollection::getNestedData (int row, QVariant NestedInfoCollection::getNestedData (int row,
@ -63,13 +63,13 @@ namespace CSMWorld
void NestedInfoCollection::setNestedData(int row, void NestedInfoCollection::setNestedData(int row,
int column, const QVariant& data, int subRow, int subColumn) int column, const QVariant& data, int subRow, int subColumn)
{ {
Record<Info> record; std::unique_ptr<Record<Info> > record(new Record<Info>);
record.assign(Collection<Info, IdAccessor<Info> >::getRecord(row)); record->assign(Collection<Info, IdAccessor<Info> >::getRecord(row));
getAdapter(Collection<Info, IdAccessor<Info> >::getColumn(column)).setData( getAdapter(Collection<Info, IdAccessor<Info> >::getColumn(column)).setData(
record, data, subRow, subColumn); *record, data, subRow, subColumn);
Collection<Info, IdAccessor<Info> >::setRecord(row, record); Collection<Info, IdAccessor<Info> >::setRecord(row, std::move(record));
} }
CSMWorld::NestedTableWrapperBase* NestedInfoCollection::nestedTable(int row, CSMWorld::NestedTableWrapperBase* NestedInfoCollection::nestedTable(int row,
@ -82,13 +82,13 @@ namespace CSMWorld
void NestedInfoCollection::setNestedTable(int row, void NestedInfoCollection::setNestedTable(int row,
int column, const CSMWorld::NestedTableWrapperBase& nestedTable) int column, const CSMWorld::NestedTableWrapperBase& nestedTable)
{ {
Record<Info> record; std::unique_ptr<Record<Info> > record(new Record<Info>);
record.assign(Collection<Info, IdAccessor<Info> >::getRecord(row)); record->assign(Collection<Info, IdAccessor<Info> >::getRecord(row));
getAdapter(Collection<Info, IdAccessor<Info> >::getColumn(column)).setTable( getAdapter(Collection<Info, IdAccessor<Info> >::getColumn(column)).setTable(
record, nestedTable); *record, nestedTable);
Collection<Info, IdAccessor<Info> >::setRecord(row, record); Collection<Info, IdAccessor<Info> >::setRecord(row, std::move(record));
} }
int NestedInfoCollection::getNestedRowsCount(int row, int column) const int NestedInfoCollection::getNestedRowsCount(int row, int column) const

View File

@ -20,7 +20,7 @@ namespace CSMWorld
{ {
std::string mId; std::string mId;
void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection<Cell>& cells); void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection<Cell, IdAccessor<Cell> >& cells);
void load (ESM::ESMReader &esm, bool &isDeleted); void load (ESM::ESMReader &esm, bool &isDeleted);
}; };
} }

View File

@ -1,6 +1,7 @@
#ifndef CSM_WOLRD_RECORD_H #ifndef CSM_WOLRD_RECORD_H
#define CSM_WOLRD_RECORD_H #define CSM_WOLRD_RECORD_H
#include <memory>
#include <stdexcept> #include <stdexcept>
namespace CSMWorld namespace CSMWorld
@ -20,9 +21,9 @@ namespace CSMWorld
virtual ~RecordBase(); virtual ~RecordBase();
virtual RecordBase *clone() const = 0; virtual std::unique_ptr<RecordBase> clone() const = 0;
virtual RecordBase *modifiedCopy() const = 0; virtual std::unique_ptr<RecordBase> modifiedCopy() const = 0;
virtual void assign (const RecordBase& record) = 0; virtual void assign (const RecordBase& record) = 0;
///< Will throw an exception if the types don't match. ///< Will throw an exception if the types don't match.
@ -45,9 +46,9 @@ namespace CSMWorld
Record(State state, Record(State state,
const ESXRecordT *base = 0, const ESXRecordT *modified = 0); const ESXRecordT *base = 0, const ESXRecordT *modified = 0);
RecordBase *clone() const override; std::unique_ptr<RecordBase> clone() const override;
RecordBase *modifiedCopy() const override; std::unique_ptr<RecordBase> modifiedCopy() const override;
void assign (const RecordBase& record) override; void assign (const RecordBase& record) override;
@ -85,15 +86,16 @@ namespace CSMWorld
} }
template <typename ESXRecordT> template <typename ESXRecordT>
RecordBase *Record<ESXRecordT>::modifiedCopy() const std::unique_ptr<RecordBase> Record<ESXRecordT>::modifiedCopy() const
{ {
return new Record<ESXRecordT> (State_ModifiedOnly, nullptr, &(this->get())); return std::make_unique<Record<ESXRecordT> >(
Record<ESXRecordT>(State_ModifiedOnly, nullptr, &(this->get())));
} }
template <typename ESXRecordT> template <typename ESXRecordT>
RecordBase *Record<ESXRecordT>::clone() const std::unique_ptr<RecordBase> Record<ESXRecordT>::clone() const
{ {
return new Record<ESXRecordT> (*this); return std::make_unique<Record<ESXRecordT> >(Record<ESXRecordT>(*this));
} }
template <typename ESXRecordT> template <typename ESXRecordT>

View File

@ -2,7 +2,7 @@
#include "cellcoordinates.hpp" #include "cellcoordinates.hpp"
CSMWorld::CellRef::CellRef() : mNew (true) CSMWorld::CellRef::CellRef() : mNew (true), mIdNum(0)
{ {
mRefNum.mIndex = 0; mRefNum.mIndex = 0;
mRefNum.mContentFile = 0; mRefNum.mContentFile = 0;

View File

@ -14,6 +14,7 @@ namespace CSMWorld
std::string mCell; std::string mCell;
std::string mOriginalCell; std::string mOriginalCell;
bool mNew; // new reference, not counted yet, ref num not assigned yet bool mNew; // new reference, not counted yet, ref num not assigned yet
unsigned int mIdNum;
CellRef(); CellRef();

View File

@ -7,8 +7,39 @@
#include "universalid.hpp" #include "universalid.hpp"
#include "record.hpp" #include "record.hpp"
#include <string_view>
namespace CSMWorld
{
template<>
void Collection<CellRef, IdAccessor<CellRef> >::removeRows (int index, int count)
{
mRecords.erase(mRecords.begin()+index, mRecords.begin()+index+count);
// index map is updated in RefCollection::removeRows()
}
template<>
void Collection<CellRef, IdAccessor<CellRef> >::insertRecord (std::unique_ptr<RecordBase> record, int index,
UniversalId::Type type)
{
int size = static_cast<int>(mRecords.size());
if (index < 0 || index > size)
throw std::runtime_error("index out of range");
std::unique_ptr<Record<CellRef> > record2(static_cast<Record<CellRef>*>(record.release()));
if (index == size)
mRecords.push_back(std::move(record2));
else
mRecords.insert(mRecords.begin()+index, std::move(record2));
// index map is updated in RefCollection::insertRecord()
}
}
void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base,
std::map<ESM::RefNum, std::string>& cache, CSMDoc::Messages& messages) std::map<unsigned int, unsigned int>& cache, CSMDoc::Messages& messages)
{ {
Record<Cell> cell = mCells.getRecord (cellIndex); Record<Cell> cell = mCells.getRecord (cellIndex);
@ -19,8 +50,9 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
ESM::MovedCellRef mref; ESM::MovedCellRef mref;
mref.mRefNum.mIndex = 0; mref.mRefNum.mIndex = 0;
bool isDeleted = false; bool isDeleted = false;
bool isMoved = false;
while (ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref)) while (ESM::Cell::getNextRef(reader, ref, isDeleted, mref, isMoved))
{ {
// Keep mOriginalCell empty when in modified (as an indicator that the // Keep mOriginalCell empty when in modified (as an indicator that the
// original cell will always be equal the current cell). // original cell will always be equal the current cell).
@ -34,7 +66,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second); ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second);
// Handle non-base moved references // Handle non-base moved references
if (!base && mref.mRefNum.mIndex != 0) if (!base && isMoved)
{ {
// Moved references must have a link back to their original cell // Moved references must have a link back to their original cell
// See discussion: https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 // See discussion: https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30
@ -59,17 +91,47 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
else else
ref.mCell = cell2.mId; ref.mCell = cell2.mId;
mref.mRefNum.mIndex = 0; if (ref.mRefNum.mContentFile != -1 && !base)
// ignore content file number
std::map<ESM::RefNum, std::string>::iterator iter = cache.begin();
unsigned int thisIndex = ref.mRefNum.mIndex & 0x00ffffff;
if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24;
for (; iter != cache.end(); ++iter)
{ {
if (thisIndex == iter->first.mIndex) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24;
break; ref.mRefNum.mIndex &= 0x00ffffff;
}
unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) |
(ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24;
std::map<unsigned int, unsigned int>::iterator iter = cache.find(refNum);
if (isMoved)
{
if (iter == cache.end())
{
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell,
mCells.getId(cellIndex));
messages.add(id, "Attempt to move a non-existent reference - RefNum index "
+ std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index "
+ std::to_string(ref.mRefNum.mContentFile),
/*hint*/"",
CSMDoc::Message::Severity_Warning);
continue;
}
int index = getIntIndex(iter->second);
// ensure we have the same record id for setRecord()
ref.mId = getRecord(index).get().mId;
ref.mIdNum = extractIdNum(ref.mId);
std::unique_ptr<Record<CellRef> > record(new Record<CellRef>);
// TODO: check whether a base record be moved
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record->mBase : record->mModified) = std::move(ref);
// overwrite original record
setRecord(index, std::move(record));
continue; // NOTE: assumed moved references are not deleted at the same time
} }
if (isDeleted) if (isDeleted)
@ -79,13 +141,15 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell,
mCells.getId (cellIndex)); mCells.getId (cellIndex));
messages.add (id, "Attempt to delete a non-existent reference"); messages.add (id, "Attempt to delete a non-existent reference - RefNum index "
+ std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index "
+ std::to_string(ref.mRefNum.mContentFile),
/*hint*/"",
CSMDoc::Message::Severity_Warning);
continue; continue;
} }
int index = getIndex (iter->second); int index = getIntIndex (iter->second);
Record<CellRef> record = getRecord (index);
if (base) if (base)
{ {
@ -94,8 +158,9 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
} }
else else
{ {
record.mState = RecordBase::State_Deleted; std::unique_ptr<Record<CellRef> > record(new Record<CellRef>(getRecord(index)));
setRecord (index, record); record->mState = RecordBase::State_Deleted;
setRecord(index, std::move(record));
} }
continue; continue;
@ -104,30 +169,47 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
if (iter==cache.end()) if (iter==cache.end())
{ {
// new reference // new reference
ref.mIdNum = mNextId; // FIXME: fragile
ref.mId = getNewId(); ref.mId = getNewId();
Record<CellRef> record; cache.emplace(refNum, ref.mIdNum);
record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
const ESM::RefNum refNum = ref.mRefNum;
std::string refId = ref.mId;
(base ? record.mBase : record.mModified) = std::move(ref);
appendRecord (record); std::unique_ptr<Record<CellRef> > record(new Record<CellRef>);
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record->mBase : record->mModified) = std::move(ref);
cache.emplace(refNum, std::move(refId)); appendRecord(std::move(record));
} }
else else
{ {
// old reference -> merge // old reference -> merge
ref.mId = iter->second; int index = getIntIndex(iter->second);
#if 0
// ref.mRefNum.mIndex : the key
// iter->second : previously cached idNum for the key
// index : position of the record for that idNum
// getRecord(index).get() : record in the index position
assert(iter->second != getRecord(index).get().mIdNum); // sanity check
int index = getIndex (ref.mId); // check if the plugin used the same RefNum index for a different record
if (ref.mRefID != getRecord(index).get().mRefID)
{
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex));
messages.add(id,
"RefNum renamed from RefID \"" + getRecord(index).get().mRefID + "\" to \""
+ ref.mRefID + "\" (RefNum index " + std::to_string(ref.mRefNum.mIndex) + ")",
/*hint*/"",
CSMDoc::Message::Severity_Info);
}
#endif
ref.mId = getRecord(index).get().mId;
ref.mIdNum = extractIdNum(ref.mId);
Record<CellRef> record = getRecord (index); std::unique_ptr<Record<CellRef> > record(new Record<CellRef>(getRecord(index)));
record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified;
(base ? record.mBase : record.mModified) = std::move(ref); (base ? record->mBase : record->mModified) = std::move(ref);
setRecord (index, record); setRecord(index, std::move(record));
} }
} }
} }
@ -136,3 +218,117 @@ std::string CSMWorld::RefCollection::getNewId()
{ {
return "ref#" + std::to_string(mNextId++); return "ref#" + std::to_string(mNextId++);
} }
unsigned int CSMWorld::RefCollection::extractIdNum(std::string_view id) const
{
std::string::size_type separator = id.find_last_of('#');
if (separator == std::string::npos)
throw std::runtime_error("invalid ref ID: " + std::string(id));
return static_cast<unsigned int>(std::stoi(std::string(id.substr(separator+1))));
}
int CSMWorld::RefCollection::getIntIndex (unsigned int id) const
{
int index = searchId(id);
if (index == -1)
throw std::runtime_error("invalid RefNum: " + std::to_string(id));
return index;
}
int CSMWorld::RefCollection::searchId (unsigned int id) const
{
std::map<unsigned int, int>::const_iterator iter = mRefIndex.find(id);
if (iter == mRefIndex.end())
return -1;
return iter->second;
}
void CSMWorld::RefCollection::removeRows (int index, int count)
{
Collection<CellRef, IdAccessor<CellRef> >::removeRows(index, count); // erase records only
std::map<unsigned int, int>::iterator iter = mRefIndex.begin();
while (iter != mRefIndex.end())
{
if (iter->second>=index)
{
if (iter->second >= index+count)
{
iter->second -= count;
++iter;
}
else
mRefIndex.erase(iter++);
}
else
++iter;
}
}
void CSMWorld::RefCollection::appendBlankRecord (const std::string& id, UniversalId::Type type)
{
std::unique_ptr<Record<CellRef> > record(new Record<CellRef>);
record->mState = Record<CellRef>::State_ModifiedOnly;
record->mModified.blank();
record->get().mId = id;
record->get().mIdNum = extractIdNum(id);
Collection<CellRef, IdAccessor<CellRef> >::appendRecord(std::move(record));
}
void CSMWorld::RefCollection::cloneRecord (const std::string& origin,
const std::string& destination,
const UniversalId::Type type)
{
std::unique_ptr<Record<CellRef> > copy(new Record<CellRef>);
copy->mModified = getRecord(origin).get();
copy->mState = RecordBase::State_ModifiedOnly;
copy->get().mId = destination;
copy->get().mIdNum = extractIdNum(destination);
insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord()
}
int CSMWorld::RefCollection::searchId(std::string_view id) const
{
return searchId(extractIdNum(id));
}
void CSMWorld::RefCollection::appendRecord (std::unique_ptr<RecordBase> record, UniversalId::Type type)
{
int index = getAppendIndex(/*id*/"", type); // for CellRef records id is ignored
mRefIndex.insert(std::make_pair(static_cast<Record<CellRef>*>(record.get())->get().mIdNum, index));
Collection<CellRef, IdAccessor<CellRef> >::insertRecord(std::move(record), index, type); // add records only
}
void CSMWorld::RefCollection::insertRecord (std::unique_ptr<RecordBase> record, int index,
UniversalId::Type type)
{
int size = getAppendIndex(/*id*/"", type); // for CellRef records id is ignored
unsigned int idNum = static_cast<Record<CellRef>*>(record.get())->get().mIdNum;
Collection<CellRef, IdAccessor<CellRef> >::insertRecord(std::move(record), index, type); // add records only
if (index < size-1)
{
for (std::map<unsigned int, int>::iterator iter(mRefIndex.begin()); iter != mRefIndex.end(); ++iter)
{
if (iter->second >= index)
++(iter->second);
}
}
mRefIndex.insert(std::make_pair(idNum, index));
}

View File

@ -2,6 +2,7 @@
#define CSM_WOLRD_REFCOLLECTION_H #define CSM_WOLRD_REFCOLLECTION_H
#include <map> #include <map>
#include <string_view>
#include "../doc/stage.hpp" #include "../doc/stage.hpp"
@ -14,12 +15,27 @@ namespace CSMWorld
struct Cell; struct Cell;
class UniversalId; class UniversalId;
template<>
void Collection<CellRef, IdAccessor<CellRef> >::removeRows (int index, int count);
template<>
void Collection<CellRef, IdAccessor<CellRef> >::insertRecord (std::unique_ptr<RecordBase> record, int index,
UniversalId::Type type);
/// \brief References in cells /// \brief References in cells
class RefCollection : public Collection<CellRef> class RefCollection : public Collection<CellRef>
{ {
Collection<Cell>& mCells; Collection<Cell>& mCells;
std::map<unsigned int, int> mRefIndex; // CellRef index keyed by CSMWorld::CellRef::mIdNum
int mNextId; int mNextId;
unsigned int extractIdNum(std::string_view id) const;
int getIntIndex (unsigned int id) const;
int searchId (unsigned int id) const;
public: public:
// MSVC needs the constructor for a class inheriting a template to be defined in header // MSVC needs the constructor for a class inheriting a template to be defined in header
RefCollection (Collection<Cell>& cells) RefCollection (Collection<Cell>& cells)
@ -27,10 +43,28 @@ namespace CSMWorld
{} {}
void load (ESM::ESMReader& reader, int cellIndex, bool base, void load (ESM::ESMReader& reader, int cellIndex, bool base,
std::map<ESM::RefNum, std::string>& cache, CSMDoc::Messages& messages); std::map<unsigned int, unsigned int>& cache, CSMDoc::Messages& messages);
///< Load a sequence of references. ///< Load a sequence of references.
std::string getNewId(); std::string getNewId();
virtual void removeRows (int index, int count);
virtual void appendBlankRecord (const std::string& id,
UniversalId::Type type = UniversalId::Type_None);
virtual void cloneRecord (const std::string& origin,
const std::string& destination,
const UniversalId::Type type);
virtual int searchId(std::string_view id) const;
virtual void appendRecord (std::unique_ptr<RecordBase> record,
UniversalId::Type type = UniversalId::Type_None);
virtual void insertRecord (std::unique_ptr<RecordBase> record,
int index,
UniversalId::Type type = UniversalId::Type_None);
}; };
} }

View File

@ -25,6 +25,9 @@ namespace CSMWorld
const RefIdColumn *mId; const RefIdColumn *mId;
const RefIdColumn *mModified; const RefIdColumn *mModified;
const RefIdColumn *mType; const RefIdColumn *mType;
const RefIdColumn *mBlocked;
BaseColumns () : mBlocked(nullptr) {}
}; };
/// \brief Base adapter for all refereceable record types /// \brief Base adapter for all refereceable record types
@ -90,6 +93,9 @@ namespace CSMWorld
if (column==mBase.mType) if (column==mBase.mType)
return static_cast<int> (mType); return static_cast<int> (mType);
if (column==mBase.mBlocked)
return (record.get().mRecordFlags & ESM::FLAG_Blocked) != 0;
return QVariant(); return QVariant();
} }
@ -102,6 +108,17 @@ namespace CSMWorld
if (column==mBase.mModified) if (column==mBase.mModified)
record.mState = static_cast<RecordBase::State> (value.toInt()); record.mState = static_cast<RecordBase::State> (value.toInt());
else if (column==mBase.mBlocked)
{
RecordT record2 = record.get();
if (value.toInt() != 0)
record2.mRecordFlags |= ESM::FLAG_Blocked;
else
record2.mRecordFlags &= ~ESM::FLAG_Blocked;
record.setModified(record2);
}
} }
template<typename RecordT> template<typename RecordT>
@ -110,6 +127,14 @@ namespace CSMWorld
return mType; return mType;
} }
// NOTE: Body Part should not have persistence (but BodyPart is not listed in the Objects
// table at the moment).
//
// Spellmaking - not persistent - currently not part of objects table
// Enchanting - not persistent - currently not part of objects table
//
// Leveled Creature - no model, so not persistent
// Leveled Item - no model, so not persistent
struct ModelColumns : public BaseColumns struct ModelColumns : public BaseColumns
{ {

View File

@ -2,6 +2,7 @@
#include <stdexcept> #include <stdexcept>
#include <memory> #include <memory>
#include <string_view>
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
@ -49,17 +50,22 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.emplace_back(Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType, mColumns.emplace_back(Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType,
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false); ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false);
baseColumns.mType = &mColumns.back(); baseColumns.mType = &mColumns.back();
mColumns.emplace_back(Columns::ColumnId_Blocked, ColumnBase::Display_Boolean,
ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh);
baseColumns.mBlocked = &mColumns.back();
ModelColumns modelColumns (baseColumns); ModelColumns modelColumns (baseColumns);
mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh);
modelColumns.mModel = &mColumns.back();
mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean); mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean);
modelColumns.mPersistence = &mColumns.back(); modelColumns.mPersistence = &mColumns.back();
mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh);
modelColumns.mModel = &mColumns.back();
NameColumns nameColumns (modelColumns); NameColumns nameColumns (modelColumns);
mColumns.emplace_back(Columns::ColumnId_Name, ColumnBase::Display_String); // Only items that can be placed in a container have the 32 character limit, but enforce
// that for all referenceable types for now.
mColumns.emplace_back(Columns::ColumnId_Name, ColumnBase::Display_String32);
nameColumns.mName = &mColumns.back(); nameColumns.mName = &mColumns.back();
mColumns.emplace_back(Columns::ColumnId_Script, ColumnBase::Display_Script); mColumns.emplace_back(Columns::ColumnId_Script, ColumnBase::Display_Script);
nameColumns.mScript = &mColumns.back(); nameColumns.mScript = &mColumns.back();
@ -231,9 +237,9 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String)); new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String32));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String)); new RefIdColumn (Columns::ColumnId_AiTargetId, CSMWorld::ColumnBase::Display_String32));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_AiTargetCell, CSMWorld::ColumnBase::Display_String)); new RefIdColumn (Columns::ColumnId_AiTargetCell, CSMWorld::ColumnBase::Display_String));
mColumns.back().addColumn( mColumns.back().addColumn(
@ -479,6 +485,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.emplace_back(Columns::ColumnId_Class, ColumnBase::Display_Class); mColumns.emplace_back(Columns::ColumnId_Class, ColumnBase::Display_Class);
npcColumns.mClass = &mColumns.back(); npcColumns.mClass = &mColumns.back();
// NAME32 enforced in IdCompletionDelegate::createEditor()
mColumns.emplace_back(Columns::ColumnId_Faction, ColumnBase::Display_Faction); mColumns.emplace_back(Columns::ColumnId_Faction, ColumnBase::Display_Faction);
npcColumns.mFaction = &mColumns.back(); npcColumns.mFaction = &mColumns.back();
@ -764,7 +771,6 @@ void CSMWorld::RefIdCollection::setNestedData(int row, int column, const QVarian
const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second);
nestedAdapter.setNestedData(&mColumns.at (column), mData, localIndex.first, data, subRow, subColumn); nestedAdapter.setNestedData(&mColumns.at (column), mData, localIndex.first, data, subRow, subColumn);
return;
} }
void CSMWorld::RefIdCollection::removeRows (int index, int count) void CSMWorld::RefIdCollection::removeRows (int index, int count)
@ -778,7 +784,6 @@ void CSMWorld::RefIdCollection::removeNestedRows(int row, int column, int subRow
const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second);
nestedAdapter.removeNestedRow(&mColumns.at (column), mData, localIndex.first, subRow); nestedAdapter.removeNestedRow(&mColumns.at (column), mData, localIndex.first, subRow);
return;
} }
void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, UniversalId::Type type) void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, UniversalId::Type type)
@ -786,7 +791,7 @@ void CSMWorld::RefIdCollection::appendBlankRecord (const std::string& id, Univer
mData.appendRecord (type, id, false); mData.appendRecord (type, id, false);
} }
int CSMWorld::RefIdCollection::searchId (const std::string& id) const int CSMWorld::RefIdCollection::searchId(std::string_view id) const
{ {
RefIdData::LocalIndex localIndex = mData.searchId (id); RefIdData::LocalIndex localIndex = mData.searchId (id);
@ -796,18 +801,18 @@ int CSMWorld::RefIdCollection::searchId (const std::string& id) const
return mData.localToGlobalIndex (localIndex); return mData.localToGlobalIndex (localIndex);
} }
void CSMWorld::RefIdCollection::replace (int index, const RecordBase& record) void CSMWorld::RefIdCollection::replace (int index, std::unique_ptr<RecordBase> record)
{ {
mData.getRecord (mData.globalToLocalIndex (index)).assign (record); mData.getRecord (mData.globalToLocalIndex (index)).assign (*record.release());
} }
void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin, void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin,
const std::string& destination, const std::string& destination,
const CSMWorld::UniversalId::Type type) const CSMWorld::UniversalId::Type type)
{ {
std::unique_ptr<RecordBase> newRecord(mData.getRecord(mData.searchId(origin)).modifiedCopy()); std::unique_ptr<RecordBase> newRecord = mData.getRecord(mData.searchId(origin)).modifiedCopy();
mAdapters.find(type)->second->setId(*newRecord, destination); mAdapters.find(type)->second->setId(*newRecord, destination);
mData.insertRecord(*newRecord, type, destination); mData.insertRecord(std::move(newRecord), type, destination);
} }
bool CSMWorld::RefIdCollection::touchRecord(const std::string& id) bool CSMWorld::RefIdCollection::touchRecord(const std::string& id)
@ -816,16 +821,16 @@ bool CSMWorld::RefIdCollection::touchRecord(const std::string& id)
return false; return false;
} }
void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record, void CSMWorld::RefIdCollection::appendRecord (std::unique_ptr<RecordBase> record,
UniversalId::Type type) UniversalId::Type type)
{ {
std::string id = findAdapter (type).getId (record); std::string id = findAdapter (type).getId (*record.get());
int index = mData.getAppendIndex (type); int index = mData.getAppendIndex (type);
mData.appendRecord (type, id, false); mData.appendRecord (type, id, false);
mData.getRecord (mData.globalToLocalIndex (index)).assign (record); mData.getRecord (mData.globalToLocalIndex (index)).assign (*record.release());
} }
const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (const std::string& id) const const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (const std::string& id) const
@ -895,7 +900,6 @@ void CSMWorld::RefIdCollection::addNestedRow(int row, int col, int position)
const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(col), localIndex.second); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(col), localIndex.second);
nestedAdapter.addNestedRow(&mColumns.at(col), mData, localIndex.first, position); nestedAdapter.addNestedRow(&mColumns.at(col), mData, localIndex.first, position);
return;
} }
void CSMWorld::RefIdCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable) void CSMWorld::RefIdCollection::setNestedTable(int row, int column, const CSMWorld::NestedTableWrapperBase& nestedTable)
@ -904,7 +908,6 @@ void CSMWorld::RefIdCollection::setNestedTable(int row, int column, const CSMWor
const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second); const CSMWorld::NestedRefIdAdapterBase& nestedAdapter = getNestedAdapter(mColumns.at(column), localIndex.second);
nestedAdapter.setNestedTable(&mColumns.at(column), mData, localIndex.first, nestedTable); nestedAdapter.setNestedTable(&mColumns.at(column), mData, localIndex.first, nestedTable);
return;
} }
CSMWorld::NestedTableWrapperBase* CSMWorld::RefIdCollection::nestedTable(int row, int column) const CSMWorld::NestedTableWrapperBase* CSMWorld::RefIdCollection::nestedTable(int row, int column) const

View File

@ -4,6 +4,7 @@
#include <vector> #include <vector>
#include <map> #include <map>
#include <deque> #include <deque>
#include <string_view>
#include "columnbase.hpp" #include "columnbase.hpp"
#include "collectionbase.hpp" #include "collectionbase.hpp"
@ -85,16 +86,16 @@ namespace CSMWorld
void appendBlankRecord (const std::string& id, UniversalId::Type type) override; void appendBlankRecord (const std::string& id, UniversalId::Type type) override;
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
int searchId (const std::string& id) const override; int searchId(std::string_view id) const override;
////< Search record with \a id. ////< Search record with \a id.
/// \return index of record (if found) or -1 (not found) /// \return index of record (if found) or -1 (not found)
void replace (int index, const RecordBase& record) override; void replace (int index, std::unique_ptr<RecordBase> record) override;
///< If the record type does not match, an exception is thrown. ///< If the record type does not match, an exception is thrown.
/// ///
/// \attention \a record must not change the ID. /// \attention \a record must not change the ID.
void appendRecord (const RecordBase& record, UniversalId::Type type) override; void appendRecord (std::unique_ptr<RecordBase> record, UniversalId::Type type) override;
///< If the record type does not match, an exception is thrown. ///< If the record type does not match, an exception is thrown.
/// ///
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types

View File

@ -2,6 +2,7 @@
#include <cassert> #include <cassert>
#include <memory> #include <memory>
#include <string_view>
CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {}
@ -74,8 +75,7 @@ int CSMWorld::RefIdData::localToGlobalIndex (const LocalIndex& index)
return globalIndex; return globalIndex;
} }
CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId ( CSMWorld::RefIdData::LocalIndex CSMWorld::RefIdData::searchId(std::string_view id) const
const std::string& id) const
{ {
std::string id2 = Misc::StringUtils::lowerCase (id); std::string id2 = Misc::StringUtils::lowerCase (id);
@ -400,7 +400,7 @@ const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStati
return mStatics; return mStatics;
} }
void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) void CSMWorld::RefIdData::insertRecord (std::unique_ptr<CSMWorld::RecordBase> record, CSMWorld::UniversalId::Type type, const std::string& id)
{ {
std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter = std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter =
mRecordContainers.find (type); mRecordContainers.find (type);
@ -408,7 +408,7 @@ void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::
if (iter==mRecordContainers.end()) if (iter==mRecordContainers.end())
throw std::logic_error ("invalid local index type"); throw std::logic_error ("invalid local index type");
iter->second->insertRecord(record); iter->second->insertRecord(std::move(record));
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id),
LocalIndex (iter->second->getSize()-1, type))); LocalIndex (iter->second->getSize()-1, type)));
@ -420,9 +420,7 @@ void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const
RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second; RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second;
std::string id = source->getId (localIndex.first); target.insertRecord(source->getRecord(localIndex.first).modifiedCopy(),
localIndex.second,
std::unique_ptr<CSMWorld::RecordBase> newRecord (source->getRecord (localIndex.first).modifiedCopy()); source->getId(localIndex.first));
target.insertRecord (*newRecord, localIndex.second, id);
} }

View File

@ -3,6 +3,9 @@
#include <vector> #include <vector>
#include <map> #include <map>
#include <memory>
#include <cassert>
#include <string_view>
#include <components/esm/loadacti.hpp> #include <components/esm/loadacti.hpp>
#include <components/esm/loadalch.hpp> #include <components/esm/loadalch.hpp>
@ -51,7 +54,7 @@ namespace CSMWorld
virtual void appendRecord (const std::string& id, bool base) = 0; virtual void appendRecord (const std::string& id, bool base) = 0;
virtual void insertRecord (RecordBase& record) = 0; virtual void insertRecord (std::unique_ptr<RecordBase> record) = 0;
virtual int load (ESM::ESMReader& reader, bool base) = 0; virtual int load (ESM::ESMReader& reader, bool base) = 0;
///< \return index of a loaded record or -1 if no record was loaded ///< \return index of a loaded record or -1 if no record was loaded
@ -66,7 +69,7 @@ namespace CSMWorld
template<typename RecordT> template<typename RecordT>
struct RefIdDataContainer : public RefIdDataContainerBase struct RefIdDataContainer : public RefIdDataContainerBase
{ {
std::vector<Record<RecordT> > mContainer; std::vector<std::unique_ptr<Record<RecordT> > > mContainer;
int getSize() const override; int getSize() const override;
@ -78,7 +81,7 @@ namespace CSMWorld
void appendRecord (const std::string& id, bool base) override; void appendRecord (const std::string& id, bool base) override;
void insertRecord (RecordBase& record) override; void insertRecord (std::unique_ptr<RecordBase> record) override;
int load (ESM::ESMReader& reader, bool base) override; int load (ESM::ESMReader& reader, bool base) override;
///< \return index of a loaded record or -1 if no record was loaded ///< \return index of a loaded record or -1 if no record was loaded
@ -91,10 +94,13 @@ namespace CSMWorld
}; };
template<typename RecordT> template<typename RecordT>
void RefIdDataContainer<RecordT>::insertRecord(RecordBase& record) void RefIdDataContainer<RecordT>::insertRecord(std::unique_ptr<RecordBase> record)
{ {
Record<RecordT>& newRecord = dynamic_cast<Record<RecordT>& >(record); assert(record != nullptr);
mContainer.push_back(newRecord); // convert base pointer to record type pointer
std::unique_ptr<Record<RecordT>> typedRecord(&dynamic_cast<Record<RecordT>&>(*record));
record.release();
mContainer.push_back(std::move(typedRecord));
} }
template<typename RecordT> template<typename RecordT>
@ -106,33 +112,33 @@ namespace CSMWorld
template<typename RecordT> template<typename RecordT>
const RecordBase& RefIdDataContainer<RecordT>::getRecord (int index) const const RecordBase& RefIdDataContainer<RecordT>::getRecord (int index) const
{ {
return mContainer.at (index); return *mContainer.at (index);
} }
template<typename RecordT> template<typename RecordT>
RecordBase& RefIdDataContainer<RecordT>::getRecord (int index) RecordBase& RefIdDataContainer<RecordT>::getRecord (int index)
{ {
return mContainer.at (index); return *mContainer.at (index);
} }
template<typename RecordT> template<typename RecordT>
unsigned int RefIdDataContainer<RecordT>::getRecordFlags (int index) const unsigned int RefIdDataContainer<RecordT>::getRecordFlags (int index) const
{ {
return mContainer.at (index).get().mRecordFlags; return mContainer.at (index)->get().mRecordFlags;
} }
template<typename RecordT> template<typename RecordT>
void RefIdDataContainer<RecordT>::appendRecord (const std::string& id, bool base) void RefIdDataContainer<RecordT>::appendRecord (const std::string& id, bool base)
{ {
Record<RecordT> record; std::unique_ptr<Record<RecordT> > record(new Record<RecordT>);
record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
record.mBase.mId = id; record->mBase.mId = id;
record.mModified.mId = id; record->mModified.mId = id;
(base ? record.mBase : record.mModified).blank(); (base ? record->mBase : record->mModified).blank();
mContainer.push_back (record); mContainer.push_back (std::move(record));
} }
template<typename RecordT> template<typename RecordT>
@ -147,7 +153,7 @@ namespace CSMWorld
int numRecords = static_cast<int>(mContainer.size()); int numRecords = static_cast<int>(mContainer.size());
for (; index < numRecords; ++index) for (; index < numRecords; ++index)
{ {
if (Misc::StringUtils::ciEqual(mContainer[index].get().mId, record.mId)) if (Misc::StringUtils::ciEqual(mContainer[index]->get().mId, record.mId))
{ {
break; break;
} }
@ -165,7 +171,7 @@ namespace CSMWorld
// Flag the record as Deleted even for a base content file. // Flag the record as Deleted even for a base content file.
// RefIdData is responsible for its erasure. // RefIdData is responsible for its erasure.
mContainer[index].mState = RecordBase::State_Deleted; mContainer[index]->mState = RecordBase::State_Deleted;
} }
else else
{ {
@ -174,22 +180,22 @@ namespace CSMWorld
appendRecord(record.mId, base); appendRecord(record.mId, base);
if (base) if (base)
{ {
mContainer.back().mBase = record; mContainer.back()->mBase = record;
} }
else else
{ {
mContainer.back().mModified = record; mContainer.back()->mModified = record;
} }
} }
else if (!base) else if (!base)
{ {
mContainer[index].setModified(record); mContainer[index]->setModified(record);
} }
else else
{ {
// Overwrite // Overwrite
mContainer[index].setModified(record); mContainer[index]->setModified(record);
mContainer[index].merge(); mContainer[index]->merge();
} }
} }
@ -208,13 +214,13 @@ namespace CSMWorld
template<typename RecordT> template<typename RecordT>
std::string RefIdDataContainer<RecordT>::getId (int index) const std::string RefIdDataContainer<RecordT>::getId (int index) const
{ {
return mContainer.at (index).get().mId; return mContainer.at (index)->get().mId;
} }
template<typename RecordT> template<typename RecordT>
void RefIdDataContainer<RecordT>::save (int index, ESM::ESMWriter& writer) const void RefIdDataContainer<RecordT>::save (int index, ESM::ESMWriter& writer) const
{ {
Record<RecordT> record = mContainer.at(index); const Record<RecordT>& record = *mContainer.at(index);
if (record.isModified() || record.mState == RecordBase::State_Deleted) if (record.isModified() || record.mState == RecordBase::State_Deleted)
{ {
@ -272,11 +278,11 @@ namespace CSMWorld
int localToGlobalIndex (const LocalIndex& index) const; int localToGlobalIndex (const LocalIndex& index) const;
LocalIndex searchId (const std::string& id) const; LocalIndex searchId(std::string_view id) const;
void erase (int index, int count); void erase (int index, int count);
void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, void insertRecord (std::unique_ptr<RecordBase> record, CSMWorld::UniversalId::Type type,
const std::string& id); const std::string& id);
const RecordBase& getRecord (const LocalIndex& index) const; const RecordBase& getRecord (const LocalIndex& index) const;

View File

@ -3,6 +3,7 @@
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <algorithm> #include <algorithm>
#include <string_view>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
@ -22,10 +23,8 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const *
size_t baseSize = mBaseDirectory.size(); size_t baseSize = mBaseDirectory.size();
const std::map<std::string, VFS::File*>& index = vfs->getIndex(); for (const auto& filepath : vfs->getRecursiveDirectoryIterator(""))
for (std::map<std::string, VFS::File*>::const_iterator it = index.begin(); it != index.end(); ++it)
{ {
std::string filepath = it->first;
if (filepath.size()<baseSize+1 || if (filepath.size()<baseSize+1 ||
filepath.substr (0, baseSize)!=mBaseDirectory || filepath.substr (0, baseSize)!=mBaseDirectory ||
(filepath[baseSize]!='/' && filepath[baseSize]!='\\')) (filepath[baseSize]!='/' && filepath[baseSize]!='\\'))
@ -83,7 +82,7 @@ int CSMWorld::Resources::getIndex (const std::string& id) const
return index; return index;
} }
int CSMWorld::Resources::searchId (const std::string& id) const int CSMWorld::Resources::searchId(std::string_view id) const
{ {
std::string id2 = Misc::StringUtils::lowerCase (id); std::string id2 = Misc::StringUtils::lowerCase (id);

View File

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <map> #include <map>
#include <vector> #include <vector>
#include <string_view>
#include "universalid.hpp" #include "universalid.hpp"
@ -35,7 +36,7 @@ namespace CSMWorld
int getIndex (const std::string& id) const; int getIndex (const std::string& id) const;
int searchId (const std::string& id) const; int searchId(std::string_view id) const;
UniversalId::Type getType() const; UniversalId::Type getType() const;
}; };

View File

@ -17,7 +17,7 @@ void CSVDoc::LoadingDocument::closeEvent (QCloseEvent *event)
} }
CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document)
: mDocument (document), mAborted (false), mMessages (nullptr), mTotalRecords (0) : mDocument (document), mTotalRecordsLabel (0), mRecordsLabel (0), mAborted (false), mMessages (nullptr), mRecords(0)
{ {
setWindowTitle (QString::fromUtf8((std::string("Opening ") + document->getSavePath().filename().string()).c_str())); setWindowTitle (QString::fromUtf8((std::string("Opening ") + document->getSavePath().filename().string()).c_str()));
@ -25,26 +25,25 @@ CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document)
mLayout = new QVBoxLayout (this); mLayout = new QVBoxLayout (this);
// file progress // total progress
mFile = new QLabel (this); mTotalRecordsLabel = new QLabel (this);
mLayout->addWidget (mFile); mLayout->addWidget (mTotalRecordsLabel);
mFileProgress = new QProgressBar (this); mTotalProgress = new QProgressBar (this);
mLayout->addWidget (mFileProgress); mLayout->addWidget (mTotalProgress);
int size = static_cast<int> (document->getContentFiles().size())+1; mTotalProgress->setMinimum (0);
if (document->isNew()) mTotalProgress->setMaximum (document->getData().getTotalRecords(document->getContentFiles()));
--size; mTotalProgress->setTextVisible (true);
mTotalProgress->setValue (0);
mTotalRecords = 0;
mFileProgress->setMinimum (0); mFilesLoaded = 0;
mFileProgress->setMaximum (size);
mFileProgress->setTextVisible (true);
mFileProgress->setValue (0);
// record progress // record progress
mLayout->addWidget (mRecords = new QLabel ("Records", this)); mLayout->addWidget (mRecordsLabel = new QLabel ("Records", this));
mRecordProgress = new QProgressBar (this); mRecordProgress = new QProgressBar (this);
@ -74,29 +73,32 @@ CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document)
connect (mButtons, SIGNAL (rejected()), this, SLOT (cancel())); connect (mButtons, SIGNAL (rejected()), this, SLOT (cancel()));
} }
void CSVDoc::LoadingDocument::nextStage (const std::string& name, int totalRecords) void CSVDoc::LoadingDocument::nextStage (const std::string& name, int fileRecords)
{ {
mFile->setText (QString::fromUtf8 (("Loading: " + name).c_str())); ++mFilesLoaded;
size_t numFiles = mDocument->getContentFiles().size();
mFileProgress->setValue (mFileProgress->value()+1); mTotalRecordsLabel->setText (QString::fromUtf8 (("Loading: "+name
+" ("+std::to_string(mFilesLoaded)+" of "+std::to_string((numFiles))+")").c_str()));
mTotalRecords = mTotalProgress->value();
mRecordProgress->setValue (0); mRecordProgress->setValue (0);
mRecordProgress->setMaximum (totalRecords>0 ? totalRecords : 1); mRecordProgress->setMaximum (fileRecords>0 ? fileRecords : 1);
mTotalRecords = totalRecords; mRecords = fileRecords;
} }
void CSVDoc::LoadingDocument::nextRecord (int records) void CSVDoc::LoadingDocument::nextRecord (int records)
{ {
if (records<=mTotalRecords) if (records <= mRecords)
{ {
mRecordProgress->setValue (records); mTotalProgress->setValue (mTotalRecords+records);
std::ostringstream stream; mRecordProgress->setValue(records);
stream << "Records: " << records << " of " << mTotalRecords; mRecordsLabel->setText(QString::fromStdString(
"Records: "+std::to_string(records)+" of "+std::to_string(mRecords)));
mRecords->setText (QString::fromUtf8 (stream.str().c_str()));
} }
} }
@ -176,12 +178,12 @@ void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed,
} }
void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name,
int totalRecords) int fileRecords)
{ {
std::map<CSMDoc::Document *, LoadingDocument *>::iterator iter = mDocuments.find (document); std::map<CSMDoc::Document *, LoadingDocument *>::iterator iter = mDocuments.find (document);
if (iter!=mDocuments.end()) if (iter!=mDocuments.end())
iter->second->nextStage (name, totalRecords); iter->second->nextStage (name, fileRecords);
} }
void CSVDoc::Loader::nextRecord (CSMDoc::Document *document, int records) void CSVDoc::Loader::nextRecord (CSMDoc::Document *document, int records)

View File

@ -25,16 +25,18 @@ namespace CSVDoc
Q_OBJECT Q_OBJECT
CSMDoc::Document *mDocument; CSMDoc::Document *mDocument;
QLabel *mFile; QLabel *mTotalRecordsLabel;
QLabel *mRecords; QLabel *mRecordsLabel;
QProgressBar *mFileProgress; QProgressBar *mTotalProgress;
QProgressBar *mRecordProgress; QProgressBar *mRecordProgress;
bool mAborted; bool mAborted;
QDialogButtonBox *mButtons; QDialogButtonBox *mButtons;
QLabel *mError; QLabel *mError;
QListWidget *mMessages; QListWidget *mMessages;
QVBoxLayout *mLayout; QVBoxLayout *mLayout;
int mRecords;
int mTotalRecords; int mTotalRecords;
int mFilesLoaded;
private: private:

View File

@ -65,6 +65,8 @@ void CSVDoc::View::setupFileMenu()
QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save");
connect (save, SIGNAL (triggered()), this, SLOT (save())); connect (save, SIGNAL (triggered()), this, SLOT (save()));
mSave = save; mSave = save;
file->addSeparator();
QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify");
connect (verify, SIGNAL (triggered()), this, SLOT (verify())); connect (verify, SIGNAL (triggered()), this, SLOT (verify()));
@ -80,6 +82,8 @@ void CSVDoc::View::setupFileMenu()
QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata");
connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView()));
file->addSeparator();
QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close");
connect (close, SIGNAL (triggered()), this, SLOT (close())); connect (close, SIGNAL (triggered()), this, SLOT (close()));
@ -156,17 +160,16 @@ void CSVDoc::View::setupWorldMenu()
{ {
QMenu *world = menuBar()->addMenu (tr ("World")); QMenu *world = menuBar()->addMenu (tr ("World"));
QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions");
connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView()));
QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells");
connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView()));
QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables"); QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables");
connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView())); connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView()));
QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references"); QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references");
connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView())); connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView()));
world->addSeparator();
QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells");
connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView()));
QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands"); QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands");
connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView())); connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView()));
@ -177,7 +180,10 @@ void CSVDoc::View::setupWorldMenu()
QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid"); QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid");
connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView()));
world->addSeparator(); // items that don't represent single record lists follow here world->addSeparator();
QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions");
connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView()));
QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap"); QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap");
connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView()));
@ -187,14 +193,19 @@ void CSVDoc::View::setupMechanicsMenu()
{ {
QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics")); QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics"));
QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts");
connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView()));
QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts");
connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView()));
QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals"); QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals");
connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView()));
QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings"); QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings");
connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView()));
QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); mechanics->addSeparator();
connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView()));
QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells"); QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells");
connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView()));
@ -204,9 +215,6 @@ void CSVDoc::View::setupMechanicsMenu()
QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects"); QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects");
connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView())); connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView()));
QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts");
connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView()));
} }
void CSVDoc::View::setupCharacterMenu() void CSVDoc::View::setupCharacterMenu()
@ -227,21 +235,25 @@ void CSVDoc::View::setupCharacterMenu()
QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns"); QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns");
connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView()));
QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts");
connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView()));
characters->addSeparator();
QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics"); QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics");
connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView()));
QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos");
connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView()));
characters->addSeparator();
QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals");
connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView()));
QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos");
connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView()));
QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos");
connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView()));
QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts");
connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView()));
} }
void CSVDoc::View::setupAssetsMenu() void CSVDoc::View::setupAssetsMenu()

View File

@ -96,7 +96,7 @@ namespace CSVRender
for (int i = 0; i < ESM::PRT_Count; ++i) for (int i = 0; i < ESM::PRT_Count; ++i)
{ {
auto type = (ESM::PartReferenceType) i; auto type = (ESM::PartReferenceType) i;
std::string partId = mActorData->getPart(type); const std::string_view partId = mActorData->getPart(type);
attachBodyPart(type, getBodyPartMesh(partId)); attachBodyPart(type, getBodyPartMesh(partId));
} }
} }
@ -115,7 +115,7 @@ namespace CSVRender
} }
} }
std::string Actor::getBodyPartMesh(const std::string& bodyPartId) std::string Actor::getBodyPartMesh(std::string_view bodyPartId)
{ {
const auto& bodyParts = mData.getBodyParts(); const auto& bodyParts = mData.getBodyParts();

View File

@ -2,6 +2,7 @@
#define OPENCS_VIEW_RENDER_ACTOR_H #define OPENCS_VIEW_RENDER_ACTOR_H
#include <string> #include <string>
#include <string_view>
#include <osg/ref_ptr> #include <osg/ref_ptr>
@ -54,7 +55,7 @@ namespace CSVRender
void loadBodyParts(); void loadBodyParts();
void attachBodyPart(ESM::PartReferenceType, const std::string& mesh); void attachBodyPart(ESM::PartReferenceType, const std::string& mesh);
std::string getBodyPartMesh(const std::string& bodyPartId); std::string getBodyPartMesh(std::string_view bodyPartId);
static const std::string MeshPrefix; static const std::string MeshPrefix;

View File

@ -18,6 +18,8 @@ CSVRender::BrushDraw::BrushDraw(osg::ref_ptr<osg::Group> parentNode, bool textur
mBrushDrawNode = new osg::Group(); mBrushDrawNode = new osg::Group();
mGeometry = new osg::Geometry(); mGeometry = new osg::Geometry();
mBrushDrawNode->addChild(mGeometry); mBrushDrawNode->addChild(mGeometry);
mBrushDrawNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
mBrushDrawNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin");
mParentNode->addChild(mBrushDrawNode); mParentNode->addChild(mBrushDrawNode);
if (mTextureMode) if (mTextureMode)
mLandSizeFactor = static_cast<float>(ESM::Land::REAL_SIZE) / static_cast<float>(ESM::Land::LAND_TEXTURE_SIZE); mLandSizeFactor = static_cast<float>(ESM::Land::REAL_SIZE) / static_cast<float>(ESM::Land::LAND_TEXTURE_SIZE);
@ -122,7 +124,14 @@ void CSVRender::BrushDraw::buildSquareGeometry(const float& radius, const osg::V
const float brushOutlineHeight (1.0f); const float brushOutlineHeight (1.0f);
float diameter = radius * 2; float diameter = radius * 2;
int resolution = static_cast<int>(2.f * diameter / mLandSizeFactor); //half a vertex resolution int resolution = static_cast<int>(2.f * diameter / mLandSizeFactor); //half a vertex resolution
float resAdjustedLandSizeFactor = mLandSizeFactor / 2; float resAdjustedLandSizeFactor = mLandSizeFactor / 2; //128
if (resolution > 128) // limit accuracy for performance
{
resolution = 128;
resAdjustedLandSizeFactor = diameter / resolution;
}
osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f);
for (int i = 0; i < resolution; i++) for (int i = 0; i < resolution; i++)
@ -215,7 +224,8 @@ void CSVRender::BrushDraw::buildCircleGeometry(const float& radius, const osg::V
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry()); osg::ref_ptr<osg::Geometry> geom (new osg::Geometry());
osg::ref_ptr<osg::Vec3Array> vertices (new osg::Vec3Array()); osg::ref_ptr<osg::Vec3Array> vertices (new osg::Vec3Array());
osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array()); osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array());
const int amountOfPoints = (osg::PI * 2.0f) * radius / 20;
const int amountOfPoints = 128;
const float step ((osg::PI * 2.0f) / static_cast<float>(amountOfPoints)); const float step ((osg::PI * 2.0f) / static_cast<float>(amountOfPoints));
const float brushOutlineHeight (1.0f); const float brushOutlineHeight (1.0f);
osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f);

View File

@ -60,7 +60,7 @@ void CSVRender::CellArrow::adjustTransform()
{ {
// position // position
const int cellSize = Constants::CellSizeInUnits; const int cellSize = Constants::CellSizeInUnits;
const int offset = cellSize / 2 + 800; const int offset = cellSize / 2 + 600;
int x = mCoordinates.getX()*cellSize + cellSize/2; int x = mCoordinates.getX()*cellSize + cellSize/2;
int y = mCoordinates.getY()*cellSize + cellSize/2; int y = mCoordinates.getY()*cellSize + cellSize/2;
@ -92,9 +92,9 @@ void CSVRender::CellArrow::buildShape()
{ {
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry); osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
const int arrowWidth = 4000; const int arrowWidth = 2700;
const int arrowLength = 1500; const int arrowLength = 1350;
const int arrowHeight = 500; const int arrowHeight = 300;
osg::Vec3Array *vertices = new osg::Vec3Array; osg::Vec3Array *vertices = new osg::Vec3Array;
for (int i2=0; i2<2; ++i2) for (int i2=0; i2<2; ++i2)

View File

@ -1,19 +1,44 @@
#include "commands.hpp" #include "commands.hpp"
#include <QPointer>
#include <components/debug/debuglog.hpp>
#include <components/esm/loadland.hpp> #include <components/esm/loadland.hpp>
#include "editmode.hpp"
#include "terrainselection.hpp" #include "terrainselection.hpp"
#include "terrainshapemode.hpp"
#include "terraintexturemode.hpp"
#include "worldspacewidget.hpp"
CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(TerrainSelection& terrainSelection, QUndoCommand* parent) CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent)
: mTerrainSelection(terrainSelection) : mWorldspaceWidget(worldspaceWidget)
{ } { }
void CSVRender::DrawTerrainSelectionCommand::redo() void CSVRender::DrawTerrainSelectionCommand::redo()
{ {
mTerrainSelection.update(); tryUpdate();
} }
void CSVRender::DrawTerrainSelectionCommand::undo() void CSVRender::DrawTerrainSelectionCommand::undo()
{ {
mTerrainSelection.update(); tryUpdate();
}
void CSVRender::DrawTerrainSelectionCommand::tryUpdate()
{
if (!mWorldspaceWidget)
{
Log(Debug::Verbose) << "Can't update terrain selection, no WorldspaceWidget found!";
return;
}
auto terrainMode = dynamic_cast<CSVRender::TerrainShapeMode*>(mWorldspaceWidget->getEditMode());
if (!terrainMode)
{
Log(Debug::Verbose) << "Can't update terrain selection in current EditMode";
return;
}
terrainMode->getTerrainSelection()->update();
} }

View File

@ -1,8 +1,12 @@
#ifndef CSV_RENDER_COMMANDS_HPP #ifndef CSV_RENDER_COMMANDS_HPP
#define CSV_RENDER_COMMANDS_HPP #define CSV_RENDER_COMMANDS_HPP
#include <QPointer>
#include <QUndoCommand> #include <QUndoCommand>
#include "worldspacewidget.hpp"
namespace CSVRender namespace CSVRender
{ {
class TerrainSelection; class TerrainSelection;
@ -21,14 +25,17 @@ namespace CSVRender
*/ */
class DrawTerrainSelectionCommand : public QUndoCommand class DrawTerrainSelectionCommand : public QUndoCommand
{ {
private: private:
TerrainSelection& mTerrainSelection; QPointer<WorldspaceWidget> mWorldspaceWidget;
public: public:
DrawTerrainSelectionCommand(TerrainSelection& terrainSelection, QUndoCommand* parent = nullptr); DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent = nullptr);
void redo() override; void redo() override;
void undo() override; void undo() override;
void tryUpdate();
}; };
} }

View File

@ -297,6 +297,8 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos)
return false; return false;
} }
mObjectsAtDragStart.clear();
for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin()); for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin());
iter!=selection.end(); ++iter) iter!=selection.end(); ++iter)
{ {
@ -305,6 +307,12 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos)
if (mSubModeId == "move") if (mSubModeId == "move")
{ {
objectTag->mObject->setEdited (Object::Override_Position); objectTag->mObject->setEdited (Object::Override_Position);
float x = objectTag->mObject->getPosition().pos[0];
float y = objectTag->mObject->getPosition().pos[1];
float z = objectTag->mObject->getPosition().pos[2];
osg::Vec3f thisPoint(x, y, z);
mDragStart = getMousePlaneCoords(pos, getProjectionSpaceCoords(thisPoint));
mObjectsAtDragStart.emplace_back(thisPoint);
mDragMode = DragMode_Move; mDragMode = DragMode_Move;
} }
else if (mSubModeId == "rotate") else if (mSubModeId == "rotate")
@ -392,29 +400,7 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou
std::vector<osg::ref_ptr<TagBase> > selection = getWorldspaceWidget().getEdited (Mask_Reference); std::vector<osg::ref_ptr<TagBase> > selection = getWorldspaceWidget().getEdited (Mask_Reference);
if (mDragMode == DragMode_Move) if (mDragMode == DragMode_Move) {}
{
osg::Vec3f eye, centre, up;
getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up);
if (diffY)
{
offset += up * diffY * speedFactor;
}
if (diffX)
{
offset += ((centre-eye) ^ up) * diffX * speedFactor;
}
if (mDragAxis!=-1)
{
for (int i=0; i<3; ++i)
{
if (i!=mDragAxis)
offset[i] = 0;
}
}
}
else if (mDragMode == DragMode_Rotate) else if (mDragMode == DragMode_Rotate)
{ {
osg::Vec3f eye, centre, up; osg::Vec3f eye, centre, up;
@ -514,17 +500,32 @@ void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, dou
return; return;
} }
int i = 0;
// Apply // Apply
for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin()); iter!=selection.end(); ++iter, i++)
{ {
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (iter->get())) if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (iter->get()))
{ {
if (mDragMode == DragMode_Move) if (mDragMode == DragMode_Move)
{ {
ESM::Position position = objectTag->mObject->getPosition(); ESM::Position position = objectTag->mObject->getPosition();
for (int i=0; i<3; ++i) osg::Vec3f mousePos = getMousePlaneCoords(pos, getProjectionSpaceCoords(mDragStart));
float addToX = mousePos.x() - mDragStart.x();
float addToY = mousePos.y() - mDragStart.y();
float addToZ = mousePos.z() - mDragStart.z();
position.pos[0] = mObjectsAtDragStart[i].x() + addToX;
position.pos[1] = mObjectsAtDragStart[i].y() + addToY;
position.pos[2] = mObjectsAtDragStart[i].z() + addToZ;
// XYZ-locking
if (mDragAxis != -1)
{ {
position.pos[i] += offset[i]; for (int j = 0; j < 3; ++j)
{
if (j != mDragAxis)
position.pos[j] = mObjectsAtDragStart[i][j];
}
} }
objectTag->mObject->setPosition(position.pos); objectTag->mObject->setPosition(position.pos);
@ -608,6 +609,7 @@ void CSVRender::InstanceMode::dragCompleted(const QPoint& pos)
} }
} }
mObjectsAtDragStart.clear();
mDragMode = DragMode_None; mDragMode = DragMode_None;
} }
@ -634,8 +636,10 @@ void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor)
std::vector<osg::ref_ptr<TagBase> > selection = std::vector<osg::ref_ptr<TagBase> > selection =
getWorldspaceWidget().getEdited (Mask_Reference); getWorldspaceWidget().getEdited (Mask_Reference);
int j = 0;
for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin()); for (std::vector<osg::ref_ptr<TagBase> >::iterator iter (selection.begin());
iter!=selection.end(); ++iter) iter!=selection.end(); ++iter, j++)
{ {
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (iter->get())) if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (iter->get()))
{ {
@ -643,6 +647,9 @@ void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor)
for (int i=0; i<3; ++i) for (int i=0; i<3; ++i)
position.pos[i] += offset[i]; position.pos[i] += offset[i];
objectTag->mObject->setPosition (position.pos); objectTag->mObject->setPosition (position.pos);
osg::Vec3f thisPoint(position.pos[0], position.pos[1], position.pos[2]);
mDragStart = getMousePlaneCoords(getWorldspaceWidget().mapFromGlobal(QCursor::pos()), getProjectionSpaceCoords(thisPoint));
mObjectsAtDragStart[j] = thisPoint;
} }
} }
} }

View File

@ -45,6 +45,8 @@ namespace CSVRender
bool mLocked; bool mLocked;
float mUnitScaleDist; float mUnitScaleDist;
osg::ref_ptr<osg::Group> mParentNode; osg::ref_ptr<osg::Group> mParentNode;
osg::Vec3f mDragStart;
std::vector<osg::Vec3f> mObjectsAtDragStart;
int getSubModeFromId (const std::string& id) const; int getSubModeFromId (const std::string& id) const;

View File

@ -682,18 +682,20 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
int cellColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> ( int cellColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (
CSMWorld::Columns::ColumnId_Cell)); CSMWorld::Columns::ColumnId_Cell));
int refNumColumn = collection.findColumnIndex (static_cast<CSMWorld::Columns::ColumnId> ( int origCellColumn = collection.findColumnIndex(static_cast<CSMWorld::Columns::ColumnId> (
CSMWorld::Columns::ColumnId_RefNum)); CSMWorld::Columns::ColumnId_OriginalCell));
if (cellIndex != originalIndex) if (cellIndex != originalIndex)
{ {
/// \todo figure out worldspace (not important until multiple worldspaces are supported) /// \todo figure out worldspace (not important until multiple worldspaces are supported)
std::string origCellId = CSMWorld::CellCoordinates(originalIndex).getId("");
std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId ("");
commands.push (new CSMWorld::ModifyCommand (*model, commands.push (new CSMWorld::ModifyCommand (*model,
model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str()))); model->index (recordIndex, origCellColumn), QString::fromUtf8 (origCellId.c_str())));
commands.push (new CSMWorld::ModifyCommand( *model, commands.push(new CSMWorld::ModifyCommand(*model,
model->index (recordIndex, refNumColumn), 0)); model->index(recordIndex, cellColumn), QString::fromUtf8(cellId.c_str())));
// NOTE: refnum is not modified for moving a reference to another cell
} }
} }

View File

@ -287,7 +287,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges()
undoStack.beginMacro ("Edit shape and normal records"); undoStack.beginMacro ("Edit shape and normal records");
// One command at the beginning of the macro for redrawing the terrain-selection grid when undoing the changes. // One command at the beginning of the macro for redrawing the terrain-selection grid when undoing the changes.
undoStack.push(new DrawTerrainSelectionCommand(*mTerrainShapeSelection)); undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget()));
for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells)
{ {
@ -358,7 +358,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges()
pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId);
} }
// One command at the end of the macro for redrawing the terrain-selection grid when redoing the changes. // One command at the end of the macro for redrawing the terrain-selection grid when redoing the changes.
undoStack.push(new DrawTerrainSelectionCommand(*mTerrainShapeSelection)); undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget()));
undoStack.endMacro(); undoStack.endMacro();
clearTransientEdits(); clearTransientEdits();
@ -1049,7 +1049,7 @@ void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int glob
*/ */
if (xIsAtCellBorder && yIsAtCellBorder) if (xIsAtCellBorder && yIsAtCellBorder)
{ {
/* /*
Handle the NW, NE, and SE corner vertices. Handle the NW, NE, and SE corner vertices.
NW corner: (+1, -1) offset to reach current cell. NW corner: (+1, -1) offset to reach current cell.
NE corner: (-1, -1) offset to reach current cell. NE corner: (-1, -1) offset to reach current cell.
@ -1132,7 +1132,7 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair<int, int>&
selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString();
else else
selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString();
if (selectAction == "Select only") if (selectAction == "Select only")
mTerrainShapeSelection->onlySelect(selections); mTerrainShapeSelection->onlySelect(selections);
else if (selectAction == "Add to selection") else if (selectAction == "Add to selection")
@ -1444,6 +1444,11 @@ void CSVRender::TerrainShapeMode::mouseMoveEvent (QMouseEvent *event)
mBrushDraw->hide(); mBrushDraw->hide();
} }
std::shared_ptr<CSVRender::TerrainSelection> CSVRender::TerrainShapeMode::getTerrainSelection()
{
return mTerrainShapeSelection;
}
void CSVRender::TerrainShapeMode::setBrushSize(int brushSize) void CSVRender::TerrainShapeMode::setBrushSize(int brushSize)
{ {
mBrushSize = brushSize; mBrushSize = brushSize;

View File

@ -92,6 +92,8 @@ namespace CSVRender
void dragMoveEvent (QDragMoveEvent *event) override; void dragMoveEvent (QDragMoveEvent *event) override;
void mouseMoveEvent (QMouseEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override;
std::shared_ptr<TerrainSelection> getTerrainSelection();
private: private:
/// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse /// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse
@ -176,7 +178,7 @@ namespace CSVRender
int mDragMode = InteractionType_None; int mDragMode = InteractionType_None;
osg::Group* mParentNode; osg::Group* mParentNode;
bool mIsEditing = false; bool mIsEditing = false;
std::unique_ptr<TerrainSelection> mTerrainShapeSelection; std::shared_ptr<TerrainSelection> mTerrainShapeSelection;
int mTotalDiffY = 0; int mTotalDiffY = 0;
std::vector<CSMWorld::CellCoordinates> mAlteredCells; std::vector<CSMWorld::CellCoordinates> mAlteredCells;
osg::Vec3d mEditingPos; osg::Vec3d mEditingPos;

View File

@ -712,6 +712,11 @@ void CSVRender::TerrainTextureMode::mouseMoveEvent (QMouseEvent *event)
mBrushDraw->hide(); mBrushDraw->hide();
} }
std::shared_ptr<CSVRender::TerrainSelection> CSVRender::TerrainTextureMode::getTerrainSelection()
{
return mTerrainTextureSelection;
}
void CSVRender::TerrainTextureMode::setBrushSize(int brushSize) void CSVRender::TerrainTextureMode::setBrushSize(int brushSize)
{ {

View File

@ -85,6 +85,8 @@ namespace CSVRender
void mouseMoveEvent (QMouseEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override;
std::shared_ptr<TerrainSelection> getTerrainSelection();
private: private:
/// \brief Handle brush mechanics, maths regarding worldspace hit etc. /// \brief Handle brush mechanics, maths regarding worldspace hit etc.
void editTerrainTextureGrid (const WorldspaceHitResult& hit); void editTerrainTextureGrid (const WorldspaceHitResult& hit);
@ -115,7 +117,7 @@ namespace CSVRender
int mDragMode; int mDragMode;
osg::Group* mParentNode; osg::Group* mParentNode;
bool mIsEditing; bool mIsEditing;
std::unique_ptr<TerrainSelection> mTerrainTextureSelection; std::shared_ptr<TerrainSelection> mTerrainTextureSelection;
const int cellSize {ESM::Land::REAL_SIZE}; const int cellSize {ESM::Land::REAL_SIZE};
const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE};

View File

@ -452,6 +452,11 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo
return hit; return hit;
} }
CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode()
{
return dynamic_cast<CSVRender::EditMode *> (mEditMode->getCurrent());
}
void CSVRender::WorldspaceWidget::abortDrag() void CSVRender::WorldspaceWidget::abortDrag()
{ {
if (mDragging) if (mDragging)
@ -697,11 +702,6 @@ void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitRes
editMode.primaryOpenPressed (hit); editMode.primaryOpenPressed (hit);
} }
CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode()
{
return dynamic_cast<CSVRender::EditMode *> (mEditMode->getCurrent());
}
void CSVRender::WorldspaceWidget::primaryOpen(bool activate) void CSVRender::WorldspaceWidget::primaryOpen(bool activate)
{ {
handleInteraction(InteractionType_PrimaryOpen, activate); handleInteraction(InteractionType_PrimaryOpen, activate);

View File

@ -189,6 +189,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation to its true state. /// Erase all overrides and restore the visual representation to its true state.
virtual void reset (unsigned int elementMask) = 0; virtual void reset (unsigned int elementMask) = 0;
EditMode *getEditMode();
protected: protected:
/// Visual elements in a scene /// Visual elements in a scene
@ -215,8 +217,6 @@ namespace CSVRender
void settingChanged (const CSMPrefs::Setting *setting) override; void settingChanged (const CSMPrefs::Setting *setting) override;
EditMode *getEditMode();
bool getSpeedMode(); bool getSpeedMode();
private: private:

View File

@ -1,5 +1,5 @@
#ifndef CSV_TOOLS_REPORTTABLE_H #ifndef CSV_TOOLS_MERGE_H
#define CSV_TOOLS_REPORTTABLE_H #define CSV_TOOLS_MERGE_H
#include <QWidget> #include <QWidget>

View File

@ -36,7 +36,7 @@ void CSVTools::SearchSubView::replace (bool selection)
// in a single string. // in a single string.
for (std::vector<int>::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) for (std::vector<int>::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter)
{ {
CSMWorld::UniversalId id = model.getUniversalId (*iter); const CSMWorld::UniversalId& id = model.getUniversalId (*iter);
CSMWorld::UniversalId::Type type = CSMWorld::UniversalId::getParentType (id.getType()); CSMWorld::UniversalId::Type type = CSMWorld::UniversalId::getParentType (id.getType());

View File

@ -179,10 +179,10 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture)
undoStack.endMacro(); undoStack.endMacro();
} }
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) if (index != -1 && !landtexturesCollection.getRecord(rowInNew).isDeleted())
{ {
mBrushTextureLabel = "Selected texture: " + newBrushTextureId + " "; mBrushTextureLabel = "Selected texture: " + newBrushTextureId + " ";
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value<QString>()); mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(rowInNew, landTextureFilename).value<QString>());
} else } else
{ {
newBrushTextureId = ""; newBrushTextureId = "";

View File

@ -84,6 +84,13 @@ void CSVWorld::CellCreator::setType (int index)
mYLabel->setVisible (index==1); mYLabel->setVisible (index==1);
mY->setVisible (index==1); mY->setVisible (index==1);
// The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell)
std::string text = mType->currentText().toStdString();
if (text == "Interior Cell")
GenericCreator::setEditorMaxLength (64);
else
GenericCreator::setEditorMaxLength (32767);
update(); update();
} }

View File

@ -538,6 +538,9 @@ void CSVWorld::EditWidget::remake(int row)
mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred); mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred);
mainLayout->addStretch(1); mainLayout->addStretch(1);
int blockedColumn = mTable->searchColumnIndex(CSMWorld::Columns::ColumnId_Blocked);
bool isBlocked = mTable->data(mTable->index(row, blockedColumn)).toInt();
int unlocked = 0; int unlocked = 0;
int locked = 0; int locked = 0;
const int columns = mTable->columnCount(); const int columns = mTable->columnCount();
@ -583,6 +586,8 @@ void CSVWorld::EditWidget::remake(int row)
NestedTable* table = NestedTable* table =
new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows);
table->resizeColumnsToContents(); table->resizeColumnsToContents();
if (isBlocked)
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
int rows = mTable->rowCount(mTable->index(row, i)); int rows = mTable->rowCount(mTable->index(row, i));
int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0);
@ -617,7 +622,9 @@ void CSVWorld::EditWidget::remake(int row)
label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
if (! (mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable)) // HACK: the blocked checkbox needs to keep the same position
// FIXME: unfortunately blocked record displays a little differently to unblocked one
if (!(mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable) || i == blockedColumn)
{ {
lockedLayout->addWidget (label, locked, 0); lockedLayout->addWidget (label, locked, 0);
lockedLayout->addWidget (editor, locked, 1); lockedLayout->addWidget (editor, locked, 1);
@ -639,7 +646,7 @@ void CSVWorld::EditWidget::remake(int row)
createEditorContextMenu(editor, display, row); createEditorContextMenu(editor, display, row);
} }
} }
else else // Flag_Dialogue_List
{ {
CSMWorld::IdTree *tree = static_cast<CSMWorld::IdTree *>(mTable); CSMWorld::IdTree *tree = static_cast<CSMWorld::IdTree *>(mTable);
mNestedTableMapper = new QDataWidgetMapper (this); mNestedTableMapper = new QDataWidgetMapper (this);
@ -686,7 +693,10 @@ void CSVWorld::EditWidget::remake(int row)
label->setEnabled(false); label->setEnabled(false);
} }
createEditorContextMenu(editor, display, row); if (!isBlocked)
createEditorContextMenu(editor, display, row);
else
editor->setEnabled(false);
} }
} }
mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i)));

View File

@ -78,6 +78,8 @@ QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptio
for (std::vector<std::pair<int, QString> >::const_iterator iter (mValues.begin()); for (std::vector<std::pair<int, QString> >::const_iterator iter (mValues.begin());
iter!=mValues.end(); ++iter) iter!=mValues.end(); ++iter)
comboBox->addItem (iter->second); comboBox->addItem (iter->second);
comboBox->setMaxVisibleItems(20);
return comboBox; return comboBox;
} }

View File

@ -184,6 +184,11 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo
connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged()));
} }
void CSVWorld::GenericCreator::setEditorMaxLength (int length)
{
mId->setMaxLength (length);
}
void CSVWorld::GenericCreator::setEditLock (bool locked) void CSVWorld::GenericCreator::setEditLock (bool locked)
{ {
mLocked = locked; mLocked = locked;

View File

@ -84,6 +84,8 @@ namespace CSVWorld
std::string getNamespace() const; std::string getNamespace() const;
void setEditorMaxLength(int length);
private: private:
void updateNamespace(); void updateNamespace();

View File

@ -81,6 +81,23 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent,
CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager();
CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent);
editor->setCompleter(completionManager.getCompleter(display).get()); editor->setCompleter(completionManager.getCompleter(display).get());
// The savegame format limits the player faction string to 32 characters.
// The region sound name is limited to 32 characters. (ESM::Region::SoundRef::mSound)
// The script name is limited to 32 characters. (ESM::Script::SCHD::mName)
// The cell name is limited to 64 characters. (ESM::Header::GMDT::mCurrentCell)
if (display == CSMWorld::ColumnBase::Display_Faction ||
display == CSMWorld::ColumnBase::Display_Sound ||
display == CSMWorld::ColumnBase::Display_Script ||
display == CSMWorld::ColumnBase::Display_Referenceable)
{
editor->setMaxLength (32);
}
else if (display == CSMWorld::ColumnBase::Display_Cell)
{
editor->setMaxLength (64);
}
return editor; return editor;
} }

View File

@ -22,7 +22,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd
std::vector<CSMWorld::UniversalId::Type> types = CSMWorld::UniversalId::listReferenceableTypes(); std::vector<CSMWorld::UniversalId::Type> types = CSMWorld::UniversalId::listReferenceableTypes();
mType = new QComboBox (this); mType = new QComboBox (this);
mType->setMaxVisibleItems(20);
for (std::vector<CSMWorld::UniversalId::Type>::const_iterator iter (types.begin()); for (std::vector<CSMWorld::UniversalId::Type>::const_iterator iter (types.begin());
iter!=types.end(); ++iter) iter!=types.end(); ++iter)
{ {
@ -31,8 +32,12 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd
mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(), mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(),
static_cast<int> (id2.getType())); static_cast<int> (id2.getType()));
} }
mType->model()->sort(0);
insertBeforeButtons (mType, false); insertBeforeButtons (mType, false);
connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int)));
} }
void CSVWorld::ReferenceableCreator::reset() void CSVWorld::ReferenceableCreator::reset()
@ -41,6 +46,30 @@ void CSVWorld::ReferenceableCreator::reset()
GenericCreator::reset(); GenericCreator::reset();
} }
void CSVWorld::ReferenceableCreator::setType (int index)
{
// container items have name limit of 32 characters
std::string text = mType->currentText().toStdString();
if (text == "Potion" ||
text == "Apparatus" ||
text == "Armor" ||
text == "Book" ||
text == "Clothing" ||
text == "Ingredient" ||
text == "ItemLevelledList" ||
text == "Light" ||
text == "Lockpick" ||
text == "Miscellaneous" ||
text == "Probe" ||
text == "Repair" ||
text == "Weapon")
{
GenericCreator::setEditorMaxLength (32);
}
else
GenericCreator::setEditorMaxLength (32767);
}
void CSVWorld::ReferenceableCreator::cloneMode (const std::string& originId, void CSVWorld::ReferenceableCreator::cloneMode (const std::string& originId,
const CSMWorld::UniversalId::Type type) const CSMWorld::UniversalId::Type type)
{ {

View File

@ -29,6 +29,9 @@ namespace CSVWorld
void toggleWidgets(bool active = true) override; void toggleWidgets(bool active = true) override;
private slots:
void setType (int index);
}; };
} }

Some files were not shown because too many files have changed in this diff Show More