1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-04-30 23:11:23 +00:00

Merge branch 'unity-fixes' into appveyor

This commit is contained in:
Alexander "Ace" Olofsson 2015-09-24 15:22:51 +02:00
commit ead82742cd
531 changed files with 12136 additions and 4702 deletions

1
.gitignore vendored
View file

@ -41,7 +41,6 @@ resources
## generated objects ## generated objects
apps/openmw/config.hpp apps/openmw/config.hpp
components/version/version.hpp
docs/mainpage.hpp docs/mainpage.hpp
moc_*.cxx moc_*.cxx
*.cxx_parameters *.cxx_parameters

View file

@ -18,8 +18,8 @@ addons:
name: "OpenMW/openmw" name: "OpenMW/openmw"
description: "<Your project description here>" description: "<Your project description here>"
notification_email: scrawl@baseoftrash.de notification_email: scrawl@baseoftrash.de
build_command_prepend: "cmake ." build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE"
build_command: "make -j3" build_command: "make"
branch_pattern: coverity_scan branch_pattern: coverity_scan
matrix: matrix:
include: include:
@ -40,8 +40,8 @@ script:
- cd ./build - cd ./build
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
after_script:
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
notifications: notifications:
recipients: recipients:
- corrmage+travis-ci@gmail.com - corrmage+travis-ci@gmail.com

View file

@ -29,6 +29,7 @@ Programmers
Cory F. Cohen (cfcohen) Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam) Cris Mihalache (Mirceam)
darkf darkf
Dieho
Dmitry Shkurskiy (endorph) Dmitry Shkurskiy (endorph)
Douglas Diniz (Dgdiniz) Douglas Diniz (Dgdiniz)
Douglas Mencken (dougmencken) Douglas Mencken (dougmencken)
@ -52,6 +53,7 @@ Programmers
jeaye jeaye
Jeffrey Haines (Jyby) Jeffrey Haines (Jyby)
Jengerer Jengerer
Jiří Kuneš (kunesj)
Joel Graff (graffy) Joel Graff (graffy)
John Blomberg (fstp) John Blomberg (fstp)
Jordan Ayers Jordan Ayers
@ -59,6 +61,7 @@ Programmers
Julien Voisin (jvoisin/ap0) Julien Voisin (jvoisin/ap0)
Karl-Felix Glatzer (k1ll) Karl-Felix Glatzer (k1ll)
Kevin Poitra (PuppyKevin) Kevin Poitra (PuppyKevin)
Koncord
Lars Söderberg (Lazaroth) Lars Söderberg (Lazaroth)
lazydev lazydev
Leon Saunders (emoose) Leon Saunders (emoose)
@ -86,6 +89,7 @@ Programmers
Nolan Poe (nopoe) Nolan Poe (nopoe)
Paul McElroy (Greendogo) Paul McElroy (Greendogo)
Pieter van der Kloet (pvdk) Pieter van der Kloet (pvdk)
pkubik
Radu-Marius Popovici (rpopovici) Radu-Marius Popovici (rpopovici)
rdimesio rdimesio
riothamus riothamus
@ -109,6 +113,7 @@ Programmers
viadanna viadanna
Vincent Heuken Vincent Heuken
vocollapse vocollapse
zelurker
Manual Manual
------ ------

View file

@ -1,3 +1,8 @@
0.36.1
------
Bug #2590: Start scripts not added correctly
0.36.0 0.36.0
------ ------

View file

@ -10,9 +10,10 @@ fi
echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse"
echo "yes" | sudo apt-add-repository ppa:openmw/openmw echo "yes" | sudo apt-add-repository ppa:openmw/openmw
echo "yes" | sudo apt-add-repository ppa:boost-latest/ppa
sudo apt-get update -qq sudo apt-get update -qq
sudo apt-get install -qq libgtest-dev google-mock sudo apt-get install -qq libgtest-dev google-mock
sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev sudo apt-get install -qq libboost-filesystem1.55-dev libboost-program-options1.55-dev libboost-system1.55-dev libboost-thread1.55-dev
sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev
sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev
sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04 sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04

9
CI/check_tabs.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash
OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} apps components)
if [[ $OUTPUT ]] ; then
echo "Error: Tab characters found!"
echo $OUTPUT
exit 1
fi

View file

@ -20,7 +20,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 36) set(OPENMW_VERSION_MINOR 36)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 1)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")
set(OPENMW_VERSION_TAGHASH "") set(OPENMW_VERSION_TAGHASH "")
@ -56,6 +56,8 @@ configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_
option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE)
option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE)
option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE)
option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" FALSE)
option(QT_STATIC "Link static build of QT into the binaries" FALSE)
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
@ -70,6 +72,7 @@ option(BUILD_OPENCS "build OpenMW Construction Set" ON)
option(BUILD_WIZARD "build Installation Wizard" ON) option(BUILD_WIZARD "build Installation Wizard" ON)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_NIFTEST "build nif file tester" OFF)
option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON) option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON)
# OS X deployment # OS X deployment
@ -199,9 +202,67 @@ IF(BOOST_STATIC)
set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_LIBS ON)
endif() endif()
find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil osgFX) find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgQt osgUtil osgFX)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
if(OSG_STATIC)
macro(use_static_osg_plugin_library PLUGIN_NAME)
set(PLUGIN_NAME_DBG ${PLUGIN_NAME}d ${PLUGIN_NAME}D ${PLUGIN_NAME}_d ${PLUGIN_NAME}_D ${PLUGIN_NAME}_debug ${PLUGIN_NAME})
# For now, users wishing to do a static build will need to pass the path to where the plugins reside
# More clever logic would need to deduce the path, probably installed under <OpenSceneGraph>/lib/osgPlugins-<X.X.X>
find_library(${PLUGIN_NAME}_LIBRARY_REL NAMES ${PLUGIN_NAME} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH})
find_library(${PLUGIN_NAME}_LIBRARY_DBG NAMES ${PLUGIN_NAME_DBG} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH})
make_library_set(${PLUGIN_NAME}_LIBRARY)
if("${${PLUGIN_NAME}_LIBRARY}" STREQUAL "")
message(FATAL_ERROR "Unable to find static OpenSceneGraph plugin: ${PLUGIN_NAME}")
endif()
set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${PLUGIN_NAME}_LIBRARY})
endmacro()
macro(use_static_osg_plugin_dep DEPENDENCY)
find_package(${DEPENDENCY} REQUIRED)
set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${DEPENDENCY}_LIBRARIES})
endmacro()
add_definitions(-DOSG_LIBRARY_STATIC)
set(PLUGIN_LIST
osgdb_png # depends on libpng, zlib
osgdb_tga
osgdb_dds
osgdb_jpeg # depends on libjpeg
)
foreach(PLUGIN ${PLUGIN_LIST})
use_static_osg_plugin_library(${PLUGIN})
endforeach()
# OSG static plugins need to linked against their respective dependencies
set(PLUGIN_DEPS_LIST
PNG # needed by osgdb_png
ZLIB # needed by osgdb_png
JPEG # needed by osgdb_jpeg
)
foreach(DEPENDENCY ${PLUGIN_DEPS_LIST})
use_static_osg_plugin_dep(${DEPENDENCY})
endforeach()
endif()
if(QT_STATIC)
if(WIN32)
if(DESIRED_QT_VERSION MATCHES 4)
# QtCore needs WSAAsyncSelect from Ws2_32.lib
set(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} Ws2_32.lib)
message("QT_QTCORE_LIBRARY: ${QT_QTCORE_LIBRARY}")
endif()
endif()
endif()
find_package(MyGUI REQUIRED) find_package(MyGUI REQUIRED)
if (${MYGUI_VERSION} VERSION_LESS "3.2.1") if (${MYGUI_VERSION} VERSION_LESS "3.2.1")
message(FATAL_ERROR "OpenMW requires MyGUI 3.2.1 or later, please install the latest version from http://mygui.info") message(FATAL_ERROR "OpenMW requires MyGUI 3.2.1 or later, please install the latest version from http://mygui.info")
@ -228,7 +289,7 @@ if(MYGUI_STATIC)
endif (MYGUI_STATIC) endif (MYGUI_STATIC)
if (APPLE) if (APPLE)
configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw-Info.plist.in
"${APP_BUNDLE_DIR}/Contents/Info.plist") "${APP_BUNDLE_DIR}/Contents/Info.plist")
configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw.icns configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw.icns
@ -245,6 +306,11 @@ add_subdirectory(files/)
if (APPLE) if (APPLE)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS")
if (OPENMW_OSX_DEPLOYMENT)
SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
endif()
else (APPLE) else (APPLE)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}")
@ -273,6 +339,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt
if (NOT WIN32 AND NOT APPLE) if (NOT WIN32 AND NOT APPLE)
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop
"${OpenMW_BINARY_DIR}/openmw.desktop") "${OpenMW_BINARY_DIR}/openmw.desktop")
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml
"${OpenMW_BINARY_DIR}/openmw.appdata.xml")
configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop
"${OpenMW_BINARY_DIR}/openmw-cs.desktop") "${OpenMW_BINARY_DIR}/openmw-cs.desktop")
endif() endif()
@ -336,6 +404,9 @@ IF(NOT WIN32 AND NOT APPLE)
IF(BUILD_ESMTOOL) IF(BUILD_ESMTOOL)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESMTOOL) ENDIF(BUILD_ESMTOOL)
IF(BUILD_NIFTEST)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" )
ENDIF(BUILD_NIFTEST)
IF(BUILD_MWINIIMPORTER) IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" ) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_MWINIIMPORTER) ENDIF(BUILD_MWINIIMPORTER)
@ -358,6 +429,7 @@ IF(NOT WIN32 AND NOT APPLE)
# Install icon and desktop file # Install icon and desktop file
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/appdata" COMPONENT "openmw")
IF(BUILD_OPENCS) IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs")
@ -366,6 +438,7 @@ IF(NOT WIN32 AND NOT APPLE)
# Install global configuration files # Install global configuration files
INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
IF(BUILD_OPENCS) IF(BUILD_OPENCS)
@ -519,6 +592,10 @@ if (BUILD_WIZARD)
add_subdirectory(apps/wizard) add_subdirectory(apps/wizard)
endif() endif()
if (BUILD_NIFTEST)
add_subdirectory(apps/niftest)
endif(BUILD_NIFTEST)
# UnitTests # UnitTests
if (BUILD_UNITTESTS) if (BUILD_UNITTESTS)
add_subdirectory( apps/openmw_test_suite ) add_subdirectory( apps/openmw_test_suite )
@ -666,13 +743,65 @@ if (APPLE)
install(CODE " install(CODE "
set(BU_CHMOD_BUNDLE_ITEMS ON) set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH})
include(BundleUtilitiesWithRPath)
" COMPONENT Runtime) " COMPONENT Runtime)
#For now, search unresolved dependencies only in default system paths, so if you put unresolveable (i.e. with @executable_path in id name) lib or framework somewhere else, it would fail set(ABSOLUTE_PLUGINS "")
set(DIRS "") set(USED_OSG_PLUGINS
osgdb_tga
osgdb_dds
osgdb_imageio
)
include(CPack) foreach (PLUGIN_NAME ${USED_OSG_PLUGINS})
set(PLUGIN_ABS "${OSG_PLUGIN_LIB_SEARCH_PATH}/${PLUGIN_NAME}.so")
set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS})
endforeach ()
get_filename_component(PLUGIN_PREFIX_DIR "${OSG_PLUGIN_LIB_SEARCH_PATH}" NAME)
# installs used plugins in bundle at given path (bundle_path must be relative to ${CMAKE_INSTALL_PREFIX})
# and returns list of install paths for all installed plugins
function (install_plugins_for_bundle bundle_path plugins_var)
set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/PlugIns/${PLUGIN_PREFIX_DIR}")
set(PLUGINS "")
set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${RELATIVE_PLUGIN_INSTALL_BASE}")
foreach (PLUGIN ${ABSOLUTE_PLUGINS})
get_filename_component(PLUGIN_RELATIVE ${PLUGIN} NAME)
get_filename_component(PLUGIN_RELATIVE_WE ${PLUGIN} NAME_WE)
set(PLUGIN_DYLIB_IN_BUNDLE "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}")
set(PLUGINS ${PLUGINS} "${PLUGIN_DYLIB_IN_BUNDLE}")
install(CODE "
copy_resolved_item_into_bundle(\"${PLUGIN}\" \"${PLUGIN_DYLIB_IN_BUNDLE}\")
" COMPONENT Runtime)
endforeach ()
set(${plugins_var} ${PLUGINS} PARENT_SCOPE)
endfunction (install_plugins_for_bundle)
install_plugins_for_bundle("${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}" PLUGINS)
install_plugins_for_bundle("${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS)
set(DIRS "${CMAKE_PREFIX_PATH}/lib")
install(CODE "
function(gp_item_default_embedded_path_override item default_embedded_path_var)
if (\${item} MATCHES ${PLUGIN_PREFIX_DIR})
set(path \"@executable_path/../PlugIns/${PLUGIN_PREFIX_DIR}\")
set(\${default_embedded_path_var} \"\${path}\" PARENT_SCOPE)
endif()
endfunction()
cmake_policy(SET CMP0009 OLD)
fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\")
fixup_bundle(\"${OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"${DIRS}\")
" COMPONENT Runtime)
include(CPack)
endif (APPLE) endif (APPLE)
# Doxygen Target -- simply run 'make doc' or 'make doc_pages' # Doxygen Target -- simply run 'make doc' or 'make doc_pages'

16
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,16 @@
Description
===========
Your pull request description should include (if applicable):
* A link back to the bug report or forum discussion that prompted the change
* Summary of the changes made
* Reasoning / motivation behind the change
* What testing you have carried out to verify the change
Other notes
===========
* Separate your work into multiple pull requests whenever possible. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time.
* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title.
* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards).

View file

@ -3,10 +3,11 @@ OpenMW
[![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740)
OpenMW is an attempt at recreating the engine for the popular role-playing game OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
* Version: 0.36.0 OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set.
* Version: 0.36.1
* License: GPL (see docs/license/GPL3.txt for more information) * License: GPL (see docs/license/GPL3.txt for more information)
* Website: http://www.openmw.org * Website: http://www.openmw.org
* IRC: #openmw on irc.freenode.net * IRC: #openmw on irc.freenode.net
@ -14,6 +15,13 @@ Morrowind by Bethesda Softworks. You need to own and install the original game f
Font Licenses: Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) * DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information)
Current Status
--------------
The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://bugs.openmw.org/versions/21) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces.
Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page.
Getting Started Getting Started
--------------- ---------------

View file

@ -493,14 +493,14 @@ void Record<ESM::Book>::print()
std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl;
if (mPrintPlain) if (mPrintPlain)
{ {
std::cout << " Text:" << std::endl; std::cout << " Text:" << std::endl;
std::cout << "START--------------------------------------" << std::endl; std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mText << std::endl; std::cout << mData.mText << std::endl;
std::cout << "END----------------------------------------" << std::endl; std::cout << "END----------------------------------------" << std::endl;
} }
else else
{ {
std::cout << " Text: [skipped]" << std::endl; std::cout << " Text: [skipped]" << std::endl;
} }
} }
@ -799,14 +799,14 @@ void Record<ESM::DialInfo>::print()
{ {
if (mPrintPlain) if (mPrintPlain)
{ {
std::cout << " Result Script:" << std::endl; std::cout << " Result Script:" << std::endl;
std::cout << "START--------------------------------------" << std::endl; std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mResultScript << std::endl; std::cout << mData.mResultScript << std::endl;
std::cout << "END----------------------------------------" << std::endl; std::cout << "END----------------------------------------" << std::endl;
} }
else else
{ {
std::cout << " Result Script: [skipped]" << std::endl; std::cout << " Result Script: [skipped]" << std::endl;
} }
} }
} }
@ -841,19 +841,13 @@ void Record<ESM::Land>::print()
std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl;
std::cout << " DataTypes: " << mData.mDataTypes << std::endl; std::cout << " DataTypes: " << mData.mDataTypes << std::endl;
// Seems like this should done with reference counting in the if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes))
// loader to me. But I'm not really knowledgable about this
// record type yet. --Cory
bool wasLoaded = (mData.mDataLoaded != 0);
if (mData.mDataTypes) mData.loadData(mData.mDataTypes);
if (mData.mDataLoaded)
{ {
std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl; std::cout << " Height Offset: " << data->mHeightOffset << std::endl;
// Lots of missing members. // Lots of missing members.
std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl; std::cout << " Unknown1: " << data->mUnk1 << std::endl;
std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl; std::cout << " Unknown2: " << data->mUnk2 << std::endl;
} }
if (!wasLoaded) mData.unloadData();
} }
template<> template<>
@ -1207,14 +1201,14 @@ void Record<ESM::Script>::print()
if (mPrintPlain) if (mPrintPlain)
{ {
std::cout << " Script:" << std::endl; std::cout << " Script:" << std::endl;
std::cout << "START--------------------------------------" << std::endl; std::cout << "START--------------------------------------" << std::endl;
std::cout << mData.mScriptText << std::endl; std::cout << mData.mScriptText << std::endl;
std::cout << "END----------------------------------------" << std::endl; std::cout << "END----------------------------------------" << std::endl;
} }
else else
{ {
std::cout << " Script: [skipped]" << std::endl; std::cout << " Script: [skipped]" << std::endl;
} }
} }

View file

@ -53,7 +53,7 @@ namespace EsmTool
} }
void setPrintPlain(bool plain) { void setPrintPlain(bool plain) {
mPrintPlain = plain; mPrintPlain = plain;
} }
virtual void load(ESM::ESMReader &esm) = 0; virtual void load(ESM::ESMReader &esm) = 0;

View file

@ -143,7 +143,7 @@ namespace ESSImport
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream); osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream);
if (!result.success()) if (!result.success())
{ {
std::cerr << "can't write global map image: " << result.message() << std::endl; std::cerr << "can't write global map image: " << result.message() << " code " << result.status() << std::endl;
return; return;
} }

View file

@ -507,60 +507,40 @@ class ConvertGAME : public Converter
public: public:
ConvertGAME() : mHasGame(false) {} ConvertGAME() : mHasGame(false) {}
std::string toString(int weatherId)
{
switch (weatherId)
{
case 0:
return "clear";
case 1:
return "cloudy";
case 2:
return "foggy";
case 3:
return "overcast";
case 4:
return "rain";
case 5:
return "thunderstorm";
case 6:
return "ashstorm";
case 7:
return "blight";
case 8:
return "snow";
case 9:
return "blizzard";
case -1:
return "";
default:
{
std::stringstream error;
error << "unknown weather id: " << weatherId;
throw std::runtime_error(error.str());
}
}
}
virtual void read(ESM::ESMReader &esm) virtual void read(ESM::ESMReader &esm)
{ {
mGame.load(esm); mGame.load(esm);
mHasGame = true; mHasGame = true;
} }
int validateWeatherID(int weatherID)
{
if(weatherID >= -1 && weatherID < 10)
{
return weatherID;
}
else
{
std::stringstream error;
error << "Invalid weather ID:" << weatherID << std::endl;
throw std::runtime_error(error.str());
}
}
virtual void write(ESM::ESMWriter &esm) virtual void write(ESM::ESMWriter &esm)
{ {
if (!mHasGame) if (!mHasGame)
return; return;
esm.startRecord(ESM::REC_WTHR); esm.startRecord(ESM::REC_WTHR);
ESM::WeatherState weather; ESM::WeatherState weather;
weather.mCurrentWeather = toString(mGame.mGMDT.mCurrentWeather); weather.mTimePassed = 0.0f;
weather.mNextWeather = toString(mGame.mGMDT.mNextWeather); weather.mFastForward = false;
weather.mRemainingTransitionTime = mGame.mGMDT.mWeatherTransition/100.f*(0.015f*24*3600); weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour;
weather.mHour = mContext->mHour; weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f);
weather.mWindSpeed = 0.f; weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather);
weather.mTimePassed = 0.0; weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather);
weather.mFirstUpdate = false; weather.mQueuedWeather = -1;
// TODO: Determine how ModRegion modifiers are saved in Morrowind.
weather.save(esm); weather.save(esm);
esm.endRecord(ESM::REC_WTHR); esm.endRecord(ESM::REC_WTHR);
} }

View file

@ -10,7 +10,21 @@ namespace ESSImport
void ActorData::load(ESM::ESMReader &esm) void ActorData::load(ESM::ESMReader &esm)
{ {
if (esm.isNextSub("ACTN")) if (esm.isNextSub("ACTN"))
{
/*
Activation flags:
ActivationFlag_UseEnabled = 1
ActivationFlag_OnActivate = 2
ActivationFlag_OnDeath = 10h
ActivationFlag_OnKnockout = 20h
ActivationFlag_OnMurder = 40h
ActivationFlag_DoorOpening = 100h
ActivationFlag_DoorClosing = 200h
ActivationFlag_DoorJammedOpening = 400h
ActivationFlag_DoorJammedClosing = 800h
*/
esm.skipHSub(); esm.skipHSub();
}
if (esm.isNextSub("STPR")) if (esm.isNextSub("STPR"))
esm.skipHSub(); esm.skipHSub();

View file

@ -166,7 +166,9 @@ namespace ESSImport
if (i >= file2.mRecords.size()) if (i >= file2.mRecords.size())
{ {
std::ios::fmtflags f(std::cout.flags());
std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl; std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl;
std::cout.flags(f);
return; return;
} }
@ -174,7 +176,9 @@ namespace ESSImport
if (rec.mName != rec2.mName) if (rec.mName != rec2.mName)
{ {
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl; std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl;
std::cout.flags(f);
return; // TODO: try to recover return; // TODO: try to recover
} }
@ -185,7 +189,9 @@ namespace ESSImport
if (j >= rec2.mSubrecords.size()) if (j >= rec2.mSubrecords.size())
{ {
std::ios::fmtflags f(std::cout.flags());
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl; std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl;
std::cout.flags(f);
return; return;
} }
@ -193,8 +199,10 @@ namespace ESSImport
if (sub.mName != sub2.mName) if (sub.mName != sub2.mName)
{ {
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl; << " (2) 0x" << sub2.mFileOffset << std::endl;
std::cout.flags(f);
break; // TODO: try to recover break; // TODO: try to recover
} }
@ -203,6 +211,8 @@ namespace ESSImport
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end()) if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
continue; continue;
std::ios::fmtflags f(std::cout.flags());
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
<< " (2) 0x" << sub2.mFileOffset << std::endl; << " (2) 0x" << sub2.mFileOffset << std::endl;
@ -235,6 +245,7 @@ namespace ESSImport
std::cout << "\033[0m"; std::cout << "\033[0m";
} }
std::cout << std::endl; std::cout << std::endl;
std::cout.flags(f);
} }
} }
} }
@ -319,7 +330,11 @@ namespace ESSImport
else else
{ {
if (unknownRecords.insert(n.val).second) if (unknownRecords.insert(n.val).second)
{
std::ios::fmtflags f(std::cerr.flags());
std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl;
std::cerr.flags(f);
}
esm.skipRecord(); esm.skipRecord();
} }

View file

@ -49,6 +49,10 @@ namespace ESSImport
std::map<std::string, ESM::NPC> mNpcs; std::map<std::string, ESM::NPC> mNpcs;
Context() Context()
: mDay(0)
, mMonth(0)
, mYear(0)
, mHour(0.f)
{ {
mPlayer.mAutoMove = 0; mPlayer.mAutoMove = 0;
ESM::CellId playerCellId; ESM::CellId playerCellId;

View file

@ -57,26 +57,6 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
// Remove what's this? button // Remove what's this? button
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
// Add version information to bottom of the window
QString revision(OPENMW_VERSION_COMMITHASH);
QString tag(OPENMW_VERSION_TAGHASH);
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
if (!revision.isEmpty() && !tag.isEmpty())
{
if (revision == tag) {
versionLabel->setText(tr("OpenMW %1 release").arg(OPENMW_VERSION));
} else {
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
}
// Add the compile date and time
versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
}
createIcons(); createIcons();
} }
@ -186,11 +166,34 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure; return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure;
} }
void Launcher::MainDialog::setVersionLabel()
{
// Add version information to bottom of the window
Version::Version v = Version::getOpenmwVersion(mGameSettings.value("resources").toUtf8().constData());
QString revision(QString::fromUtf8(v.mCommitHash.c_str()));
QString tag(QString::fromUtf8(v.mTagHash.c_str()));
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
if (!v.mVersion.empty() && (revision.isEmpty() || revision == tag))
versionLabel->setText(tr("OpenMW %1 release").arg(QString::fromUtf8(v.mVersion.c_str())));
else
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
// Add the compile date and time
versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
}
bool Launcher::MainDialog::setup() bool Launcher::MainDialog::setup()
{ {
if (!setupGameSettings()) if (!setupGameSettings())
return false; return false;
setVersionLabel();
mLauncherSettings.setContentList(mGameSettings); mLauncherSettings.setContentList(mGameSettings);
if (!setupGraphicsSettings()) if (!setupGraphicsSettings())
@ -309,11 +312,11 @@ bool Launcher::MainDialog::setupGameSettings()
mGameSettings.readUserFile(stream); mGameSettings.readUserFile(stream);
} }
// Now the rest // Now the rest - priority: user > local > global
QStringList paths; QStringList paths;
paths.append(userPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
paths.append(globalPath + QString("openmw.cfg")); paths.append(globalPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
paths.append(userPath + QString("openmw.cfg"));
foreach (const QString &path, paths) { foreach (const QString &path, paths) {
qDebug() << "Loading config file:" << qPrintable(path); qDebug() << "Loading config file:" << qPrintable(path);

View file

@ -72,6 +72,8 @@ namespace Launcher
bool setupGameSettings(); bool setupGameSettings();
bool setupGraphicsSettings(); bool setupGraphicsSettings();
void setVersionLabel();
void loadSettings(); void loadSettings();
void saveSettings(); void saveSettings();

View file

@ -0,0 +1,19 @@
set(NIFTEST
niftest.cpp
)
source_group(components\\nif\\tests FILES ${NIFTEST})
# Main executable
add_executable(niftest
${NIFTEST}
)
target_link_libraries(niftest
${Boost_FILESYSTEM_LIBRARY}
components
)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(niftest gcov)
endif()

165
apps/niftest/niftest.cpp Normal file
View file

@ -0,0 +1,165 @@
///Program to test .nif files both on the FileSystem and in BSA archives.
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <components/nif/niffile.hpp>
#include <components/files/constrainedfilestream.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/bsaarchive.hpp>
#include <components/vfs/filesystemarchive.hpp>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
// Create local aliases for brevity
namespace bpo = boost::program_options;
namespace bfs = boost::filesystem;
///See if the file has the named extension
bool hasExtension(std::string filename, std::string extensionToFind)
{
std::string extension = filename.substr(filename.find_last_of(".")+1);
//Convert strings to lower case for comparison
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower);
if(extension == extensionToFind)
return true;
else
return false;
}
///See if the file has the "nif" extension.
bool isNIF(std::string filename)
{
return hasExtension(filename,"nif");
}
///See if the file has the "bsa" extension.
bool isBSA(std::string filename)
{
return hasExtension(filename,"bsa");
}
/// Check all the nif files in a given VFS::Archive
/// \note Takes ownership!
/// \note Can not read a bsa file inside of a bsa file.
void readVFS(VFS::Archive* anArchive,std::string archivePath = "")
{
VFS::Manager myManager(true);
myManager.addArchive(anArchive);
myManager.buildIndex();
std::map<std::string, VFS::File*> files=myManager.getIndex();
for(std::map<std::string, VFS::File*>::const_iterator it=files.begin(); it!=files.end(); ++it)
{
std::string name = it->first;
try{
if(isNIF(name))
{
// std::cout << "Decoding: " << name << std::endl;
Nif::NIFFile temp_nif(myManager.get(name),archivePath+name);
}
else if(isBSA(name))
{
if(!archivePath.empty() && !isBSA(archivePath))
{
// std::cout << "Reading BSA File: " << name << std::endl;
readVFS(new VFS::BsaArchive(archivePath+name),archivePath+name+"/");
// std::cout << "Done with BSA File: " << name << std::endl;
}
}
}
catch (std::exception& e)
{
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
}
}
}
std::vector<std::string> parseOptions (int argc, char** argv)
{
bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n"
"Usages:\n"
" niftool <nif files, BSA files, or directories>\n"
" Scan the file or directories for nif errors.\n\n"
"Allowed options");
desc.add_options()
("help,h", "print help message.")
("input-file", bpo::value< std::vector<std::string> >(), "input file")
;
//Default option if none provided
bpo::positional_options_description p;
p.add("input-file", -1);
bpo::variables_map variables;
try
{
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).
options(desc).positional(p).run();
bpo::store(valid_opts, variables);
}
catch(std::exception &e)
{
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n"
<< desc << std::endl;
exit(1);
}
bpo::notify(variables);
if (variables.count ("help"))
{
std::cout << desc << std::endl;
exit(1);
}
if (variables.count("input-file"))
{
return variables["input-file"].as< std::vector<std::string> >();
}
std::cout << "No input files or directories specified!" << std::endl;
std::cout << desc << std::endl;
exit(1);
}
int main(int argc, char **argv)
{
std::vector<std::string> files = parseOptions (argc, argv);
// std::cout << "Reading Files" << std::endl;
for(std::vector<std::string>::const_iterator it=files.begin(); it!=files.end(); ++it)
{
std::string name = *it;
try{
if(isNIF(name))
{
//std::cout << "Decoding: " << name << std::endl;
Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name.c_str()),name);
}
else if(isBSA(name))
{
// std::cout << "Reading BSA File: " << name << std::endl;
readVFS(new VFS::BsaArchive(name));
}
else if(bfs::is_directory(bfs::path(name)))
{
// std::cout << "Reading All Files in: " << name << std::endl;
readVFS(new VFS::FileSystemArchive(name),name);
}
else
{
std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl;
}
}
catch (std::exception& e)
{
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
}
}
return 0;
}

View file

@ -23,7 +23,7 @@ opencs_units (model/world
opencs_units_noqt (model/world opencs_units_noqt (model/world
universalid record commands columnbase 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 idcompletionmanager metadata
@ -35,13 +35,18 @@ opencs_hdrs_noqt (model/world
opencs_units (model/tools opencs_units (model/tools
tools reportmodel tools reportmodel mergeoperation
) )
opencs_units_noqt (model/tools opencs_units_noqt (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 startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
mergestages
)
opencs_hdrs_noqt (model/tools
mergestate
) )
@ -64,7 +69,7 @@ opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview cellcreator referenceablecreator referencecreator scenesubview
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
dialoguespinbox recordbuttonbar tableeditidaction dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator
) )
opencs_units_noqt (view/world opencs_units_noqt (view/world
@ -94,7 +99,7 @@ opencs_hdrs_noqt (view/render
opencs_units (view/tools opencs_units (view/tools
reportsubview reporttable searchsubview searchbox reportsubview reporttable searchsubview searchbox merge
) )
opencs_units_noqt (view/tools opencs_units_noqt (view/tools
@ -192,6 +197,7 @@ if(APPLE)
MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs"
MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION}
MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION}
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs-Info.plist.in"
) )
set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES

View file

@ -1,4 +1,3 @@
#include "editor.hpp" #include "editor.hpp"
#include <QApplication> #include <QApplication>
@ -21,7 +20,8 @@
CS::Editor::Editor () CS::Editor::Editor ()
: mUserSettings (mCfgMgr), mDocumentManager (mCfgMgr), : mUserSettings (mCfgMgr), mDocumentManager (mCfgMgr),
mViewManager (mDocumentManager), mPid(""), mViewManager (mDocumentManager), mPid(""),
mLock(), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) mLock(), mMerge (mDocumentManager),
mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL)
{ {
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig(); std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig();
@ -40,9 +40,12 @@ CS::Editor::Editor ()
mNewGame.setLocalData (mLocal); mNewGame.setLocalData (mLocal);
mFileDialog.setLocalData (mLocal); mFileDialog.setLocalData (mLocal);
mMerge.setLocalData (mLocal);
connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)), connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)),
this, SLOT (documentAdded (CSMDoc::Document *))); this, SLOT (documentAdded (CSMDoc::Document *)));
connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)),
this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *)));
connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()), connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()),
this, SLOT (lastDocumentDeleted())); this, SLOT (lastDocumentDeleted()));
@ -50,6 +53,7 @@ CS::Editor::Editor ()
connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ()));
connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ()));
connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ())); connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ()));
connect (&mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *)));
connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ())); connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ()));
connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ())); connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ()));
@ -360,7 +364,21 @@ void CS::Editor::documentAdded (CSMDoc::Document *document)
mViewManager.addView (document); mViewManager.addView (document);
} }
void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document)
{
if (mMerge.getDocument()==document)
mMerge.cancel();
}
void CS::Editor::lastDocumentDeleted() void CS::Editor::lastDocumentDeleted()
{ {
QApplication::quit(); QApplication::quit();
} }
void CS::Editor::mergeDocument (CSMDoc::Document *document)
{
mMerge.configure (document);
mMerge.show();
mMerge.raise();
mMerge.activateWindow();
}

View file

@ -27,11 +27,18 @@
#include "view/settings/dialog.hpp" #include "view/settings/dialog.hpp"
#include "view/tools/merge.hpp"
namespace VFS namespace VFS
{ {
class Manager; class Manager;
} }
namespace CSMDoc
{
class Document;
}
namespace CS namespace CS
{ {
class Editor : public QObject class Editor : public QObject
@ -55,6 +62,7 @@ namespace CS
boost::interprocess::file_lock mLock; boost::interprocess::file_lock mLock;
boost::filesystem::ofstream mPidFile; boost::filesystem::ofstream mPidFile;
bool mFsStrict; bool mFsStrict;
CSVTools::Merge mMerge;
void setupDataFiles (const Files::PathContainer& dataDirs); void setupDataFiles (const Files::PathContainer& dataDirs);
@ -94,8 +102,12 @@ namespace CS
void documentAdded (CSMDoc::Document *document); void documentAdded (CSMDoc::Document *document);
void documentAboutToBeRemoved (CSMDoc::Document *document);
void lastDocumentDeleted(); void lastDocumentDeleted();
void mergeDocument (CSMDoc::Document *document);
private: private:
QString mIpcServerName; QString mIpcServerName;

View file

@ -1,4 +1,3 @@
#include "editor.hpp" #include "editor.hpp"
#include <exception> #include <exception>

View file

@ -1,4 +1,3 @@
#include "blacklist.hpp" #include "blacklist.hpp"
#include <algorithm> #include <algorithm>

View file

@ -798,9 +798,9 @@ void CSMDoc::Document::addGmsts()
"sBookSkillMessage", "sBookSkillMessage",
"sBounty", "sBounty",
"sBreath", "sBreath",
"sBribe", "sBribe 10 Gold",
"sBribe", "sBribe 100 Gold",
"sBribe", "sBribe 1000 Gold",
"sBribeFail", "sBribeFail",
"sBribeSuccess", "sBribeSuccess",
"sBuy", "sBuy",
@ -2250,13 +2250,13 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM
ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager,
const std::vector<std::string>& blacklistedScripts) const std::vector<std::string>& blacklistedScripts)
: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), : mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager),
mTools (*this), mTools (*this, encoding),
mProjectPath ((configuration.getUserDataPath() / "projects") / mProjectPath ((configuration.getUserDataPath() / "projects") /
(savePath.filename().string() + ".project")), (savePath.filename().string() + ".project")),
mSavingOperation (*this, mProjectPath, encoding), mSavingOperation (*this, mProjectPath, encoding),
mSaving (&mSavingOperation), mSaving (&mSavingOperation),
mResDir(resDir), mResDir(resDir),
mRunner (mProjectPath), mIdCompletionManager(mData) mRunner (mProjectPath), mDirty (false), mIdCompletionManager(mData)
{ {
if (mContentFiles.empty()) if (mContentFiles.empty())
throw std::runtime_error ("Empty content file sequence"); throw std::runtime_error ("Empty content file sequence");
@ -2294,6 +2294,8 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM
connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool)));
connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)),
this, SIGNAL (mergeDone (CSMDoc::Document*)));
connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool)));
@ -2323,7 +2325,7 @@ int CSMDoc::Document::getState() const
{ {
int state = 0; int state = 0;
if (!mUndoStack.isClean()) if (!mUndoStack.isClean() || mDirty)
state |= State_Modified; state |= State_Modified;
if (mSaving.isRunning()) if (mSaving.isRunning())
@ -2388,6 +2390,12 @@ void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const C
emit stateChanged (getState(), this); emit stateChanged (getState(), this);
} }
void CSMDoc::Document::runMerge (std::auto_ptr<CSMDoc::Document> target)
{
mTools.runMerge (target);
emit stateChanged (getState(), this);
}
void CSMDoc::Document::abortOperation (int type) void CSMDoc::Document::abortOperation (int type)
{ {
if (type==State_Saving) if (type==State_Saving)
@ -2409,6 +2417,9 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type)
void CSMDoc::Document::operationDone (int type, bool failed) void CSMDoc::Document::operationDone (int type, bool failed)
{ {
if (type==CSMDoc::State_Saving && !failed)
mDirty = false;
emit stateChanged (getState(), this); emit stateChanged (getState(), this);
} }
@ -2485,3 +2496,8 @@ CSMWorld::IdCompletionManager &CSMDoc::Document::getIdCompletionManager()
{ {
return mIdCompletionManager; return mIdCompletionManager;
} }
void CSMDoc::Document::flagAsDirty()
{
mDirty = true;
}

View file

@ -68,6 +68,7 @@ namespace CSMDoc
boost::filesystem::path mResDir; boost::filesystem::path mResDir;
Blacklist mBlacklist; Blacklist mBlacklist;
Runner mRunner; Runner mRunner;
bool mDirty;
CSMWorld::IdCompletionManager mIdCompletionManager; CSMWorld::IdCompletionManager mIdCompletionManager;
@ -130,6 +131,8 @@ namespace CSMDoc
void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search);
void runMerge (std::auto_ptr<CSMDoc::Document> target);
void abortOperation (int type); void abortOperation (int type);
const CSMWorld::Data& getData() const; const CSMWorld::Data& getData() const;
@ -150,12 +153,18 @@ namespace CSMDoc
CSMWorld::IdCompletionManager &getIdCompletionManager(); CSMWorld::IdCompletionManager &getIdCompletionManager();
void flagAsDirty();
signals: signals:
void stateChanged (int state, CSMDoc::Document *document); void stateChanged (int state, CSMDoc::Document *document);
void progress (int current, int max, int type, int threads, CSMDoc::Document *document); void progress (int current, int max, int type, int threads, CSMDoc::Document *document);
/// \attention When this signal is emitted, *this hands over the ownership of the
/// document. This signal must be handled to avoid a leak.
void mergeDone (CSMDoc::Document *document);
private slots: private slots:
void modificationStateChanged (bool clean); void modificationStateChanged (bool clean);
@ -173,4 +182,3 @@ namespace CSMDoc
} }
#endif #endif

View file

@ -1,4 +1,3 @@
#include "documentmanager.hpp" #include "documentmanager.hpp"
#include <algorithm> #include <algorithm>
@ -57,10 +56,24 @@ bool CSMDoc::DocumentManager::isEmpty()
void CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files, const boost::filesystem::path& savePath, void CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files, const boost::filesystem::path& savePath,
bool new_) bool new_)
{ {
Document *document = new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); Document *document = makeDocument (files, savePath, new_);
insertDocument (document);
}
CSMDoc::Document *CSMDoc::DocumentManager::makeDocument (
const std::vector< boost::filesystem::path >& files,
const boost::filesystem::path& savePath, bool new_)
{
return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts);
}
void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document)
{
mDocuments.push_back (document); mDocuments.push_back (document);
connect (document, SIGNAL (mergeDone (CSMDoc::Document*)),
this, SLOT (insertDocument (CSMDoc::Document*)));
emit loadRequest (document); emit loadRequest (document);
mLoader.hasThingsToDo().wakeAll(); mLoader.hasThingsToDo().wakeAll();
@ -73,6 +86,8 @@ void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document)
if (iter==mDocuments.end()) if (iter==mDocuments.end())
throw std::runtime_error ("removing invalid document"); throw std::runtime_error ("removing invalid document");
emit documentAboutToBeRemoved (document);
mDocuments.erase (iter); mDocuments.erase (iter);
document->deleteLater(); document->deleteLater();

View file

@ -56,6 +56,15 @@ namespace CSMDoc
///< \param new_ Do not load the last content file in \a files and instead create in an ///< \param new_ Do not load the last content file in \a files and instead create in an
/// appropriate way. /// appropriate way.
/// Create a new document. The ownership of the created document is transferred to
/// the calling function. The DocumentManager does not manage it. Loading has not
/// taken place at the point when the document is returned.
///
/// \param new_ Do not load the last content file in \a files and instead create in an
/// appropriate way.
Document *makeDocument (const std::vector< boost::filesystem::path >& files,
const boost::filesystem::path& savePath, bool new_);
void setResourceDir (const boost::filesystem::path& parResDir); void setResourceDir (const boost::filesystem::path& parResDir);
void setEncoding (ToUTF8::FromType encoding); void setEncoding (ToUTF8::FromType encoding);
@ -84,10 +93,16 @@ namespace CSMDoc
void removeDocument (CSMDoc::Document *document); void removeDocument (CSMDoc::Document *document);
///< Emits the lastDocumentDeleted signal, if applicable. ///< Emits the lastDocumentDeleted signal, if applicable.
/// Hand over document to *this. The ownership is transferred. The DocumentManager
/// will initiate the load procedure, if necessary
void insertDocument (CSMDoc::Document *document);
signals: signals:
void documentAdded (CSMDoc::Document *document); void documentAdded (CSMDoc::Document *document);
void documentAboutToBeRemoved (CSMDoc::Document *document);
void loadRequest (CSMDoc::Document *document); void loadRequest (CSMDoc::Document *document);
void lastDocumentDeleted(); void lastDocumentDeleted();

View file

@ -1,4 +1,3 @@
#include "loader.hpp" #include "loader.hpp"
#include <QTimer> #include <QTimer>

View file

@ -1,4 +1,3 @@
#include "messages.hpp" #include "messages.hpp"
CSMDoc::Message::Message() {} CSMDoc::Message::Message() {}
@ -8,6 +7,20 @@ CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& me
: mId (id), mMessage (message), mHint (hint), mSeverity (severity) : mId (id), mMessage (message), mHint (hint), mSeverity (severity)
{} {}
std::string CSMDoc::Message::toString (Severity severity)
{
switch (severity)
{
case CSMDoc::Message::Severity_Info: return "Information";
case CSMDoc::Message::Severity_Warning: return "Warning";
case CSMDoc::Message::Severity_Error: return "Error";
case CSMDoc::Message::Severity_SeriousError: return "Serious Error";
case CSMDoc::Message::Severity_Default: break;
}
return "";
}
CSMDoc::Messages::Messages (Message::Severity default_) CSMDoc::Messages::Messages (Message::Severity default_)
: mDefault (default_) : mDefault (default_)

View file

@ -31,6 +31,8 @@ namespace CSMDoc
Message (const CSMWorld::UniversalId& id, const std::string& message, Message (const CSMWorld::UniversalId& id, const std::string& message,
const std::string& hint, Severity severity); const std::string& hint, Severity severity);
static std::string toString (Severity severity);
}; };
class Messages class Messages

View file

@ -1,4 +1,3 @@
#include "operation.hpp" #include "operation.hpp"
#include <string> #include <string>

View file

@ -83,7 +83,9 @@ namespace CSMDoc
void executeStage(); void executeStage();
void operationDone(); protected slots:
virtual void operationDone();
}; };
} }

View file

@ -1,4 +1,3 @@
#include "operationholder.hpp" #include "operationholder.hpp"
#include "../settings/usersettings.hpp" #include "../settings/usersettings.hpp"

View file

@ -1,4 +1,3 @@
#include "runner.hpp" #include "runner.hpp"
#include <QApplication> #include <QApplication>

View file

@ -1,4 +1,3 @@
#include "saving.hpp" #include "saving.hpp"
#include "../world/data.hpp" #include "../world/data.hpp"
@ -81,22 +80,25 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::StartScript> > appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::StartScript> >
(mDocument.getData().getStartScripts(), mState)); (mDocument.getData().getStartScripts(), mState));
appendStage (new WriteDialogueCollectionStage (mDocument, mState, false));
appendStage (new WriteDialogueCollectionStage (mDocument, mState, true));
appendStage (new WriteRefIdCollectionStage (mDocument, mState)); appendStage (new WriteRefIdCollectionStage (mDocument, mState));
appendStage (new CollectionReferencesStage (mDocument, mState)); appendStage (new CollectionReferencesStage (mDocument, mState));
appendStage (new WriteCellCollectionStage (mDocument, mState)); appendStage (new WriteCellCollectionStage (mDocument, mState));
// Dialogue can reference objects and cells so must be written after these records for vanilla-compatible files
appendStage (new WriteDialogueCollectionStage (mDocument, mState, false));
appendStage (new WriteDialogueCollectionStage (mDocument, mState, true));
appendStage (new WritePathgridCollectionStage (mDocument, mState)); appendStage (new WritePathgridCollectionStage (mDocument, mState));
appendStage (new WriteLandCollectionStage (mDocument, mState));
appendStage (new WriteLandTextureCollectionStage (mDocument, mState)); appendStage (new WriteLandTextureCollectionStage (mDocument, mState));
// references Land Textures
appendStage (new WriteLandCollectionStage (mDocument, mState));
// close file and clean up // close file and clean up
appendStage (new CloseSaveStage (mState)); appendStage (new CloseSaveStage (mState));

View file

@ -1,4 +1,3 @@
#include "savingstages.hpp" #include "savingstages.hpp"
#include <fstream> #include <fstream>
@ -416,15 +415,16 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages)
if (land.mState==CSMWorld::RecordBase::State_Modified || if (land.mState==CSMWorld::RecordBase::State_Modified ||
land.mState==CSMWorld::RecordBase::State_ModifiedOnly) land.mState==CSMWorld::RecordBase::State_ModifiedOnly)
{ {
CSMWorld::Land record = land.get(); const CSMWorld::Land& record = land.get();
mState.getWriter().startRecord (record.mLand->sRecordId); mState.getWriter().startRecord (record.sRecordId);
record.mLand->save (mState.getWriter()); record.save (mState.getWriter());
if(record.mLand->mLandData)
record.mLand->mLandData->save (mState.getWriter());
mState.getWriter().endRecord (record.mLand->sRecordId); if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes))
data->save (mState.getWriter());
mState.getWriter().endRecord (record.sRecordId);
} }
else if (land.mState==CSMWorld::RecordBase::State_Deleted) else if (land.mState==CSMWorld::RecordBase::State_Deleted)
{ {
@ -455,6 +455,8 @@ void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& mess
mState.getWriter().startRecord (record.sRecordId); mState.getWriter().startRecord (record.sRecordId);
mState.getWriter().writeHNString("NAME", record.mId);
record.save (mState.getWriter()); record.save (mState.getWriter());
mState.getWriter().endRecord (record.sRecordId); mState.getWriter().endRecord (record.sRecordId);

View file

@ -1,4 +1,3 @@
#include "savingstate.hpp" #include "savingstate.hpp"
#include "operation.hpp" #include "operation.hpp"

View file

@ -1,4 +1,3 @@
#include "stage.hpp" #include "stage.hpp"
CSMDoc::Stage::~Stage() {} CSMDoc::Stage::~Stage() {}

View file

@ -12,7 +12,7 @@ namespace CSMDoc
State_Saving = 16, State_Saving = 16,
State_Verifying = 32, State_Verifying = 32,
State_Compiling = 64, // not implemented yet State_Merging = 64,
State_Searching = 128, State_Searching = 128,
State_Loading = 256 // pseudo-state; can not be encountered in a loaded document State_Loading = 256 // pseudo-state; can not be encountered in a loaded document
}; };

View file

@ -1,4 +1,3 @@
#include "andnode.hpp" #include "andnode.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "booleannode.hpp" #include "booleannode.hpp"
CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {} CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {}

View file

@ -1,4 +1,3 @@
#include "leafnode.hpp" #include "leafnode.hpp"
std::vector<int> CSMFilter::LeafNode::getReferencedColumns() const std::vector<int> CSMFilter::LeafNode::getReferencedColumns() const

View file

@ -1,4 +1,3 @@
#include "narynode.hpp" #include "narynode.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "node.hpp" #include "node.hpp"
CSMFilter::Node::Node() {} CSMFilter::Node::Node() {}

View file

@ -1,4 +1,3 @@
#include "notnode.hpp" #include "notnode.hpp"
CSMFilter::NotNode::NotNode (boost::shared_ptr<Node> child) : UnaryNode (child, "not") {} CSMFilter::NotNode::NotNode (boost::shared_ptr<Node> child) : UnaryNode (child, "not") {}

View file

@ -1,4 +1,3 @@
#include "ornode.hpp" #include "ornode.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "parser.hpp" #include "parser.hpp"
#include <cctype> #include <cctype>

View file

@ -1,4 +1,3 @@
#include "textnode.hpp" #include "textnode.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "unarynode.hpp" #include "unarynode.hpp"
CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr<Node> child, const std::string& name) CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr<Node> child, const std::string& name)

View file

@ -1,4 +1,3 @@
#include "valuenode.hpp" #include "valuenode.hpp"
#include <sstream> #include <sstream>

View file

@ -162,7 +162,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
ritd->setDeclaredValues (values); ritd->setDeclaredValues (values);
} }
declareSection ("table-input", "Table Input"); declareSection ("table-input", "ID Tables");
{ {
QString inPlaceEdit ("Edit in Place"); QString inPlaceEdit ("Edit in Place");
QString editRecord ("Edit Record"); QString editRecord ("Edit Record");
@ -215,9 +215,24 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
"Jump to the added or cloned record."); "Jump to the added or cloned record.");
jumpToAdded->setDefaultValue (defaultValue); jumpToAdded->setDefaultValue (defaultValue);
jumpToAdded->setDeclaredValues (jumpValues); jumpToAdded->setDeclaredValues (jumpValues);
Setting *extendedConfig = createSetting (Type_CheckBox, "extended-config",
"Manually specify affected record types for an extended delete/revert");
extendedConfig->setDefaultValue("false");
extendedConfig->setToolTip("Delete and revert commands have an extended form that also affects "
"associated records.\n\n"
"If this option is enabled, types of affected records are selected "
"manually before a command execution.\nOtherwise, all associated "
"records are deleted/reverted immediately.");
} }
declareSection ("report-input", "Report Input"); declareSection ("dialogues", "ID Dialogues");
{
Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar");
toolbar->setDefaultValue ("true");
}
declareSection ("report-input", "Reports");
{ {
QString none ("None"); QString none ("None");
QString edit ("Edit"); QString edit ("Edit");
@ -310,6 +325,15 @@ void CSMSettings::UserSettings::buildSettingModelDefaults()
"<li>Strict: Promote warning to an error</li>" "<li>Strict: Promote warning to an error</li>"
"</ul>"); "</ul>");
Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar");
toolbar->setDefaultValue ("true");
Setting *delay = createSetting (Type_SpinBox, "compile-delay",
"Delay between updating of source errors");
delay->setDefaultValue (100);
delay->setRange (0, 10000);
delay->setToolTip ("Delay in milliseconds");
Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int");
formatInt->setDefaultValues (QStringList() << "Dark magenta"); formatInt->setDefaultValues (QStringList() << "Dark magenta");
formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip);

View file

@ -1,4 +1,3 @@
#include "birthsigncheck.hpp" #include "birthsigncheck.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "classcheck.hpp" #include "classcheck.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "factioncheck.hpp" #include "factioncheck.hpp"
#include <sstream> #include <sstream>

View file

@ -0,0 +1,133 @@
#include "magiceffectcheck.hpp"
#include <components/misc/resourcehelpers.hpp>
#include "../world/resources.hpp"
#include "../world/data.hpp"
namespace
{
void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string text)
{
if (!text.empty())
{
messages.push_back(std::make_pair(id, text));
}
}
}
bool CSMTools::MagicEffectCheckStage::isTextureExists(const std::string &texture, bool isIcon) const
{
const CSMWorld::Resources &textures = isIcon ? mIcons : mTextures;
bool exists = false;
if (textures.searchId(texture) != -1)
{
exists = true;
}
else
{
std::string ddsTexture = texture;
if (Misc::ResourceHelpers::changeExtensionToDds(ddsTexture) && textures.searchId(ddsTexture) != -1)
{
exists = true;
}
}
return exists;
}
std::string CSMTools::MagicEffectCheckStage::checkReferenceable(const std::string &id,
const CSMWorld::UniversalId &type,
const std::string &column) const
{
std::string error;
if (!id.empty())
{
CSMWorld::RefIdData::LocalIndex index = mReferenceables.getDataSet().searchId(id);
if (index.first == -1)
{
error = "No such " + column + " '" + id + "'";
}
else if (index.second != type.getType())
{
error = column + " is not of type " + type.getTypeName();
}
}
return error;
}
std::string CSMTools::MagicEffectCheckStage::checkSound(const std::string &id, const std::string &column) const
{
std::string error;
if (!id.empty() && mSounds.searchId(id) == -1)
{
error = "No such " + column + " '" + id + "'";
}
return error;
}
CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection<ESM::MagicEffect> &effects,
const CSMWorld::IdCollection<ESM::Sound> &sounds,
const CSMWorld::RefIdCollection &referenceables,
const CSMWorld::Resources &icons,
const CSMWorld::Resources &textures)
: mMagicEffects(effects),
mSounds(sounds),
mReferenceables(referenceables),
mIcons(icons),
mTextures(textures)
{}
int CSMTools::MagicEffectCheckStage::setup()
{
return mMagicEffects.getSize();
}
void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages)
{
ESM::MagicEffect effect = mMagicEffects.getRecord(stage).get();
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId);
if (effect.mData.mBaseCost < 0.0f)
{
messages.push_back(std::make_pair(id, "Base Cost is negative"));
}
if (effect.mIcon.empty())
{
messages.push_back(std::make_pair(id, "Icon is not specified"));
}
else if (!isTextureExists(effect.mIcon, true))
{
messages.push_back(std::make_pair(id, "No such Icon '" + effect.mIcon + "'"));
}
if (!effect.mParticle.empty() && !isTextureExists(effect.mParticle, false))
{
messages.push_back(std::make_pair(id, "No such Particle '" + effect.mParticle + "'"));
}
addMessageIfNotEmpty(messages,
id,
checkReferenceable(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting Object"));
addMessageIfNotEmpty(messages,
id,
checkReferenceable(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit Object"));
addMessageIfNotEmpty(messages,
id,
checkReferenceable(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area Object"));
addMessageIfNotEmpty(messages,
id,
checkReferenceable(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt Object"));
addMessageIfNotEmpty(messages, id, checkSound(effect.mCastSound, "Casting Sound"));
addMessageIfNotEmpty(messages, id, checkSound(effect.mHitSound, "Hit Sound"));
addMessageIfNotEmpty(messages, id, checkSound(effect.mAreaSound, "Area Sound"));
addMessageIfNotEmpty(messages, id, checkSound(effect.mBoltSound, "Bolt Sound"));
if (effect.mDescription.empty())
{
messages.push_back(std::make_pair(id, "Description is empty"));
}
}

View file

@ -0,0 +1,50 @@
#ifndef CSM_TOOLS_MAGICEFFECTCHECK_HPP
#define CSM_TOOLS_MAGICEFFECTCHECK_HPP
#include <components/esm/loadmgef.hpp>
#include <components/esm/loadsoun.hpp>
#include "../world/idcollection.hpp"
#include "../world/refidcollection.hpp"
#include "../doc/stage.hpp"
namespace CSMWorld
{
class Resources;
}
namespace CSMTools
{
/// \brief VerifyStage: make sure that magic effect records are internally consistent
class MagicEffectCheckStage : public CSMDoc::Stage
{
const CSMWorld::IdCollection<ESM::MagicEffect> &mMagicEffects;
const CSMWorld::IdCollection<ESM::Sound> &mSounds;
const CSMWorld::RefIdCollection &mReferenceables;
const CSMWorld::Resources &mIcons;
const CSMWorld::Resources &mTextures;
private:
bool isTextureExists(const std::string &texture, bool isIcon) const;
std::string checkReferenceable(const std::string &id,
const CSMWorld::UniversalId &type,
const std::string &column) const;
std::string checkSound(const std::string &id, const std::string &column) const;
public:
MagicEffectCheckStage(const CSMWorld::IdCollection<ESM::MagicEffect> &effects,
const CSMWorld::IdCollection<ESM::Sound> &sounds,
const CSMWorld::RefIdCollection &referenceables,
const CSMWorld::Resources &icons,
const CSMWorld::Resources &textures);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages &messages);
///< Messages resulting from this tage will be appended to \a messages.
};
}
#endif

View file

@ -1,4 +1,3 @@
#include "mandatoryid.hpp" #include "mandatoryid.hpp"
#include "../world/collectionbase.hpp" #include "../world/collectionbase.hpp"

View file

@ -0,0 +1,59 @@
#include "mergeoperation.hpp"
#include "../doc/state.hpp"
#include "../doc/document.hpp"
#include "mergestages.hpp"
CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding)
: CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document)
{
appendStage (new StartMergeStage (mState));
appendStage (new MergeIdCollectionStage<ESM::Global> (mState, &CSMWorld::Data::getGlobals));
appendStage (new MergeIdCollectionStage<ESM::GameSetting> (mState, &CSMWorld::Data::getGmsts));
appendStage (new MergeIdCollectionStage<ESM::Skill> (mState, &CSMWorld::Data::getSkills));
appendStage (new MergeIdCollectionStage<ESM::Class> (mState, &CSMWorld::Data::getClasses));
appendStage (new MergeIdCollectionStage<ESM::Faction> (mState, &CSMWorld::Data::getFactions));
appendStage (new MergeIdCollectionStage<ESM::Race> (mState, &CSMWorld::Data::getRaces));
appendStage (new MergeIdCollectionStage<ESM::Sound> (mState, &CSMWorld::Data::getSounds));
appendStage (new MergeIdCollectionStage<ESM::Script> (mState, &CSMWorld::Data::getScripts));
appendStage (new MergeIdCollectionStage<ESM::Region> (mState, &CSMWorld::Data::getRegions));
appendStage (new MergeIdCollectionStage<ESM::BirthSign> (mState, &CSMWorld::Data::getBirthsigns));
appendStage (new MergeIdCollectionStage<ESM::Spell> (mState, &CSMWorld::Data::getSpells));
appendStage (new MergeIdCollectionStage<ESM::Dialogue> (mState, &CSMWorld::Data::getTopics));
appendStage (new MergeIdCollectionStage<ESM::Dialogue> (mState, &CSMWorld::Data::getJournals));
appendStage (new MergeIdCollectionStage<CSMWorld::Cell> (mState, &CSMWorld::Data::getCells));
appendStage (new MergeIdCollectionStage<ESM::Filter> (mState, &CSMWorld::Data::getFilters));
appendStage (new MergeIdCollectionStage<ESM::Enchantment> (mState, &CSMWorld::Data::getEnchantments));
appendStage (new MergeIdCollectionStage<ESM::BodyPart> (mState, &CSMWorld::Data::getBodyParts));
appendStage (new MergeIdCollectionStage<ESM::DebugProfile> (mState, &CSMWorld::Data::getDebugProfiles));
appendStage (new MergeIdCollectionStage<ESM::SoundGenerator> (mState, &CSMWorld::Data::getSoundGens));
appendStage (new MergeIdCollectionStage<ESM::MagicEffect> (mState, &CSMWorld::Data::getMagicEffects));
appendStage (new MergeIdCollectionStage<ESM::StartScript> (mState, &CSMWorld::Data::getStartScripts));
appendStage (new MergeIdCollectionStage<CSMWorld::Pathgrid, CSMWorld::SubCellCollection<CSMWorld::Pathgrid> > (mState, &CSMWorld::Data::getPathgrids));
appendStage (new MergeIdCollectionStage<CSMWorld::Info, CSMWorld::InfoCollection> (mState, &CSMWorld::Data::getTopicInfos));
appendStage (new MergeIdCollectionStage<CSMWorld::Info, CSMWorld::InfoCollection> (mState, &CSMWorld::Data::getJournalInfos));
appendStage (new MergeRefIdsStage (mState));
appendStage (new MergeReferencesStage (mState));
appendStage (new MergeReferencesStage (mState));
appendStage (new ListLandTexturesMergeStage (mState));
appendStage (new MergeLandTexturesStage (mState));
appendStage (new MergeLandStage (mState));
appendStage (new FinishMergedDocumentStage (mState, encoding));
}
void CSMTools::MergeOperation::setTarget (std::auto_ptr<CSMDoc::Document> document)
{
mState.mTarget = document;
}
void CSMTools::MergeOperation::operationDone()
{
CSMDoc::Operation::operationDone();
if (mState.mCompleted)
emit mergeDone (mState.mTarget.release());
}

View file

@ -0,0 +1,45 @@
#ifndef CSM_TOOLS_MERGEOPERATION_H
#define CSM_TOOLS_MERGEOPERATION_H
#include <memory>
#include <components/to_utf8/to_utf8.hpp>
#include "../doc/operation.hpp"
#include "mergestate.hpp"
namespace CSMDoc
{
class Document;
}
namespace CSMTools
{
class MergeOperation : public CSMDoc::Operation
{
Q_OBJECT
MergeState mState;
public:
MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding);
/// \attention Do not call this function while a merge is running.
void setTarget (std::auto_ptr<CSMDoc::Document> document);
protected slots:
virtual void operationDone();
signals:
/// \attention When this signal is emitted, *this hands over the ownership of the
/// document. This signal must be handled to avoid a leak.
void mergeDone (CSMDoc::Document *document);
};
}
#endif

View file

@ -0,0 +1,258 @@
#include "mergestages.hpp"
#include <sstream>
#include <components/misc/stringops.hpp>
#include "mergestate.hpp"
#include "../doc/document.hpp"
#include "../world/data.hpp"
CSMTools::StartMergeStage::StartMergeStage (MergeState& state)
: mState (state)
{}
int CSMTools::StartMergeStage::setup()
{
return 1;
}
void CSMTools::StartMergeStage::perform (int stage, CSMDoc::Messages& messages)
{
mState.mCompleted = false;
mState.mTextureIndices.clear();
}
CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding)
: mState (state), mEncoder (encoding)
{}
int CSMTools::FinishMergedDocumentStage::setup()
{
return 1;
}
void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages)
{
// We know that the content file list contains at least two entries and that the first one
// does exist on disc (otherwise it would have been impossible to initiate a merge on that
// document).
boost::filesystem::path path = mState.mSource.getContentFiles()[0];
ESM::ESMReader reader;
reader.setEncoder (&mEncoder);
reader.open (path.string());
CSMWorld::MetaData source;
source.mId = "sys::meta";
source.load (reader);
CSMWorld::MetaData target = mState.mTarget->getData().getMetaData();
target.mAuthor = source.mAuthor;
target.mDescription = source.mDescription;
mState.mTarget->getData().setMetaData (target);
mState.mCompleted = true;
}
CSMTools::MergeRefIdsStage::MergeRefIdsStage (MergeState& state) : mState (state) {}
int CSMTools::MergeRefIdsStage::setup()
{
return mState.mSource.getData().getReferenceables().getSize();
}
void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages)
{
mState.mSource.getData().getReferenceables().copyTo (
stage, mState.mTarget->getData().getReferenceables());
}
CSMTools::MergeReferencesStage::MergeReferencesStage (MergeState& state)
: mState (state)
{}
int CSMTools::MergeReferencesStage::setup()
{
mIndex.clear();
return mState.mSource.getData().getReferences().getSize();
}
void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::CellRef>& record =
mState.mSource.getData().getReferences().getRecord (stage);
if (!record.isDeleted())
{
CSMWorld::CellRef ref = record.get();
ref.mOriginalCell = ref.mCell;
ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++;
ref.mRefNum.mContentFile = 0;
CSMWorld::Record<CSMWorld::CellRef> newRecord (
CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref);
mState.mTarget->getData().getReferences().appendRecord (newRecord);
}
}
CSMTools::ListLandTexturesMergeStage::ListLandTexturesMergeStage (MergeState& state)
: mState (state)
{}
int CSMTools::ListLandTexturesMergeStage::setup()
{
return mState.mSource.getData().getLand().getSize();
}
void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::Land>& record =
mState.mSource.getData().getLand().getRecord (stage);
if (!record.isDeleted())
{
const CSMWorld::Land& land = record.get();
// make sure record is loaded
land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML |
ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM);
if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX))
{
// list texture indices
std::pair<uint16_t, int> key;
key.second = land.mPlugin;
for (int i=0; i<ESM::Land::LAND_NUM_TEXTURES; ++i)
{
key.first = data->mTextures[i];
mState.mTextureIndices[key] = -1;
}
}
}
}
CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state)
: mState (state), mNext (mState.mTextureIndices.end())
{}
int CSMTools::MergeLandTexturesStage::setup()
{
// Should use the size of mState.mTextureIndices instead, but that is not available at this
// point. Unless there are any errors in the land and land texture records this will not
// make a difference.
return mState.mSource.getData().getLandTextures().getSize();
}
void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages)
{
if (stage==0)
mNext = mState.mTextureIndices.begin();
bool found = false;
do
{
if (mNext==mState.mTextureIndices.end())
return;
mNext->second = stage+1;
std::ostringstream stream;
stream << mNext->first.first-1 << "_" << mNext->first.second;
int index = mState.mSource.getData().getLandTextures().searchId (stream.str());
if (index!=-1)
{
CSMWorld::LandTexture texture =
mState.mSource.getData().getLandTextures().getRecord (index).get();
std::ostringstream stream;
stream << mNext->second-1 << "_0";
texture.mIndex = mNext->second-1;
texture.mId = stream.str();
CSMWorld::Record<CSMWorld::LandTexture> newRecord (
CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture);
mState.mTarget->getData().getLandTextures().appendRecord (newRecord);
found = true;
}
++mNext;
}
while (!found);
}
CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) {}
int CSMTools::MergeLandStage::setup()
{
return mState.mSource.getData().getLand().getSize();
}
void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::Land>& record =
mState.mSource.getData().getLand().getRecord (stage);
if (!record.isDeleted())
{
const CSMWorld::Land& land = record.get();
land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML |
ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM);
CSMWorld::Land newLand (land);
newLand.mEsm = 0; // avoid potential dangling pointer (ESMReader isn't needed anyway,
// because record is already fully loaded)
newLand.mPlugin = 0;
if (land.mDataTypes & ESM::Land::DATA_VTEX)
{
// adjust land texture references
if (ESM::Land::LandData *data = newLand.getLandData())
{
std::pair<uint16_t, int> key;
key.second = land.mPlugin;
for (int i=0; i<ESM::Land::LAND_NUM_TEXTURES; ++i)
{
key.first = data->mTextures[i];
std::map<std::pair<uint16_t, int>, int>::const_iterator iter =
mState.mTextureIndices.find (key);
if (iter!=mState.mTextureIndices.end())
data->mTextures[i] = iter->second;
else
data->mTextures[i] = 0;
}
}
}
CSMWorld::Record<CSMWorld::Land> newRecord (
CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand);
mState.mTarget->getData().getLand().appendRecord (newRecord);
}
}

View file

@ -0,0 +1,166 @@
#ifndef CSM_TOOLS_MERGESTAGES_H
#define CSM_TOOLS_MERGESTAGES_H
#include <algorithm>
#include <map>
#include <components/to_utf8/to_utf8.hpp>
#include "../doc/stage.hpp"
#include "../world/data.hpp"
#include "mergestate.hpp"
namespace CSMTools
{
class StartMergeStage : public CSMDoc::Stage
{
MergeState& mState;
public:
StartMergeStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class FinishMergedDocumentStage : public CSMDoc::Stage
{
MergeState& mState;
ToUTF8::Utf8Encoder mEncoder;
public:
FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
template<typename RecordType, typename Collection = CSMWorld::IdCollection<RecordType> >
class MergeIdCollectionStage : public CSMDoc::Stage
{
MergeState& mState;
Collection& (CSMWorld::Data::*mAccessor)();
public:
MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)());
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
template<typename RecordType, typename Collection>
MergeIdCollectionStage<RecordType, Collection>::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)())
: mState (state), mAccessor (accessor)
{}
template<typename RecordType, typename Collection>
int MergeIdCollectionStage<RecordType, Collection>::setup()
{
return (mState.mSource.getData().*mAccessor)().getSize();
}
template<typename RecordType, typename Collection>
void MergeIdCollectionStage<RecordType, Collection>::perform (int stage, CSMDoc::Messages& messages)
{
const Collection& source = (mState.mSource.getData().*mAccessor)();
Collection& target = (mState.mTarget->getData().*mAccessor)();
const CSMWorld::Record<RecordType>& record = source.getRecord (stage);
if (!record.isDeleted())
target.appendRecord (CSMWorld::Record<RecordType> (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get()));
}
class MergeRefIdsStage : public CSMDoc::Stage
{
MergeState& mState;
public:
MergeRefIdsStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class MergeReferencesStage : public CSMDoc::Stage
{
MergeState& mState;
std::map<std::string, int> mIndex;
public:
MergeReferencesStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class ListLandTexturesMergeStage : public CSMDoc::Stage
{
MergeState& mState;
public:
ListLandTexturesMergeStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class MergeLandTexturesStage : public CSMDoc::Stage
{
MergeState& mState;
std::map<std::pair<uint16_t, int>, int>::iterator mNext;
public:
MergeLandTexturesStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class MergeLandStage : public CSMDoc::Stage
{
MergeState& mState;
public:
MergeLandStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
}
#endif

View file

@ -0,0 +1,24 @@
#ifndef CSM_TOOLS_MERGESTATE_H
#define CSM_TOOLS_MERGESTATE_H
#include <stdint.h>
#include <memory>
#include <map>
#include "../doc/document.hpp"
namespace CSMTools
{
struct MergeState
{
std::auto_ptr<CSMDoc::Document> mTarget;
CSMDoc::Document& mSource;
bool mCompleted;
std::map<std::pair<uint16_t, int>, int> mTextureIndices; // (texture, content file) -> new texture
MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {}
};
}
#endif

View file

@ -1,4 +1,3 @@
#include "racecheck.hpp" #include "racecheck.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "regioncheck.hpp" #include "regioncheck.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "reportmodel.hpp" #include "reportmodel.hpp"
#include <stdexcept> #include <stdexcept>
@ -85,14 +84,8 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const
if (index.column()==mColumnSeverity) if (index.column()==mColumnSeverity)
{ {
switch (mRows.at (index.row()).mSeverity) return QString::fromUtf8 (
{ CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str());
case CSMDoc::Message::Severity_Info: return "Information";
case CSMDoc::Message::Severity_Warning: return "Warning";
case CSMDoc::Message::Severity_Error: return "Error";
case CSMDoc::Message::Severity_SeriousError: return "Serious Error";
case CSMDoc::Message::Severity_Default: break;
}
} }
return QVariant(); return QVariant();

View file

@ -1,4 +1,3 @@
#include "scriptcheck.hpp" #include "scriptcheck.hpp"
#include <components/compiler/tokenloc.hpp> #include <components/compiler/tokenloc.hpp>

View file

@ -1,4 +1,3 @@
#include "search.hpp" #include "search.hpp"
#include <stdexcept> #include <stdexcept>

View file

@ -1,4 +1,3 @@
#include "searchoperation.hpp" #include "searchoperation.hpp"
#include "../doc/state.hpp" #include "../doc/state.hpp"

View file

@ -1,4 +1,3 @@
#include "searchstage.hpp" #include "searchstage.hpp"
#include "../world/idtablebase.hpp" #include "../world/idtablebase.hpp"

View file

@ -1,4 +1,3 @@
#include "skillcheck.hpp" #include "skillcheck.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "soundcheck.hpp" #include "soundcheck.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "spellcheck.hpp" #include "spellcheck.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "startscriptcheck.hpp" #include "startscriptcheck.hpp"
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>

View file

@ -1,4 +1,3 @@
#include "tools.hpp" #include "tools.hpp"
#include <QThreadPool> #include <QThreadPool>
@ -28,6 +27,8 @@
#include "searchoperation.hpp" #include "searchoperation.hpp"
#include "pathgridcheck.hpp" #include "pathgridcheck.hpp"
#include "soundgencheck.hpp" #include "soundgencheck.hpp"
#include "magiceffectcheck.hpp"
#include "mergeoperation.hpp"
CSMDoc::OperationHolder *CSMTools::Tools::get (int type) CSMDoc::OperationHolder *CSMTools::Tools::get (int type)
{ {
@ -35,6 +36,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::get (int type)
{ {
case CSMDoc::State_Verifying: return &mVerifier; case CSMDoc::State_Verifying: return &mVerifier;
case CSMDoc::State_Searching: return &mSearch; case CSMDoc::State_Searching: return &mSearch;
case CSMDoc::State_Merging: return &mMerge;
} }
return 0; return 0;
@ -108,15 +110,21 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier()
mData.getSounds(), mData.getSounds(),
mData.getReferenceables())); mData.getReferenceables()));
mVerifierOperation->appendStage (new MagicEffectCheckStage (mData.getMagicEffects(),
mData.getSounds(),
mData.getReferenceables(),
mData.getResources (CSMWorld::UniversalId::Type_Icons),
mData.getResources (CSMWorld::UniversalId::Type_Textures)));
mVerifier.setOperation (mVerifierOperation); mVerifier.setOperation (mVerifierOperation);
} }
return &mVerifier; return &mVerifier;
} }
CSMTools::Tools::Tools (CSMDoc::Document& document) CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding)
: mDocument (document), mData (document.getData()), mVerifierOperation (0), : mDocument (document), mData (document.getData()), mVerifierOperation (0),
mSearchOperation (0), mNextReportNumber (0) mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0), mEncoding (encoding)
{ {
// index 0: load error log // index 0: load error log
mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel));
@ -126,6 +134,10 @@ CSMTools::Tools::Tools (CSMDoc::Document& document)
connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool)));
connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)), connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)),
this, SLOT (verifierMessage (const CSMDoc::Message&, int))); this, SLOT (verifierMessage (const CSMDoc::Message&, int)));
connect (&mMerge, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int)));
connect (&mMerge, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool)));
// don't need to connect report message, since there are no messages for merge
} }
CSMTools::Tools::~Tools() CSMTools::Tools::~Tools()
@ -142,6 +154,12 @@ CSMTools::Tools::~Tools()
delete mSearchOperation; delete mSearchOperation;
} }
if (mMergeOperation)
{
mMerge.abortAndWait();
delete mMergeOperation;
}
for (std::map<int, ReportModel *>::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) for (std::map<int, ReportModel *>::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter)
delete iter->second; delete iter->second;
} }
@ -183,6 +201,25 @@ void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Se
mSearch.start(); mSearch.start();
} }
void CSMTools::Tools::runMerge (std::auto_ptr<CSMDoc::Document> target)
{
// not setting an active report, because merge does not produce messages
if (!mMergeOperation)
{
mMergeOperation = new MergeOperation (mDocument, mEncoding);
mMerge.setOperation (mMergeOperation);
connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)),
this, SIGNAL (mergeDone (CSMDoc::Document*)));
}
target->flagAsDirty();
mMergeOperation->setTarget (target);
mMerge.start();
}
void CSMTools::Tools::abortOperation (int type) void CSMTools::Tools::abortOperation (int type)
{ {
if (CSMDoc::OperationHolder *operation = get (type)) if (CSMDoc::OperationHolder *operation = get (type))
@ -195,6 +232,7 @@ int CSMTools::Tools::getRunningOperations() const
{ {
CSMDoc::State_Verifying, CSMDoc::State_Verifying,
CSMDoc::State_Searching, CSMDoc::State_Searching,
CSMDoc::State_Merging,
-1 -1
}; };
@ -225,4 +263,3 @@ void CSMTools::Tools::verifierMessage (const CSMDoc::Message& message, int type)
if (iter!=mActiveReports.end()) if (iter!=mActiveReports.end())
mReports[iter->second]->add (message); mReports[iter->second]->add (message);
} }

View file

@ -1,9 +1,14 @@
#ifndef CSM_TOOLS_TOOLS_H #ifndef CSM_TOOLS_TOOLS_H
#define CSM_TOOLS_TOOLS_H #define CSM_TOOLS_TOOLS_H
#include <memory>
#include <map>
#include <components/to_utf8/to_utf8.hpp>
#include <QObject> #include <QObject>
#include <map> #include <boost/filesystem/path.hpp>
#include "../doc/operationholder.hpp" #include "../doc/operationholder.hpp"
@ -24,6 +29,7 @@ namespace CSMTools
class ReportModel; class ReportModel;
class Search; class Search;
class SearchOperation; class SearchOperation;
class MergeOperation;
class Tools : public QObject class Tools : public QObject
{ {
@ -35,9 +41,12 @@ namespace CSMTools
CSMDoc::OperationHolder mVerifier; CSMDoc::OperationHolder mVerifier;
SearchOperation *mSearchOperation; SearchOperation *mSearchOperation;
CSMDoc::OperationHolder mSearch; CSMDoc::OperationHolder mSearch;
MergeOperation *mMergeOperation;
CSMDoc::OperationHolder mMerge;
std::map<int, ReportModel *> mReports; std::map<int, ReportModel *> mReports;
int mNextReportNumber; int mNextReportNumber;
std::map<int, int> mActiveReports; // type, report number std::map<int, int> mActiveReports; // type, report number
ToUTF8::FromType mEncoding;
// not implemented // not implemented
Tools (const Tools&); Tools (const Tools&);
@ -53,7 +62,7 @@ namespace CSMTools
public: public:
Tools (CSMDoc::Document& document); Tools (CSMDoc::Document& document, ToUTF8::FromType encoding);
virtual ~Tools(); virtual ~Tools();
@ -68,6 +77,8 @@ namespace CSMTools
void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); void runSearch (const CSMWorld::UniversalId& searchId, const Search& search);
void runMerge (std::auto_ptr<CSMDoc::Document> target);
void abortOperation (int type); void abortOperation (int type);
///< \attention The operation is not aborted immediately. ///< \attention The operation is not aborted immediately.
@ -85,6 +96,10 @@ namespace CSMTools
void progress (int current, int max, int type); void progress (int current, int max, int type);
void done (int type, bool failed); void done (int type, bool failed);
/// \attention When this signal is emitted, *this hands over the ownership of the
/// document. This signal must be handled to avoid a leak.
void mergeDone (CSMDoc::Document *document);
}; };
} }

View file

@ -1,4 +1,3 @@
#include "cell.hpp" #include "cell.hpp"
#include <sstream> #include <sstream>

View file

@ -1,4 +1,3 @@
#include "cellcoordinates.hpp" #include "cellcoordinates.hpp"
#include <ostream> #include <ostream>

View file

@ -1,4 +1,3 @@
#include "cellselection.hpp" #include "cellselection.hpp"
#include <cmath> #include <cmath>

View file

@ -1,4 +1,3 @@
#include "collectionbase.hpp" #include "collectionbase.hpp"
#include <stdexcept> #include <stdexcept>

View file

@ -82,7 +82,6 @@ bool CSMWorld::ColumnBase::isId (Display display)
Display_EffectId, Display_EffectId,
Display_PartRefType, Display_PartRefType,
Display_AiPackageType, Display_AiPackageType,
Display_YesNo,
Display_InfoCondFunc, Display_InfoCondFunc,
Display_InfoCondVar, Display_InfoCondVar,
Display_InfoCondComp, Display_InfoCondComp,

View file

@ -118,7 +118,6 @@ namespace CSMWorld
Display_EffectId, Display_EffectId,
Display_PartRefType, Display_PartRefType,
Display_AiPackageType, Display_AiPackageType,
Display_YesNo,
Display_InfoCondFunc, Display_InfoCondFunc,
Display_InfoCondVar, Display_InfoCondVar,
Display_InfoCondComp, Display_InfoCondComp,
@ -192,6 +191,12 @@ namespace CSMWorld
ColumnBase::Display_NestedHeader, flags) ColumnBase::Display_NestedHeader, flags)
{} {}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
// There is nothing to do here.
// This prevents exceptions from parent's implementation
}
virtual QVariant get (const Record<ESXRecordT>& record) const virtual QVariant get (const Record<ESXRecordT>& record) const
{ {
return true; // required by IdTree::hasChildren() return true; // required by IdTree::hasChildren()

View file

@ -0,0 +1,28 @@
#include "columnimp.hpp"
CSMWorld::BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn<ESM::BodyPart> *meshType)
: mMeshType(meshType)
{}
QVariant CSMWorld::BodyPartRaceColumn::get(const Record<ESM::BodyPart> &record) const
{
if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin)
{
return QString::fromUtf8(record.get().mRace.c_str());
}
return QVariant(QVariant::UserType);
}
void CSMWorld::BodyPartRaceColumn::set(Record<ESM::BodyPart> &record, const QVariant &data)
{
ESM::BodyPart record2 = record.get();
record2.mRace = data.toString().toUtf8().constData();
record.setModified(record2);
}
bool CSMWorld::BodyPartRaceColumn::isEditable() const
{
return true;
}

View file

@ -9,6 +9,10 @@
#include <QColor> #include <QColor>
#include <components/esm/loadbody.hpp>
#include <components/esm/loadskil.hpp>
#include <components/esm/loadrace.hpp>
#include "columnbase.hpp" #include "columnbase.hpp"
#include "columns.hpp" #include "columns.hpp"
#include "info.hpp" #include "info.hpp"
@ -1911,8 +1915,8 @@ namespace CSMWorld
template<typename ESXRecordT> template<typename ESXRecordT>
struct MeshTypeColumn : public Column<ESXRecordT> struct MeshTypeColumn : public Column<ESXRecordT>
{ {
MeshTypeColumn() MeshTypeColumn(int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue)
: Column<ESXRecordT> (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType) : Column<ESXRecordT> (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType, flags)
{} {}
virtual QVariant get (const Record<ESXRecordT>& record) const virtual QVariant get (const Record<ESXRecordT>& record) const
@ -2380,6 +2384,17 @@ namespace CSMWorld
return true; return true;
} }
}; };
struct BodyPartRaceColumn : public RaceColumn<ESM::BodyPart>
{
const MeshTypeColumn<ESM::BodyPart> *mMeshType;
BodyPartRaceColumn(const MeshTypeColumn<ESM::BodyPart> *meshType);
virtual QVariant get(const Record<ESM::BodyPart> &record) const;
virtual void set(Record<ESM::BodyPart> &record, const QVariant &data);
virtual bool isEditable() const;
};
} }
#endif #endif

View file

@ -1,4 +1,3 @@
#include "columns.hpp" #include "columns.hpp"
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
@ -35,6 +34,8 @@ namespace CSMWorld
{ ColumnId_Volume, "Volume" }, { ColumnId_Volume, "Volume" },
{ ColumnId_MinRange, "Min Range" }, { ColumnId_MinRange, "Min Range" },
{ ColumnId_MaxRange, "Max Range" }, { ColumnId_MaxRange, "Max Range" },
{ ColumnId_MinMagnitude, "Min Magnitude" },
{ ColumnId_MaxMagnitude, "Max Magnitude" },
{ ColumnId_SoundFile, "Sound File" }, { ColumnId_SoundFile, "Sound File" },
{ ColumnId_MapColour, "Map Colour" }, { ColumnId_MapColour, "Map Colour" },
{ ColumnId_SleepEncounter, "Sleep Encounter" }, { ColumnId_SleepEncounter, "Sleep Encounter" },
@ -70,7 +71,6 @@ namespace CSMWorld
{ ColumnId_Weight, "Weight" }, { ColumnId_Weight, "Weight" },
{ ColumnId_EnchantmentPoints, "Enchantment Points" }, { ColumnId_EnchantmentPoints, "Enchantment Points" },
{ ColumnId_Quality, "Quality" }, { ColumnId_Quality, "Quality" },
{ ColumnId_Ai, "AI" },
{ ColumnId_AiHello, "AI Hello" }, { ColumnId_AiHello, "AI Hello" },
{ ColumnId_AiFlee, "AI Flee" }, { ColumnId_AiFlee, "AI Flee" },
{ ColumnId_AiFight, "AI Fight" }, { ColumnId_AiFight, "AI Fight" },
@ -107,7 +107,6 @@ namespace CSMWorld
{ ColumnId_OriginalCreature, "Original Creature" }, { ColumnId_OriginalCreature, "Original Creature" },
{ ColumnId_Biped, "Biped" }, { ColumnId_Biped, "Biped" },
{ ColumnId_HasWeapon, "Has Weapon" }, { ColumnId_HasWeapon, "Has Weapon" },
{ ColumnId_NoMovement, "No Movement" },
{ ColumnId_Swims, "Swims" }, { ColumnId_Swims, "Swims" },
{ ColumnId_Flies, "Flies" }, { ColumnId_Flies, "Flies" },
{ ColumnId_Walks, "Walks" }, { ColumnId_Walks, "Walks" },
@ -199,8 +198,6 @@ namespace CSMWorld
{ ColumnId_RotY, "Rotation Y"}, { ColumnId_RotY, "Rotation Y"},
{ ColumnId_RotZ, "Rotation Z"}, { ColumnId_RotZ, "Rotation Z"},
{ ColumnId_Skill, "Skill" },
{ ColumnId_OwnerGlobal, "Owner Global" }, { ColumnId_OwnerGlobal, "Owner Global" },
{ ColumnId_DefaultProfile, "Default Profile" }, { ColumnId_DefaultProfile, "Default Profile" },
{ ColumnId_BypassNewGame, "Bypass New Game" }, { ColumnId_BypassNewGame, "Bypass New Game" },
@ -265,13 +262,13 @@ namespace CSMWorld
{ ColumnId_LevelledList,"Levelled List" }, { ColumnId_LevelledList,"Levelled List" },
{ ColumnId_LevelledItemId,"Levelled Item" }, { ColumnId_LevelledItemId,"Levelled Item" },
{ ColumnId_LevelledItemLevel,"Level" }, { ColumnId_LevelledItemLevel,"Item Level" },
{ ColumnId_LevelledItemType, "Calculate all levels <= player" }, { ColumnId_LevelledItemType, "Calculate all levels <= player" },
{ ColumnId_LevelledItemTypeEach, "Select a new item each instance" }, { ColumnId_LevelledItemTypeEach, "Select a new item each instance" },
{ ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_LevelledItemChanceNone, "Chance None" },
{ ColumnId_PowerList, "Powers" }, { ColumnId_PowerList, "Powers" },
{ ColumnId_SkillImpact, "Skills" }, { ColumnId_SkillImpact, "Skill" },
{ ColumnId_InfoList, "Info List" }, { ColumnId_InfoList, "Info List" },
{ ColumnId_InfoCondition, "Info Conditions" }, { ColumnId_InfoCondition, "Info Conditions" },
@ -281,26 +278,24 @@ namespace CSMWorld
{ ColumnId_InfoCondValue, "Values" }, { ColumnId_InfoCondValue, "Values" },
{ ColumnId_OriginalCell, "Original Cell" }, { ColumnId_OriginalCell, "Original Cell" },
{ ColumnId_NpcAttributes, "Attributes" }, { ColumnId_NpcAttributes, "NPC Attributes" },
{ ColumnId_NpcSkills, "Skills" }, { ColumnId_NpcSkills, "NPC Skill" },
{ ColumnId_UChar, "Value [0..255]" }, { ColumnId_UChar, "Value [0..255]" },
{ ColumnId_NpcMisc, "Misc" }, { ColumnId_NpcMisc, "NPC Misc" },
{ ColumnId_NpcLevel, "Level" }, { ColumnId_Level, "Level" },
{ ColumnId_NpcFactionID, "Faction ID" }, { ColumnId_NpcFactionID, "Faction ID" },
{ ColumnId_NpcHealth, "Health" }, { ColumnId_Mana, "Mana" },
{ ColumnId_NpcMana, "Mana" }, { ColumnId_Fatigue, "Fatigue" },
{ ColumnId_NpcFatigue, "Fatigue" }, { ColumnId_NpcDisposition, "NPC Disposition" },
{ ColumnId_NpcDisposition, "Disposition" },
{ ColumnId_NpcReputation, "Reputation" }, { ColumnId_NpcReputation, "Reputation" },
{ ColumnId_NpcRank, "Rank" }, { ColumnId_NpcRank, "NPC Rank" },
{ ColumnId_NpcGold, "Gold" }, { ColumnId_Gold, "Gold" },
{ ColumnId_NpcPersistence, "Persistent" }, { ColumnId_NpcPersistence, "Persistent" },
{ ColumnId_RaceAttributes, "Attributes" }, { ColumnId_RaceAttributes, "Race Attributes" },
{ ColumnId_RaceMaleValue, "Male" }, { ColumnId_RaceMaleValue, "Male Attrib" },
{ ColumnId_RaceFemaleValue, "Female" }, { ColumnId_RaceFemaleValue, "Female Attrib" },
{ ColumnId_RaceSkillBonus, "Skill Bonus" }, { ColumnId_RaceSkillBonus, "Skill Bonus" },
{ ColumnId_RaceSkill, "Skills" },
{ ColumnId_RaceBonus, "Bonus" }, { ColumnId_RaceBonus, "Bonus" },
{ ColumnId_Interior, "Interior" }, { ColumnId_Interior, "Interior" },
@ -315,6 +310,13 @@ namespace CSMWorld
{ ColumnId_FileDescription, "File Description" }, { ColumnId_FileDescription, "File Description" },
{ ColumnId_Author, "Author" }, { ColumnId_Author, "Author" },
{ ColumnId_CreatureAttributes, "Creature Attributes" },
{ ColumnId_AttributeValue, "Attrib Value" },
{ ColumnId_CreatureAttack, "Creature Attack" },
{ ColumnId_MinAttack, "Min Attack" },
{ ColumnId_MaxAttack, "Max Attack" },
{ ColumnId_CreatureMisc, "Creature Misc" },
{ ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue1, "Use value 1" },
{ ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue2, "Use value 2" },
{ ColumnId_UseValue3, "Use value 3" }, { ColumnId_UseValue3, "Use value 3" },
@ -536,11 +538,6 @@ namespace
"AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0
}; };
static const char *sAiWanderRepeat[] =
{
"No", "Yes", 0
};
static const char *sInfoCondFunc[] = static const char *sInfoCondFunc[] =
{ {
" ", "Function", "Global", "Local", "Journal", " ", "Function", "Global", "Local", "Journal",
@ -580,12 +577,10 @@ namespace
case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_EffectId: return sEffectId;
case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType;
case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType;
case CSMWorld::Columns::ColumnId_AiWanderRepeat: return sAiWanderRepeat;
case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc;
// FIXME: don't have dynamic value enum delegate, use Display_String for now // FIXME: don't have dynamic value enum delegate, use Display_String for now
//case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond;
case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp;
case CSMWorld::Columns::ColumnId_RaceSkill: return sSkills;
default: return 0; default: return 0;
} }

View file

@ -65,7 +65,7 @@ namespace CSMWorld
ColumnId_Weight = 50, ColumnId_Weight = 50,
ColumnId_EnchantmentPoints = 51, ColumnId_EnchantmentPoints = 51,
ColumnId_Quality = 52, ColumnId_Quality = 52,
ColumnId_Ai = 53, // unused
ColumnId_AiHello = 54, ColumnId_AiHello = 54,
ColumnId_AiFlee = 55, ColumnId_AiFlee = 55,
ColumnId_AiFight = 56, ColumnId_AiFight = 56,
@ -102,7 +102,7 @@ namespace CSMWorld
ColumnId_OriginalCreature = 87, ColumnId_OriginalCreature = 87,
ColumnId_Biped = 88, ColumnId_Biped = 88,
ColumnId_HasWeapon = 89, ColumnId_HasWeapon = 89,
ColumnId_NoMovement = 90, // unused
ColumnId_Swims = 91, ColumnId_Swims = 91,
ColumnId_Flies = 92, ColumnId_Flies = 92,
ColumnId_Walks = 93, ColumnId_Walks = 93,
@ -189,7 +189,7 @@ namespace CSMWorld
ColumnId_RotX = 174, ColumnId_RotX = 174,
ColumnId_RotY = 175, ColumnId_RotY = 175,
ColumnId_RotZ = 176, ColumnId_RotZ = 176,
ColumnId_Skill = 177, // unused
ColumnId_OwnerGlobal = 178, ColumnId_OwnerGlobal = 178,
ColumnId_DefaultProfile = 179, ColumnId_DefaultProfile = 179,
ColumnId_BypassNewGame = 180, ColumnId_BypassNewGame = 180,
@ -276,22 +276,22 @@ namespace CSMWorld
ColumnId_NpcSkills = 249, ColumnId_NpcSkills = 249,
ColumnId_UChar = 250, ColumnId_UChar = 250,
ColumnId_NpcMisc = 251, ColumnId_NpcMisc = 251,
ColumnId_NpcLevel = 252, ColumnId_Level = 252,
ColumnId_NpcFactionID = 253, ColumnId_NpcFactionID = 253,
ColumnId_NpcHealth = 254, // unused
ColumnId_NpcMana = 255, ColumnId_Mana = 255,
ColumnId_NpcFatigue = 256, ColumnId_Fatigue = 256,
ColumnId_NpcDisposition = 257, ColumnId_NpcDisposition = 257,
ColumnId_NpcReputation = 258, ColumnId_NpcReputation = 258,
ColumnId_NpcRank = 259, ColumnId_NpcRank = 259,
ColumnId_NpcGold = 260, ColumnId_Gold = 260,
ColumnId_NpcPersistence = 261, ColumnId_NpcPersistence = 261,
ColumnId_RaceAttributes = 262, ColumnId_RaceAttributes = 262,
ColumnId_RaceMaleValue = 263, ColumnId_RaceMaleValue = 263,
ColumnId_RaceFemaleValue = 264, ColumnId_RaceFemaleValue = 264,
ColumnId_RaceSkillBonus = 265, ColumnId_RaceSkillBonus = 265,
ColumnId_RaceSkill = 266, // unused
ColumnId_RaceBonus = 267, ColumnId_RaceBonus = 267,
ColumnId_Interior = 268, ColumnId_Interior = 268,
@ -306,6 +306,16 @@ namespace CSMWorld
ColumnId_FileDescription = 276, ColumnId_FileDescription = 276,
ColumnId_Author = 277, ColumnId_Author = 277,
ColumnId_MinMagnitude = 278,
ColumnId_MaxMagnitude = 279,
ColumnId_CreatureAttributes = 280,
ColumnId_AttributeValue = 281,
ColumnId_CreatureAttack = 282,
ColumnId_MinAttack = 283,
ColumnId_MaxAttack = 284,
ColumnId_CreatureMisc = 285,
// 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.
ColumnId_UseValue1 = 0x10000, ColumnId_UseValue1 = 0x10000,

View file

@ -1,4 +1,3 @@
#include "commanddispatcher.hpp" #include "commanddispatcher.hpp"
#include <algorithm> #include <algorithm>

View file

@ -21,19 +21,31 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI
// Replace proxy with actual model // Replace proxy with actual model
mIndex = proxy->mapToSource (index); mIndex = proxy->mapToSource (index);
mModel = proxy->sourceModel(); mModel = proxy->sourceModel();
}
if (mIndex.parent().isValid())
{
setText ("Modify " + dynamic_cast<CSMWorld::IdTree*>(mModel)->nestedHeaderData ( setText ("Modify " + dynamic_cast<CSMWorld::IdTree*>(mModel)->nestedHeaderData (
mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString());
} }
else else
{
setText ("Modify " + mModel->headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); setText ("Modify " + mModel->headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString());
}
// Remember record state before the modification // Remember record state before the modification
if (CSMWorld::IdTable *table = dynamic_cast<IdTable *>(mModel)) if (CSMWorld::IdTable *table = dynamic_cast<IdTable *>(mModel))
{ {
mHasRecordState = true; mHasRecordState = true;
int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification); int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification);
mRecordStateIndex = table->index(mIndex.row(), stateColumnIndex);
int rowIndex = mIndex.row();
if (mIndex.parent().isValid())
{
rowIndex = mIndex.parent().row();
}
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());
} }
} }
@ -282,21 +294,24 @@ CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model,
std::string title = std::string title =
model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData();
setText (("Delete row in " + title + " sub-table of " + mId).c_str()); setText (("Delete row in " + title + " sub-table of " + mId).c_str());
QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn);
mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this);
} }
void CSMWorld::DeleteNestedCommand::redo() void CSMWorld::DeleteNestedCommand::redo()
{ {
const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn);
mModel.removeRows (mNestedRow, 1, parentIndex); mModel.removeRows (mNestedRow, 1, parentIndex);
mModifyParentCommand->redo();
} }
void CSMWorld::DeleteNestedCommand::undo() void CSMWorld::DeleteNestedCommand::undo()
{ {
const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn);
mModel.setNestedTable(parentIndex, getOld()); mModel.setNestedTable(parentIndex, getOld());
mModifyParentCommand->undo();
} }
CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent)
@ -310,20 +325,23 @@ CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& i
std::string title = std::string title =
model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData();
setText (("Add row in " + title + " sub-table of " + mId).c_str()); setText (("Add row in " + title + " sub-table of " + mId).c_str());
QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn);
mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this);
} }
void CSMWorld::AddNestedCommand::redo() void CSMWorld::AddNestedCommand::redo()
{ {
const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn);
mModel.addNestedRow (parentIndex, mNewRow); mModel.addNestedRow (parentIndex, mNewRow);
mModifyParentCommand->redo();
} }
void CSMWorld::AddNestedCommand::undo() void CSMWorld::AddNestedCommand::undo()
{ {
const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn);
mModel.setNestedTable(parentIndex, getOld()); mModel.setNestedTable(parentIndex, getOld());
mModifyParentCommand->undo();
} }
CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn) CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn)

View file

@ -200,6 +200,9 @@ namespace CSMWorld
int mNestedRow; int mNestedRow;
// The command to redo/undo the Modified status of a record
ModifyCommand *mModifyParentCommand;
public: public:
DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0);
@ -219,6 +222,9 @@ namespace CSMWorld
int mParentColumn; int mParentColumn;
// The command to redo/undo the Modified status of a record
ModifyCommand *mModifyParentCommand;
public: public:
AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0);

View file

@ -1,4 +1,3 @@
#include "data.hpp" #include "data.hpp"
#include <stdexcept> #include <stdexcept>
@ -62,7 +61,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec
CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager)
: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells),
mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(resourcesManager.getVFS()) mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS()))
{ {
int index = 0; int index = 0;
@ -141,7 +140,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
index = mRaces.getColumns()-1; index = mRaces.getColumns()-1;
mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter()));
mRaces.getNestableColumn(index)->addColumn( mRaces.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_RaceAttributes, ColumnBase::Display_String, new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute,
ColumnBase::Flag_Dialogue, false)); ColumnBase::Flag_Dialogue, false));
mRaces.getNestableColumn(index)->addColumn( mRaces.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_RaceMaleValue, ColumnBase::Display_Integer)); new NestedChildColumn (Columns::ColumnId_RaceMaleValue, ColumnBase::Display_Integer));
@ -152,7 +151,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
index = mRaces.getColumns()-1; index = mRaces.getColumns()-1;
mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter()));
mRaces.getNestableColumn(index)->addColumn( mRaces.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_RaceSkill, ColumnBase::Display_RaceSkill)); new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact));
mRaces.getNestableColumn(index)->addColumn( mRaces.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer));
@ -224,9 +223,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
mSpells.getNestableColumn(index)->addColumn( mSpells.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light
mSpells.getNestableColumn(index)->addColumn( mSpells.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer));
mSpells.getNestableColumn(index)->addColumn( mSpells.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer));
mTopics.addColumn (new StringIdColumn<ESM::Dialogue>); mTopics.addColumn (new StringIdColumn<ESM::Dialogue>);
mTopics.addColumn (new RecordStateColumn<ESM::Dialogue>); mTopics.addColumn (new RecordStateColumn<ESM::Dialogue>);
@ -340,9 +339,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
mEnchantments.getNestableColumn(index)->addColumn( mEnchantments.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light
mEnchantments.getNestableColumn(index)->addColumn( mEnchantments.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer));
mEnchantments.getNestableColumn(index)->addColumn( mEnchantments.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer));
mBodyParts.addColumn (new StringIdColumn<ESM::BodyPart>); mBodyParts.addColumn (new StringIdColumn<ESM::BodyPart>);
mBodyParts.addColumn (new RecordStateColumn<ESM::BodyPart>); mBodyParts.addColumn (new RecordStateColumn<ESM::BodyPart>);
@ -352,9 +351,12 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
mBodyParts.addColumn (new FlagColumn<ESM::BodyPart> (Columns::ColumnId_Female, ESM::BodyPart::BPF_Female)); mBodyParts.addColumn (new FlagColumn<ESM::BodyPart> (Columns::ColumnId_Female, ESM::BodyPart::BPF_Female));
mBodyParts.addColumn (new FlagColumn<ESM::BodyPart> (Columns::ColumnId_Playable, mBodyParts.addColumn (new FlagColumn<ESM::BodyPart> (Columns::ColumnId_Playable,
ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true));
mBodyParts.addColumn (new MeshTypeColumn<ESM::BodyPart>);
int meshTypeFlags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh;
MeshTypeColumn<ESM::BodyPart> *meshTypeColumn = new MeshTypeColumn<ESM::BodyPart>(meshTypeFlags);
mBodyParts.addColumn (meshTypeColumn);
mBodyParts.addColumn (new ModelColumn<ESM::BodyPart>); mBodyParts.addColumn (new ModelColumn<ESM::BodyPart>);
mBodyParts.addColumn (new RaceColumn<ESM::BodyPart>); mBodyParts.addColumn (new BodyPartRaceColumn(meshTypeColumn));
mSoundGens.addColumn (new StringIdColumn<ESM::SoundGenerator>); mSoundGens.addColumn (new StringIdColumn<ESM::SoundGenerator>);
mSoundGens.addColumn (new RecordStateColumn<ESM::SoundGenerator>); mSoundGens.addColumn (new RecordStateColumn<ESM::SoundGenerator>);
@ -537,14 +539,14 @@ CSMWorld::Data::~Data()
delete mReader; delete mReader;
} }
Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() boost::shared_ptr<Resource::ResourceSystem> CSMWorld::Data::getResourceSystem()
{ {
return &mResourceSystem; return mResourceSystem;
} }
const Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() const boost::shared_ptr<const Resource::ResourceSystem> CSMWorld::Data::getResourceSystem() const
{ {
return &mResourceSystem; return mResourceSystem;
} }
const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const
@ -773,11 +775,21 @@ const CSMWorld::IdCollection<CSMWorld::Land>& CSMWorld::Data::getLand() const
return mLand; return mLand;
} }
CSMWorld::IdCollection<CSMWorld::Land>& CSMWorld::Data::getLand()
{
return mLand;
}
const CSMWorld::IdCollection<CSMWorld::LandTexture>& CSMWorld::Data::getLandTextures() const const CSMWorld::IdCollection<CSMWorld::LandTexture>& CSMWorld::Data::getLandTextures() const
{ {
return mLandTextures; return mLandTextures;
} }
CSMWorld::IdCollection<CSMWorld::LandTexture>& CSMWorld::Data::getLandTextures()
{
return mLandTextures;
}
const CSMWorld::IdCollection<ESM::SoundGenerator>& CSMWorld::Data::getSoundGens() const const CSMWorld::IdCollection<ESM::SoundGenerator>& CSMWorld::Data::getSoundGens() const
{ {
return mSoundGens; return mSoundGens;
@ -828,6 +840,12 @@ const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const
return mMetaData.getRecord (0).get(); return mMetaData.getRecord (0).get();
} }
void CSMWorld::Data::setMetaData (const MetaData& metaData)
{
Record<MetaData> record (RecordBase::State_ModifiedOnly, 0, &metaData);
mMetaData.setRecord (0, record);
}
QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id)
{ {
std::map<UniversalId::Type, QAbstractItemModel *>::iterator iter = mModelIndex.find (id.getType()); std::map<UniversalId::Type, QAbstractItemModel *>::iterator iter = mModelIndex.find (id.getType());
@ -939,8 +957,10 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
{ {
int index = mLand.load(*mReader, mBase); int index = mLand.load(*mReader, mBase);
if (index!=-1 && !mBase) // Load all land data for now. A future optimisation may only load non-base data
mLand.getRecord (index).mModified.mLand->loadData ( // if a suitable mechanism for avoiding race conditions can be established.
if (index!=-1/* && !mBase*/)
mLand.getRecord (index).get().loadData (
ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR |
ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM);

View file

@ -113,7 +113,7 @@ namespace CSMWorld
std::map<std::string, std::map<ESM::RefNum, std::string> > mRefLoadCache; std::map<std::string, std::map<ESM::RefNum, std::string> > mRefLoadCache;
int mReaderIndex; int mReaderIndex;
Resource::ResourceSystem mResourceSystem; boost::shared_ptr<Resource::ResourceSystem> mResourceSystem;
std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders; std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders;
@ -138,9 +138,9 @@ namespace CSMWorld
const VFS::Manager* getVFS() const; const VFS::Manager* getVFS() const;
Resource::ResourceSystem* getResourceSystem(); boost::shared_ptr<Resource::ResourceSystem> getResourceSystem();
const Resource::ResourceSystem* getResourceSystem() const; boost::shared_ptr<const Resource::ResourceSystem> getResourceSystem() const;
const IdCollection<ESM::Global>& getGlobals() const; const IdCollection<ESM::Global>& getGlobals() const;
@ -232,8 +232,12 @@ namespace CSMWorld
const IdCollection<CSMWorld::Land>& getLand() const; const IdCollection<CSMWorld::Land>& getLand() const;
IdCollection<CSMWorld::Land>& getLand();
const IdCollection<CSMWorld::LandTexture>& getLandTextures() const; const IdCollection<CSMWorld::LandTexture>& getLandTextures() const;
IdCollection<CSMWorld::LandTexture>& getLandTextures();
const IdCollection<ESM::SoundGenerator>& getSoundGens() const; const IdCollection<ESM::SoundGenerator>& getSoundGens() const;
IdCollection<ESM::SoundGenerator>& getSoundGens(); IdCollection<ESM::SoundGenerator>& getSoundGens();
@ -255,6 +259,8 @@ namespace CSMWorld
const MetaData& getMetaData() const; const MetaData& getMetaData() const;
void setMetaData (const MetaData& metaData);
QAbstractItemModel *getTableModel (const UniversalId& id); QAbstractItemModel *getTableModel (const UniversalId& id);
///< If no table model is available for \a id, an exception is thrown. ///< If no table model is available for \a id, an exception is thrown.
/// ///

View file

@ -76,8 +76,15 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value
if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole)
{ {
mIdCollection->setData (index.row(), index.column(), value); mIdCollection->setData (index.row(), index.column(), value);
emit dataChanged(index, index);
emit dataChanged (index, index); // Modifying a value can also change the Modified status of a record.
int stateColumn = searchColumnIndex(Columns::ColumnId_Modification);
if (stateColumn != -1)
{
QModelIndex stateIndex = this->index(index.row(), stateColumn);
emit dataChanged(stateIndex, stateIndex);
}
return true; return true;
} }

View file

@ -1,4 +1,3 @@
#include "idtablebase.hpp" #include "idtablebase.hpp"
CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {} CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {}

View file

@ -1,29 +1,40 @@
#include "idtableproxymodel.hpp" #include "idtableproxymodel.hpp"
#include <vector> #include <vector>
#include "idtablebase.hpp" #include "idtablebase.hpp"
namespace
{
std::string getEnumValue(const std::vector<std::string> &values, int index)
{
if (index < 0 || index >= static_cast<int>(values.size()))
{
return "";
}
return values[index];
}
}
void CSMWorld::IdTableProxyModel::updateColumnMap() void CSMWorld::IdTableProxyModel::updateColumnMap()
{ {
mColumnMap.clear(); Q_ASSERT(mSourceModel != NULL);
mColumnMap.clear();
if (mFilter) if (mFilter)
{ {
std::vector<int> columns = mFilter->getReferencedColumns(); std::vector<int> columns = mFilter->getReferencedColumns();
const IdTableBase& table = dynamic_cast<const IdTableBase&> (*sourceModel());
for (std::vector<int>::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter) for (std::vector<int>::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter)
mColumnMap.insert (std::make_pair (*iter, mColumnMap.insert (std::make_pair (*iter,
table.searchColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (*iter)))); mSourceModel->searchColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (*iter))));
} }
} }
bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent)
const const
{ {
Q_ASSERT(mSourceModel != NULL);
// It is not possible to use filterAcceptsColumn() and check for // It is not possible to use filterAcceptsColumn() and check for
// sourceModel()->headerData (sourceColumn, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags) // sourceModel()->headerData (sourceColumn, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags)
// because the sourceColumn parameter excludes the hidden columns, i.e. wrong columns can // because the sourceColumn parameter excludes the hidden columns, i.e. wrong columns can
@ -35,19 +46,40 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI
if (!mFilter) if (!mFilter)
return true; return true;
return mFilter->test ( return mFilter->test (*mSourceModel, sourceRow, mColumnMap);
dynamic_cast<IdTableBase&> (*sourceModel()), sourceRow, mColumnMap);
} }
CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent)
: QSortFilterProxyModel (parent) : QSortFilterProxyModel (parent),
mSourceModel(NULL)
{ {
setSortCaseSensitivity (Qt::CaseInsensitive); setSortCaseSensitivity (Qt::CaseInsensitive);
} }
QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const
{ {
return mapFromSource (dynamic_cast<IdTableBase&> (*sourceModel()).getModelIndex (id, column)); Q_ASSERT(mSourceModel != NULL);
return mapFromSource(mSourceModel->getModelIndex (id, column));
}
void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model)
{
QSortFilterProxyModel::setSourceModel(model);
mSourceModel = dynamic_cast<IdTableBase *>(sourceModel());
connect(mSourceModel,
SIGNAL(rowsInserted(const QModelIndex &, int, int)),
this,
SLOT(sourceRowsInserted(const QModelIndex &, int, int)));
connect(mSourceModel,
SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
this,
SLOT(sourceRowsRemoved(const QModelIndex &, int, int)));
connect(mSourceModel,
SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
this,
SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &)));
} }
void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr<CSMFilter::Node>& filter) void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr<CSMFilter::Node>& filter)
@ -60,11 +92,54 @@ void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr<CSMFilter::
bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{ {
Columns::ColumnId id = static_cast<Columns::ColumnId>(left.data(ColumnBase::Role_ColumnId).toInt());
EnumColumnCache::const_iterator valuesIt = mEnumColumnCache.find(id);
if (valuesIt == mEnumColumnCache.end())
{
if (Columns::hasEnums(id))
{
valuesIt = mEnumColumnCache.insert(std::make_pair(id, Columns::getEnums(id))).first;
}
}
if (valuesIt != mEnumColumnCache.end())
{
std::string first = getEnumValue(valuesIt->second, left.data().toInt());
std::string second = getEnumValue(valuesIt->second, right.data().toInt());
return first < second;
}
return QSortFilterProxyModel::lessThan(left, right); return QSortFilterProxyModel::lessThan(left, right);
} }
QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const
{
Q_ASSERT(mSourceModel != NULL);
int idColumn = mSourceModel->findColumnIndex(Columns::ColumnId_Id);
return mSourceModel->data(mSourceModel->index(sourceRow, idColumn)).toString();
}
void CSMWorld::IdTableProxyModel::refreshFilter() void CSMWorld::IdTableProxyModel::refreshFilter()
{ {
updateColumnMap(); updateColumnMap();
invalidateFilter(); invalidateFilter();
} }
void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end)
{
refreshFilter();
if (!parent.isValid())
{
emit rowAdded(getRecordId(end).toUtf8().constData());
}
}
void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/)
{
refreshFilter();
}
void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/)
{
refreshFilter();
}

View file

@ -11,6 +11,8 @@
#include "../filter/node.hpp" #include "../filter/node.hpp"
#include "columns.hpp"
namespace CSMWorld namespace CSMWorld
{ {
class IdTableProxyModel : public QSortFilterProxyModel class IdTableProxyModel : public QSortFilterProxyModel
@ -20,6 +22,15 @@ namespace CSMWorld
boost::shared_ptr<CSMFilter::Node> mFilter; boost::shared_ptr<CSMFilter::Node> mFilter;
std::map<int, int> mColumnMap; // column ID, column index in this model (or -1) std::map<int, int> mColumnMap; // column ID, column index in this model (or -1)
// Cache of enum values for enum columns (e.g. Modified, Record Type).
// Used to speed up comparisons during the sort by such columns.
typedef std::map<Columns::ColumnId, std::vector<std::string> > EnumColumnCache;
mutable EnumColumnCache mEnumColumnCache;
protected:
IdTableBase *mSourceModel;
private: private:
void updateColumnMap(); void updateColumnMap();
@ -30,15 +41,31 @@ namespace CSMWorld
virtual QModelIndex getModelIndex (const std::string& id, int column) const; virtual QModelIndex getModelIndex (const std::string& id, int column) const;
virtual void setSourceModel(QAbstractItemModel *model);
void setFilter (const boost::shared_ptr<CSMFilter::Node>& filter); void setFilter (const boost::shared_ptr<CSMFilter::Node>& filter);
void refreshFilter(); void refreshFilter();
protected: protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const; virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const;
QString getRecordId(int sourceRow) const;
protected slots:
virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end);
virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end);
virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
signals:
void rowAdded(const std::string &id);
}; };
} }

View file

@ -95,8 +95,15 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value,
const std::pair<int, int>& parentAddress(unfoldIndexAddress(index.internalId())); const std::pair<int, int>& parentAddress(unfoldIndexAddress(index.internalId()));
mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column()); mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column());
emit dataChanged(index, index);
emit dataChanged (index, index); // Modifying a value can also change the Modified status of a record.
int stateColumn = searchColumnIndex(Columns::ColumnId_Modification);
if (stateColumn != -1)
{
QModelIndex stateIndex = this->index(index.parent().row(), stateColumn);
emit dataChanged(stateIndex, stateIndex);
}
return true; return true;
} }
@ -171,10 +178,10 @@ QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& par
encodedId = this->foldIndexAddress(parent); encodedId = this->foldIndexAddress(parent);
} }
if (row<0 || row>=idCollection()->getSize()) if (row < 0 || row >= rowCount(parent))
return QModelIndex(); return QModelIndex();
if (column<0 || column>=idCollection()->getColumns()) if (column < 0 || column >= columnCount(parent))
return QModelIndex(); return QModelIndex();
return createIndex(row, column, encodedId); // store internal id return createIndex(row, column, encodedId); // store internal id

View file

@ -1,4 +1,3 @@
#include "infocollection.hpp" #include "infocollection.hpp"
#include <stdexcept> #include <stdexcept>

View file

@ -9,16 +9,17 @@ namespace
{ {
QString toLower(const QString &str) QString toLower(const QString &str)
{ {
return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toStdString()).c_str()); return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toUtf8().constData()).c_str());
} }
} }
CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent) CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent)
: IdTableProxyModel(parent), : IdTableProxyModel(parent),
mType(type), mType(type),
mSourceModel(NULL), mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic :
mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : Columns::ColumnId_Journal),
Columns::ColumnId_Journal) mInfoColumnIndex(-1),
mLastAddedSourceRow(-1)
{ {
Q_ASSERT(type == UniversalId::Type_TopicInfos || type == UniversalId::Type_JournalInfos); Q_ASSERT(type == UniversalId::Type_TopicInfos || type == UniversalId::Type_JournalInfos);
} }
@ -26,23 +27,18 @@ CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type t
void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceModel) void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
{ {
IdTableProxyModel::setSourceModel(sourceModel); IdTableProxyModel::setSourceModel(sourceModel);
mSourceModel = dynamic_cast<IdTableBase *>(sourceModel);
if (mSourceModel != NULL) if (mSourceModel != NULL)
{ {
connect(mSourceModel, mInfoColumnIndex = mSourceModel->findColumnIndex(mInfoColumnId);
SIGNAL(rowsInserted(const QModelIndex &, int, int)),
this,
SLOT(modelRowsChanged(const QModelIndex &, int, int)));
connect(mSourceModel,
SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
this,
SLOT(modelRowsChanged(const QModelIndex &, int, int)));
mFirstRowCache.clear(); mFirstRowCache.clear();
} }
} }
bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{ {
Q_ASSERT(mSourceModel != NULL);
QModelIndex first = mSourceModel->index(getFirstInfoRow(left.row()), left.column()); QModelIndex first = mSourceModel->index(getFirstInfoRow(left.row()), left.column());
QModelIndex second = mSourceModel->index(getFirstInfoRow(right.row()), right.column()); QModelIndex second = mSourceModel->index(getFirstInfoRow(right.row()), right.column());
@ -56,8 +52,10 @@ bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QMod
int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const
{ {
Q_ASSERT(mSourceModel != NULL);
int row = currentRow; int row = currentRow;
int column = mSourceModel->findColumnIndex(mInfoColumnId); int column = mInfoColumnIndex;
QString info = toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()); QString info = toLower(mSourceModel->data(mSourceModel->index(row, column)).toString());
if (mFirstRowCache.contains(info)) if (mFirstRowCache.contains(info))
@ -73,7 +71,41 @@ int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const
return row; return row;
} }
void CSMWorld::InfoTableProxyModel::modelRowsChanged(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/)
{ {
refreshFilter();
mFirstRowCache.clear(); mFirstRowCache.clear();
} }
void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end)
{
refreshFilter();
if (!parent.isValid())
{
mFirstRowCache.clear();
// We can't re-sort the model here, because the topic of the added row isn't set yet.
// Store the row index for using in the first dataChanged() after this row insertion.
mLastAddedSourceRow = end;
}
}
void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
refreshFilter();
if (mLastAddedSourceRow != -1 &&
topLeft.row() <= mLastAddedSourceRow && bottomRight.row() >= mLastAddedSourceRow)
{
// Now the topic of the last added row is set,
// so we can re-sort the model to ensure the corrent position of this row
int column = sortColumn();
Qt::SortOrder order = sortOrder();
sort(mInfoColumnIndex); // Restore the correct position of an added row
sort(column, order); // Restore the original sort order
emit rowAdded(getRecordId(mLastAddedSourceRow).toUtf8().constData());
// Make sure that we perform a re-sorting only in the first dataChanged() after a row insertion
mLastAddedSourceRow = -1;
}
}

View file

@ -16,25 +16,29 @@ namespace CSMWorld
Q_OBJECT Q_OBJECT
UniversalId::Type mType; UniversalId::Type mType;
IdTableBase *mSourceModel;
Columns::ColumnId mInfoColumnId; Columns::ColumnId mInfoColumnId;
///< Contains ID for Topic or Journal ID ///< Contains ID for Topic or Journal ID
int mInfoColumnIndex;
int mLastAddedSourceRow;
mutable QHash<QString, int> mFirstRowCache; mutable QHash<QString, int> mFirstRowCache;
int getFirstInfoRow(int currentRow) const; int getFirstInfoRow(int currentRow) const;
///< Finds the first row with the same topic (journal entry) as in \a currentRow ///< Finds the first row with the same topic (journal entry) as in \a currentRow
///< \a currentRow is a row of the source model.
public: public:
InfoTableProxyModel(UniversalId::Type type, QObject *parent = 0); InfoTableProxyModel(UniversalId::Type type, QObject *parent = 0);
void setSourceModel(QAbstractItemModel *sourceModel); virtual void setSourceModel(QAbstractItemModel *sourceModel);
protected: protected:
virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
private slots: protected slots:
void modelRowsChanged(const QModelIndex &parent, int start, int end); virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end);
virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end);
virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
}; };
} }

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