Merge branch 'master' into osx-ogre-19

Conflicts:
	CMakeLists.txt
actorid
Nikolay Kasyanov 11 years ago
commit 58add97676

53
.gitignore vendored

@ -1,24 +1,55 @@
build ## make
*~
Doxygen
prebuilt
apps/openmw/config.hpp
Docs/mainpage.hpp
CMakeFiles CMakeFiles
*/CMakeFiles */CMakeFiles
CMakeCache.txt CMakeCache.txt
moc_*.cxx
cmake_install.cmake cmake_install.cmake
*.[ao]
Makefile Makefile
makefile makefile
data build
prebuilt
## doxygen
Doxygen
## ides/editors
*~
*.kdev4 *.kdev4
CMakeLists.txt.user
*.swp *.swp
*.swo *.swo
*.kate-swp *.kate-swp
.cproject .cproject
.project .project
.settings/ .settings
.directory .directory
## qt-creator
CMakeLists.txt.user*
## resources
data
resources
/*.cfg
/*.desktop
/*.install
## binaries
/esmtool
/mwiniimport
/omwlauncher
/openmw
/opencs
## generated objects
apps/openmw/config.hpp
components/version/version.hpp
Docs/mainpage.hpp
moc_*.cxx
*.cxx_parameters
*qrc_launcher.cxx
*qrc_resources.cxx
*__*
*ui_datafilespage.h
*ui_graphicspage.h
*ui_mainwindow.h
*ui_playpage.h
*.[ao]
*.so

@ -4,18 +4,18 @@ compiler:
branches: branches:
only: only:
- master - master
- next - /openmw-.*$/
before_install: before_install:
- pwd - pwd
- git submodule update --init --recursive - git fetch --tags
- 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/deps - echo "yes" | sudo apt-add-repository ppa:openmw/openmw
- sudo apt-get update -qq - sudo apt-get update -qq
- sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock
- sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libqt4-dev
- sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libopenal-dev
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
- sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev - sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev
- sudo mkdir /usr/src/gtest/build - sudo mkdir /usr/src/gtest/build
- cd /usr/src/gtest/build - cd /usr/src/gtest/build
- sudo cmake .. -DBUILD_SHARED_LIBS=1 - sudo cmake .. -DBUILD_SHARED_LIBS=1
@ -26,7 +26,7 @@ before_script:
- cd - - cd -
- mkdir build - mkdir build
- cd build - cd build
- cmake .. -DOGRE_STATIC=1 -DMYGUI_STATIC=1 -DBOOST_STATIC=1 -DSDL2_STATIC=1 -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DBUILD_WITH_DPKG=1
script: script:
- make -j4 - make -j4
after_script: after_script:
@ -37,3 +37,9 @@ notifications:
email: email:
on_success: change on_success: change
on_failure: always on_failure: always
irc:
channels:
- "chat.freenode.net#openmw"
on_success: change
on_failure: always

@ -14,15 +14,30 @@ endif (APPLE)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/)
include (OpenMWMacros) include(OpenMWMacros)
# Version # Version
set (OPENMW_VERSION_MAJOR 0) include(GetGitRevisionDescription)
set (OPENMW_VERSION_MINOR 26)
set (OPENMW_VERSION_RELEASE 0)
set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") get_git_tag_revision(TAGHASH --tags --max-count=1 "HEAD...")
get_git_head_revision(REFSPEC COMMITHASH)
git_describe(VERSION --tags ${TAGHASH})
string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}")
if (MATCH)
string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}")
string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}")
string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}")
set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}")
set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}")
set(OPENMW_VERSION_TAGHASH "${TAGHASH}")
message(STATUS "Configuring OpenMW ${OPENMW_VERSION}...")
else (MATCH)
message(FATAL_ERROR "Failed to get valid version information from Git")
endif (MATCH)
# doxygen main page # doxygen main page
@ -50,7 +65,12 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON)
# OS X deployment # OS X deployment
option(OPENMW_OSX_DEPLOYMENT OFF) option(OPENMW_OSX_DEPLOYMENT OFF)
find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") if(UNIX AND NOT APPLE)
option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF)
if(BUILD_WITH_DPKG)
find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems")
endif(BUILD_WITH_DPKG)
endif(UNIX AND NOT APPLE)
# Location of morrowind data files # Location of morrowind data files
if (APPLE) if (APPLE)
@ -79,7 +99,6 @@ set(OENGINE_OGRE
${LIBDIR}/openengine/ogre/renderer.cpp ${LIBDIR}/openengine/ogre/renderer.cpp
${LIBDIR}/openengine/ogre/fader.cpp ${LIBDIR}/openengine/ogre/fader.cpp
${LIBDIR}/openengine/ogre/lights.cpp ${LIBDIR}/openengine/ogre/lights.cpp
${LIBDIR}/openengine/ogre/particles.cpp
${LIBDIR}/openengine/ogre/selectionbuffer.cpp ${LIBDIR}/openengine/ogre/selectionbuffer.cpp
${LIBDIR}/openengine/ogre/imagerotate.cpp ${LIBDIR}/openengine/ogre/imagerotate.cpp
) )
@ -89,14 +108,10 @@ set(OENGINE_GUI
) )
set(OENGINE_BULLET set(OENGINE_BULLET
${LIBDIR}/openengine/bullet/btKinematicCharacterController.cpp
${LIBDIR}/openengine/bullet/btKinematicCharacterController.h
${LIBDIR}/openengine/bullet/BtOgre.cpp ${LIBDIR}/openengine/bullet/BtOgre.cpp
${LIBDIR}/openengine/bullet/BtOgreExtras.h ${LIBDIR}/openengine/bullet/BtOgreExtras.h
${LIBDIR}/openengine/bullet/BtOgreGP.h ${LIBDIR}/openengine/bullet/BtOgreGP.h
${LIBDIR}/openengine/bullet/BtOgrePG.h ${LIBDIR}/openengine/bullet/BtOgrePG.h
${LIBDIR}/openengine/bullet/CMotionState.cpp
${LIBDIR}/openengine/bullet/CMotionState.h
${LIBDIR}/openengine/bullet/physic.cpp ${LIBDIR}/openengine/bullet/physic.cpp
${LIBDIR}/openengine/bullet/physic.hpp ${LIBDIR}/openengine/bullet/physic.hpp
${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp ${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp
@ -181,10 +196,11 @@ if (WIN32)
set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_LIBS ON)
set(PLATFORM_INCLUDE_DIR "platform") set(PLATFORM_INCLUDE_DIR "platform")
add_definitions(-DBOOST_ALL_NO_LIB) add_definitions(-DBOOST_ALL_NO_LIB)
# Suppress WinMain(), provided by SDL
add_definitions(-DSDL_MAIN_HANDLED)
else (WIN32) else (WIN32)
set(PLATFORM_INCLUDE_DIR "") set(PLATFORM_INCLUDE_DIR "")
find_path (UUID_INCLUDE_DIR uuid/uuid.h)
include_directories(${UUID_INCLUDE_DIR})
endif (WIN32) endif (WIN32)
if (MSVC10) if (MSVC10)
set(PLATFORM_INCLUDE_DIR "") set(PLATFORM_INCLUDE_DIR "")
@ -208,7 +224,7 @@ if (HAVE_UNORDERED_MAP)
endif () endif ()
set(BOOST_COMPONENTS system filesystem program_options thread date_time wave) set(BOOST_COMPONENTS system filesystem program_options)
IF(BOOST_STATIC) IF(BOOST_STATIC)
set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_LIBS ON)
@ -236,7 +252,6 @@ include_directories("."
${MYGUI_INCLUDE_DIRS} ${MYGUI_INCLUDE_DIRS}
${MYGUI_PLATFORM_INCLUDE_DIRS} ${MYGUI_PLATFORM_INCLUDE_DIRS}
${OPENAL_INCLUDE_DIR} ${OPENAL_INCLUDE_DIR}
${UUID_INCLUDE_DIR}
${LIBDIR} ${LIBDIR}
) )
@ -318,6 +333,9 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg
configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg
"${OpenMW_BINARY_DIR}/opencs.cfg") "${OpenMW_BINARY_DIR}/opencs.cfg")
configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters
"${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY)
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")
@ -378,7 +396,6 @@ IF(NOT WIN32 AND NOT APPLE)
# Install licenses # Install licenses
INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "Daedric Font License.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" )
ENDIF (DPKG_PROGRAM) ENDIF (DPKG_PROGRAM)
@ -402,46 +419,6 @@ IF(NOT WIN32 AND NOT APPLE)
# Install resources # Install resources
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources")
INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources")
IF (DPKG_PROGRAM)
## Debian Specific
IF(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.git")
EXEC_PROGRAM("git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "describe" OUTPUT_VARIABLE GIT_VERSION )
STRING(REGEX REPLACE "openmw-" "" VERSION_STRING "${GIT_VERSION}")
EXEC_PROGRAM("git" ARGS "config --get user.name" OUTPUT_VARIABLE GIT_NAME )
EXEC_PROGRAM("git" ARGS "config --get user.email" OUTPUT_VARIABLE GIT_EMAIL)
SET(PACKAGE_MAINTAINER "${GIT_NAME} <${GIT_EMAIL}>")
ELSE()
SET(VERSION_STRING "${OPENMW_VERSION}")
SET(PACKAGE_MAINTAINER "unknown")
ENDIF()
SET(CPACK_GENERATOR "DEB")
SET(CPACK_PACKAGE_NAME "openmw")
SET(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://openmw.org")
SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PACKAGE_MAINTAINER}")
SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A reimplementation of The Elder Scrolls III: Morrowind
OpenMW is a reimplementation of the Bethesda Game Studios game The Elder Scrolls III: Morrowind.
Data files from the original game is required to run it.")
SET(CPACK_DEBIAN_PACKAGE_NAME "openmw")
SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}")
SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter")
SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)")
SET(CPACK_DEBIAN_PACKAGE_SECTION "Games")
STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE)
EXECUTE_PROCESS(
COMMAND ${DPKG_PROGRAM} --print-architecture
OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME_LOWERCASE}_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
INCLUDE(CPack)
ENDIF(DPKG_PROGRAM)
ENDIF(NOT WIN32 AND NOT APPLE) ENDIF(NOT WIN32 AND NOT APPLE)
if(WIN32) if(WIN32)
@ -453,13 +430,22 @@ if(WIN32)
"${OpenMW_SOURCE_DIR}/GPL3.txt" "${OpenMW_SOURCE_DIR}/GPL3.txt"
"${OpenMW_SOURCE_DIR}/OFL.txt" "${OpenMW_SOURCE_DIR}/OFL.txt"
"${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt"
"${OpenMW_SOURCE_DIR}/Daedric Font License.txt"
"${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/settings-default.cfg"
"${OpenMW_BINARY_DIR}/transparency-overrides.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg"
"${OpenMW_BINARY_DIR}/Release/mwiniimport.exe"
"${OpenMW_BINARY_DIR}/Release/omwlauncher.exe"
"${OpenMW_BINARY_DIR}/Release/openmw.exe" "${OpenMW_BINARY_DIR}/Release/openmw.exe"
DESTINATION ".") DESTINATION ".")
IF(BUILD_LAUNCHER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/omwlauncher.exe" DESTINATION ".")
ENDIF(BUILD_LAUNCHER)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe" DESTINATION ".")
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION ".")
ENDIF(BUILD_OPENCS)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".")
SET(CPACK_GENERATOR "NSIS") SET(CPACK_GENERATOR "NSIS")
@ -469,7 +455,13 @@ if(WIN32)
SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR}) SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR})
SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;omwlauncher;OpenMW Launcher") SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW")
IF(BUILD_LAUNCHER)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};omwlauncher;OpenMW Launcher")
ENDIF(BUILD_LAUNCHER)
IF(BUILD_OPENCS)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};opencs;OpenMW Construction Set")
ENDIF(BUILD_OPENCS)
SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'")
SET(CPACK_NSIS_DELETE_ICONS_EXTRA " SET(CPACK_NSIS_DELETE_ICONS_EXTRA "
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
@ -666,8 +658,11 @@ if (APPLE)
set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
set(APPS "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}")
set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/OpenCS.app")
set(PLUGINS "")
set(ABSOLUTE_PLUGINS "") set(ABSOLUTE_PLUGINS "")
foreach (PLUGIN ${USED_OGRE_PLUGINS}) foreach (PLUGIN ${USED_OGRE_PLUGINS})
@ -725,7 +720,8 @@ if (APPLE)
cmake_policy(SET CMP0009 OLD) cmake_policy(SET CMP0009 OLD)
set(BU_CHMOD_BUNDLE_ITEMS ON) set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities) include(BundleUtilities)
fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\")
fixup_bundle(\"${OPENCS_APP}\" \"\" \"${DIRS}\")
" COMPONENT Runtime) " COMPONENT Runtime)
include(CPack) include(CPack)
endif (APPLE) endif (APPLE)

@ -1,10 +0,0 @@
Dongle's Oblivion Daedric font set
http://www.uesp.net/wiki/Lore:Daedric_Alphabet#Daedric_Font
---------------------------------------------------
This was done entirely as a personal project. Bethesda Softworks graciously granted me the permission for it. I am not connected with them in any way.
You may freely use these fonts to create anything you'd like. You may re-distribute the fonts freely, over the Internet, or by any other means. Always keep the .zip file intact, and this read me included.
Please do not modify and redistribute the fonts without my permission.
You may NOT sell any of these fonts under any circumstances. This includes putting them on compilation font CDs for sale, putting them in a "members only" pay-area of a website, or any other means of financial gain connected in ANY way with the redistribution of any of these fonts.
You have my permission to create and sell any artwork made with these fonts, however you may need to contact Bethesda Softworks before doing so.

@ -1,160 +0,0 @@
#Getting OpenMW Working on OS X
## Initial setup
First of all, clone OpenMW repo.
$ git clone github.com/zinnschlag/openmw
Or use your github url if you forked.
About dependencies: I prefer not to install them globally (i. e. in /usr/local/), so I'm installing them in directory in my home directory. If OpenMW sources is in $HOME/path/openmw, I'm using $HOME/path/libs/root as prefix for boost and other libs.
It's useful to create env var for lib install prefix:
$ export OMW_LIB_PREFIX=$HOME/path/libs/root`
Most of libs can be installed from [Homebrew][homebrew]. Only mpg123 needs to be installed from source (due to lack of universal compilation support). I think that some of libs can be installed from MacPorts or Fink too.
As OpenMW currently only supports i386 architecture on OS X, denendencies also should support it. Set some env vars in current terminal:
$ export CFLAGS="-arch i386"
$ export CXXFLAGS="-arch i386"
$ export LDFLAGS="-arch i386"
If you close your terminal, you should set env vars again before pcoceeding to next steps!
## Boost
Download [boost][boost] and install it with the following command:
$ cd /path/to/boost/source
$ ./bootstrap.sh --prefix=$OMW_LIB_PREFIX
$ ./bjam --build-dir=build --layout=versioned \
--toolset=darwin architecture=x86 address-model=32 \
--link-shared,static --prefix=$OMW_LIB_PREFIX install
Alternatively you can install boost with homebrew:
$ brew install boost --universal
I think MacPorts also should support universal build for boost.
## Ogre
Download [Ogre][] SDK (tested with 1.7.3), unpack it somewhere and move
`lib/Release/Ogre.framework` into `/Library/Frameworks`.
## OIS
Download patched [OIS][] and use the XCode project provided. Be sure to set your build architecture to
`i386`. Once it built, locate built OIS.framework with Xcode and move it to `/Library/Frameworks`.
## mpg123
Download [MPG 123][mpg123] and build it:
$ cd /path/to/mpg123/source
$ ./configure --prefix=$OMW_LIB_PREFIX --disable-debug \
--disable-dependency-tracking \
--with-optimization=4 \
--with-audio=dummy \
--with-default-audio=dummy \
--with-cpu=sse_alone \
$ make install
## libsndfile
Download [libsndfile][] and build it:
$ cd /path/to/libsndfile/source
$ ./configure --prefix=$OMW_LIB_PREFIX \
--disable-dependency-tracking
$ make install
or install with homebrew:
$ brew install libsndfile --universal
## Bullet
Download [Bullet][] and build it:
$ cd /path/to/bullet/source
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$OMW_LIB_PREFIX \
-DBUILD_EXTRAS=OFF \
-DBUILD_DEMOS=OFF \
-DCMAKE_OSX_ARCHITECTURES=i386 \
-DCMAKE_INSTALL_NAME_DIR=$OMW_LIB_RPEFIX/lib \
-G"Unix Makefiles" ../
$ make install
or install with homebrew:
$ brew install bullet --HEAD --universal
I prefer head because 2.79 has some issue which causes OpenMW to lag. Also you can edit formula and install 2.77, which is stable and haven't mentioned issue.
## Qt
Install [Qt][qt]. Qt SDK distributed by Nokia is not an option because it's 64 bit only, and OpenMW currently doesn't build for 64 bit on OS X. I'm installing it from Homebrew:
$ brew install qt --universal
## Run CMake
Generate the Makefile for OpenMW as follows and build OpenMW:
$ mkdir /path/to/openmw/build/dir
$ cd /path/to/open/build/dir
$ cmake \
-D CMAKE_OSX_ARCHITECTURES=i386 \
-D OGRE_SDK=/path/to/ogre/sdk \
-D BOOST_INCLUDEDIR=$OMW_LIB_PREFIX/include/boost-1_45 \
-D BOOST_LIBRARYDIR=$OMW_LIB_PREFIX/lib \
-D SNDFILE_INCLUDE_DIR=$OMW_LIB_PREFIX/include \
-D SNDFILE_LIBRARY=$OMW_LIB_PREFIX/lib/libsndfile.a \
-D MPG123_LIBRARY=$OMW_LIB_PREFIX/lib/libmpg123.a \
-D MPG123_INCLUDE_DIR=$OMW_LIB_PREFIX/include \
-D BULLET_DYNAMICS_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletDynamics.a \
-D BULLET_COLLISION_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletCollision.a \
-D BULLET_MATH_LIBRARY=$OMW_LIB_PREFIX/lib/libLinearMath.a \
-D BULLET_SOFTBODY_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletSoftBody.a \
-D BULLET_INCLUDE_DIR=$OMW_LIB_PREFIX/include/bullet/ \
-G "Unix Makefiles" /path/to/openmw/source/dir
$ make
You can use `-G"Xcode"` if you prefer Xcode, or -G"Eclipse CDT4 - Unix Makefiles"
if you prefer Eclipse. You also can specify `-D CMAKE_BUILD_TYPE=Debug` for debug
build. As for CMake 2.8.7 and Xcode 4.3, Xcode generator is broken. Sadly Eclipse CDT also cannot import generated project at least on my machine.
If all libs installed via homebrew (excluding mpg123), then command would be even simplier:
$ cmake \
-D CMAKE_OSX_ARCHITECTURES="i386" \
-D OGRE_SDK=/path/to/ogre/sdk \
-D MPG123_LIBRARY=$OMW_LIB_PREFIX/lib/libmpg123.a \
-D MPG123_INCLUDE_DIR=$OMW_LIB_PREFIX/include \
-G "Unix Makefiles" /path/to/openmw/source/dir
$ make
Note for users with recent Xcode versions: you must explicitly specify what set of compilers do you use! If not, gcc will be used for C and Clang for C++. Just add this two -D's to command: `-D CMAKE_C_COMPILER=/usr/bin/clang` and `-D CMAKE_CXX_COMPILER=/usr/bin/clang`
Note for Xcode 4.3 users: you should specify full path to used SDK, because current CMake (2.8.7) couldn't find SDKs inside Xcode app bundle:
-D CMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk"
# Run
From your build directory run:
$ OpenMW.app/Contents/MacOS/openmw
or:
$ open OpenMW.app
Enjoy!
[homebrew]: https://github.com/mxcl/homebrew
[boost]: http://www.boost.org
[Ogre]: http://www.ogre3d.org
[Bullet]: http://bulletphysics.org
[OIS]: https://github.com/corristo/ois-fork
[mpg123]: http://www.mpg123.de
[libsndfile]: http://www.mega-nerd.com/libsndfile
[official website]: http://openmw.com
[Will Thimbleby's Ogre Framework]: http://www.thimbleby.net/ogre/
[qt]: http://qt.nokia.com/

@ -184,7 +184,7 @@ int list(Bsa::BSAFile& bsa, Arguments& info)
{ {
// List all files // List all files
const Bsa::BSAFile::FileList &files = bsa.getList(); const Bsa::BSAFile::FileList &files = bsa.getList();
for(int i=0; i<files.size(); i++) for(unsigned int i=0; i<files.size(); i++)
{ {
if(info.longformat) if(info.longformat)
{ {

@ -236,7 +236,9 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
// Loop through all the references // Loop through all the references
ESM::CellRef ref; ESM::CellRef ref;
if(!quiet) std::cout << " References:\n"; if(!quiet) std::cout << " References:\n";
while(cell.getNextRef(esm, ref))
bool deleted = false;
while(cell.getNextRef(esm, ref, deleted))
{ {
if (save) { if (save) {
info.data.mCellRefs[&cell].push_back(ref); info.data.mCellRefs[&cell].push_back(ref);
@ -244,13 +246,14 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
if(quiet) continue; if(quiet) continue;
std::cout << " Refnum: " << ref.mRefnum << std::endl; std::cout << " Refnum: " << ref.mRefNum.mIndex << std::endl;
std::cout << " ID: '" << ref.mRefID << "'\n"; std::cout << " ID: '" << ref.mRefID << "'\n";
std::cout << " Owner: '" << ref.mOwner << "'\n"; std::cout << " Owner: '" << ref.mOwner << "'\n";
std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n"; std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n";
std::cout << " Uses/health: '" << ref.mCharge << "'\n"; std::cout << " Uses/health: '" << ref.mCharge << "'\n";
std::cout << " Gold value: '" << ref.mGoldValue << "'\n"; std::cout << " Gold value: '" << ref.mGoldValue << "'\n";
std::cout << " Blocked: '" << static_cast<int>(ref.mReferenceBlocked) << "'" << std::endl; std::cout << " Blocked: '" << static_cast<int>(ref.mReferenceBlocked) << "'" << std::endl;
std::cout << " Deleted: " << deleted << std::endl;
} }
} }
@ -305,14 +308,14 @@ int load(Arguments& info)
info.data.author = esm.getAuthor(); info.data.author = esm.getAuthor();
info.data.description = esm.getDesc(); info.data.description = esm.getDesc();
info.data.masters = esm.getMasters(); info.data.masters = esm.getGameFiles();
if (!quiet) if (!quiet)
{ {
std::cout << "Author: " << esm.getAuthor() << std::endl std::cout << "Author: " << esm.getAuthor() << std::endl
<< "Description: " << esm.getDesc() << std::endl << "Description: " << esm.getDesc() << std::endl
<< "File format version: " << esm.getFVer() << std::endl; << "File format version: " << esm.getFVer() << std::endl;
std::vector<ESM::Header::MasterData> m = esm.getMasters(); std::vector<ESM::Header::MasterData> m = esm.getGameFiles();
if (!m.empty()) if (!m.empty())
{ {
std::cout << "Masters:" << std::endl; std::cout << "Masters:" << std::endl;
@ -339,6 +342,8 @@ int load(Arguments& info)
} }
std::string id = esm.getHNOString("NAME"); std::string id = esm.getHNOString("NAME");
if (id.empty())
id = esm.getHNOString("INAM");
if(!quiet && interested) if(!quiet && interested)
std::cout << "\nRecord: " << n.toString() std::cout << "\nRecord: " << n.toString()

@ -680,7 +680,7 @@ std::string creatureFlags(int flags)
if (flags & ESM::Creature::Walks) properties += "Walks "; if (flags & ESM::Creature::Walks) properties += "Walks ";
if (flags & ESM::Creature::Swims) properties += "Swims "; if (flags & ESM::Creature::Swims) properties += "Swims ";
if (flags & ESM::Creature::Flies) properties += "Flies "; if (flags & ESM::Creature::Flies) properties += "Flies ";
if (flags & ESM::Creature::Biped) properties += "Biped "; if (flags & ESM::Creature::Bipedal) properties += "Bipedal ";
if (flags & ESM::Creature::Respawn) properties += "Respawn "; if (flags & ESM::Creature::Respawn) properties += "Respawn ";
if (flags & ESM::Creature::Weapon) properties += "Weapon "; if (flags & ESM::Creature::Weapon) properties += "Weapon ";
if (flags & ESM::Creature::Skeleton) properties += "Skeleton "; if (flags & ESM::Creature::Skeleton) properties += "Skeleton ";
@ -691,7 +691,7 @@ std::string creatureFlags(int flags)
ESM::Creature::Walks| ESM::Creature::Walks|
ESM::Creature::Swims| ESM::Creature::Swims|
ESM::Creature::Flies| ESM::Creature::Flies|
ESM::Creature::Biped| ESM::Creature::Bipedal|
ESM::Creature::Respawn| ESM::Creature::Respawn|
ESM::Creature::Weapon| ESM::Creature::Weapon|
ESM::Creature::Skeleton| ESM::Creature::Skeleton|
@ -717,16 +717,26 @@ std::string landFlags(int flags)
return properties; return properties;
} }
std::string leveledListFlags(int flags) std::string itemListFlags(int flags)
{ {
std::string properties = ""; std::string properties = "";
if (flags == 0) properties += "[None] "; if (flags == 0) properties += "[None] ";
if (flags & ESM::LeveledListBase::AllLevels) properties += "AllLevels "; if (flags & ESM::ItemLevList::AllLevels) properties += "AllLevels ";
// This flag apparently not present on creature lists... if (flags & ESM::ItemLevList::Each) properties += "Each ";
if (flags & ESM::LeveledListBase::Each) properties += "Each ";
int unused = (0xFFFFFFFF ^ int unused = (0xFFFFFFFF ^
(ESM::LeveledListBase::AllLevels| (ESM::ItemLevList::AllLevels|
ESM::LeveledListBase::Each)); ESM::ItemLevList::Each));
if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
}
std::string creatureListFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::CreatureLevList::AllLevels) properties += "AllLevels ";
int unused = (0xFFFFFFFF ^ ESM::CreatureLevList::AllLevels);
if (flags & unused) properties += "Invalid "; if (flags & unused) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags); properties += str(boost::format("(0x%08X)") % flags);
return properties; return properties;
@ -764,34 +774,19 @@ std::string magicEffectFlags(int flags)
{ {
std::string properties = ""; std::string properties = "";
if (flags == 0) properties += "[None] "; if (flags == 0) properties += "[None] ";
// Enchanting & SpellMaking occur on the same list of effects. if (flags & ESM::MagicEffect::TargetAttribute) properties += "TargetAttribute ";
// "EXTRA SPELL" appears in the construction set under both the if (flags & ESM::MagicEffect::TargetSkill) properties += "TargetSkill ";
// spell making and enchanting tabs as an allowed effect. Since
// most of the effects without this flags are defective in various
// ways, it's still very unclear what these flag bits are.
if (flags & ESM::MagicEffect::SpellMaking) properties += "SpellMaking ";
if (flags & ESM::MagicEffect::Enchanting) properties += "Enchanting ";
if (flags & 0x00000040) properties += "RangeNoSelf ";
if (flags & 0x00000080) properties += "RangeTouch ";
if (flags & 0x00000100) properties += "RangeTarget ";
if (flags & 0x00001000) properties += "Unknown2 ";
if (flags & 0x00000001) properties += "AffectSkill ";
if (flags & 0x00000002) properties += "AffectAttribute ";
if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration "; if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration ";
if (flags & 0x00000008) properties += "NoMagnitude "; if (flags & ESM::MagicEffect::NoMagnitude) properties += "NoMagnitude ";
if (flags & 0x00000010) properties += "Negative "; if (flags & ESM::MagicEffect::Harmful) properties += "Harmful ";
if (flags & 0x00000020) properties += "Unknown1 "; if (flags & ESM::MagicEffect::ContinuousVfx) properties += "ContinuousVFX ";
// ESM componet says 0x800 is negative, but none of the magic if (flags & ESM::MagicEffect::CastSelf) properties += "CastSelf ";
// effects have this flags set. if (flags & ESM::MagicEffect::CastTouch) properties += "CastTouch ";
if (flags & ESM::MagicEffect::Negative) properties += "Unused "; if (flags & ESM::MagicEffect::CastTarget) properties += "CastTarget ";
// Since only Chameleon has this flag it could be anything if (flags & ESM::MagicEffect::UncappedDamage) properties += "UncappedDamage ";
// that uniquely distinguishes Chameleon. if (flags & ESM::MagicEffect::NonRecastable) properties += "NonRecastable ";
if (flags & 0x00002000) properties += "Chameleon "; if (flags & ESM::MagicEffect::Unreflectable) properties += "Unreflectable ";
if (flags & 0x00004000) properties += "Bound "; if (flags & ESM::MagicEffect::CasterLinked) properties += "CasterLinked ";
if (flags & 0x00008000) properties += "Summon ";
// Calm, Demoralize, Frenzy, Lock, Open, Rally, Soultrap, Turn Unded
if (flags & 0x00010000) properties += "Unknown3 ";
if (flags & 0x00020000) properties += "Absorb ";
if (flags & 0xFFFC0000) properties += "Invalid "; if (flags & 0xFFFC0000) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags); properties += str(boost::format("(0x%08X)") % flags);
return properties; return properties;

@ -50,7 +50,8 @@ std::string cellFlags(int flags);
std::string containerFlags(int flags); std::string containerFlags(int flags);
std::string creatureFlags(int flags); std::string creatureFlags(int flags);
std::string landFlags(int flags); std::string landFlags(int flags);
std::string leveledListFlags(int flags); std::string creatureListFlags(int flags);
std::string itemListFlags(int flags);
std::string lightFlags(int flags); std::string lightFlags(int flags);
std::string magicEffectFlags(int flags); std::string magicEffectFlags(int flags);
std::string npcFlags(int flags); std::string npcFlags(int flags);

@ -13,8 +13,8 @@ void printAIPackage(ESM::AIPackage p)
std::cout << " Distance: " << p.mWander.mDistance << std::endl; std::cout << " Distance: " << p.mWander.mDistance << std::endl;
std::cout << " Duration: " << p.mWander.mDuration << std::endl; std::cout << " Duration: " << p.mWander.mDuration << std::endl;
std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl; std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl;
if (p.mWander.mUnk != 1) if (p.mWander.mShouldRepeat != 1)
std::cout << " Unknown: " << (int)p.mWander.mUnk << std::endl; std::cout << " Should repeat: " << (bool)p.mWander.mShouldRepeat << std::endl;
std::cout << " Idle: "; std::cout << " Idle: ";
for (int i = 0; i != 8; i++) for (int i = 0; i != 8; i++)
@ -740,8 +740,8 @@ void Record<ESM::DialInfo>::print()
if (mData.mClass != "") if (mData.mClass != "")
std::cout << " Class: " << mData.mClass << std::endl; std::cout << " Class: " << mData.mClass << std::endl;
std::cout << " Factionless: " << mData.mFactionLess << std::endl; std::cout << " Factionless: " << mData.mFactionLess << std::endl;
if (mData.mNpcFaction != "") if (mData.mFaction != "")
std::cout << " NPC Faction: " << mData.mNpcFaction << std::endl; std::cout << " NPC Faction: " << mData.mFaction << std::endl;
if (mData.mData.mRank != -1) if (mData.mData.mRank != -1)
std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl; std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl;
if (mData.mPcFaction != "") if (mData.mPcFaction != "")
@ -834,7 +834,7 @@ template<>
void Record<ESM::CreatureLevList>::print() void Record<ESM::CreatureLevList>::print()
{ {
std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl;
std::cout << " Flags: " << leveledListFlags(mData.mFlags) << std::endl; std::cout << " Flags: " << creatureListFlags(mData.mFlags) << std::endl;
std::cout << " Number of items: " << mData.mList.size() << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl;
std::vector<ESM::LeveledListBase::LevelItem>::iterator iit; std::vector<ESM::LeveledListBase::LevelItem>::iterator iit;
for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++) for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++)
@ -846,11 +846,11 @@ template<>
void Record<ESM::ItemLevList>::print() void Record<ESM::ItemLevList>::print()
{ {
std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl;
std::cout << " Flags: " << leveledListFlags(mData.mFlags) << std::endl; std::cout << " Flags: " << itemListFlags(mData.mFlags) << std::endl;
std::cout << " Number of items: " << mData.mList.size() << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl;
std::vector<ESM::LeveledListBase::LevelItem>::iterator iit; std::vector<ESM::LeveledListBase::LevelItem>::iterator iit;
for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++) for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++)
std::cout << " Inventory: Count: " << iit->mLevel std::cout << " Inventory: Level: " << iit->mLevel
<< " Item: " << iit->mId << std::endl; << " Item: " << iit->mId << std::endl;
} }
@ -958,7 +958,7 @@ void Record<ESM::MagicEffect>::print()
std::cout << " RGB Color: " << "(" std::cout << " RGB Color: " << "("
<< mData.mData.mRed << "," << mData.mData.mRed << ","
<< mData.mData.mGreen << "," << mData.mData.mGreen << ","
<< mData.mData.mGreen << ")" << std::endl; << mData.mData.mBlue << ")" << std::endl;
} }
template<> template<>
@ -989,8 +989,7 @@ void Record<ESM::NPC>::print()
std::cout << " Faction: " << mData.mFaction << std::endl; std::cout << " Faction: " << mData.mFaction << std::endl;
std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl; std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl;
// Seriously? if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
if (mData.mNpdt52.mGold == -10)
{ {
std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl; std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl;
std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl;
@ -1022,7 +1021,7 @@ void Record<ESM::NPC>::print()
std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl; std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl;
std::cout << " Skills:" << std::endl; std::cout << " Skills:" << std::endl;
for (int i = 0; i != 27; i++) for (int i = 0; i != ESM::Skill::Length; i++)
std::cout << " " << skillLabel(i) << ": " std::cout << " " << skillLabel(i) << ": "
<< (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl; << (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl;

@ -24,7 +24,12 @@ namespace EsmTool
bool mPrintPlain; bool mPrintPlain;
public: public:
RecordBase () { mPrintPlain = false; } RecordBase ()
: mFlags(0)
, mPrintPlain(false)
{
}
virtual ~RecordBase() {} virtual ~RecordBase() {}
const std::string &getId() const { const std::string &getId() const {

@ -11,7 +11,9 @@ set(LAUNCHER
settings/launchersettings.cpp settings/launchersettings.cpp
utils/checkablemessagebox.cpp utils/checkablemessagebox.cpp
utils/profilescombobox.cpp
utils/textinputdialog.cpp utils/textinputdialog.cpp
utils/lineedit.cpp
${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc
) )
@ -32,8 +34,9 @@ set(LAUNCHER_HEADER
settings/settingsbase.hpp settings/settingsbase.hpp
utils/checkablemessagebox.hpp utils/checkablemessagebox.hpp
utils/profilescombobox.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/lineedit.hpp
) )
if(NOT WIN32) if(NOT WIN32)
LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp) LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp)
@ -48,8 +51,11 @@ set(LAUNCHER_HEADER_MOC
playpage.hpp playpage.hpp
textslotmsgbox.hpp textslotmsgbox.hpp
utils/checkablemessagebox.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
utils/checkablemessagebox.hpp
utils/profilescombobox.hpp
utils/lineedit.hpp
) )
if(NOT WIN32) if(NOT WIN32)
@ -62,6 +68,7 @@ set(LAUNCHER_UI
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui
${CMAKE_SOURCE_DIR}/files/ui/playpage.ui ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
) )
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
@ -130,8 +137,3 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(omwlauncher gcov) target_link_libraries(omwlauncher gcov)
endif() endif()
# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream
if (UNIX AND NOT APPLE)
target_link_libraries(omwlauncher dl Xt)
endif()

@ -4,548 +4,300 @@
#include <QMessageBox> #include <QMessageBox>
#include <QCheckBox> #include <QCheckBox>
#include <QMenu> #include <QMenu>
#include <QSortFilterProxyModel>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/fileorderlist/model/datafilesmodel.hpp> #include <components/contentselector/model/esmfile.hpp>
#include <components/fileorderlist/model/pluginsproxymodel.hpp>
#include <components/fileorderlist/model/esm/esmfile.hpp>
#include <components/fileorderlist/utils/lineedit.hpp> #include <components/contentselector/model/naturalsort.hpp>
#include <components/fileorderlist/utils/naturalsort.hpp>
#include <components/fileorderlist/utils/profilescombobox.hpp> #include "utils/textinputdialog.hpp"
#include "utils/profilescombobox.hpp"
#include "settings/gamesettings.hpp" #include "settings/gamesettings.hpp"
#include "settings/launchersettings.hpp" #include "settings/launchersettings.hpp"
#include "utils/textinputdialog.hpp" #include "components/contentselector/view/contentselector.hpp"
DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent)
: mCfgMgr(cfg) : mCfgMgr(cfg)
, mGameSettings(gameSettings) , mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings) , mLauncherSettings(launcherSettings)
, QWidget(parent) , QWidget(parent)
{ {
setupUi(this); ui.setupUi (this);
setObjectName ("DataFilesPage");
// Models mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget);
mDataFilesModel = new DataFilesModel(this);
mMastersProxyModel = new QSortFilterProxyModel();
mMastersProxyModel->setFilterRegExp(QString("^.*\\.esm"));
mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
mMastersProxyModel->setSourceModel(mDataFilesModel);
mPluginsProxyModel = new PluginsProxyModel();
mPluginsProxyModel->setFilterRegExp(QString("^.*\\.esp"));
mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
mPluginsProxyModel->setSourceModel(mDataFilesModel);
mFilterProxyModel = new QSortFilterProxyModel();
mFilterProxyModel->setDynamicSortFilter(true);
mFilterProxyModel->setSourceModel(mPluginsProxyModel);
QCheckBox checkBox;
unsigned int height = checkBox.sizeHint().height() + 4;
mastersTable->setModel(mMastersProxyModel);
mastersTable->setObjectName("MastersTable");
mastersTable->setContextMenuPolicy(Qt::CustomContextMenu);
mastersTable->setSortingEnabled(false);
mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows);
mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
mastersTable->setAlternatingRowColors(true);
mastersTable->horizontalHeader()->setStretchLastSection(true);
mastersTable->horizontalHeader()->hide();
// Set the row height to the size of the checkboxes
mastersTable->verticalHeader()->setDefaultSectionSize(height);
mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
mastersTable->verticalHeader()->hide();
pluginsTable->setModel(mFilterProxyModel);
pluginsTable->setObjectName("PluginsTable");
pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu);
pluginsTable->setSortingEnabled(false);
pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
pluginsTable->setAlternatingRowColors(true);
pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
pluginsTable->horizontalHeader()->setStretchLastSection(true);
pluginsTable->horizontalHeader()->hide();
pluginsTable->verticalHeader()->setDefaultSectionSize(height);
pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
// Adjust the tableview widths inside the splitter
QList<int> sizeList;
sizeList << mLauncherSettings.value(QString("General/MastersTable/width"), QString("200")).toInt();
sizeList << mLauncherSettings.value(QString("General/PluginTable/width"), QString("340")).toInt();
splitter->setSizes(sizeList);
// Create a dialog for the new profile name input
mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this);
connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int)));
connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString)));
connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
connect(pluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
connect(mastersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews()));
connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString)));
connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter())); buildView();
createActions();
setupDataFiles(); setupDataFiles();
} }
void DataFilesPage::createActions() void Launcher::DataFilesPage::loadSettings()
{
// Add the actions to the toolbuttons
newProfileButton->setDefaultAction(newProfileAction);
deleteProfileButton->setDefaultAction(deleteProfileAction);
// Context menu actions
mContextMenu = new QMenu(this);
mContextMenu->addAction(checkAction);
mContextMenu->addAction(uncheckAction);
}
void DataFilesPage::setupDataFiles()
{ {
// Set the encoding to the one found in openmw.cfg or the default
mDataFilesModel->setEncoding(mGameSettings.value(QString("encoding"), QString("win1252")));
QStringList paths = mGameSettings.getDataDirs(); QStringList paths = mGameSettings.getDataDirs();
paths.insert (0, mDataLocal);
PathIterator pathIterator (paths);
foreach (const QString &path, paths) { QString profileName = ui.profilesComboBox->currentText();
mDataFilesModel->addFiles(path);
}
QString dataLocal = mGameSettings.getDataLocal();
if (!dataLocal.isEmpty())
mDataFilesModel->addFiles(dataLocal);
// Sort by date accessed for now
mDataFilesModel->sort(3);
QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/"));
QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
if (!profiles.isEmpty())
profilesComboBox->addItems(profiles);
// Add the current profile if empty
if (profilesComboBox->findText(profile) == -1 && !profile.isEmpty())
profilesComboBox->addItem(profile);
if (profilesComboBox->findText(QString("Default")) == -1)
profilesComboBox->addItem(QString("Default"));
if (profile.isEmpty() || profile == QLatin1String("Default")) {
deleteProfileAction->setEnabled(false);
profilesComboBox->setEditEnabled(false);
profilesComboBox->setCurrentIndex(profilesComboBox->findText(QString("Default")));
} else {
profilesComboBox->setEditEnabled(true);
profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile));
}
// We do this here to prevent deletion of profiles when initializing the combobox
connect(profilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString)));
connect(profilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString)));
loadSettings();
}
void DataFilesPage::loadSettings()
{
QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
if (profile.isEmpty()) QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName, Qt::MatchExactly);
return;
mDataFilesModel->uncheckAll(); QStringList filepaths;
QStringList masters = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); foreach (const QString &file, files)
QStringList plugins = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly); {
QString filepath = pathIterator.findFirstPath (file);
foreach (const QString &master, masters) { if (!filepath.isEmpty())
QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(master)); filepaths << filepath;
if (index.isValid())
mDataFilesModel->setCheckState(index, Qt::Checked);
} }
foreach (const QString &plugin, plugins) { mSelector->setProfileContent (filepaths);
QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(plugin));
if (index.isValid())
mDataFilesModel->setCheckState(index, Qt::Checked);
}
} }
void DataFilesPage::saveSettings() void Launcher::DataFilesPage::saveSettings(const QString &profile)
{ {
if (mDataFilesModel->rowCount() < 1) QString profileName = profile;
return;
QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); if (profileName.isEmpty())
profileName = ui.profilesComboBox->currentText();
if (profile.isEmpty()) {
profile = profilesComboBox->currentText();
mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile);
}
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); //retrieve the files selected for the profile
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
mGameSettings.remove(QString("master")); removeProfile (profileName);
mGameSettings.remove(QString("plugin"));
QStringList items = mDataFilesModel->checkedItems(); mGameSettings.remove(QString("content"));
foreach(const QString &item, items) { //set the value of the current profile (not necessarily the profile being saved!)
mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText());
if (item.endsWith(QString(".esm"), Qt::CaseInsensitive)) { foreach(const ContentSelectorModel::EsmFile *item, items) {
mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item); mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName());
mGameSettings.setMultiValue(QString("master"), item); mGameSettings.setMultiValue(QString("content"), item->fileName());
} else if (item.endsWith(QString(".esp"), Qt::CaseInsensitive)) {
mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item);
mGameSettings.setMultiValue(QString("plugin"), item);
}
} }
} }
void DataFilesPage::updateOkButton(const QString &text) void Launcher::DataFilesPage::buildView()
{ {
// We do this here because we need the profiles combobox text ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
if (text.isEmpty()) {
mNewProfileDialog->setOkButtonEnabled(false);
return;
}
(profilesComboBox->findText(text) == -1) //tool buttons
? mNewProfileDialog->setOkButtonEnabled(true) ui.newProfileButton->setToolTip ("Create a new profile");
: mNewProfileDialog->setOkButtonEnabled(false); ui.deleteProfileButton->setToolTip ("Delete an existing profile");
}
void DataFilesPage::updateSplitter() //combo box
{ ui.profilesComboBox->addItem ("Default");
// Sigh, update the saved splitter size in settings only when moved ui.profilesComboBox->setPlaceholderText (QString("Select a profile..."));
// Since getting mSplitter->sizes() if page is hidden returns invalid values
QList<int> sizes = splitter->sizes();
mLauncherSettings.setValue(QString("General/MastersTable/width"), QString::number(sizes.at(0))); // Add the actions to the toolbuttons
mLauncherSettings.setValue(QString("General/PluginsTable/width"), QString::number(sizes.at(1))); ui.newProfileButton->setDefaultAction (ui.newProfileAction);
} ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction);
void DataFilesPage::updateViews() //establish connections
{ connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)),
// Ensure the columns are hidden because sort() re-enables them this, SLOT (slotProfileChanged(int)));
mastersTable->setColumnHidden(1, true);
mastersTable->setColumnHidden(2, true);
mastersTable->setColumnHidden(3, true);
mastersTable->setColumnHidden(4, true);
mastersTable->setColumnHidden(5, true);
mastersTable->setColumnHidden(6, true);
mastersTable->setColumnHidden(7, true);
mastersTable->setColumnHidden(8, true);
pluginsTable->setColumnHidden(1, true);
pluginsTable->setColumnHidden(2, true);
pluginsTable->setColumnHidden(3, true);
pluginsTable->setColumnHidden(4, true);
pluginsTable->setColumnHidden(5, true);
pluginsTable->setColumnHidden(6, true);
pluginsTable->setColumnHidden(7, true);
pluginsTable->setColumnHidden(8, true);
}
void DataFilesPage::setProfilesComboBoxIndex(int index) connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)),
{ this, SLOT (slotProfileRenamed(QString, QString)));
profilesComboBox->setCurrentIndex(index);
connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)),
this, SLOT (slotProfileChangedByUser(QString, QString)));
} }
void DataFilesPage::slotCurrentIndexChanged(int index) void Launcher::DataFilesPage::removeProfile(const QString &profile)
{ {
emit profileChanged(index); mLauncherSettings.remove(QString("Profiles/") + profile);
} }
QAbstractItemModel* DataFilesPage::profilesComboBoxModel() QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const
{ {
return profilesComboBox->model(); return ui.profilesComboBox->model();
} }
int DataFilesPage::profilesComboBoxIndex() int Launcher::DataFilesPage::profilesIndex() const
{ {
return profilesComboBox->currentIndex(); return ui.profilesComboBox->currentIndex();
} }
void DataFilesPage::on_newProfileAction_triggered() void Launcher::DataFilesPage::setProfile(int index, bool savePrevious)
{ {
if (mNewProfileDialog->exec() == QDialog::Accepted) { if (index >= -1 && index < ui.profilesComboBox->count())
QString profile = mNewProfileDialog->lineEdit()->text(); {
profilesComboBox->addItem(profile); QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex());
profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile)); QString current = ui.profilesComboBox->itemText(index);
setProfile (previous, current, savePrevious);
} }
} }
void DataFilesPage::on_deleteProfileAction_triggered() void Launcher::DataFilesPage::setProfile (const QString &previous, const QString &current, bool savePrevious)
{ {
QString profile = profilesComboBox->currentText(); //abort if no change (poss. duplicate signal)
if (previous == current)
if (profile.isEmpty()) return;
return;
QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Delete Profile"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Cancel);
msgBox.setText(tr("Are you sure you want to delete <b>%0</b>?").arg(profile));
QAbstractButton *deleteButton = if (!previous.isEmpty() && savePrevious)
msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); saveSettings (previous);
msgBox.exec(); ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current));
if (msgBox.clickedButton() == deleteButton) { loadSettings();
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master"));
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin"));
// Remove the profile from the combobox checkForDefaultProfile();
profilesComboBox->removeItem(profilesComboBox->findText(profile));
}
} }
void DataFilesPage::on_checkAction_triggered() void Launcher::DataFilesPage::slotProfileDeleted (const QString &item)
{ {
if (pluginsTable->hasFocus()) removeProfile (item);
setPluginsCheckstates(Qt::Checked);
if (mastersTable->hasFocus())
setMastersCheckstates(Qt::Checked);
} }
void DataFilesPage::on_uncheckAction_triggered() void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString &current)
{ {
if (pluginsTable->hasFocus()) setProfile(previous, current, true);
setPluginsCheckstates(Qt::Unchecked); emit signalProfileChanged (ui.profilesComboBox->findText(current));
if (mastersTable->hasFocus())
setMastersCheckstates(Qt::Unchecked);
} }
void DataFilesPage::setMastersCheckstates(Qt::CheckState state) void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const QString &current)
{ {
if (!mastersTable->selectionModel()->hasSelection()) { if (previous.isEmpty())
return; return;
}
QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes();
foreach (const QModelIndex &index, indexes)
{
if (!index.isValid())
return;
QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); // Save the new profile name
saveSettings();
if (!sourceIndex.isValid()) // Remove the old one
return; removeProfile (previous);
mDataFilesModel->setCheckState(sourceIndex, state); loadSettings();
}
} }
void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) void Launcher::DataFilesPage::slotProfileChanged(int index)
{ {
if (!pluginsTable->selectionModel()->hasSelection()) { setProfile (index, true);
return;
}
QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes();
foreach (const QModelIndex &index, indexes)
{
if (!index.isValid())
return;
QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
mFilterProxyModel->mapToSource(index));
if (!sourceIndex.isValid())
return;
mDataFilesModel->setCheckState(sourceIndex, state);
}
} }
void DataFilesPage::setCheckState(QModelIndex index) void Launcher::DataFilesPage::setupDataFiles()
{ {
if (!index.isValid()) QStringList paths = mGameSettings.getDataDirs();
return;
QObject *object = QObject::sender(); foreach (const QString &path, paths)
mSelector->addFiles(path);
// Not a signal-slot call mDataLocal = mGameSettings.getDataLocal();
if (!object)
return;
if (!mDataLocal.isEmpty())
mSelector->addFiles(mDataLocal);
if (object->objectName() == QLatin1String("PluginsTable")) { QStringList profiles;
QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile");
mFilterProxyModel->mapToSource(index));
if (sourceIndex.isValid()) { foreach (QString key, mLauncherSettings.getSettings().keys())
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked) {
? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) if (key.contains("Profiles/"))
: mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); {
QString profile = key.mid (9);
if (profile != "currentprofile")
{
if (!profiles.contains(profile))
profiles << profile;
}
} }
} }
if (object->objectName() == QLatin1String("MastersTable")) { foreach (const QString &item, profiles)
QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); addProfile (item, false);
if (sourceIndex.isValid()) {
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
: mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
}
}
return; setProfile (ui.profilesComboBox->findText(currentProfile), false);
}
void DataFilesPage::filterChanged(const QString filter) loadSettings();
{
QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString);
mFilterProxyModel->setFilterRegExp(regExp);
} }
void DataFilesPage::profileChanged(const QString &previous, const QString &current) void Launcher::DataFilesPage::on_newProfileAction_triggered()
{ {
// Prevent the deletion of the default profile TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this);
if (current == QLatin1String("Default")) {
deleteProfileAction->setEnabled(false);
profilesComboBox->setEditEnabled(false);
} else {
deleteProfileAction->setEnabled(true);
profilesComboBox->setEditEnabled(true);
}
if (previous.isEmpty()) if (newDialog.exec() != QDialog::Accepted)
return; return;
if (profilesComboBox->findText(previous) == -1) QString profile = newDialog.getText();
return; // Profile was deleted
// Store the previous profile
mLauncherSettings.setValue(QString("Profiles/currentprofile"), previous);
saveSettings();
mLauncherSettings.setValue(QString("Profiles/currentprofile"), current);
loadSettings();
}
void DataFilesPage::profileRenamed(const QString &previous, const QString &current) if (profile.isEmpty())
{ return;
if (previous.isEmpty())
return;
// Save the new profile name
mLauncherSettings.setValue(QString("Profiles/currentprofile"), current);
saveSettings(); saveSettings();
// Remove the old one mSelector->clearCheckStates();
mLauncherSettings.remove(QString("Profiles/") + previous + QString("/master"));
mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin"));
// Remove the profile from the combobox addProfile(profile, true);
profilesComboBox->removeItem(profilesComboBox->findText(previous));
loadSettings(); mSelector->setGameFile();
saveSettings();
emit signalProfileChanged (ui.profilesComboBox->findText(profile));
} }
void DataFilesPage::showContextMenu(const QPoint &point) void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurrent)
{ {
QObject *object = QObject::sender(); if (profile.isEmpty())
return;
// Not a signal-slot call if (ui.profilesComboBox->findText (profile) != -1)
if (!object)
return; return;
if (object->objectName() == QLatin1String("PluginsTable")) { ui.profilesComboBox->addItem (profile);
if (!pluginsTable->selectionModel()->hasSelection())
return;
QPoint globalPos = pluginsTable->mapToGlobal(point); if (setAsCurrent)
QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes(); setProfile (ui.profilesComboBox->findText (profile), false);
}
// Show the check/uncheck actions depending on the state of the selected items void Launcher::DataFilesPage::on_deleteProfileAction_triggered()
uncheckAction->setEnabled(false); {
checkAction->setEnabled(false); QString profile = ui.profilesComboBox->currentText();
foreach (const QModelIndex &index, indexes) if (profile.isEmpty())
{ return;
if (!index.isValid())
return;
QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( if (!showDeleteMessageBox (profile))
mFilterProxyModel->mapToSource(index)); return;
if (!sourceIndex.isValid()) // Remove the profile from the combobox
return; ui.profilesComboBox->removeItem (ui.profilesComboBox->findText (profile));
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked) removeProfile(profile);
? uncheckAction->setEnabled(true)
: checkAction->setEnabled(true);
}
// Show menu saveSettings();
mContextMenu->exec(globalPos);
}
if (object->objectName() == QLatin1String("MastersTable")) { loadSettings();
if (!mastersTable->selectionModel()->hasSelection())
return;
QPoint globalPos = mastersTable->mapToGlobal(point); checkForDefaultProfile();
QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); }
// Show the check/uncheck actions depending on the state of the selected items void Launcher::DataFilesPage::checkForDefaultProfile()
uncheckAction->setEnabled(false); {
checkAction->setEnabled(false); //don't allow deleting "Default" profile
bool success = (ui.profilesComboBox->currentText() != "Default");
foreach (const QModelIndex &index, indexes) ui.deleteProfileAction->setEnabled (success);
{ ui.profilesComboBox->setEditEnabled (success);
if (!index.isValid()) }
return;
QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text)
{
QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Delete Profile"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Cancel);
msgBox.setText(tr("Are you sure you want to delete <b>%0</b>?").arg(text));
if (!sourceIndex.isValid()) QAbstractButton *deleteButton =
return; msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked) msgBox.exec();
? uncheckAction->setEnabled(true)
: checkAction->setEnabled(true);
}
mContextMenu->exec(globalPos); return (msgBox.clickedButton() == deleteButton);
}
} }

@ -1,88 +1,136 @@
#ifndef DATAFILESPAGE_H #ifndef DATAFILESPAGE_H
#define DATAFILESPAGE_H #define DATAFILESPAGE_H
#include "ui_datafilespage.h"
#include <QWidget> #include <QWidget>
#include <QModelIndex>
#include "ui_datafilespage.h"
#include <QDir>
#include <QFile>
class QSortFilterProxyModel; class QSortFilterProxyModel;
class QAbstractItemModel; class QAbstractItemModel;
class QAction;
class QMenu; class QMenu;
class DataFilesModel;
class TextInputDialog;
class GameSettings;
class LauncherSettings;
class PluginsProxyModel;
namespace Files { struct ConfigurationManager; } namespace Files { struct ConfigurationManager; }
namespace ContentSelectorView { class ContentSelector; }
class DataFilesPage : public QWidget, private Ui::DataFilesPage namespace Launcher
{ {
Q_OBJECT class TextInputDialog;
class GameSettings;
class LauncherSettings;
class ProfilesComboBox;
class DataFilesPage : public QWidget
{
Q_OBJECT
ContentSelectorView::ContentSelector *mSelector;
Ui::DataFilesPage ui;
public:
explicit DataFilesPage (Files::ConfigurationManager &cfg, GameSettings &gameSettings,
LauncherSettings &launcherSettings, QWidget *parent = 0);
QAbstractItemModel* profilesModel() const;
int profilesIndex() const;
//void writeConfig(QString profile = QString());
void saveSettings(const QString &profile = "");
void loadSettings();
signals:
void signalProfileChanged (int index);
public slots:
void slotProfileChanged (int index);
private slots:
void slotProfileChangedByUser(const QString &previous, const QString &current);
void slotProfileRenamed(const QString &previous, const QString &current);
void slotProfileDeleted(const QString &item);
public: void on_newProfileAction_triggered();
DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0); void on_deleteProfileAction_triggered();
QAbstractItemModel* profilesComboBoxModel(); private:
int profilesComboBoxIndex();
void writeConfig(QString profile = QString()); QMenu *mContextMenu;
void saveSettings();
signals: Files::ConfigurationManager &mCfgMgr;
void profileChanged(int index);
public slots: GameSettings &mGameSettings;
void setCheckState(QModelIndex index); LauncherSettings &mLauncherSettings;
void setProfilesComboBoxIndex(int index);
void filterChanged(const QString filter); QString mDataLocal;
void showContextMenu(const QPoint &point);
void profileChanged(const QString &previous, const QString &current);
void profileRenamed(const QString &previous, const QString &current);
void updateOkButton(const QString &text);
void updateSplitter();
void updateViews();
// Action slots void setPluginsCheckstates(Qt::CheckState state);
void on_newProfileAction_triggered();
void on_deleteProfileAction_triggered();
void on_checkAction_triggered();
void on_uncheckAction_triggered();
private slots: void buildView();
void slotCurrentIndexChanged(int index); void setupDataFiles();
void setupConfig();
void readConfig();
void setProfile (int index, bool savePrevious);
void setProfile (const QString &previous, const QString &current, bool savePrevious);
void removeProfile (const QString &profile);
bool showDeleteMessageBox (const QString &text);
void addProfile (const QString &profile, bool setAsCurrent);
void checkForDefaultProfile();
private: class PathIterator
DataFilesModel *mDataFilesModel; {
QStringList::ConstIterator mCitEnd;
QStringList::ConstIterator mCitCurrent;
QStringList::ConstIterator mCitBegin;
QString mFile;
QString mFilePath;
PluginsProxyModel *mPluginsProxyModel; public:
QSortFilterProxyModel *mMastersProxyModel; PathIterator (const QStringList &list)
{
mCitBegin = list.constBegin();
mCitCurrent = mCitBegin;
mCitEnd = list.constEnd();
}
QSortFilterProxyModel *mFilterProxyModel; QString findFirstPath (const QString &file)
{
mCitCurrent = mCitBegin;
mFile = file;
return path();
}
QMenu *mContextMenu; QString findNextPath () { return path(); }
Files::ConfigurationManager &mCfgMgr; private:
GameSettings &mGameSettings; QString path ()
LauncherSettings &mLauncherSettings; {
bool success = false;
QDir dir;
QFileInfo file;
TextInputDialog *mNewProfileDialog; while (!success)
{
if (mCitCurrent == mCitEnd)
break;
void setMastersCheckstates(Qt::CheckState state); dir.setPath (*(mCitCurrent++));
void setPluginsCheckstates(Qt::CheckState state); file.setFile (dir.absoluteFilePath (mFile));
void createActions(); success = file.exists();
void setupDataFiles(); }
void setupConfig();
void readConfig();
void loadSettings(); if (success)
return file.absoluteFilePath();
}; return "";
}
};
};
}
#endif #endif

@ -4,20 +4,19 @@
#include <QMessageBox> #include <QMessageBox>
#include <QDir> #include <QDir>
#ifdef __APPLE__ #ifdef MAC_OS_X_VERSION_MIN_REQUIRED
#undef MAC_OS_X_VERSION_MIN_REQUIRED
// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#endif #endif // MAC_OS_X_VERSION_MIN_REQUIRED
#include <SDL.h>
#include <cstdlib> #include <SDL.h>
#include <boost/math/common_factor.hpp> #include <boost/math/common_factor.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/files/ogreplugin.hpp>
#include <components/fileorderlist/utils/naturalsort.hpp> #include <components/contentselector/model/naturalsort.hpp>
#include "settings/graphicssettings.hpp" #include "settings/graphicssettings.hpp"
@ -33,11 +32,12 @@ QString getAspect(int x, int y)
return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
} }
GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent)
: mCfgMgr(cfg) : mCfgMgr(cfg)
, mGraphicsSettings(graphicsSetting) , mGraphicsSettings(graphicsSetting)
, QWidget(parent) , QWidget(parent)
{ {
setObjectName ("GraphicsPage");
setupUi(this); setupUi(this);
// Set the maximum res we can set in windowed mode // Set the maximum res we can set in windowed mode
@ -52,15 +52,11 @@ GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &g
} }
bool GraphicsPage::setupOgre() bool Launcher::GraphicsPage::setupOgre()
{ {
// Create a log manager so we can surpress debug text to stdout/stderr
Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager;
logMgr->createLog((mCfgMgr.getLogPath().string() + "/launcherOgre.log"), true, false, false);
try try
{ {
mOgre = new Ogre::Root("", "", "./launcherOgre.log"); mOgre = mOgreInit.init(mCfgMgr.getLogPath().string() + "/launcherOgre.log");
} }
catch(Ogre::Exception &ex) catch(Ogre::Exception &ex)
{ {
@ -78,40 +74,6 @@ bool GraphicsPage::setupOgre()
return false; return false;
} }
std::string pluginDir;
const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR");
if (pluginEnv)
pluginDir = pluginEnv;
else
{
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
pluginDir = ".\\";
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
pluginDir = OGRE_PLUGIN_DIR;
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
pluginDir = OGRE_PLUGIN_DIR_REL;
#endif
}
QDir dir(QString::fromStdString(pluginDir));
pluginDir = dir.absolutePath().toStdString();
Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre);
Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mOgre);
Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre);
#ifdef ENABLE_PLUGIN_GL
mGLPlugin = new Ogre::GLPlugin();
mOgre->installPlugin(mGLPlugin);
#endif
#ifdef ENABLE_PLUGIN_Direct3D9
mD3D9Plugin = new Ogre::D3D9Plugin();
mOgre->installPlugin(mD3D9Plugin);
#endif
// Get the available renderers and put them in the combobox // Get the available renderers and put them in the combobox
const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers(); const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers();
@ -133,7 +95,7 @@ bool GraphicsPage::setupOgre()
msgBox.setIcon(QMessageBox::Critical); msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>Could not select a valid render system</b><br><br> \ msgBox.setText(tr("<br><b>Could not select a valid render system</b><br><br> \
Please make sure the plugins.cfg file exists and contains a valid rendering plugin.<br>")); Please make sure Ogre plugins were installed correctly.<br>"));
msgBox.exec(); msgBox.exec();
return false; return false;
} }
@ -156,7 +118,7 @@ bool GraphicsPage::setupOgre()
return true; return true;
} }
bool GraphicsPage::setupSDL() bool Launcher::GraphicsPage::setupSDL()
{ {
int displays = SDL_GetNumVideoDisplays(); int displays = SDL_GetNumVideoDisplays();
@ -179,7 +141,7 @@ bool GraphicsPage::setupSDL()
return true; return true;
} }
bool GraphicsPage::loadSettings() bool Launcher::GraphicsPage::loadSettings()
{ {
if (!setupSDL()) if (!setupSDL())
return false; return false;
@ -218,7 +180,7 @@ bool GraphicsPage::loadSettings()
return true; return true;
} }
void GraphicsPage::saveSettings() void Launcher::GraphicsPage::saveSettings()
{ {
vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true")) vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true"))
: mGraphicsSettings.setValue(QString("Video/vsync"), QString("false")); : mGraphicsSettings.setValue(QString("Video/vsync"), QString("false"));
@ -245,7 +207,7 @@ void GraphicsPage::saveSettings()
mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex())); mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex()));
} }
QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer) QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer)
{ {
QStringList result; QStringList result;
@ -278,7 +240,7 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy
return result; return result;
} }
QStringList GraphicsPage::getAvailableResolutions(int screen) QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
{ {
QStringList result; QStringList result;
SDL_DisplayMode mode; SDL_DisplayMode mode;
@ -325,7 +287,7 @@ QStringList GraphicsPage::getAvailableResolutions(int screen)
return result; return result;
} }
QRect GraphicsPage::getMaximumResolution() QRect Launcher::GraphicsPage::getMaximumResolution()
{ {
QRect max; QRect max;
int screens = QApplication::desktop()->screenCount(); int screens = QApplication::desktop()->screenCount();
@ -340,7 +302,7 @@ QRect GraphicsPage::getMaximumResolution()
return max; return max;
} }
void GraphicsPage::rendererChanged(const QString &renderer) void Launcher::GraphicsPage::rendererChanged(const QString &renderer)
{ {
mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString()); mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString());
@ -349,7 +311,7 @@ void GraphicsPage::rendererChanged(const QString &renderer)
antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
} }
void GraphicsPage::screenChanged(int screen) void Launcher::GraphicsPage::screenChanged(int screen)
{ {
if (screen >= 0) { if (screen >= 0) {
resolutionComboBox->clear(); resolutionComboBox->clear();
@ -357,7 +319,7 @@ void GraphicsPage::screenChanged(int screen)
} }
} }
void GraphicsPage::slotFullScreenChanged(int state) void Launcher::GraphicsPage::slotFullScreenChanged(int state)
{ {
if (state == Qt::Checked) { if (state == Qt::Checked) {
standardRadioButton->toggle(); standardRadioButton->toggle();
@ -371,7 +333,7 @@ void GraphicsPage::slotFullScreenChanged(int state)
} }
} }
void GraphicsPage::slotStandardToggled(bool checked) void Launcher::GraphicsPage::slotStandardToggled(bool checked)
{ {
if (checked) { if (checked) {
resolutionComboBox->setEnabled(true); resolutionComboBox->setEnabled(true);

@ -5,62 +5,53 @@
#include <OgreRoot.h> #include <OgreRoot.h>
#include <OgreRenderSystem.h> #include <OgreRenderSystem.h>
//#include <OgreConfigFile.h>
//#include <OgreConfigDialog.h>
// Static plugin headers #include <components/ogreinit/ogreinit.hpp>
#ifdef ENABLE_PLUGIN_GL
# include "OgreGLPlugin.h"
#endif
#ifdef ENABLE_PLUGIN_Direct3D9
# include "OgreD3D9Plugin.h"
#endif
#include "ui_graphicspage.h" #include "ui_graphicspage.h"
class GraphicsSettings;
namespace Files { struct ConfigurationManager; } namespace Files { struct ConfigurationManager; }
class GraphicsPage : public QWidget, private Ui::GraphicsPage namespace Launcher
{ {
Q_OBJECT class GraphicsSettings;
public: class GraphicsPage : public QWidget, private Ui::GraphicsPage
GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); {
Q_OBJECT
void saveSettings(); public:
bool loadSettings(); GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0);
public slots: void saveSettings();
void rendererChanged(const QString &renderer); bool loadSettings();
void screenChanged(int screen);
private slots: public slots:
void slotFullScreenChanged(int state); void rendererChanged(const QString &renderer);
void slotStandardToggled(bool checked); void screenChanged(int screen);
private: private slots:
Ogre::Root *mOgre; void slotFullScreenChanged(int state);
Ogre::RenderSystem *mSelectedRenderSystem; void slotStandardToggled(bool checked);
Ogre::RenderSystem *mOpenGLRenderSystem;
Ogre::RenderSystem *mDirect3DRenderSystem;
#ifdef ENABLE_PLUGIN_GL
Ogre::GLPlugin* mGLPlugin;
#endif
#ifdef ENABLE_PLUGIN_Direct3D9
Ogre::D3D9Plugin* mD3D9Plugin;
#endif
Files::ConfigurationManager &mCfgMgr; private:
GraphicsSettings &mGraphicsSettings; OgreInit::OgreInit mOgreInit;
Ogre::Root *mOgre;
Ogre::RenderSystem *mSelectedRenderSystem;
Ogre::RenderSystem *mOpenGLRenderSystem;
Ogre::RenderSystem *mDirect3DRenderSystem;
QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); Files::ConfigurationManager &mCfgMgr;
QStringList getAvailableResolutions(int screen); GraphicsSettings &mGraphicsSettings;
QRect getMaximumResolution();
bool setupOgre(); QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer);
bool setupSDL(); QStringList getAvailableResolutions(int screen);
}; QRect getMaximumResolution();
bool setupOgre();
bool setupSDL();
};
}
#endif #endif

@ -3,19 +3,20 @@
#include <QDir> #include <QDir>
#include <QDebug> #include <QDebug>
#ifdef __APPLE__ #ifdef MAC_OS_X_VERSION_MIN_REQUIRED
#undef MAC_OS_X_VERSION_MIN_REQUIRED
// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#endif #endif // MAC_OS_X_VERSION_MIN_REQUIRED
#include <SDL.h> #include <SDL.h>
#include "maindialog.hpp" #include "maindialog.hpp"
// SDL workaround
#include "graphicspage.hpp"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
SDL_SetMainReady();
if (SDL_Init(SDL_INIT_VIDEO) != 0) if (SDL_Init(SDL_INIT_VIDEO) != 0)
{ {
qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError()); qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError());
@ -49,7 +50,7 @@ int main(int argc, char *argv[])
// Support non-latin characters // Support non-latin characters
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
MainDialog mainWin; Launcher::MainDialog mainWin;
if (mainWin.setup()) { if (mainWin.setup()) {
mainWin.show(); mainWin.show();
@ -61,4 +62,3 @@ int main(int argc, char *argv[])
SDL_Quit(); SDL_Quit();
return returnValue; return returnValue;
} }

@ -1,5 +1,11 @@
#include "maindialog.hpp" #include "maindialog.hpp"
#include <components/version/version.hpp>
#include <QLabel>
#include <QDate>
#include <QTime>
#include <QPushButton>
#include <QFontDatabase> #include <QFontDatabase>
#include <QInputDialog> #include <QInputDialog>
#include <QFileDialog> #include <QFileDialog>
@ -23,8 +29,8 @@
#include "graphicspage.hpp" #include "graphicspage.hpp"
#include "datafilespage.hpp" #include "datafilespage.hpp"
MainDialog::MainDialog() Launcher::MainDialog::MainDialog(QWidget *parent)
: mGameSettings(mCfgMgr) : mGameSettings(mCfgMgr), QMainWindow (parent)
{ {
// Install the stylesheet font // Install the stylesheet font
QFile file; QFile file;
@ -66,10 +72,26 @@ MainDialog::MainDialog()
// 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);
if (revision == tag) {
versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION));
} else {
versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10)));
}
// Add the compile date and time
versionLabel->setToolTip(tr("Compiled on %0 %1").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();
} }
void MainDialog::createIcons() void Launcher::MainDialog::createIcons()
{ {
if (!QIcon::hasThemeIcon("document-new")) if (!QIcon::hasThemeIcon("document-new"))
QIcon::setThemeName("tango"); QIcon::setThemeName("tango");
@ -101,15 +123,15 @@ void MainDialog::createIcons()
} }
void MainDialog::createPages() void Launcher::MainDialog::createPages()
{ {
mPlayPage = new PlayPage(this); mPlayPage = new PlayPage(this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this);
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
// Set the combobox of the play page to imitate the combobox on the datafilespage // Set the combobox of the play page to imitate the combobox on the datafilespage
mPlayPage->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel()); mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex()); mPlayPage->setProfilesIndex(mDataFilesPage->profilesIndex());
// Add the pages to the stacked widget // Add the pages to the stacked widget
pagesWidget->addWidget(mPlayPage); pagesWidget->addWidget(mPlayPage);
@ -121,12 +143,12 @@ void MainDialog::createPages()
connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play())); connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play()));
connect(mPlayPage, SIGNAL(profileChanged(int)), mDataFilesPage, SLOT(setProfilesComboBoxIndex(int))); connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int)));
connect(mDataFilesPage, SIGNAL(profileChanged(int)), mPlayPage, SLOT(setProfilesComboBoxIndex(int))); connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int)));
} }
bool MainDialog::showFirstRunDialog() bool Launcher::MainDialog::showFirstRunDialog()
{ {
QStringList iniPaths; QStringList iniPaths;
@ -218,7 +240,7 @@ bool MainDialog::showFirstRunDialog()
} }
// Create the file if it doesn't already exist, else the importer will fail // Create the file if it doesn't already exist, else the importer will fail
QString path = QString::fromStdString(mCfgMgr.getUserPath().string()) + QString("openmw.cfg"); QString path = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + QString("openmw.cfg");
QFile file(path); QFile file(path);
if (!file.exists()) { if (!file.exists()) {
@ -261,19 +283,11 @@ bool MainDialog::showFirstRunDialog()
// Add a new profile // Add a new profile
if (msgBox.isChecked()) { if (msgBox.isChecked()) {
mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported")); mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported"));
mLauncherSettings.remove(QString("Profiles/Imported/content"));
mLauncherSettings.remove(QString("Profiles/Imported/master")); QStringList contents = mGameSettings.values(QString("content"));
mLauncherSettings.remove(QString("Profiles/Imported/plugin")); foreach (const QString &content, contents) {
mLauncherSettings.setMultiValue(QString("Profiles/Imported/content"), content);
QStringList masters = mGameSettings.values(QString("master"));
QStringList plugins = mGameSettings.values(QString("plugin"));
foreach (const QString &master, masters) {
mLauncherSettings.setMultiValue(QString("Profiles/Imported/master"), master);
}
foreach (const QString &plugin, plugins) {
mLauncherSettings.setMultiValue(QString("Profiles/Imported/plugin"), plugin);
} }
} }
@ -282,7 +296,7 @@ bool MainDialog::showFirstRunDialog()
return true; return true;
} }
bool MainDialog::setup() bool Launcher::MainDialog::setup()
{ {
if (!setupLauncherSettings()) if (!setupLauncherSettings())
return false; return false;
@ -311,19 +325,37 @@ bool MainDialog::setup()
return true; return true;
} }
void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous)
{ {
if (!current) if (!current)
current = previous; current = previous;
pagesWidget->setCurrentIndex(iconWidget->row(current)); int currentIndex = iconWidget->row(current);
int previousIndex = iconWidget->row(previous);
pagesWidget->setCurrentIndex(currentIndex);
DataFilesPage *previousPage = dynamic_cast<DataFilesPage *>(pagesWidget->widget(previousIndex));
DataFilesPage *currentPage = dynamic_cast<DataFilesPage *>(pagesWidget->widget(currentIndex));
//special call to update/save data files page list view when it's displayed/hidden.
if (previousPage)
{
if (previousPage->objectName() == "DataFilesPage")
previousPage->saveSettings();
}
else if (currentPage)
{
if (currentPage->objectName() == "DataFilesPage")
currentPage->loadSettings();
}
} }
bool MainDialog::setupLauncherSettings() bool Launcher::MainDialog::setupLauncherSettings()
{ {
mLauncherSettings.setMultiValueEnabled(true); mLauncherSettings.setMultiValueEnabled(true);
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QStringList paths; QStringList paths;
paths.append(QString("launcher.cfg")); paths.append(QString("launcher.cfg"));
@ -356,7 +388,7 @@ bool MainDialog::setupLauncherSettings()
} }
#ifndef WIN32 #ifndef WIN32
bool expansions(UnshieldThread& cd) bool Launcher::expansions(Launcher::UnshieldThread& cd)
{ {
if(cd.BloodmoonDone()) if(cd.BloodmoonDone())
{ {
@ -367,7 +399,7 @@ bool expansions(UnshieldThread& cd)
QMessageBox expansionsBox; QMessageBox expansionsBox;
expansionsBox.setText(QObject::tr("<br>Would you like to install expansions now ? (make sure you have the disc)<br> \ expansionsBox.setText(QObject::tr("<br>Would you like to install expansions now ? (make sure you have the disc)<br> \
If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.<br>")); If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.<br>"));
QAbstractButton* tribunalButton = NULL; QAbstractButton* tribunalButton = NULL;
if(!cd.TribunalDone()) if(!cd.TribunalDone())
tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole); tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole);
@ -386,7 +418,7 @@ bool expansions(UnshieldThread& cd)
{ {
TextSlotMsgBox cdbox; TextSlotMsgBox cdbox;
cdbox.setStandardButtons(QMessageBox::Cancel); cdbox.setStandardButtons(QMessageBox::Cancel);
QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
@ -405,7 +437,7 @@ bool expansions(UnshieldThread& cd)
{ {
TextSlotMsgBox cdbox; TextSlotMsgBox cdbox;
cdbox.setStandardButtons(QMessageBox::Cancel); cdbox.setStandardButtons(QMessageBox::Cancel);
QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
@ -427,11 +459,37 @@ bool expansions(UnshieldThread& cd)
} }
#endif // WIN32 #endif // WIN32
bool MainDialog::setupGameSettings() bool Launcher::MainDialog::setupGameSettings()
{ {
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
// Load the user config file first, separately
// So we can write it properly, uncontaminated
QString path = userPath + QLatin1String("openmw.cfg");
QFile file(path);
qDebug() << "Loading config file:" << qPrintable(path);
if (file.exists()) {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(QObject::tr("<br><b>Could not open %0 for reading</b><br><br> \
Please make sure you have the right permissions \
and try again.<br>").arg(file.fileName()));
msgBox.exec();
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGameSettings.readUserFile(stream);
}
// Now the rest
QStringList paths; QStringList paths;
paths.append(userPath + QString("openmw.cfg")); paths.append(userPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg")); paths.append(QString("openmw.cfg"));
@ -467,7 +525,7 @@ bool MainDialog::setupGameSettings()
foreach (const QString path, mGameSettings.getDataDirs()) { foreach (const QString path, mGameSettings.getDataDirs()) {
QDir dir(path); QDir dir(path);
QStringList filters; QStringList filters;
filters << "*.esp" << "*.esm"; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon";
if (!dir.entryList(filters).isEmpty()) if (!dir.entryList(filters).isEmpty())
dataDirs.append(path); dataDirs.append(path);
@ -485,12 +543,12 @@ bool MainDialog::setupGameSettings()
QAbstractButton *dirSelectButton = QAbstractButton *dirSelectButton =
msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole); msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole);
#ifndef WIN32 #ifndef WIN32
QAbstractButton *cdSelectButton = QAbstractButton *cdSelectButton =
msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole);
#endif #endif
msgBox.exec(); msgBox.exec();
@ -505,14 +563,14 @@ bool MainDialog::setupGameSettings()
#ifndef WIN32 #ifndef WIN32
else if(msgBox.clickedButton() == cdSelectButton) { else if(msgBox.clickedButton() == cdSelectButton) {
UnshieldThread cd; UnshieldThread cd;
{ {
TextSlotMsgBox cdbox; TextSlotMsgBox cdbox;
cdbox.setStandardButtons(QMessageBox::Cancel); cdbox.setStandardButtons(QMessageBox::Cancel);
QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&)));
QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject()));
cd.SetMorrowindPath( cd.SetMorrowindPath(
QFileDialog::getOpenFileName( QFileDialog::getOpenFileName(
NULL, NULL,
@ -526,11 +584,11 @@ bool MainDialog::setupGameSettings()
QObject::tr("Select where to extract files to"), QObject::tr("Select where to extract files to"),
QDir::currentPath(), QDir::currentPath(),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData()); QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData());
cd.start(); cd.start();
cdbox.exec(); cdbox.exec();
} }
while(expansions(cd)); while(expansions(cd));
selectedFile = QString::fromStdString(cd.GetMWEsmPath()); selectedFile = QString::fromStdString(cd.GetMWEsmPath());
@ -550,11 +608,11 @@ bool MainDialog::setupGameSettings()
return true; return true;
} }
bool MainDialog::setupGraphicsSettings() bool Launcher::MainDialog::setupGraphicsSettings()
{ {
mGraphicsSettings.setMultiValueEnabled(false); mGraphicsSettings.setMultiValueEnabled(false);
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
QFile localDefault(QString("settings-default.cfg")); QFile localDefault(QString("settings-default.cfg"));
@ -604,7 +662,7 @@ bool MainDialog::setupGraphicsSettings()
return true; return true;
} }
void MainDialog::loadSettings() void Launcher::MainDialog::loadSettings()
{ {
int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt(); int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt();
int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt(); int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt();
@ -616,7 +674,7 @@ void MainDialog::loadSettings()
move(posX, posY); move(posX, posY);
} }
void MainDialog::saveSettings() void Launcher::MainDialog::saveSettings()
{ {
QString width = QString::number(this->width()); QString width = QString::number(this->width());
QString height = QString::number(this->height()); QString height = QString::number(this->height());
@ -634,14 +692,14 @@ void MainDialog::saveSettings()
} }
bool MainDialog::writeSettings() bool Launcher::MainDialog::writeSettings()
{ {
// Now write all config files // Now write all config files
saveSettings(); saveSettings();
mGraphicsPage->saveSettings(); mGraphicsPage->saveSettings();
mDataFilesPage->saveSettings(); mDataFilesPage->saveSettings();
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QDir dir(userPath); QDir dir(userPath);
if (!dir.exists()) { if (!dir.exists()) {
@ -727,13 +785,13 @@ bool MainDialog::writeSettings()
return true; return true;
} }
void MainDialog::closeEvent(QCloseEvent *event) void Launcher::MainDialog::closeEvent(QCloseEvent *event)
{ {
writeSettings(); writeSettings();
event->accept(); event->accept();
} }
void MainDialog::play() void Launcher::MainDialog::play()
{ {
if (!writeSettings()) { if (!writeSettings()) {
qApp->quit(); qApp->quit();
@ -742,11 +800,11 @@ void MainDialog::play()
if(!mGameSettings.hasMaster()) { if(!mGameSettings.hasMaster()) {
QMessageBox msgBox; QMessageBox msgBox;
msgBox.setWindowTitle(tr("No master file selected")); msgBox.setWindowTitle(tr("No game file selected"));
msgBox.setIcon(QMessageBox::Warning); msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>You do not have any master files selected.</b><br><br> \ msgBox.setText(tr("<br><b>You do not have no game file selected.</b><br><br> \
OpenMW will not start without a master file selected.<br>")); OpenMW will not start without a game file selected.<br>"));
msgBox.exec(); msgBox.exec();
return; return;
} }
@ -756,7 +814,7 @@ void MainDialog::play()
qApp->quit(); qApp->quit();
} }
bool MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached) bool Launcher::MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached)
{ {
QString path = name; QString path = name;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN

@ -11,57 +11,59 @@
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
class QListWidget;
class QListWidgetItem; class QListWidgetItem;
class QStackedWidget;
class QStringList;
class QStringListModel;
class QString;
class PlayPage; namespace Launcher
class GraphicsPage;
class DataFilesPage;
class MainDialog : public QMainWindow, private Ui::MainWindow
{ {
Q_OBJECT class PlayPage;
class GraphicsPage;
class DataFilesPage;
class UnshieldThread;
#ifndef WIN32
bool expansions(Launcher::UnshieldThread& cd);
#endif
public: class MainDialog : public QMainWindow, private Ui::MainWindow
MainDialog(); {
bool setup(); Q_OBJECT
bool showFirstRunDialog();
public slots: public:
void changePage(QListWidgetItem *current, QListWidgetItem *previous); explicit MainDialog(QWidget *parent = 0);
void play(); bool setup();
bool showFirstRunDialog();
private: public slots:
void createIcons(); void changePage(QListWidgetItem *current, QListWidgetItem *previous);
void createPages(); void play();
bool setupLauncherSettings(); private:
bool setupGameSettings(); void createIcons();
bool setupGraphicsSettings(); void createPages();
void loadSettings(); bool setupLauncherSettings();
void saveSettings(); bool setupGameSettings();
bool writeSettings(); bool setupGraphicsSettings();
inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); } void loadSettings();
bool startProgram(const QString &name, const QStringList &arguments, bool detached = false); void saveSettings();
bool writeSettings();
void closeEvent(QCloseEvent *event); inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); }
bool startProgram(const QString &name, const QStringList &arguments, bool detached = false);
PlayPage *mPlayPage; void closeEvent(QCloseEvent *event);
GraphicsPage *mGraphicsPage;
DataFilesPage *mDataFilesPage;
Files::ConfigurationManager mCfgMgr; PlayPage *mPlayPage;
GraphicsPage *mGraphicsPage;
DataFilesPage *mDataFilesPage;
GameSettings mGameSettings; Files::ConfigurationManager mCfgMgr;
GraphicsSettings mGraphicsSettings;
LauncherSettings mLauncherSettings;
}; GameSettings mGameSettings;
GraphicsSettings mGraphicsSettings;
LauncherSettings mLauncherSettings;
};
}
#endif #endif

@ -6,8 +6,9 @@
#include <QPlastiqueStyle> #include <QPlastiqueStyle>
#endif #endif
PlayPage::PlayPage(QWidget *parent) : QWidget(parent) Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
{ {
setObjectName ("PlayPage");
setupUi(this); setupUi(this);
// Hacks to get the stylesheet look properly // Hacks to get the stylesheet look properly
@ -17,27 +18,22 @@ PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
#endif #endif
profilesComboBox->setView(new QListView()); profilesComboBox->setView(new QListView());
connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int))); connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int)));
connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked())); connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked()));
} }
void PlayPage::setProfilesComboBoxModel(QAbstractItemModel *model) void Launcher::PlayPage::setProfilesModel(QAbstractItemModel *model)
{ {
profilesComboBox->setModel(model); profilesComboBox->setModel(model);
} }
void PlayPage::setProfilesComboBoxIndex(int index) void Launcher::PlayPage::setProfilesIndex(int index)
{ {
profilesComboBox->setCurrentIndex(index); profilesComboBox->setCurrentIndex(index);
} }
void PlayPage::slotCurrentIndexChanged(int index) void Launcher::PlayPage::slotPlayClicked()
{
emit profileChanged(index);
}
void PlayPage::slotPlayClicked()
{ {
emit playButtonClicked(); emit playButtonClicked();
} }

@ -9,27 +9,28 @@ class QComboBox;
class QPushButton; class QPushButton;
class QAbstractItemModel; class QAbstractItemModel;
class PlayPage : public QWidget, private Ui::PlayPage namespace Launcher
{ {
Q_OBJECT class PlayPage : public QWidget, private Ui::PlayPage
{
Q_OBJECT
public: public:
PlayPage(QWidget *parent = 0); PlayPage(QWidget *parent = 0);
void setProfilesComboBoxModel(QAbstractItemModel *model); void setProfilesModel(QAbstractItemModel *model);
signals: signals:
void profileChanged(int index); void signalProfileChanged(int index);
void playButtonClicked(); void playButtonClicked();
public slots: public slots:
void setProfilesComboBoxIndex(int index); void setProfilesIndex(int index);
private slots: private slots:
void slotCurrentIndexChanged(int index); void slotPlayClicked();
void slotPlayClicked();
}; };
}
#endif #endif

@ -9,6 +9,7 @@
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <boost/version.hpp> #include <boost/version.hpp>
/** /**
* Workaround for problems with whitespaces in paths in older versions of Boost library * Workaround for problems with whitespaces in paths in older versions of Boost library
*/ */
@ -26,16 +27,16 @@ namespace boost
#endif /* (BOOST_VERSION <= 104600) */ #endif /* (BOOST_VERSION <= 104600) */
GameSettings::GameSettings(Files::ConfigurationManager &cfg) Launcher::GameSettings::GameSettings(Files::ConfigurationManager &cfg)
: mCfgMgr(cfg) : mCfgMgr(cfg)
{ {
} }
GameSettings::~GameSettings() Launcher::GameSettings::~GameSettings()
{ {
} }
void GameSettings::validatePaths() void Launcher::GameSettings::validatePaths()
{ {
if (mSettings.isEmpty() || !mDataDirs.isEmpty()) if (mSettings.isEmpty() || !mDataDirs.isEmpty())
return; // Don't re-validate paths if they are already parsed return; // Don't re-validate paths if they are already parsed
@ -81,14 +82,24 @@ void GameSettings::validatePaths()
} }
} }
QStringList GameSettings::values(const QString &key, const QStringList &defaultValues) QStringList Launcher::GameSettings::values(const QString &key, const QStringList &defaultValues)
{ {
if (!mSettings.values(key).isEmpty()) if (!mSettings.values(key).isEmpty())
return mSettings.values(key); return mSettings.values(key);
return defaultValues; return defaultValues;
} }
bool GameSettings::readFile(QTextStream &stream) bool Launcher::GameSettings::readFile(QTextStream &stream)
{
return readFile(stream, mSettings);
}
bool Launcher::GameSettings::readUserFile(QTextStream &stream)
{
return readFile(stream, mUserSettings);
}
bool Launcher::GameSettings::readFile(QTextStream &stream, QMap<QString, QString> &settings)
{ {
QMap<QString, QString> cache; QMap<QString, QString> cache;
QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
@ -106,10 +117,10 @@ bool GameSettings::readFile(QTextStream &stream)
// Don't remove existing data entries // Don't remove existing data entries
if (key != QLatin1String("data")) if (key != QLatin1String("data"))
mSettings.remove(key); settings.remove(key);
QStringList values = cache.values(key); QStringList values = cache.values(key);
values.append(mSettings.values(key)); values.append(settings.values(key));
if (!values.contains(value)) { if (!values.contains(value)) {
cache.insertMulti(key, value); cache.insertMulti(key, value);
@ -117,35 +128,36 @@ bool GameSettings::readFile(QTextStream &stream)
} }
} }
if (mSettings.isEmpty()) { if (settings.isEmpty()) {
mSettings = cache; // This is the first time we read a file settings = cache; // This is the first time we read a file
validatePaths(); validatePaths();
return true; return true;
} }
// Merge the changed keys with those which didn't // Merge the changed keys with those which didn't
mSettings.unite(cache); settings.unite(cache);
validatePaths(); validatePaths();
return true; return true;
} }
bool GameSettings::writeFile(QTextStream &stream)
bool Launcher::GameSettings::writeFile(QTextStream &stream)
{ {
// Iterate in reverse order to preserve insertion order // Iterate in reverse order to preserve insertion order
QMapIterator<QString, QString> i(mSettings); QMapIterator<QString, QString> i(mUserSettings);
i.toBack(); i.toBack();
while (i.hasPrevious()) { while (i.hasPrevious()) {
i.previous(); i.previous();
if (i.key() == QLatin1String("master") || i.key() == QLatin1String("plugin")) if (i.key() == QLatin1String("content"))
continue; continue;
// Quote paths with spaces // Quote paths with spaces
if (i.key() == QLatin1String("data") if (i.key() == QLatin1String("data")
|| i.key() == QLatin1String("data-local") || i.key() == QLatin1String("data-local")
|| i.key() == QLatin1String("resources")) || i.key() == QLatin1String("resources"))
{ {
if (i.value().contains(QChar(' '))) if (i.value().contains(QChar(' ')))
{ {
@ -161,15 +173,24 @@ bool GameSettings::writeFile(QTextStream &stream)
} }
QStringList masters = mSettings.values(QString("master")); QStringList content = mUserSettings.values(QString("content"));
for (int i = masters.count(); i--;) { for (int i = content.count(); i--;) {
stream << "master=" << masters.at(i) << "\n"; stream << "content=" << content.at(i) << "\n";
} }
QStringList plugins = mSettings.values(QString("plugin")); return true;
for (int i = plugins.count(); i--;) { }
stream << "plugin=" << plugins.at(i) << "\n";
bool Launcher::GameSettings::hasMaster()
{
bool result = false;
QStringList content = mSettings.values(QString("content"));
for (int i = 0; i < content.count(); ++i) {
if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) {
result = true;
break;
}
} }
return true; return result;
} }

@ -8,55 +8,72 @@
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
namespace Files { typedef std::vector<boost::filesystem::path> PathContainer; namespace Files
struct ConfigurationManager;}
class GameSettings
{ {
public: typedef std::vector<boost::filesystem::path> PathContainer;
GameSettings(Files::ConfigurationManager &cfg); struct ConfigurationManager;
~GameSettings(); }
inline QString value(const QString &key, const QString &defaultValue = QString()) namespace Launcher
{
class GameSettings
{ {
return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); public:
} GameSettings(Files::ConfigurationManager &cfg);
~GameSettings();
inline QString value(const QString &key, const QString &defaultValue = QString())
{
return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key);
}
inline void setValue(const QString &key, const QString &value)
{
mSettings.insert(key, value);
}
inline void setMultiValue(const QString &key, const QString &value) inline void setValue(const QString &key, const QString &value)
{ {
QStringList values = mSettings.values(key); mSettings.insert(key, value);
if (!values.contains(value)) mUserSettings.insert(key, value);
mSettings.insertMulti(key, value); }
}
inline void remove(const QString &key) inline void setMultiValue(const QString &key, const QString &value)
{ {
mSettings.remove(key); QStringList values = mSettings.values(key);
} if (!values.contains(value))
mSettings.insertMulti(key, value);
values = mUserSettings.values(key);
if (!values.contains(value))
mUserSettings.insertMulti(key, value);
}
inline void remove(const QString &key)
{
mSettings.remove(key);
mUserSettings.remove(key);
}
inline QStringList getDataDirs() { return mDataDirs; }
inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); }
inline QString getDataLocal() {return mDataLocal; }
bool hasMaster();
inline QStringList getDataDirs() { return mDataDirs; } QStringList values(const QString &key, const QStringList &defaultValues = QStringList());
inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); }
inline QString getDataLocal() {return mDataLocal; }
inline bool hasMaster() { return mSettings.count(QString("master")) > 0; }
QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); bool readFile(QTextStream &stream);
bool readFile(QTextStream &stream); bool readFile(QTextStream &stream, QMap<QString, QString> &settings);
bool writeFile(QTextStream &stream); bool readUserFile(QTextStream &stream);
private: bool writeFile(QTextStream &stream);
Files::ConfigurationManager &mCfgMgr;
void validatePaths(); private:
QMap<QString, QString> mSettings; Files::ConfigurationManager &mCfgMgr;
QStringList mDataDirs; void validatePaths();
QString mDataLocal; QMap<QString, QString> mSettings;
}; QMap<QString, QString> mUserSettings;
QStringList mDataDirs;
QString mDataLocal;
};
}
#endif // GAMESETTINGS_HPP #endif // GAMESETTINGS_HPP

@ -5,15 +5,15 @@
#include <QRegExp> #include <QRegExp>
#include <QMap> #include <QMap>
GraphicsSettings::GraphicsSettings() Launcher::GraphicsSettings::GraphicsSettings()
{ {
} }
GraphicsSettings::~GraphicsSettings() Launcher::GraphicsSettings::~GraphicsSettings()
{ {
} }
bool GraphicsSettings::writeFile(QTextStream &stream) bool Launcher::GraphicsSettings::writeFile(QTextStream &stream)
{ {
QString sectionPrefix; QString sectionPrefix;
QRegExp sectionRe("([^/]+)/(.+)$"); QRegExp sectionRe("([^/]+)/(.+)$");

@ -3,14 +3,16 @@
#include "settingsbase.hpp" #include "settingsbase.hpp"
class GraphicsSettings : public SettingsBase<QMap<QString, QString> > namespace Launcher
{ {
public: class GraphicsSettings : public SettingsBase<QMap<QString, QString> >
GraphicsSettings(); {
~GraphicsSettings(); public:
GraphicsSettings();
~GraphicsSettings();
bool writeFile(QTextStream &stream); bool writeFile(QTextStream &stream);
};
};
}
#endif // GRAPHICSSETTINGS_HPP #endif // GRAPHICSSETTINGS_HPP

@ -5,15 +5,17 @@
#include <QRegExp> #include <QRegExp>
#include <QMap> #include <QMap>
LauncherSettings::LauncherSettings() #include <QDebug>
Launcher::LauncherSettings::LauncherSettings()
{ {
} }
LauncherSettings::~LauncherSettings() Launcher::LauncherSettings::~LauncherSettings()
{ {
} }
QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags) QStringList Launcher::LauncherSettings::values(const QString &key, Qt::MatchFlags flags)
{ {
QMap<QString, QString> settings = SettingsBase::getSettings(); QMap<QString, QString> settings = SettingsBase::getSettings();
@ -34,7 +36,7 @@ QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags)
return result; return result;
} }
QStringList LauncherSettings::subKeys(const QString &key) QStringList Launcher::LauncherSettings::subKeys(const QString &key)
{ {
QMap<QString, QString> settings = SettingsBase::getSettings(); QMap<QString, QString> settings = SettingsBase::getSettings();
QStringList keys = settings.uniqueKeys(); QStringList keys = settings.uniqueKeys();
@ -44,12 +46,9 @@ QStringList LauncherSettings::subKeys(const QString &key)
QStringList result; QStringList result;
foreach (const QString &currentKey, keys) { foreach (const QString &currentKey, keys) {
if (keyRe.indexIn(currentKey) != -1) { if (keyRe.indexIn(currentKey) != -1) {
QString prefixedKey = keyRe.cap(1); QString prefixedKey = keyRe.cap(1);
if(prefixedKey.startsWith(key)) { if(prefixedKey.startsWith(key)) {
QString subKey = prefixedKey.remove(key); QString subKey = prefixedKey.remove(key);
if (!subKey.isEmpty()) if (!subKey.isEmpty())
result.append(subKey); result.append(subKey);
@ -61,7 +60,7 @@ QStringList LauncherSettings::subKeys(const QString &key)
return result; return result;
} }
bool LauncherSettings::writeFile(QTextStream &stream) bool Launcher::LauncherSettings::writeFile(QTextStream &stream)
{ {
QString sectionPrefix; QString sectionPrefix;
QRegExp sectionRe("([^/]+)/(.+)$"); QRegExp sectionRe("([^/]+)/(.+)$");

@ -3,17 +3,19 @@
#include "settingsbase.hpp" #include "settingsbase.hpp"
class LauncherSettings : public SettingsBase<QMap<QString, QString> > namespace Launcher
{ {
public: class LauncherSettings : public SettingsBase<QMap<QString, QString> >
LauncherSettings(); {
~LauncherSettings(); public:
LauncherSettings();
~LauncherSettings();
QStringList subKeys(const QString &key); QStringList subKeys(const QString &key);
QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly); QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly);
bool writeFile(QTextStream &stream); bool writeFile(QTextStream &stream);
};
};
}
#endif // LAUNCHERSETTINGS_HPP #endif // LAUNCHERSETTINGS_HPP

@ -7,103 +7,105 @@
#include <QRegExp> #include <QRegExp>
#include <QMap> #include <QMap>
template <class Map> namespace Launcher
class SettingsBase
{ {
template <class Map>
class SettingsBase
{
public: public:
SettingsBase() { mMultiValue = false; } SettingsBase() { mMultiValue = false; }
~SettingsBase() {} ~SettingsBase() {}
inline QString value(const QString &key, const QString &defaultValue = QString()) inline QString value(const QString &key, const QString &defaultValue = QString())
{ {
return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key);
} }
inline void setValue(const QString &key, const QString &value) inline void setValue(const QString &key, const QString &value)
{ {
QStringList values = mSettings.values(key); QStringList values = mSettings.values(key);
if (!values.contains(value)) if (!values.contains(value))
mSettings.insert(key, value); mSettings.insert(key, value);
} }
inline void setMultiValue(const QString &key, const QString &value) inline void setMultiValue(const QString &key, const QString &value)
{ {
QStringList values = mSettings.values(key); QStringList values = mSettings.values(key);
if (!values.contains(value)) if (!values.contains(value))
mSettings.insertMulti(key, value); mSettings.insertMulti(key, value);
} }
inline void setMultiValueEnabled(bool enable) inline void setMultiValueEnabled(bool enable)
{ {
mMultiValue = enable; mMultiValue = enable;
} }
inline void remove(const QString &key) inline void remove(const QString &key)
{ {
mSettings.remove(key); mSettings.remove(key);
} }
Map getSettings() {return mSettings;} Map getSettings() {return mSettings;}
bool readFile(QTextStream &stream) bool readFile(QTextStream &stream)
{ {
mCache.clear(); mCache.clear();
QString sectionPrefix; QString sectionPrefix;
QRegExp sectionRe("^\\[([^]]+)\\]"); QRegExp sectionRe("^\\[([^]]+)\\]");
QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$");
while (!stream.atEnd()) { while (!stream.atEnd()) {
QString line = stream.readLine(); QString line = stream.readLine();
if (line.isEmpty() || line.startsWith("#")) if (line.isEmpty() || line.startsWith("#"))
continue; continue;
if (sectionRe.exactMatch(line)) { if (sectionRe.exactMatch(line)) {
sectionPrefix = sectionRe.cap(1); sectionPrefix = sectionRe.cap(1);
sectionPrefix.append("/"); sectionPrefix.append("/");
continue; continue;
} }
if (keyRe.indexIn(line) != -1) { if (keyRe.indexIn(line) != -1) {
QString key = keyRe.cap(1).trimmed(); QString key = keyRe.cap(1).trimmed();
QString value = keyRe.cap(2).trimmed(); QString value = keyRe.cap(2).trimmed();
if (!sectionPrefix.isEmpty()) if (!sectionPrefix.isEmpty())
key.prepend(sectionPrefix); key.prepend(sectionPrefix);
mSettings.remove(key); mSettings.remove(key);
QStringList values = mCache.values(key); QStringList values = mCache.values(key);
if (!values.contains(value)) { if (!values.contains(value)) {
if (mMultiValue) { if (mMultiValue) {
mCache.insertMulti(key, value); mCache.insertMulti(key, value);
} else { } else {
mCache.insert(key, value); mCache.insert(key, value);
}
} }
} }
} }
}
if (mSettings.isEmpty()) { if (mSettings.isEmpty()) {
mSettings = mCache; // This is the first time we read a file mSettings = mCache; // This is the first time we read a file
return true;
}
// Merge the changed keys with those which didn't
mSettings.unite(mCache);
return true; return true;
} }
// Merge the changed keys with those which didn't private:
mSettings.unite(mCache); Map mSettings;
return true; Map mCache;
}
private:
Map mSettings;
Map mCache;
bool mMultiValue;
};
bool mMultiValue;
};
}
#endif // SETTINGSBASE_HPP #endif // SETTINGSBASE_HPP

@ -1,6 +1,6 @@
#include "textslotmsgbox.hpp" #include "textslotmsgbox.hpp"
void TextSlotMsgBox::setTextSlot(const QString& string) void Launcher::TextSlotMsgBox::setTextSlot(const QString& string)
{ {
setText(string); setText(string);
} }

@ -3,11 +3,13 @@
#include <QMessageBox> #include <QMessageBox>
class TextSlotMsgBox : public QMessageBox namespace Launcher
{ {
Q_OBJECT class TextSlotMsgBox : public QMessageBox
public slots: {
void setTextSlot(const QString& string); Q_OBJECT
}; public slots:
void setTextSlot(const QString& string);
};
}
#endif #endif

@ -235,7 +235,7 @@ namespace
{ {
for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir )
{ {
if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
return dir->path(); return dir->path();
} }
} }
@ -243,7 +243,7 @@ namespace
{ {
for ( bfs::directory_iterator end, dir(in); dir != end; ++dir ) for ( bfs::directory_iterator end, dir(in); dir != end; ++dir )
{ {
if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
return dir->path(); return dir->path();
} }
} }
@ -255,7 +255,7 @@ namespace
{ {
for(bfs::directory_iterator end, dir(in); dir != end; ++dir) for(bfs::directory_iterator end, dir(in); dir != end; ++dir)
{ {
if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
return true; return true;
} }
@ -292,30 +292,30 @@ namespace
} }
bool UnshieldThread::SetMorrowindPath(const std::string& path) bool Launcher::UnshieldThread::SetMorrowindPath(const std::string& path)
{ {
mMorrowindPath = path; mMorrowindPath = path;
return true; return true;
} }
bool UnshieldThread::SetTribunalPath(const std::string& path) bool Launcher::UnshieldThread::SetTribunalPath(const std::string& path)
{ {
mTribunalPath = path; mTribunalPath = path;
return true; return true;
} }
bool UnshieldThread::SetBloodmoonPath(const std::string& path) bool Launcher::UnshieldThread::SetBloodmoonPath(const std::string& path)
{ {
mBloodmoonPath = path; mBloodmoonPath = path;
return true; return true;
} }
void UnshieldThread::SetOutputPath(const std::string& path) void Launcher::UnshieldThread::SetOutputPath(const std::string& path)
{ {
mOutputPath = path; mOutputPath = path;
} }
bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) bool Launcher::UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index)
{ {
bool success; bool success;
bfs::path dirname; bfs::path dirname;
@ -349,7 +349,7 @@ bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, cons
return success; return success;
} }
void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) void Launcher::UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini)
{ {
Unshield * unshield; Unshield * unshield;
unshield = unshield_open(cab.c_str()); unshield = unshield_open(cab.c_str());
@ -369,7 +369,7 @@ void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_d
} }
bool UnshieldThread::extract() bool Launcher::UnshieldThread::extract()
{ {
bfs::path outputDataFilesDir = mOutputPath; bfs::path outputDataFilesDir = mOutputPath;
outputDataFilesDir /= "Data Files"; outputDataFilesDir /= "Data Files";
@ -475,7 +475,7 @@ bool UnshieldThread::extract()
return true; return true;
} }
void UnshieldThread::Done() void Launcher::UnshieldThread::Done()
{ {
// Get rid of unnecessary files // Get rid of unnecessary files
bfs::remove_all(mOutputPath / "extract-temp"); bfs::remove_all(mOutputPath / "extract-temp");
@ -491,28 +491,28 @@ void UnshieldThread::Done()
bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003")); bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003"));
} }
std::string UnshieldThread::GetMWEsmPath() std::string Launcher::UnshieldThread::GetMWEsmPath()
{ {
return findFile(mOutputPath / "Data Files", "morrowind.esm").string(); return findFile(mOutputPath / "Data Files", "morrowind.esm").string();
} }
bool UnshieldThread::TribunalDone() bool Launcher::UnshieldThread::TribunalDone()
{ {
return mTribunalDone; return mTribunalDone;
} }
bool UnshieldThread::BloodmoonDone() bool Launcher::UnshieldThread::BloodmoonDone()
{ {
return mBloodmoonDone; return mBloodmoonDone;
} }
void UnshieldThread::run() void Launcher::UnshieldThread::run()
{ {
extract(); extract();
emit close(); emit close();
} }
UnshieldThread::UnshieldThread() Launcher::UnshieldThread::UnshieldThread()
{ {
unshield_set_log_level(0); unshield_set_log_level(0);
mMorrowindDone = false; mMorrowindDone = false;

@ -7,50 +7,52 @@
#include <libunshield.h> #include <libunshield.h>
class UnshieldThread : public QThread namespace Launcher
{ {
Q_OBJECT class UnshieldThread : public QThread
{
Q_OBJECT
public: public:
bool SetMorrowindPath(const std::string& path); bool SetMorrowindPath(const std::string& path);
bool SetTribunalPath(const std::string& path); bool SetTribunalPath(const std::string& path);
bool SetBloodmoonPath(const std::string& path); bool SetBloodmoonPath(const std::string& path);
void SetOutputPath(const std::string& path); void SetOutputPath(const std::string& path);
bool extract();
bool TribunalDone(); bool extract();
bool BloodmoonDone();
void Done(); bool TribunalDone();
bool BloodmoonDone();
std::string GetMWEsmPath(); void Done();
UnshieldThread(); std::string GetMWEsmPath();
private: UnshieldThread();
void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); private:
bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index);
boost::filesystem::path mMorrowindPath;
boost::filesystem::path mTribunalPath;
boost::filesystem::path mBloodmoonPath;
bool mMorrowindDone; void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false);
bool mTribunalDone; bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index);
bool mBloodmoonDone;
boost::filesystem::path mOutputPath; boost::filesystem::path mMorrowindPath;
boost::filesystem::path mTribunalPath;
boost::filesystem::path mBloodmoonPath;
bool mMorrowindDone;
bool mTribunalDone;
bool mBloodmoonDone;
protected: boost::filesystem::path mOutputPath;
virtual void run();
signals:
void signalGUI(QString);
void close();
};
protected:
virtual void run();
signals:
void signalGUI(QString);
void close();
};
}
#endif #endif

@ -54,72 +54,61 @@
Emulates the QMessageBox API with Emulates the QMessageBox API with
static conveniences. The message label can open external URLs. static conveniences. The message label can open external URLs.
*/ */
Launcher::CheckableMessageBoxPrivate::CheckableMessageBoxPrivate(QDialog *q)
class CheckableMessageBoxPrivate
{
public:
CheckableMessageBoxPrivate(QDialog *q)
: clickedButton(0) : clickedButton(0)
{ {
QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
pixmapLabel = new QLabel(q); pixmapLabel = new QLabel(q);
sizePolicy.setHorizontalStretch(0); sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0); sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth()); sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth());
pixmapLabel->setSizePolicy(sizePolicy); pixmapLabel->setSizePolicy(sizePolicy);
pixmapLabel->setVisible(false); pixmapLabel->setVisible(false);
QSpacerItem *pixmapSpacer = QSpacerItem *pixmapSpacer =
new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
messageLabel = new QLabel(q); messageLabel = new QLabel(q);
messageLabel->setMinimumSize(QSize(300, 0)); messageLabel->setMinimumSize(QSize(300, 0));
messageLabel->setWordWrap(true); messageLabel->setWordWrap(true);
messageLabel->setOpenExternalLinks(true); messageLabel->setOpenExternalLinks(true);
messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse); messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse);
QSpacerItem *checkBoxRightSpacer = QSpacerItem *checkBoxRightSpacer =
new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
QSpacerItem *buttonSpacer = QSpacerItem *buttonSpacer =
new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::Minimum); new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::Minimum);
checkBox = new QCheckBox(q); checkBox = new QCheckBox(q);
checkBox->setText(CheckableMessageBox::tr("Do not ask again")); checkBox->setText(Launcher::CheckableMessageBox::tr("Do not ask again"));
buttonBox = new QDialogButtonBox(q); buttonBox = new QDialogButtonBox(q);
buttonBox->setOrientation(Qt::Horizontal); buttonBox->setOrientation(Qt::Horizontal);
buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
QVBoxLayout *verticalLayout = new QVBoxLayout(); QVBoxLayout *verticalLayout = new QVBoxLayout();
verticalLayout->addWidget(pixmapLabel); verticalLayout->addWidget(pixmapLabel);
verticalLayout->addItem(pixmapSpacer); verticalLayout->addItem(pixmapSpacer);
QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout();
horizontalLayout_2->addLayout(verticalLayout); horizontalLayout_2->addLayout(verticalLayout);
horizontalLayout_2->addWidget(messageLabel); horizontalLayout_2->addWidget(messageLabel);
QHBoxLayout *horizontalLayout = new QHBoxLayout(); QHBoxLayout *horizontalLayout = new QHBoxLayout();
horizontalLayout->addWidget(checkBox); horizontalLayout->addWidget(checkBox);
horizontalLayout->addItem(checkBoxRightSpacer); horizontalLayout->addItem(checkBoxRightSpacer);
QVBoxLayout *verticalLayout_2 = new QVBoxLayout(q); QVBoxLayout *verticalLayout_2 = new QVBoxLayout(q);
verticalLayout_2->addLayout(horizontalLayout_2); verticalLayout_2->addLayout(horizontalLayout_2);
verticalLayout_2->addLayout(horizontalLayout); verticalLayout_2->addLayout(horizontalLayout);
verticalLayout_2->addItem(buttonSpacer); verticalLayout_2->addItem(buttonSpacer);
verticalLayout_2->addWidget(buttonBox); verticalLayout_2->addWidget(buttonBox);
} }
QLabel *pixmapLabel;
QLabel *messageLabel;
QCheckBox *checkBox;
QDialogButtonBox *buttonBox;
QAbstractButton *clickedButton;
};
CheckableMessageBox::CheckableMessageBox(QWidget *parent) : Launcher::CheckableMessageBox::CheckableMessageBox(QWidget *parent) :
QDialog(parent), QDialog(parent),
d(new CheckableMessageBoxPrivate(this)) d(new Launcher::CheckableMessageBoxPrivate(this))
{ {
setModal(true); setModal(true);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -129,102 +118,102 @@ CheckableMessageBox::CheckableMessageBox(QWidget *parent) :
SLOT(slotClicked(QAbstractButton*))); SLOT(slotClicked(QAbstractButton*)));
} }
CheckableMessageBox::~CheckableMessageBox() Launcher::CheckableMessageBox::~CheckableMessageBox()
{ {
delete d; delete d;
} }
void CheckableMessageBox::slotClicked(QAbstractButton *b) void Launcher::CheckableMessageBox::slotClicked(QAbstractButton *b)
{ {
d->clickedButton = b; d->clickedButton = b;
} }
QAbstractButton *CheckableMessageBox::clickedButton() const QAbstractButton *Launcher::CheckableMessageBox::clickedButton() const
{ {
return d->clickedButton; return d->clickedButton;
} }
QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::clickedStandardButton() const
{ {
if (d->clickedButton) if (d->clickedButton)
return d->buttonBox->standardButton(d->clickedButton); return d->buttonBox->standardButton(d->clickedButton);
return QDialogButtonBox::NoButton; return QDialogButtonBox::NoButton;
} }
QString CheckableMessageBox::text() const QString Launcher::CheckableMessageBox::text() const
{ {
return d->messageLabel->text(); return d->messageLabel->text();
} }
void CheckableMessageBox::setText(const QString &t) void Launcher::CheckableMessageBox::setText(const QString &t)
{ {
d->messageLabel->setText(t); d->messageLabel->setText(t);
} }
QPixmap CheckableMessageBox::iconPixmap() const QPixmap Launcher::CheckableMessageBox::iconPixmap() const
{ {
if (const QPixmap *p = d->pixmapLabel->pixmap()) if (const QPixmap *p = d->pixmapLabel->pixmap())
return QPixmap(*p); return QPixmap(*p);
return QPixmap(); return QPixmap();
} }
void CheckableMessageBox::setIconPixmap(const QPixmap &p) void Launcher::CheckableMessageBox::setIconPixmap(const QPixmap &p)
{ {
d->pixmapLabel->setPixmap(p); d->pixmapLabel->setPixmap(p);
d->pixmapLabel->setVisible(!p.isNull()); d->pixmapLabel->setVisible(!p.isNull());
} }
bool CheckableMessageBox::isChecked() const bool Launcher::CheckableMessageBox::isChecked() const
{ {
return d->checkBox->isChecked(); return d->checkBox->isChecked();
} }
void CheckableMessageBox::setChecked(bool s) void Launcher::CheckableMessageBox::setChecked(bool s)
{ {
d->checkBox->setChecked(s); d->checkBox->setChecked(s);
} }
QString CheckableMessageBox::checkBoxText() const QString Launcher::CheckableMessageBox::checkBoxText() const
{ {
return d->checkBox->text(); return d->checkBox->text();
} }
void CheckableMessageBox::setCheckBoxText(const QString &t) void Launcher::CheckableMessageBox::setCheckBoxText(const QString &t)
{ {
d->checkBox->setText(t); d->checkBox->setText(t);
} }
bool CheckableMessageBox::isCheckBoxVisible() const bool Launcher::CheckableMessageBox::isCheckBoxVisible() const
{ {
return d->checkBox->isVisible(); return d->checkBox->isVisible();
} }
void CheckableMessageBox::setCheckBoxVisible(bool v) void Launcher::CheckableMessageBox::setCheckBoxVisible(bool v)
{ {
d->checkBox->setVisible(v); d->checkBox->setVisible(v);
} }
QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const QDialogButtonBox::StandardButtons Launcher::CheckableMessageBox::standardButtons() const
{ {
return d->buttonBox->standardButtons(); return d->buttonBox->standardButtons();
} }
void CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) void Launcher::CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s)
{ {
d->buttonBox->setStandardButtons(s); d->buttonBox->setStandardButtons(s);
} }
QPushButton *CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const QPushButton *Launcher::CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const
{ {
return d->buttonBox->button(b); return d->buttonBox->button(b);
} }
QPushButton *CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) QPushButton *Launcher::CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role)
{ {
return d->buttonBox->addButton(text, role); return d->buttonBox->addButton(text, role);
} }
QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::defaultButton() const
{ {
foreach (QAbstractButton *b, d->buttonBox->buttons()) foreach (QAbstractButton *b, d->buttonBox->buttons())
if (QPushButton *pb = qobject_cast<QPushButton *>(b)) if (QPushButton *pb = qobject_cast<QPushButton *>(b))
@ -233,7 +222,7 @@ QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const
return QDialogButtonBox::NoButton; return QDialogButtonBox::NoButton;
} }
void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) void Launcher::CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s)
{ {
if (QPushButton *b = d->buttonBox->button(s)) { if (QPushButton *b = d->buttonBox->button(s)) {
b->setDefault(true); b->setDefault(true);
@ -242,7 +231,7 @@ void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s)
} }
QDialogButtonBox::StandardButton QDialogButtonBox::StandardButton
CheckableMessageBox::question(QWidget *parent, Launcher::CheckableMessageBox::question(QWidget *parent,
const QString &title, const QString &title,
const QString &question, const QString &question,
const QString &checkBoxText, const QString &checkBoxText,
@ -263,7 +252,7 @@ CheckableMessageBox::question(QWidget *parent,
return mb.clickedStandardButton(); return mb.clickedStandardButton();
} }
QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) QMessageBox::StandardButton Launcher::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db)
{ {
return static_cast<QMessageBox::StandardButton>(int(db)); return static_cast<QMessageBox::StandardButton>(int(db));
} }

@ -34,67 +34,83 @@
#include <QMessageBox> #include <QMessageBox>
#include <QDialog> #include <QDialog>
class CheckableMessageBoxPrivate; class QCheckBox;
class CheckableMessageBox : public QDialog namespace Launcher
{ {
Q_OBJECT class CheckableMessageBoxPrivate
Q_PROPERTY(QString text READ text WRITE setText) {
Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) public:
Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked)
Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) QLabel *pixmapLabel;
Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) QLabel *messageLabel;
Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) QCheckBox *checkBox;
QDialogButtonBox *buttonBox;
public: QAbstractButton *clickedButton;
explicit CheckableMessageBox(QWidget *parent);
virtual ~CheckableMessageBox(); public:
CheckableMessageBoxPrivate(QDialog *q);
static QDialogButtonBox::StandardButton };
question(QWidget *parent,
const QString &title, class CheckableMessageBox : public QDialog
const QString &question, {
const QString &checkBoxText, Q_OBJECT
bool *checkBoxSetting, Q_PROPERTY(QString text READ text WRITE setText)
QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap)
QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No); Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked)
Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText)
QString text() const; Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons)
void setText(const QString &); Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton)
bool isChecked() const; public:
void setChecked(bool s); explicit CheckableMessageBox(QWidget *parent);
virtual ~CheckableMessageBox();
QString checkBoxText() const;
void setCheckBoxText(const QString &); static QDialogButtonBox::StandardButton
question(QWidget *parent,
bool isCheckBoxVisible() const; const QString &title,
void setCheckBoxVisible(bool); const QString &question,
const QString &checkBoxText,
QDialogButtonBox::StandardButtons standardButtons() const; bool *checkBoxSetting,
void setStandardButtons(QDialogButtonBox::StandardButtons s); QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No,
QPushButton *button(QDialogButtonBox::StandardButton b) const; QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No);
QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role);
QString text() const;
QDialogButtonBox::StandardButton defaultButton() const; void setText(const QString &);
void setDefaultButton(QDialogButtonBox::StandardButton s);
bool isChecked() const;
// See static QMessageBox::standardPixmap() void setChecked(bool s);
QPixmap iconPixmap() const;
void setIconPixmap (const QPixmap &p); QString checkBoxText() const;
void setCheckBoxText(const QString &);
// Query the result
QAbstractButton *clickedButton() const; bool isCheckBoxVisible() const;
QDialogButtonBox::StandardButton clickedStandardButton() const; void setCheckBoxVisible(bool);
// Conversion convenience QDialogButtonBox::StandardButtons standardButtons() const;
static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); void setStandardButtons(QDialogButtonBox::StandardButtons s);
QPushButton *button(QDialogButtonBox::StandardButton b) const;
private slots: QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role);
void slotClicked(QAbstractButton *b);
QDialogButtonBox::StandardButton defaultButton() const;
private: void setDefaultButton(QDialogButtonBox::StandardButton s);
CheckableMessageBoxPrivate *d;
}; // See static QMessageBox::standardPixmap()
QPixmap iconPixmap() const;
void setIconPixmap (const QPixmap &p);
// Query the result
QAbstractButton *clickedButton() const;
QDialogButtonBox::StandardButton clickedStandardButton() const;
// Conversion convenience
static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton);
private slots:
void slotClicked(QAbstractButton *b);
private:
CheckableMessageBoxPrivate *d;
};
}
#endif // CHECKABLEMESSAGEBOX_HPP #endif // CHECKABLEMESSAGEBOX_HPP

@ -1,10 +1,12 @@
#include <QToolButton>
#include <QStyle>
#include "lineedit.hpp" #include "lineedit.hpp"
LineEdit::LineEdit(QWidget *parent) LineEdit::LineEdit(QWidget *parent)
: QLineEdit(parent) : QLineEdit(parent)
{
setupClearButton();
}
void LineEdit::setupClearButton()
{ {
mClearButton = new QToolButton(this); mClearButton = new QToolButton(this);
QPixmap pixmap(":images/clear.png"); QPixmap pixmap(":images/clear.png");
@ -15,13 +17,6 @@ LineEdit::LineEdit(QWidget *parent)
mClearButton->hide(); mClearButton->hide();
connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear()));
connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&))); connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&)));
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
setObjectName(QString("LineEdit"));
setStyleSheet(QString("LineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1));
QSize msz = minimumSizeHint();
setMinimumSize(qMax(msz.width(), mClearButton->sizeHint().height() + frameWidth * 2 + 2),
qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2));
} }
void LineEdit::resizeEvent(QResizeEvent *) void LineEdit::resizeEvent(QResizeEvent *)

@ -11,6 +11,9 @@
#define LINEEDIT_H #define LINEEDIT_H
#include <QLineEdit> #include <QLineEdit>
#include <QStyle>
#include <QStylePainter>
#include <QToolButton>
class QToolButton; class QToolButton;
@ -18,6 +21,8 @@ class LineEdit : public QLineEdit
{ {
Q_OBJECT Q_OBJECT
QString mPlaceholderText;
public: public:
LineEdit(QWidget *parent = 0); LineEdit(QWidget *parent = 0);
@ -27,8 +32,10 @@ protected:
private slots: private slots:
void updateClearButton(const QString &text); void updateClearButton(const QString &text);
private: protected:
QToolButton *mClearButton; QToolButton *mClearButton;
void setupClearButton();
}; };
#endif // LIENEDIT_H #endif // LIENEDIT_H

@ -5,18 +5,12 @@
#include <QKeyEvent> #include <QKeyEvent>
#include "profilescombobox.hpp" #include "profilescombobox.hpp"
#include "comboboxlineedit.hpp"
ProfilesComboBox::ProfilesComboBox(QWidget *parent) : ProfilesComboBox::ProfilesComboBox(QWidget *parent) :
QComboBox(parent) ContentSelectorView::ComboBox(parent)
{ {
mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore connect(this, SIGNAL(activated(int)), this,
setEditEnabled(true); SLOT(slotIndexChangedByUser(int)));
setValidator(mValidator);
setCompleter(0);
connect(this, SIGNAL(currentIndexChanged(int)), this,
SLOT(slotIndexChanged(int)));
setInsertPolicy(QComboBox::NoInsert); setInsertPolicy(QComboBox::NoInsert);
} }
@ -37,6 +31,7 @@ void ProfilesComboBox::setEditEnabled(bool editable)
setValidator(mValidator); setValidator(mValidator);
ComboBoxLineEdit *edit = new ComboBoxLineEdit(this); ComboBoxLineEdit *edit = new ComboBoxLineEdit(this);
setLineEdit(edit); setLineEdit(edit);
setCompleter(0); setCompleter(0);
@ -45,6 +40,9 @@ void ProfilesComboBox::setEditEnabled(bool editable)
connect(lineEdit(), SIGNAL(textChanged(QString)), this, connect(lineEdit(), SIGNAL(textChanged(QString)), this,
SLOT(slotTextChanged(QString))); SLOT(slotTextChanged(QString)));
connect (lineEdit(), SIGNAL(textChanged(QString)), this,
SIGNAL (signalProfileTextChanged (QString)));
} }
void ProfilesComboBox::slotTextChanged(const QString &text) void ProfilesComboBox::slotTextChanged(const QString &text)
@ -82,11 +80,20 @@ void ProfilesComboBox::slotEditingFinished()
emit(profileRenamed(previous, current)); emit(profileRenamed(previous, current));
} }
void ProfilesComboBox::slotIndexChanged(int index) void ProfilesComboBox::slotIndexChangedByUser(int index)
{ {
if (index == -1) if (index == -1)
return; return;
emit(profileChanged(mOldProfile, currentText())); emit (signalProfileChanged(mOldProfile, currentText()));
mOldProfile = itemText(index); mOldProfile = currentText();
}
ProfilesComboBox::ComboBoxLineEdit::ComboBoxLineEdit (QWidget *parent)
: LineEdit (parent)
{
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
setObjectName(QString("ComboBoxLineEdit"));
setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1));
} }

@ -0,0 +1,47 @@
#ifndef PROFILESCOMBOBOX_HPP
#define PROFILESCOMBOBOX_HPP
#include "components/contentselector/view/combobox.hpp"
#include "lineedit.hpp"
#include <QDebug>
class QString;
class ProfilesComboBox : public ContentSelectorView::ComboBox
{
Q_OBJECT
public:
class ComboBoxLineEdit : public LineEdit
{
public:
explicit ComboBoxLineEdit (QWidget *parent = 0);
};
public:
explicit ProfilesComboBox(QWidget *parent = 0);
void setEditEnabled(bool editable);
void setCurrentProfile(int index)
{
ComboBox::setCurrentIndex(index);
mOldProfile = currentText();
}
signals:
void signalProfileTextChanged(const QString &item);
void signalProfileChanged(const QString &previous, const QString &current);
void signalProfileChanged(int index);
void profileRenamed(const QString &oldName, const QString &newName);
private slots:
void slotEditingFinished();
void slotIndexChangedByUser(int index);
void slotTextChanged(const QString &text);
private:
QString mOldProfile;
};
#endif // PROFILESCOMBOBOX_HPP

@ -7,19 +7,18 @@
#include <QValidator> #include <QValidator>
#include <QLabel> #include <QLabel>
#include <components/fileorderlist/utils/lineedit.hpp> Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) :
TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) :
QDialog(parent) QDialog(parent)
{ {
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
mButtonBox = new QDialogButtonBox(this); mButtonBox = new QDialogButtonBox(this);
mButtonBox->addButton(QDialogButtonBox::Ok); mButtonBox->addButton(QDialogButtonBox::Ok);
mButtonBox->addButton(QDialogButtonBox::Cancel); mButtonBox->addButton(QDialogButtonBox::Cancel);
mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false);
// Line edit // Line edit
QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore
mLineEdit = new LineEdit(this); mLineEdit = new DialogLineEdit(this);
mLineEdit->setValidator(validator); mLineEdit->setValidator(validator);
mLineEdit->setCompleter(0); mLineEdit->setCompleter(0);
@ -38,34 +37,51 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid
Q_UNUSED(title); Q_UNUSED(title);
#endif #endif
setOkButtonEnabled(false);
setModal(true); setModal(true);
connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject()));
connect(mLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateOkButton(QString)));
} }
int TextInputDialog::exec() int Launcher::TextInputDialog::exec()
{ {
mLineEdit->clear(); mLineEdit->clear();
mLineEdit->setFocus(); mLineEdit->setFocus();
return QDialog::exec(); return QDialog::exec();
} }
void TextInputDialog::setOkButtonEnabled(bool enabled) QString Launcher::TextInputDialog::getText() const
{ {
QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); return mLineEdit->text();
okButton->setEnabled(enabled); }
QPalette *palette = new QPalette(); void Launcher::TextInputDialog::slotUpdateOkButton(QString text)
palette->setColor(QPalette::Text,Qt::red); {
bool enabled = !(text.isEmpty());
mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled);
if (enabled) { if (enabled)
mLineEdit->setPalette(QApplication::palette()); mLineEdit->setPalette(QApplication::palette());
} else { else
{
// Existing profile name, make the text red // Existing profile name, make the text red
QPalette *palette = new QPalette();
palette->setColor(QPalette::Text,Qt::red);
mLineEdit->setPalette(*palette); mLineEdit->setPalette(*palette);
} }
}
Launcher::TextInputDialog::DialogLineEdit::DialogLineEdit (QWidget *parent) :
LineEdit (parent)
{
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
setObjectName(QString("LineEdit"));
setStyleSheet(QString("LineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1));
QSize msz = minimumSizeHint();
setMinimumSize(qMax(msz.width(), mClearButton->sizeHint().height() + frameWidth * 2 + 2),
qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2));
} }

@ -2,27 +2,39 @@
#define TEXTINPUTDIALOG_HPP #define TEXTINPUTDIALOG_HPP
#include <QDialog> #include <QDialog>
//#include "lineedit.hpp"
#include "lineedit.hpp"
class QDialogButtonBox; class QDialogButtonBox;
class LineEdit;
class TextInputDialog : public QDialog namespace Launcher
{ {
Q_OBJECT class TextInputDialog : public QDialog
public: {
explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); Q_OBJECT
inline LineEdit *lineEdit() { return mLineEdit; }
void setOkButtonEnabled(bool enabled); class DialogLineEdit : public LineEdit
{
public:
explicit DialogLineEdit (QWidget *parent = 0);
};
DialogLineEdit *mLineEdit;
QDialogButtonBox *mButtonBox;
public:
explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0);
~TextInputDialog () {}
LineEdit *mLineEdit; QString getText() const;
int exec(); int exec();
private: private slots:
QDialogButtonBox *mButtonBox; void slotUpdateOkButton(QString text);
};
}; }
#endif // TEXTINPUTDIALOG_HPP #endif // TEXTINPUTDIALOG_HPP

@ -16,7 +16,7 @@ MwIniImporter::MwIniImporter()
const char *map[][2] = const char *map[][2] =
{ {
{ "fps", "General:Show FPS" }, { "fps", "General:Show FPS" },
{ "nosound", "General:Disable Audio" }, { "no-sound", "General:Disable Audio" },
{ 0, 0 } { 0, 0 }
}; };
const char *fallback[] = { const char *fallback[] = {
@ -623,6 +623,17 @@ MwIniImporter::MwIniImporter()
"Moons:Masser Fade Out Finish", "Moons:Masser Fade Out Finish",
"Moons:Script Color", "Moons:Script Color",
// blood
"Blood:Model 0",
"Blood:Model 1",
"Blood:Model 2",
"Blood:Texture 0",
"Blood:Texture 1",
"Blood:Texture 2",
"Blood:Texture Name 0",
"Blood:Texture Name 1",
"Blood:Texture Name 2",
0 0
}; };
@ -813,8 +824,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con
} }
void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) const { void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) const {
std::vector<std::string> esmFiles; std::vector<std::string> contentFiles;
std::vector<std::string> espFiles;
std::string baseGameFile("Game Files:GameFile"); std::string baseGameFile("Game Files:GameFile");
std::string gameFile(""); std::string gameFile("");
@ -832,29 +842,19 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) co
std::string filetype(entry->substr(entry->length()-3)); std::string filetype(entry->substr(entry->length()-3));
Misc::StringUtils::toLower(filetype); Misc::StringUtils::toLower(filetype);
if(filetype.compare("esm") == 0) { if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) {
esmFiles.push_back(*entry); contentFiles.push_back(*entry);
}
else if(filetype.compare("esp") == 0) {
espFiles.push_back(*entry);
} }
} }
gameFile = ""; gameFile = "";
} }
cfg.erase("master"); cfg.erase("content");
cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("master", std::vector<std::string>() ) ); cfg.insert( std::make_pair("content", std::vector<std::string>() ) );
for(std::vector<std::string>::const_iterator it=esmFiles.begin(); it!=esmFiles.end(); ++it) {
cfg["master"].push_back(*it);
}
cfg.erase("plugin");
cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("plugin", std::vector<std::string>() ) );
for(std::vector<std::string>::const_iterator it=espFiles.begin(); it!=espFiles.end(); ++it) { for(std::vector<std::string>::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) {
cfg["plugin"].push_back(*it); cfg["content"].push_back(*it);
} }
} }

@ -5,11 +5,11 @@ opencs_units (. editor)
set (CMAKE_BUILD_TYPE DEBUG) set (CMAKE_BUILD_TYPE DEBUG)
opencs_units (model/doc opencs_units (model/doc
document document operation saving
) )
opencs_units_noqt (model/doc opencs_units_noqt (model/doc
documentmanager documentmanager stage savingstate savingstages
) )
opencs_hdrs_noqt (model/doc opencs_hdrs_noqt (model/doc
@ -24,27 +24,27 @@ 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 scriptcontext cell refidcollection
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection
) )
opencs_hdrs_noqt (model/world opencs_hdrs_noqt (model/world
columnimp idcollection collection columnimp idcollection collection info
) )
opencs_units (model/tools opencs_units (model/tools
tools operation reportmodel tools reportmodel
) )
opencs_units_noqt (model/tools opencs_units_noqt (model/tools
stage verifier mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck birthsigncheck spellcheck referenceablecheck
) )
opencs_units (view/doc opencs_units (view/doc
viewmanager view operations operation subview startup filedialog newgame filewidget viewmanager view operations operation subview startup filedialog newgame
adjusterwidget filewidget adjusterwidget
) )
@ -60,13 +60,17 @@ opencs_hdrs_noqt (view/doc
opencs_units (view/world opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool
scenetoolmode scenetoolmode infocreator
) )
opencs_units (view/render
scenewidget
)
opencs_units_noqt (view/world opencs_units_noqt (view/world
dialoguesubview subviews dialoguesubview subviews
enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate
scripthighlighter idvalidator scripthighlighter idvalidator dialoguecreator
) )
@ -124,11 +128,13 @@ opencs_units (view/filter
set (OPENCS_US set (OPENCS_US
) )
set (OPENCS_RES ../../files/opencs/resources.qrc set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc
../../files/launcher/launcher.qrc ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc
) )
set (OPENCS_UI ../../files/ui/datafilespage.ui set (OPENCS_UI
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui
) )
source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR}) source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR})
@ -137,7 +143,7 @@ if(WIN32)
set(QT_USE_QTMAIN TRUE) set(QT_USE_QTMAIN TRUE)
endif(WIN32) endif(WIN32)
find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork QtXml QtXmlPatterns REQUIRED) find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED)
include(${QT_USE_FILE}) include(${QT_USE_FILE})
qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI})
@ -146,15 +152,46 @@ qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR})
if(APPLE)
set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns)
else()
set (OPENCS_MAC_ICON "")
endif(APPLE)
add_executable(opencs add_executable(opencs
MACOSX_BUNDLE
${OPENCS_SRC} ${OPENCS_SRC}
${OPENCS_UI_HDR} ${OPENCS_UI_HDR}
${OPENCS_MOC_SRC} ${OPENCS_MOC_SRC}
${OPENCS_RES_SRC} ${OPENCS_RES_SRC}
${OPENCS_MAC_ICON}
) )
if(APPLE)
set_target_properties(opencs PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}"
OUTPUT_NAME "OpenCS"
MACOSX_BUNDLE_ICON_FILE "opencs.icns"
MACOSX_BUNDLE_BUNDLE_NAME "OpenCS"
MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs"
MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION}
MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION}
)
set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES
MACOSX_PACKAGE_LOCATION Resources)
endif(APPLE)
target_link_libraries(opencs target_link_libraries(opencs
${Boost_LIBRARIES} ${Boost_LIBRARIES}
${QT_LIBRARIES} ${QT_LIBRARIES}
components components
) )
if(DPKG_PROGRAM)
INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs)
endif()
if(APPLE)
INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE)
endif()

@ -6,17 +6,21 @@
#include <QLocalSocket> #include <QLocalSocket>
#include <QMessageBox> #include <QMessageBox>
#include <OgreRoot.h>
#include <OgreRenderWindow.h>
#include "model/doc/document.hpp" #include "model/doc/document.hpp"
#include "model/world/data.hpp" #include "model/world/data.hpp"
CS::Editor::Editor()
CS::Editor::Editor() : mViewManager (mDocumentManager) : mDocumentManager (mCfgMgr), mViewManager (mDocumentManager)
{ {
mIpcServerName = "org.openmw.OpenCS"; mIpcServerName = "org.openmw.OpenCS";
setupDataFiles(); setupDataFiles();
mNewGame.setLocalData (mLocal); mNewGame.setLocalData (mLocal);
mFileDialog.setLocalData (mLocal);
connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ()));
connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ()));
@ -28,23 +32,27 @@ CS::Editor::Editor() : mViewManager (mDocumentManager)
connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ())); connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ()));
connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ())); connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ()));
connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles())); connect (&mFileDialog, SIGNAL(signalOpenFiles (const boost::filesystem::path&)),
connect (&mFileDialog, SIGNAL(createNewFile()), this, SLOT(createNewFile())); this, SLOT(openFiles (const boost::filesystem::path&)));
connect (&mFileDialog, SIGNAL(signalCreateNewFile (const boost::filesystem::path&)),
this, SLOT(createNewFile (const boost::filesystem::path&)));
connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)),
this, SLOT (createNewGame (const boost::filesystem::path&))); this, SLOT (createNewGame (const boost::filesystem::path&)));
} }
void CS::Editor::setupDataFiles() void CS::Editor::setupDataFiles()
{ {
boost::program_options::variables_map variables; boost::program_options::variables_map variables;
boost::program_options::options_description desc; boost::program_options::options_description desc("Syntax: opencs <options>\nAllowed options");
desc.add_options() desc.add_options()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()) ("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken())
("data-local", boost::program_options::value<std::string>()->default_value("")) ("data-local", boost::program_options::value<std::string>()->default_value(""))
("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false)) ("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
("encoding", boost::program_options::value<std::string>()->default_value("win1252")); ("encoding", boost::program_options::value<std::string>()->default_value("win1252"))
("resources", boost::program_options::value<std::string>()->default_value("resources"));
boost::program_options::notify(variables); boost::program_options::notify(variables);
@ -78,14 +86,13 @@ void CS::Editor::setupDataFiles()
return; return;
} }
// Set the charset for reading the esm/esp files
QString encoding = QString::fromStdString(variables["encoding"].as<std::string>());
mFileDialog.setEncoding(encoding);
dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end());
mDocumentManager.setResourceDir (variables["resources"].as<std::string>());
for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter)
{ {
QString path = QString::fromStdString(iter->string()); QString path = QString::fromStdString(iter->string());
mFileDialog.addFiles(path); mFileDialog.addFiles(path);
} }
@ -109,48 +116,39 @@ void CS::Editor::createGame()
void CS::Editor::createAddon() void CS::Editor::createAddon()
{ {
mStartup.hide(); mStartup.hide();
mFileDialog.showDialog (CSVDoc::ContentAction_New);
mFileDialog.newFile();
} }
void CS::Editor::loadDocument() void CS::Editor::loadDocument()
{ {
mStartup.hide(); mStartup.hide();
mFileDialog.showDialog (CSVDoc::ContentAction_Edit);
mFileDialog.openFile();
} }
void CS::Editor::openFiles() void CS::Editor::openFiles (const boost::filesystem::path &savePath)
{ {
std::vector<boost::filesystem::path> files; std::vector<boost::filesystem::path> files;
QStringList paths = mFileDialog.checkedItemsPaths();
foreach (const QString &path, paths) { foreach (const QString &path, mFileDialog.selectedFilePaths())
files.push_back(path.toStdString()); files.push_back(path.toStdString());
}
/// \todo Get the save path from the file dialogue CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, false);
CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), false);
mViewManager.addView (document); mViewManager.addView (document);
mFileDialog.hide(); mFileDialog.hide();
} }
void CS::Editor::createNewFile() void CS::Editor::createNewFile (const boost::filesystem::path &savePath)
{ {
std::vector<boost::filesystem::path> files; std::vector<boost::filesystem::path> files;
QStringList paths = mFileDialog.checkedItemsPaths();
foreach (const QString &path, paths) { foreach (const QString &path, mFileDialog.selectedFilePaths()) {
files.push_back(path.toStdString()); files.push_back(path.toStdString());
} }
files.push_back(mFileDialog.fileName().toStdString()); files.push_back(mFileDialog.filename().toStdString());
/// \todo Get the save path from the file dialogue.
CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), true); CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, true);
mViewManager.addView (document); mViewManager.addView (document);
mFileDialog.hide(); mFileDialog.hide();
@ -212,6 +210,20 @@ int CS::Editor::run()
if (mLocal.empty()) if (mLocal.empty())
return 1; return 1;
// TODO: setting
Ogre::Root::getSingleton().setRenderSystem(Ogre::Root::getSingleton().getRenderSystemByName("OpenGL Rendering Subsystem"));
Ogre::Root::getSingleton().initialise(false);
// Create a hidden background window to keep resources
Ogre::NameValuePairList params;
params.insert(std::make_pair("title", ""));
params.insert(std::make_pair("FSAA", "0"));
params.insert(std::make_pair("vsync", "false"));
params.insert(std::make_pair("hidden", "true"));
Ogre::RenderWindow* hiddenWindow = Ogre::Root::getSingleton().createRenderWindow("InactiveHidden", 1, 1, false, &params);
hiddenWindow->setActive(false);
mStartup.show(); mStartup.show();
QApplication::setQuitOnLastWindowClosed (true); QApplication::setQuitOnLastWindowClosed (true);

@ -26,15 +26,15 @@ namespace CS
{ {
Q_OBJECT Q_OBJECT
Files::ConfigurationManager mCfgMgr;
CSMSettings::UserSettings mUserSettings; CSMSettings::UserSettings mUserSettings;
CSMDoc::DocumentManager mDocumentManager; CSMDoc::DocumentManager mDocumentManager;
CSVDoc::ViewManager mViewManager; CSVDoc::ViewManager mViewManager;
CSVDoc::StartupDialogue mStartup; CSVDoc::StartupDialogue mStartup;
CSVDoc::NewGameDialogue mNewGame; CSVDoc::NewGameDialogue mNewGame;
CSVSettings::UserSettingsDialog mSettings; CSVSettings::UserSettingsDialog mSettings;
FileDialog mFileDialog; CSVDoc::FileDialog mFileDialog;
Files::ConfigurationManager mCfgMgr;
boost::filesystem::path mLocal; boost::filesystem::path mLocal;
void setupDataFiles(); void setupDataFiles();
@ -59,8 +59,8 @@ namespace CS
void createAddon(); void createAddon();
void loadDocument(); void loadDocument();
void openFiles(); void openFiles (const boost::filesystem::path &path);
void createNewFile(); void createNewFile (const boost::filesystem::path& path);
void createNewGame (const boost::filesystem::path& file); void createNewGame (const boost::filesystem::path& file);
void showStartup(); void showStartup();

@ -7,6 +7,12 @@
#include <QApplication> #include <QApplication>
#include <QIcon> #include <QIcon>
#include <components/ogreinit/ogreinit.hpp>
#ifdef Q_OS_MAC
#include <QDir>
#endif
class Application : public QApplication class Application : public QApplication
{ {
private: private:
@ -33,7 +39,32 @@ class Application : public QApplication
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
Q_INIT_RESOURCE (resources); Q_INIT_RESOURCE (resources);
// TODO: Ogre startup shouldn't be here, but it currently has to:
// SceneWidget destructor will delete the created render window, which would be called _after_ Root has shut down :(
Application mApplication (argc, argv); Application mApplication (argc, argv);
OgreInit::OgreInit ogreInit;
ogreInit.init("./opencsOgre.log"); // TODO log path?
#ifdef Q_OS_MAC
QDir dir(QCoreApplication::applicationDirPath());
if (dir.dirName() == "MacOS") {
dir.cdUp();
dir.cdUp();
dir.cdUp();
}
QDir::setCurrent(dir.absolutePath());
// force Qt to load only LOCAL plugins, don't touch system Qt installation
QDir pluginsPath(QCoreApplication::applicationDirPath());
pluginsPath.cdUp();
pluginsPath.cd("Plugins");
QStringList libraryPaths;
libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath();
mApplication.setLibraryPaths(libraryPaths);
#endif
mApplication.setWindowIcon (QIcon (":./opencs.png")); mApplication.setWindowIcon (QIcon (":./opencs.png"));
@ -42,7 +73,7 @@ int main(int argc, char *argv[])
if(!editor.makeIPCServer()) if(!editor.makeIPCServer())
{ {
editor.connectToIPCServer(); editor.connectToIPCServer();
return 0; // return 0;
} }
return editor.run(); return editor.run();

@ -1,8 +1,15 @@
#include "document.hpp" #include "document.hpp"
#include <cassert> #include <cassert>
#include <boost/filesystem.hpp>
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>
#endif
void CSMDoc::Document::load (const std::vector<boost::filesystem::path>::const_iterator& begin, void CSMDoc::Document::load (const std::vector<boost::filesystem::path>::const_iterator& begin,
const std::vector<boost::filesystem::path>::const_iterator& end, bool lastAsModified) const std::vector<boost::filesystem::path>::const_iterator& end, bool lastAsModified)
{ {
assert (begin!=end); assert (begin!=end);
@ -12,10 +19,10 @@ void CSMDoc::Document::load (const std::vector<boost::filesystem::path>::const_i
--end2; --end2;
for (std::vector<boost::filesystem::path>::const_iterator iter (begin); iter!=end2; ++iter) for (std::vector<boost::filesystem::path>::const_iterator iter (begin); iter!=end2; ++iter)
getData().loadFile (*iter, true); getData().loadFile (*iter, true, false);
if (lastAsModified) if (lastAsModified)
getData().loadFile (*end2, false); getData().loadFile (*end2, false, false);
} }
void CSMDoc::Document::addGmsts() void CSMDoc::Document::addGmsts()
@ -2058,9 +2065,9 @@ void CSMDoc::Document::addOptionalGlobals()
{ {
static const char *sGlobals[] = static const char *sGlobals[] =
{ {
"dayspassed", "DaysPassed",
"pcwerewolf", "PCWerewolf",
"pcyear", "PCYear",
0 0
}; };
@ -2137,11 +2144,86 @@ void CSMDoc::Document::createBase()
getData().getSkills().add (record); getData().getSkills().add (record);
} }
static const char *sVoice[] =
{
"Intruder",
"Attack",
"Hello",
"Thief",
"Alarm",
"Idle",
"Flee",
"Hit",
0
};
for (int i=0; sVoice[i]; ++i)
{
ESM::Dialogue record;
record.mId = sVoice[i];
record.mType = ESM::Dialogue::Voice;
record.blank();
getData().getTopics().add (record);
}
static const char *sGreetings[] =
{
"Greeting 0",
"Greeting 1",
"Greeting 2",
"Greeting 3",
"Greeting 4",
"Greeting 5",
"Greeting 6",
"Greeting 7",
"Greeting 8",
"Greeting 9",
0
};
for (int i=0; sGreetings[i]; ++i)
{
ESM::Dialogue record;
record.mId = sGreetings[i];
record.mType = ESM::Dialogue::Greeting;
record.blank();
getData().getTopics().add (record);
}
static const char *sPersuasion[] =
{
"Intimidate Success",
"Intimidate Fail",
"Service Refusal",
"Admire Success",
"Taunt Success",
"Bribe Success",
"Info Refusal",
"Admire Fail",
"Taunt Fail",
"Bribe Fail",
0
};
for (int i=0; sPersuasion[i]; ++i)
{
ESM::Dialogue record;
record.mId = sPersuasion[i];
record.mType = ESM::Dialogue::Persuasion;
record.blank();
getData().getTopics().add (record);
}
} }
CSMDoc::Document::Document (const std::vector<boost::filesystem::path>& files, CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_)
const boost::filesystem::path& savePath, bool new_) : mSavePath (savePath), mContentFiles (files), mTools (mData), mResDir(resDir),
: mSavePath (savePath), mTools (mData) mProjectPath ((configuration.getUserDataPath() / "projects") /
(savePath.filename().string() + ".project")),
mSaving (*this, mProjectPath)
{ {
if (files.empty()) if (files.empty())
throw std::runtime_error ("Empty content file sequence"); throw std::runtime_error ("Empty content file sequence");
@ -2158,6 +2240,44 @@ CSMDoc::Document::Document (const std::vector<boost::filesystem::path>& files,
load (files.begin(), end, !new_); load (files.begin(), end, !new_);
} }
if (new_)
{
mData.setDescription ("");
mData.setAuthor ("");
}
bool filtersFound = false;
if (boost::filesystem::exists (mProjectPath))
{
filtersFound = true;
}
else
{
boost::filesystem::path locCustomFiltersPath (configuration.getUserDataPath());
locCustomFiltersPath /= "defaultfilters";
if (boost::filesystem::exists(locCustomFiltersPath))
{
boost::filesystem::copy_file (locCustomFiltersPath, mProjectPath);
filtersFound = true;
}
else
{
boost::filesystem::path filters(mResDir);
filters /= "defaultfilters";
if (boost::filesystem::exists(filters))
{
boost::filesystem::copy_file(filters, mProjectPath);
filtersFound = true;
}
}
}
if (filtersFound)
getData().loadFile (mProjectPath, false, true);
addOptionalGmsts(); addOptionalGmsts();
addOptionalGlobals(); addOptionalGlobals();
@ -2166,9 +2286,10 @@ CSMDoc::Document::Document (const std::vector<boost::filesystem::path>& files,
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)), this, SLOT (operationDone (int))); connect (&mTools, SIGNAL (done (int)), this, SLOT (operationDone (int)));
// dummy implementation -> remove when proper save is implemented. connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
mSaveCount = 0; connect (&mSaving, SIGNAL (done (int)), this, SLOT (operationDone (int)));
connect (&mSaveTimer, SIGNAL(timeout()), this, SLOT (saving())); connect (&mSaving, SIGNAL (reportMessage (const QString&, int)),
this, SLOT (reportMessage (const QString&, int)));
} }
CSMDoc::Document::~Document() CSMDoc::Document::~Document()
@ -2187,7 +2308,7 @@ int CSMDoc::Document::getState() const
if (!mUndoStack.isClean()) if (!mUndoStack.isClean())
state |= State_Modified; state |= State_Modified;
if (mSaveCount) if (mSaving.isRunning())
state |= State_Locked | State_Saving | State_Operation; state |= State_Locked | State_Saving | State_Operation;
if (int operations = mTools.getRunningOperations()) if (int operations = mTools.getRunningOperations())
@ -2201,12 +2322,20 @@ const boost::filesystem::path& CSMDoc::Document::getSavePath() const
return mSavePath; return mSavePath;
} }
const std::vector<boost::filesystem::path>& CSMDoc::Document::getContentFiles() const
{
return mContentFiles;
}
void CSMDoc::Document::save() void CSMDoc::Document::save()
{ {
mSaveCount = 1; if (mSaving.isRunning())
mSaveTimer.start (500); throw std::logic_error (
"Failed to initiate save, because a save operation is already running.");
mSaving.start();
emit stateChanged (getState(), this); emit stateChanged (getState(), this);
emit progress (1, 16, State_Saving, 1, this);
} }
CSMWorld::UniversalId CSMDoc::Document::verify() CSMWorld::UniversalId CSMDoc::Document::verify()
@ -2218,44 +2347,26 @@ CSMWorld::UniversalId CSMDoc::Document::verify()
void CSMDoc::Document::abortOperation (int type) void CSMDoc::Document::abortOperation (int type)
{ {
mTools.abortOperation (type);
if (type==State_Saving) if (type==State_Saving)
{ mSaving.abort();
mSaveCount=0; else
mSaveTimer.stop(); mTools.abortOperation (type);
emit stateChanged (getState(), this);
}
} }
void CSMDoc::Document::modificationStateChanged (bool clean) void CSMDoc::Document::modificationStateChanged (bool clean)
{ {
emit stateChanged (getState(), this); emit stateChanged (getState(), this);
} }
void CSMDoc::Document::reportMessage (const QString& message, int type)
void CSMDoc::Document::operationDone (int type)
{ {
emit stateChanged (getState(), this); /// \todo find a better way to get these messages to the user.
std::cout << message.toUtf8().constData() << std::endl;
} }
void CSMDoc::Document::saving() void CSMDoc::Document::operationDone (int type)
{ {
++mSaveCount; emit stateChanged (getState(), this);
emit progress (mSaveCount, 16, State_Saving, 1, this);
if (mSaveCount>15)
{
//clear the stack before resetting the save state
//to avoid emitting incorrect states
mUndoStack.setClean();
mSaveCount = 0;
mSaveTimer.stop();
emit stateChanged (getState(), this);
}
} }
const CSMWorld::Data& CSMDoc::Document::getData() const const CSMWorld::Data& CSMDoc::Document::getData() const

@ -14,6 +14,7 @@
#include "../tools/tools.hpp" #include "../tools/tools.hpp"
#include "state.hpp" #include "state.hpp"
#include "saving.hpp"
class QAbstractItemModel; class QAbstractItemModel;
@ -23,6 +24,11 @@ namespace ESM
struct Global; struct Global;
} }
namespace Files
{
class ConfigurationManager;
}
namespace CSMDoc namespace CSMDoc
{ {
class Document : public QObject class Document : public QObject
@ -32,16 +38,17 @@ namespace CSMDoc
private: private:
boost::filesystem::path mSavePath; boost::filesystem::path mSavePath;
std::vector<boost::filesystem::path> mContentFiles;
CSMWorld::Data mData; CSMWorld::Data mData;
CSMTools::Tools mTools; CSMTools::Tools mTools;
boost::filesystem::path mProjectPath;
Saving mSaving;
boost::filesystem::path mResDir;
// It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is
// using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late.
QUndoStack mUndoStack; QUndoStack mUndoStack;
int mSaveCount; ///< dummy implementation -> remove when proper save is implemented.
QTimer mSaveTimer; ///< dummy implementation -> remove when proper save is implemented.
// not implemented // not implemented
Document (const Document&); Document (const Document&);
Document& operator= (const Document&); Document& operator= (const Document&);
@ -64,8 +71,7 @@ namespace CSMDoc
public: public:
Document (const std::vector<boost::filesystem::path>& files, Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_);
const boost::filesystem::path& savePath, bool new_);
~Document(); ~Document();
@ -75,6 +81,10 @@ namespace CSMDoc
const boost::filesystem::path& getSavePath() const; const boost::filesystem::path& getSavePath() const;
const std::vector<boost::filesystem::path>& getContentFiles() const;
///< \attention The last element in this collection is the file that is being edited,
/// but with its original path instead of the save path.
void save(); void save();
CSMWorld::UniversalId verify(); CSMWorld::UniversalId verify();
@ -98,10 +108,9 @@ namespace CSMDoc
void modificationStateChanged (bool clean); void modificationStateChanged (bool clean);
void operationDone (int type); void reportMessage (const QString& message, int type);
void saving(); void operationDone (int type);
///< dummy implementation -> remove when proper save is implemented.
public slots: public slots:
@ -110,3 +119,4 @@ namespace CSMDoc
} }
#endif #endif

@ -4,9 +4,22 @@
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
#include <boost/filesystem.hpp>
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>
#endif
#include "document.hpp" #include "document.hpp"
CSMDoc::DocumentManager::DocumentManager() {} CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration)
: mConfiguration (configuration)
{
boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects";
if (!boost::filesystem::is_directory (projectPath))
boost::filesystem::create_directories (projectPath);
}
CSMDoc::DocumentManager::~DocumentManager() CSMDoc::DocumentManager::~DocumentManager()
{ {
@ -17,7 +30,7 @@ CSMDoc::DocumentManager::~DocumentManager()
CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files, const boost::filesystem::path& savePath, CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files, const boost::filesystem::path& savePath,
bool new_) bool new_)
{ {
Document *document = new Document (files, savePath, new_); Document *document = new Document (mConfiguration, files, savePath, mResDir, new_);
mDocuments.push_back (document); mDocuments.push_back (document);
@ -35,4 +48,9 @@ bool CSMDoc::DocumentManager::removeDocument (Document *document)
delete document; delete document;
return mDocuments.empty(); return mDocuments.empty();
} }
void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& parResDir)
{
mResDir = boost::filesystem::system_complete(parResDir);
}

@ -6,6 +6,11 @@
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
namespace Files
{
class ConfigurationManager;
}
namespace CSMDoc namespace CSMDoc
{ {
class Document; class Document;
@ -13,18 +18,18 @@ namespace CSMDoc
class DocumentManager class DocumentManager
{ {
std::vector<Document *> mDocuments; std::vector<Document *> mDocuments;
const Files::ConfigurationManager& mConfiguration;
DocumentManager (const DocumentManager&); DocumentManager (const DocumentManager&);
DocumentManager& operator= (const DocumentManager&); DocumentManager& operator= (const DocumentManager&);
public: public:
DocumentManager(); DocumentManager (const Files::ConfigurationManager& configuration);
~DocumentManager(); ~DocumentManager();
Document *addDocument (const std::vector<boost::filesystem::path>& files, Document *addDocument (const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_);
const boost::filesystem::path& savePath, bool new_);
///< The ownership of the returned document is not transferred to the caller. ///< The ownership of the returned document is not transferred to the caller.
/// ///
/// \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
@ -32,6 +37,10 @@ namespace CSMDoc
bool removeDocument (Document *document); bool removeDocument (Document *document);
///< \return last document removed? ///< \return last document removed?
void setResourceDir (const boost::filesystem::path& parResDir);
private:
boost::filesystem::path mResDir;
}; };
} }

@ -6,16 +6,16 @@
#include <QTimer> #include <QTimer>
#include "../doc/state.hpp" #include "state.hpp"
#include "stage.hpp" #include "stage.hpp"
void CSMTools::Operation::prepareStages() void CSMDoc::Operation::prepareStages()
{ {
mCurrentStage = mStages.begin(); mCurrentStage = mStages.begin();
mCurrentStep = 0; mCurrentStep = 0;
mCurrentStepTotal = 0; mCurrentStepTotal = 0;
mTotalSteps = 0; mTotalSteps = 0;
mError = false;
for (std::vector<std::pair<Stage *, int> >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) for (std::vector<std::pair<Stage *, int> >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter)
{ {
@ -24,38 +24,61 @@ void CSMTools::Operation::prepareStages()
} }
} }
CSMTools::Operation::Operation (int type) : mType (type) {} CSMDoc::Operation::Operation (int type, bool ordered, bool finalAlways)
: mType (type), mOrdered (ordered), mFinalAlways (finalAlways)
{
connect (this, SIGNAL (finished()), this, SLOT (operationDone()));
}
CSMTools::Operation::~Operation() CSMDoc::Operation::~Operation()
{ {
for (std::vector<std::pair<Stage *, int> >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) for (std::vector<std::pair<Stage *, int> >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter)
delete iter->first; delete iter->first;
} }
void CSMTools::Operation::run() void CSMDoc::Operation::run()
{ {
prepareStages(); prepareStages();
QTimer timer; QTimer timer;
timer.connect (&timer, SIGNAL (timeout()), this, SLOT (verify())); timer.connect (&timer, SIGNAL (timeout()), this, SLOT (executeStage()));
timer.start (0); timer.start (0);
exec(); exec();
} }
void CSMTools::Operation::appendStage (Stage *stage) void CSMDoc::Operation::appendStage (Stage *stage)
{ {
mStages.push_back (std::make_pair (stage, 0)); mStages.push_back (std::make_pair (stage, 0));
} }
void CSMTools::Operation::abort() bool CSMDoc::Operation::hasError() const
{ {
exit(); return mError;
} }
void CSMTools::Operation::verify() void CSMDoc::Operation::abort()
{
if (!isRunning())
return;
mError = true;
if (mFinalAlways)
{
if (mStages.begin()!=mStages.end() && mCurrentStage!=--mStages.end())
{
mCurrentStep = 0;
mCurrentStage = --mStages.end();
}
}
else
mCurrentStage = mStages.end();
}
void CSMDoc::Operation::executeStage()
{ {
std::vector<std::string> messages; std::vector<std::string> messages;
@ -68,7 +91,16 @@ void CSMTools::Operation::verify()
} }
else else
{ {
mCurrentStage->first->perform (mCurrentStep++, messages); try
{
mCurrentStage->first->perform (mCurrentStep++, messages);
}
catch (const std::exception& e)
{
emit reportMessage (e.what(), mType);
abort();
}
++mCurrentStepTotal; ++mCurrentStepTotal;
break; break;
} }
@ -81,4 +113,9 @@ void CSMTools::Operation::verify()
if (mCurrentStage==mStages.end()) if (mCurrentStage==mStages.end())
exit(); exit();
}
void CSMDoc::Operation::operationDone()
{
emit done (mType);
} }

@ -1,11 +1,11 @@
#ifndef CSM_TOOLS_OPERATION_H #ifndef CSM_DOC_OPERATION_H
#define CSM_TOOLS_OPERATION_H #define CSM_DOC_OPERATION_H
#include <vector> #include <vector>
#include <QThread> #include <QThread>
namespace CSMTools namespace CSMDoc
{ {
class Stage; class Stage;
@ -19,12 +19,17 @@ namespace CSMTools
int mCurrentStep; int mCurrentStep;
int mCurrentStepTotal; int mCurrentStepTotal;
int mTotalSteps; int mTotalSteps;
int mOrdered;
bool mFinalAlways;
bool mError;
void prepareStages(); void prepareStages();
public: public:
Operation (int type); Operation (int type, bool ordered, bool finalAlways = false);
///< \param ordered Stages must be executed in the given order.
/// \param finalAlways Execute last stage even if an error occurred during earlier stages.
virtual ~Operation(); virtual ~Operation();
@ -35,19 +40,25 @@ namespace CSMTools
/// ///
/// \attention Do no call this function while this Operation is running. /// \attention Do no call this function while this Operation is running.
bool hasError() const;
signals: signals:
void progress (int current, int max, int type); void progress (int current, int max, int type);
void reportMessage (const QString& message, int type); void reportMessage (const QString& message, int type);
void done (int type);
public slots: public slots:
void abort(); void abort();
private slots: private slots:
void verify(); void executeStage();
void operationDone();
}; };
} }

@ -0,0 +1,71 @@
#include "saving.hpp"
#include "../world/data.hpp"
#include "../world/idcollection.hpp"
#include "state.hpp"
#include "savingstages.hpp"
#include "document.hpp"
CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& projectPath)
: Operation (State_Saving, true, true), mDocument (document), mState (*this, projectPath)
{
// save project file
appendStage (new OpenSaveStage (mDocument, mState, true));
appendStage (new WriteHeaderStage (mDocument, mState, true));
appendStage (new WriteFilterStage (mDocument, mState, CSMFilter::Filter::Scope_Project));
appendStage (new CloseSaveStage (mState));
// save content file
appendStage (new OpenSaveStage (mDocument, mState, false));
appendStage (new WriteHeaderStage (mDocument, mState, false));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Global> >
(mDocument.getData().getGlobals(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::GameSetting> >
(mDocument.getData().getGmsts(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Skill> >
(mDocument.getData().getSkills(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Class> >
(mDocument.getData().getClasses(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Faction> >
(mDocument.getData().getFactions(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Race> >
(mDocument.getData().getRaces(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Sound> >
(mDocument.getData().getSounds(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Script> >
(mDocument.getData().getScripts(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Region> >
(mDocument.getData().getRegions(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::BirthSign> >
(mDocument.getData().getBirthsigns(), mState));
appendStage (new WriteCollectionStage<CSMWorld::IdCollection<ESM::Spell> >
(mDocument.getData().getSpells(), mState));
appendStage (new WriteDialogueCollectionStage (mDocument, mState, false));
appendStage (new WriteDialogueCollectionStage (mDocument, mState, true));
appendStage (new WriteRefIdCollectionStage (mDocument, mState));
appendStage (new CloseSaveStage (mState));
appendStage (new FinalSavingStage (mDocument, mState));
}

@ -0,0 +1,27 @@
#ifndef CSM_DOC_SAVING_H
#define CSM_DOC_SAVING_H
#include <boost/filesystem/path.hpp>
#include "operation.hpp"
#include "savingstate.hpp"
namespace CSMDoc
{
class Document;
class Saving : public Operation
{
Q_OBJECT
Document& mDocument;
SavingState mState;
public:
Saving (Document& document, const boost::filesystem::path& projectPath);
};
}
#endif

@ -0,0 +1,263 @@
#include "savingstages.hpp"
#include <fstream>
#include <boost/filesystem.hpp>
#include <QUndoStack>
#include <components/esm/loaddial.hpp>
#include "../world/infocollection.hpp"
#include "document.hpp"
#include "savingstate.hpp"
CSMDoc::OpenSaveStage::OpenSaveStage (Document& document, SavingState& state, bool projectFile)
: mDocument (document), mState (state), mProjectFile (projectFile)
{}
int CSMDoc::OpenSaveStage::setup()
{
return 1;
}
void CSMDoc::OpenSaveStage::perform (int stage, std::vector<std::string>& messages)
{
mState.start (mDocument, mProjectFile);
mState.getStream().open ((mProjectFile ? mState.getPath() : mState.getTmpPath()).string().c_str());
if (!mState.getStream().is_open())
throw std::runtime_error ("failed to open stream for saving");
}
CSMDoc::WriteHeaderStage::WriteHeaderStage (Document& document, SavingState& state, bool simple)
: mDocument (document), mState (state), mSimple (simple)
{}
int CSMDoc::WriteHeaderStage::setup()
{
return 1;
}
void CSMDoc::WriteHeaderStage::perform (int stage, std::vector<std::string>& messages)
{
mState.getWriter().setVersion();
mState.getWriter().clearMaster();
mState.getWriter().setFormat (0);
if (mSimple)
{
mState.getWriter().setAuthor ("");
mState.getWriter().setDescription ("");
mState.getWriter().setRecordCount (0);
}
else
{
mState.getWriter().setAuthor (mDocument.getData().getAuthor());
mState.getWriter().setDescription (mDocument.getData().getDescription());
mState.getWriter().setRecordCount (
mDocument.getData().count (CSMWorld::RecordBase::State_Modified) +
mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) +
mDocument.getData().count (CSMWorld::RecordBase::State_Deleted));
/// \todo refine dependency list (at least remove redundant dependencies)
std::vector<boost::filesystem::path> dependencies = mDocument.getContentFiles();
std::vector<boost::filesystem::path>::const_iterator end (--dependencies.end());
for (std::vector<boost::filesystem::path>::const_iterator iter (dependencies.begin());
iter!=end; ++iter)
{
std::string name = iter->filename().string();
uint64_t size = boost::filesystem::file_size (*iter);
mState.getWriter().addMaster (name, size);
}
}
mState.getWriter().save (mState.getStream());
}
CSMDoc::WriteDialogueCollectionStage::WriteDialogueCollectionStage (Document& document,
SavingState& state, bool journal)
: mDocument (document), mState (state),
mTopics (journal ? document.getData().getJournals() : document.getData().getTopics()),
mInfos (journal ? document.getData().getJournalInfos() : document.getData().getTopicInfos())
{}
int CSMDoc::WriteDialogueCollectionStage::setup()
{
return mTopics.getSize();
}
void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vector<std::string>& messages)
{
const CSMWorld::Record<ESM::Dialogue>& topic = mTopics.getRecord (stage);
CSMWorld::RecordBase::State state = topic.mState;
if (state==CSMWorld::RecordBase::State_Deleted)
{
// if the topic is deleted, we do not need to bother with INFO records.
/// \todo wrote record with delete flag
return;
}
// Test, if we need to save anything associated info records.
bool infoModified = false;
CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId);
for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter)
{
CSMWorld::RecordBase::State state = iter->mState;
if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly ||
state==CSMWorld::RecordBase::State_Deleted)
{
infoModified = true;
break;
}
}
if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly ||
infoModified)
{
mState.getWriter().startRecord (topic.mModified.sRecordId);
mState.getWriter().writeHNCString ("NAME", topic.mModified.mId);
topic.mModified.save (mState.getWriter());
mState.getWriter().endRecord (topic.mModified.sRecordId);
// write modified selected info records
for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second;
++iter)
{
CSMWorld::RecordBase::State state = iter->mState;
if (state==CSMWorld::RecordBase::State_Deleted)
{
/// \todo wrote record with delete flag
}
else if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly)
{
ESM::DialInfo info = iter->get();
info.mId = info.mId.substr (info.mId.find_last_of ('#')+1);
if (iter!=range.first)
{
CSMWorld::InfoCollection::RecordConstIterator prev = iter;
--prev;
info.mPrev =
prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1);
}
CSMWorld::InfoCollection::RecordConstIterator next = iter;
++next;
if (next!=range.second)
{
info.mNext =
next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1);
}
mState.getWriter().startRecord (info.sRecordId);
mState.getWriter().writeHNCString ("INAM", info.mId);
info.save (mState.getWriter());
mState.getWriter().endRecord (info.sRecordId);
}
}
}
}
CSMDoc::WriteRefIdCollectionStage::WriteRefIdCollectionStage (Document& document, SavingState& state)
: mDocument (document), mState (state)
{}
int CSMDoc::WriteRefIdCollectionStage::setup()
{
return mDocument.getData().getReferenceables().getSize();
}
void CSMDoc::WriteRefIdCollectionStage::perform (int stage, std::vector<std::string>& messages)
{
mDocument.getData().getReferenceables().save (stage, mState.getWriter());
}
CSMDoc::WriteFilterStage::WriteFilterStage (Document& document, SavingState& state,
CSMFilter::Filter::Scope scope)
: WriteCollectionStage<CSMWorld::IdCollection<CSMFilter::Filter> > (document.getData().getFilters(),
state),
mDocument (document), mScope (scope)
{}
void CSMDoc::WriteFilterStage::perform (int stage, std::vector<std::string>& messages)
{
const CSMWorld::Record<CSMFilter::Filter>& record =
mDocument.getData().getFilters().getRecord (stage);
if (record.get().mScope==mScope)
WriteCollectionStage<CSMWorld::IdCollection<CSMFilter::Filter> >::perform (stage, messages);
}
CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state)
: mState (state)
{}
int CSMDoc::CloseSaveStage::setup()
{
return 1;
}
void CSMDoc::CloseSaveStage::perform (int stage, std::vector<std::string>& messages)
{
mState.getStream().close();
if (!mState.getStream())
throw std::runtime_error ("saving failed");
}
CSMDoc::FinalSavingStage::FinalSavingStage (Document& document, SavingState& state)
: mDocument (document), mState (state)
{}
int CSMDoc::FinalSavingStage::setup()
{
return 1;
}
void CSMDoc::FinalSavingStage::perform (int stage, std::vector<std::string>& messages)
{
if (mState.hasError())
{
mState.getWriter().close();
mState.getStream().close();
if (boost::filesystem::exists (mState.getTmpPath()))
boost::filesystem::remove (mState.getTmpPath());
}
else if (!mState.isProjectFile())
{
if (boost::filesystem::exists (mState.getPath()))
boost::filesystem::remove (mState.getPath());
boost::filesystem::rename (mState.getTmpPath(), mState.getPath());
mDocument.getUndoStack().setClean();
}
}

@ -0,0 +1,201 @@
#ifndef CSM_DOC_SAVINGSTAGES_H
#define CSM_DOC_SAVINGSTAGES_H
#include "stage.hpp"
#include "../world/record.hpp"
#include "../world/idcollection.hpp"
#include "../filter/filter.hpp"
#include "savingstate.hpp"
namespace ESM
{
struct Dialogue;
}
namespace CSMWorld
{
class InfoCollection;
}
namespace CSMDoc
{
class Document;
class SavingState;
class OpenSaveStage : public Stage
{
Document& mDocument;
SavingState& mState;
bool mProjectFile;
public:
OpenSaveStage (Document& document, SavingState& state, bool projectFile);
///< \param projectFile Saving the project file instead of the content file.
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class WriteHeaderStage : public Stage
{
Document& mDocument;
SavingState& mState;
bool mSimple;
public:
WriteHeaderStage (Document& document, SavingState& state, bool simple);
///< \param simple Simplified header (used for project files).
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
template<class CollectionT>
class WriteCollectionStage : public Stage
{
const CollectionT& mCollection;
SavingState& mState;
public:
WriteCollectionStage (const CollectionT& collection, SavingState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
template<class CollectionT>
WriteCollectionStage<CollectionT>::WriteCollectionStage (const CollectionT& collection,
SavingState& state)
: mCollection (collection), mState (state)
{}
template<class CollectionT>
int WriteCollectionStage<CollectionT>::setup()
{
return mCollection.getSize();
}
template<class CollectionT>
void WriteCollectionStage<CollectionT>::perform (int stage, std::vector<std::string>& messages)
{
CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState;
if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly)
{
std::string type;
for (int i=0; i<4; ++i)
/// \todo make endianess agnostic (change ESMWriter interface?)
type += reinterpret_cast<const char *> (&mCollection.getRecord (stage).mModified.sRecordId)[i];
mState.getWriter().startRecord (mCollection.getRecord (stage).mModified.sRecordId);
mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage));
mCollection.getRecord (stage).mModified.save (mState.getWriter());
mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId);
}
else if (state==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
}
}
class WriteDialogueCollectionStage : public Stage
{
Document& mDocument;
SavingState& mState;
const CSMWorld::IdCollection<ESM::Dialogue>& mTopics;
CSMWorld::InfoCollection& mInfos;
public:
WriteDialogueCollectionStage (Document& document, SavingState& state, bool journal);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class WriteRefIdCollectionStage : public Stage
{
Document& mDocument;
SavingState& mState;
public:
WriteRefIdCollectionStage (Document& document, SavingState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class WriteFilterStage : public WriteCollectionStage<CSMWorld::IdCollection<CSMFilter::Filter> >
{
Document& mDocument;
CSMFilter::Filter::Scope mScope;
public:
WriteFilterStage (Document& document, SavingState& state, CSMFilter::Filter::Scope scope);
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class CloseSaveStage : public Stage
{
SavingState& mState;
public:
CloseSaveStage (SavingState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class FinalSavingStage : public Stage
{
Document& mDocument;
SavingState& mState;
public:
FinalSavingStage (Document& document, SavingState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
}
#endif

@ -0,0 +1,65 @@
#include "savingstate.hpp"
#include "operation.hpp"
#include "document.hpp"
CSMDoc::SavingState::SavingState (Operation& operation, const boost::filesystem::path& projectPath)
: mOperation (operation),
/// \todo set encoding properly, once config implementation has been fixed.
mEncoder (ToUTF8::calculateEncoding ("win1252")),
mProjectPath (projectPath), mProjectFile (false)
{
mWriter.setEncoder (&mEncoder);
}
bool CSMDoc::SavingState::hasError() const
{
return mOperation.hasError();
}
void CSMDoc::SavingState::start (Document& document, bool project)
{
mProjectFile = project;
if (mStream.is_open())
mStream.close();
mStream.clear();
if (project)
mPath = mProjectPath;
else
mPath = document.getSavePath();
boost::filesystem::path file (mPath.filename().string() + ".tmp");
mTmpPath = mPath.parent_path();
mTmpPath /= file;
}
const boost::filesystem::path& CSMDoc::SavingState::getPath() const
{
return mPath;
}
const boost::filesystem::path& CSMDoc::SavingState::getTmpPath() const
{
return mTmpPath;
}
std::ofstream& CSMDoc::SavingState::getStream()
{
return mStream;
}
ESM::ESMWriter& CSMDoc::SavingState::getWriter()
{
return mWriter;
}
bool CSMDoc::SavingState::isProjectFile() const
{
return mProjectFile;
}

@ -0,0 +1,50 @@
#ifndef CSM_DOC_SAVINGSTATE_H
#define CSM_DOC_SAVINGSTATE_H
#include <fstream>
#include <boost/filesystem/path.hpp>
#include <components/esm/esmwriter.hpp>
namespace CSMDoc
{
class Operation;
class Document;
class SavingState
{
Operation& mOperation;
boost::filesystem::path mPath;
boost::filesystem::path mTmpPath;
ToUTF8::Utf8Encoder mEncoder;
std::ofstream mStream;
ESM::ESMWriter mWriter;
boost::filesystem::path mProjectPath;
bool mProjectFile;
public:
SavingState (Operation& operation, const boost::filesystem::path& projectPath);
bool hasError() const;
void start (Document& document, bool project);
///< \param project Save project file instead of content file.
const boost::filesystem::path& getPath() const;
const boost::filesystem::path& getTmpPath() const;
std::ofstream& getStream();
ESM::ESMWriter& getWriter();
bool isProjectFile() const;
///< Currently saving project file? (instead of content file)
};
}
#endif

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

@ -1,10 +1,10 @@
#ifndef CSM_TOOLS_STAGE_H #ifndef CSM_DOC_STAGE_H
#define CSM_TOOLS_STAGE_H #define CSM_DOC_STAGE_H
#include <vector> #include <vector>
#include <string> #include <string>
namespace CSMTools namespace CSMDoc
{ {
class Stage class Stage
{ {
@ -16,7 +16,7 @@ namespace CSMTools
///< \return number of steps ///< \return number of steps
virtual void perform (int stage, std::vector<std::string>& messages) = 0; virtual void perform (int stage, std::vector<std::string>& messages) = 0;
///< Messages resulting from this tage will be appended to \a messages. ///< Messages resulting from this stage will be appended to \a messages.
}; };
} }

@ -7,8 +7,6 @@ namespace CSMFilter
{ {
class AndNode : public NAryNode class AndNode : public NAryNode
{ {
bool mAnd;
public: public:
AndNode (const std::vector<boost::shared_ptr<Node> >& nodes); AndNode (const std::vector<boost::shared_ptr<Node> >& nodes);

@ -7,8 +7,6 @@ namespace CSMFilter
{ {
class OrNode : public NAryNode class OrNode : public NAryNode
{ {
bool mAnd;
public: public:
OrNode (const std::vector<boost::shared_ptr<Node> >& nodes); OrNode (const std::vector<boost::shared_ptr<Node> >& nodes);

@ -61,11 +61,11 @@ namespace CSMFilter
bool isString() const; bool isString() const;
}; };
Token::Token (Type type) : mType (type) {} Token::Token (Type type) : mType (type), mNumber(0.0) {}
Token::Token (Type type, const std::string& string) : mType (type), mString (string) {} Token::Token (Type type, const std::string& string) : mType (type), mString (string), mNumber(0.0) {}
Token::Token (const std::string& string) : mType (Type_String), mString (string) {} Token::Token (const std::string& string) : mType (Type_String), mString (string), mNumber(0.0) {}
Token::Token (double number) : mType (Type_Number), mNumber (number) {} Token::Token (double number) : mType (Type_Number), mNumber (number) {}
@ -614,4 +614,4 @@ boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::getFilter() const
throw std::logic_error ("No filter available"); throw std::logic_error ("No filter available");
return mFilter; return mFilter;
} }

@ -44,7 +44,11 @@ namespace CSMSettings
inline QStringPair *getValuePair() { return mValuePair; } inline QStringPair *getValuePair() { return mValuePair; }
/// set value range (spinbox / integer use) /// set value range (spinbox / integer use)
inline void setValuePair (QStringPair valuePair) { mValuePair = new QStringPair(valuePair); } inline void setValuePair (QStringPair valuePair)
{
delete mValuePair;
mValuePair = new QStringPair(valuePair);
}
inline bool isMultivalue () { return mIsMultiValue; } inline bool isMultivalue () { return mIsMultiValue; }

@ -251,7 +251,7 @@ void CSMSettings::UserSettings::loadSettings (const QString &fileName)
bool localOk = loadFromFile(localFilePath); bool localOk = loadFromFile(localFilePath);
//user //user
mUserFilePath = QString::fromStdString(mCfgMgr.getUserPath().string()) + fileName; mUserFilePath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + fileName;
loadFromFile(mUserFilePath); loadFromFile(mUserFilePath);
if (!(localOk || globalOk)) if (!(localOk || globalOk))

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMTools namespace CSMTools
{ {
/// \brief VerifyStage: make sure that birthsign records are internally consistent /// \brief VerifyStage: make sure that birthsign records are internally consistent
class BirthsignCheckStage : public Stage class BirthsignCheckStage : public CSMDoc::Stage
{ {
const CSMWorld::IdCollection<ESM::BirthSign>& mBirthsigns; const CSMWorld::IdCollection<ESM::BirthSign>& mBirthsigns;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMTools namespace CSMTools
{ {
/// \brief VerifyStage: make sure that class records are internally consistent /// \brief VerifyStage: make sure that class records are internally consistent
class ClassCheckStage : public Stage class ClassCheckStage : public CSMDoc::Stage
{ {
const CSMWorld::IdCollection<ESM::Class>& mClasses; const CSMWorld::IdCollection<ESM::Class>& mClasses;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMTools namespace CSMTools
{ {
/// \brief VerifyStage: make sure that faction records are internally consistent /// \brief VerifyStage: make sure that faction records are internally consistent
class FactionCheckStage : public Stage class FactionCheckStage : public CSMDoc::Stage
{ {
const CSMWorld::IdCollection<ESM::Faction>& mFactions; const CSMWorld::IdCollection<ESM::Faction>& mFactions;

@ -6,7 +6,7 @@
#include "../world/universalid.hpp" #include "../world/universalid.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMWorld namespace CSMWorld
{ {
@ -16,7 +16,7 @@ namespace CSMWorld
namespace CSMTools namespace CSMTools
{ {
/// \brief Verify stage: make sure that records with specific IDs exist. /// \brief Verify stage: make sure that records with specific IDs exist.
class MandatoryIdStage : public Stage class MandatoryIdStage : public CSMDoc::Stage
{ {
const CSMWorld::CollectionBase& mIdCollection; const CSMWorld::CollectionBase& mIdCollection;
CSMWorld::UniversalId mCollectionId; CSMWorld::UniversalId mCollectionId;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMTools namespace CSMTools
{ {
/// \brief VerifyStage: make sure that race records are internally consistent /// \brief VerifyStage: make sure that race records are internally consistent
class RaceCheckStage : public Stage class RaceCheckStage : public CSMDoc::Stage
{ {
const CSMWorld::IdCollection<ESM::Race>& mRaces; const CSMWorld::IdCollection<ESM::Race>& mRaces;
bool mPlayable; bool mPlayable;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,78 @@
#ifndef REFERENCEABLECHECKSTAGE_H
#define REFERENCEABLECHECKSTAGE_H
#include "../world/universalid.hpp"
#include "../doc/stage.hpp"
#include "../world/data.hpp"
#include "../world/refiddata.hpp"
namespace CSMTools
{
class ReferenceableCheckStage : public CSMDoc::Stage
{
public:
ReferenceableCheckStage(const CSMWorld::RefIdData& referenceable,
const CSMWorld::IdCollection<ESM::Race>& races,
const CSMWorld::IdCollection<ESM::Class>& classes,
const CSMWorld::IdCollection<ESM::Faction>& factions);
virtual void perform(int stage, std::vector< std::string >& messages);
virtual int setup();
private:
//CONCRETE CHECKS
void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, std::vector< std::string >& messages);
void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, std::vector< std::string >& messages);
void potionCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Potion>& records, std::vector<std::string>& messages);
void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Apparatus>& records, std::vector<std::string>& messages);
void armorCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Armor>& records, std::vector<std::string>& messages);
void clothingCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Clothing>& records, std::vector<std::string>& messages);
void containerCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Container>& records, std::vector<std::string>& messages);
void creatureCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Creature>& records, std::vector<std::string>& messages);
void doorCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Door>& records, std::vector<std::string>& messages);
void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Ingredient>& records, std::vector<std::string>& messages);
void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::CreatureLevList>& records, std::vector<std::string>& messages);
void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::ItemLevList>& records, std::vector<std::string>& messages);
void lightCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Light>& records, std::vector<std::string>& messages);
void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Lockpick>& records, std::vector<std::string>& messages);
void miscCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Miscellaneous>& records, std::vector<std::string>& messages);
void npcCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::NPC>& records, std::vector<std::string>& messages);
void weaponCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Weapon>& records, std::vector<std::string>& messages);
void probeCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Probe>& records, std::vector<std::string>& messages);
void repairCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Repair>& records, std::vector<std::string>& messages);
void staticCheck(int stage, const CSMWorld::RefIdDataContainer<ESM::Static>& records, std::vector<std::string>& messages);
//FINAL CHECK
void finalCheck(std::vector<std::string>& messages);
//TEMPLATE CHECKS
template<typename ITEM> void inventoryItemCheck(const ITEM& someItem,
std::vector<std::string>& messages,
const std::string& someID,
bool enchantable); //for all enchantable items.
template<typename ITEM> void inventoryItemCheck(const ITEM& someItem,
std::vector<std::string>& messages,
const std::string& someID); //for non-enchantable items.
template<typename TOOL> void toolCheck(const TOOL& someTool,
std::vector<std::string>& messages,
const std::string& someID,
bool canbebroken); //for tools with uses.
template<typename TOOL> void toolCheck(const TOOL& someTool,
std::vector<std::string>& messages,
const std::string& someID); //for tools without uses.
template<typename LIST> void listCheck(const LIST& someList,
std::vector< std::string >& messages,
const std::string& someID);
const CSMWorld::RefIdData& mReferencables;
const CSMWorld::IdCollection<ESM::Race>& mRaces;
const CSMWorld::IdCollection<ESM::Class>& mClasses;
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
bool mPlayerPresent;
};
}
#endif // REFERENCEABLECHECKSTAGE_H

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMTools namespace CSMTools
{ {
/// \brief VerifyStage: make sure that region records are internally consistent /// \brief VerifyStage: make sure that region records are internally consistent
class RegionCheckStage : public Stage class RegionCheckStage : public CSMDoc::Stage
{ {
const CSMWorld::IdCollection<ESM::Region>& mRegions; const CSMWorld::IdCollection<ESM::Region>& mRegions;

@ -68,4 +68,4 @@ void CSMTools::ReportModel::add (const std::string& row)
const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const
{ {
return mRows.at (row).first; return mRows.at (row).first;
} }

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMTools namespace CSMTools
{ {
/// \brief VerifyStage: make sure that skill records are internally consistent /// \brief VerifyStage: make sure that skill records are internally consistent
class SkillCheckStage : public Stage class SkillCheckStage : public CSMDoc::Stage
{ {
const CSMWorld::IdCollection<ESM::Skill>& mSkills; const CSMWorld::IdCollection<ESM::Skill>& mSkills;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMTools namespace CSMTools
{ {
/// \brief VerifyStage: make sure that sound records are internally consistent /// \brief VerifyStage: make sure that sound records are internally consistent
class SoundCheckStage : public Stage class SoundCheckStage : public CSMDoc::Stage
{ {
const CSMWorld::IdCollection<ESM::Sound>& mSounds; const CSMWorld::IdCollection<ESM::Sound>& mSounds;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
#include "stage.hpp" #include "../doc/stage.hpp"
namespace CSMTools namespace CSMTools
{ {
/// \brief VerifyStage: make sure that spell records are internally consistent /// \brief VerifyStage: make sure that spell records are internally consistent
class SpellCheckStage : public Stage class SpellCheckStage : public CSMDoc::Stage
{ {
const CSMWorld::IdCollection<ESM::Spell>& mSpells; const CSMWorld::IdCollection<ESM::Spell>& mSpells;

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

@ -3,9 +3,8 @@
#include <QThreadPool> #include <QThreadPool>
#include "verifier.hpp"
#include "../doc/state.hpp" #include "../doc/state.hpp"
#include "../doc/operation.hpp"
#include "../world/data.hpp" #include "../world/data.hpp"
#include "../world/universalid.hpp" #include "../world/universalid.hpp"
@ -20,8 +19,9 @@
#include "regioncheck.hpp" #include "regioncheck.hpp"
#include "birthsigncheck.hpp" #include "birthsigncheck.hpp"
#include "spellcheck.hpp" #include "spellcheck.hpp"
#include "referenceablecheck.hpp"
CSMTools::Operation *CSMTools::Tools::get (int type) CSMDoc::Operation *CSMTools::Tools::get (int type)
{ {
switch (type) switch (type)
{ {
@ -31,19 +31,19 @@ CSMTools::Operation *CSMTools::Tools::get (int type)
return 0; return 0;
} }
const CSMTools::Operation *CSMTools::Tools::get (int type) const const CSMDoc::Operation *CSMTools::Tools::get (int type) const
{ {
return const_cast<Tools *> (this)->get (type); return const_cast<Tools *> (this)->get (type);
} }
CSMTools::Verifier *CSMTools::Tools::getVerifier() CSMDoc::Operation *CSMTools::Tools::getVerifier()
{ {
if (!mVerifier) if (!mVerifier)
{ {
mVerifier = new Verifier; mVerifier = new CSMDoc::Operation (CSMDoc::State_Verifying, false);
connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int)));
connect (mVerifier, SIGNAL (finished()), this, SLOT (verifierDone())); connect (mVerifier, SIGNAL (done (int)), this, SIGNAL (done (int)));
connect (mVerifier, SIGNAL (reportMessage (const QString&, int)), connect (mVerifier, SIGNAL (reportMessage (const QString&, int)),
this, SLOT (verifierMessage (const QString&, int))); this, SLOT (verifierMessage (const QString&, int)));
@ -75,6 +75,8 @@ CSMTools::Verifier *CSMTools::Tools::getVerifier()
mVerifier->appendStage (new BirthsignCheckStage (mData.getBirthsigns())); mVerifier->appendStage (new BirthsignCheckStage (mData.getBirthsigns()));
mVerifier->appendStage (new SpellCheckStage (mData.getSpells())); mVerifier->appendStage (new SpellCheckStage (mData.getSpells()));
mVerifier->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions()));
} }
return mVerifier; return mVerifier;
@ -103,7 +105,7 @@ CSMWorld::UniversalId CSMTools::Tools::runVerifier()
void CSMTools::Tools::abortOperation (int type) void CSMTools::Tools::abortOperation (int type)
{ {
if (Operation *operation = get (type)) if (CSMDoc::Operation *operation = get (type))
operation->abort(); operation->abort();
} }
@ -118,7 +120,7 @@ int CSMTools::Tools::getRunningOperations() const
int result = 0; int result = 0;
for (int i=0; sOperations[i]!=-1; ++i) for (int i=0; sOperations[i]!=-1; ++i)
if (const Operation *operation = get (sOperations[i])) if (const CSMDoc::Operation *operation = get (sOperations[i]))
if (operation->isRunning()) if (operation->isRunning())
result |= sOperations[i]; result |= sOperations[i];
@ -133,15 +135,11 @@ CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId&
return mReports.at (id.getIndex()); return mReports.at (id.getIndex());
} }
void CSMTools::Tools::verifierDone()
{
emit done (CSMDoc::State_Verifying);
}
void CSMTools::Tools::verifierMessage (const QString& message, int type) void CSMTools::Tools::verifierMessage (const QString& message, int type)
{ {
std::map<int, int>::iterator iter = mActiveReports.find (type); std::map<int, int>::iterator iter = mActiveReports.find (type);
if (iter!=mActiveReports.end()) if (iter!=mActiveReports.end())
mReports[iter->second]->add (message.toStdString()); mReports[iter->second]->add (message.toStdString());
} }

@ -11,10 +11,13 @@ namespace CSMWorld
class UniversalId; class UniversalId;
} }
namespace CSMTools namespace CSMDoc
{ {
class Verifier;
class Operation; class Operation;
}
namespace CSMTools
{
class ReportModel; class ReportModel;
class Tools : public QObject class Tools : public QObject
@ -22,7 +25,7 @@ namespace CSMTools
Q_OBJECT Q_OBJECT
CSMWorld::Data& mData; CSMWorld::Data& mData;
Verifier *mVerifier; CSMDoc::Operation *mVerifier;
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
@ -31,12 +34,12 @@ namespace CSMTools
Tools (const Tools&); Tools (const Tools&);
Tools& operator= (const Tools&); Tools& operator= (const Tools&);
Verifier *getVerifier(); CSMDoc::Operation *getVerifier();
Operation *get (int type); CSMDoc::Operation *get (int type);
///< Returns a 0-pointer, if operation hasn't been used yet. ///< Returns a 0-pointer, if operation hasn't been used yet.
const Operation *get (int type) const; const CSMDoc::Operation *get (int type) const;
///< Returns a 0-pointer, if operation hasn't been used yet. ///< Returns a 0-pointer, if operation hasn't been used yet.
public: public:
@ -58,8 +61,6 @@ namespace CSMTools
private slots: private slots:
void verifierDone();
void verifierMessage (const QString& message, int type); void verifierMessage (const QString& message, int type);
signals: signals:

@ -1,7 +0,0 @@
#include "verifier.hpp"
#include "../doc/state.hpp"
CSMTools::Verifier::Verifier() : Operation (CSMDoc::State_Verifying)
{}

@ -1,17 +0,0 @@
#ifndef CSM_TOOLS_VERIFIER_H
#define CSM_TOOLS_VERIFIER_H
#include "operation.hpp"
namespace CSMTools
{
class Verifier : public Operation
{
public:
Verifier();
};
}
#endif

@ -51,6 +51,18 @@ namespace CSMWorld
Collection (const Collection&); Collection (const Collection&);
Collection& operator= (const Collection&); Collection& operator= (const Collection&);
protected:
const std::map<std::string, int>& getIdMap() const;
const std::vector<Record<ESXRecordT> >& getRecords() const;
bool reorderRowsImp (int baseIndex, const std::vector<int>& newOrder);
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
/// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
///
/// \return Success?
public: public:
Collection(); Collection();
@ -86,6 +98,10 @@ namespace CSMWorld
UniversalId::Type type = UniversalId::Type_None); UniversalId::Type type = UniversalId::Type_None);
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
virtual void cloneRecord(const std::string& origin,
const std::string& destination,
const UniversalId::Type type);
virtual int searchId (const std::string& id) const; virtual int searchId (const std::string& id) const;
////< Search record with \a id. ////< Search record with \a id.
/// \return index of record (if found) or -1 (not found) /// \return index of record (if found) or -1 (not found)
@ -104,7 +120,8 @@ namespace CSMWorld
virtual const Record<ESXRecordT>& getRecord (int index) const; virtual const Record<ESXRecordT>& getRecord (int index) const;
virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const; virtual int getAppendIndex (const std::string& id,
UniversalId::Type type = UniversalId::Type_None) const;
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
virtual std::vector<std::string> getIds (bool listDeleted = true) const; virtual std::vector<std::string> getIds (bool listDeleted = true) const;
@ -112,12 +129,87 @@ namespace CSMWorld
/// ///
/// \param listDeleted include deleted record in the list /// \param listDeleted include deleted record in the list
virtual void insertRecord (const RecordBase& record, int index,
UniversalId::Type type = UniversalId::Type_None);
///< Insert record before index.
///
/// If the record type does not match, an exception is thrown.
///
/// If the index is invalid either generally (by being out of range) or for the particular
/// record, an exception is thrown.
virtual bool reorderRows (int baseIndex, const std::vector<int>& newOrder);
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
/// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
///
/// \return Success?
void addColumn (Column<ESXRecordT> *column); void addColumn (Column<ESXRecordT> *column);
void setRecord (int index, const Record<ESXRecordT>& record); void setRecord (int index, const Record<ESXRecordT>& record);
///< \attention This function must not change the ID. ///< \attention This function must not change the ID.
}; };
template<typename ESXRecordT, typename IdAccessorT>
const std::map<std::string, int>& Collection<ESXRecordT, IdAccessorT>::getIdMap() const
{
return mIndex;
}
template<typename ESXRecordT, typename IdAccessorT>
const std::vector<Record<ESXRecordT> >& Collection<ESXRecordT, IdAccessorT>::getRecords() const
{
return mRecords;
}
template<typename ESXRecordT, typename IdAccessorT>
bool Collection<ESXRecordT, IdAccessorT>::reorderRowsImp (int baseIndex,
const std::vector<int>& newOrder)
{
if (!newOrder.empty())
{
int size = static_cast<int> (newOrder.size());
// check that all indices are present
std::vector<int> test (newOrder);
std::sort (test.begin(), test.end());
if (*test.begin()!=0 || *--test.end()!=size-1)
return false;
// reorder records
std::vector<Record<ESXRecordT> > buffer (size);
for (int i=0; i<size; ++i)
{
buffer[newOrder[i]] = mRecords [baseIndex+i];
buffer[newOrder[i]].setModified (buffer[newOrder[i]].get());
}
std::copy (buffer.begin(), buffer.end(), mRecords.begin()+baseIndex);
// adjust index
for (std::map<std::string, int>::iterator iter (mIndex.begin()); iter!=mIndex.end();
++iter)
if (iter->second>=baseIndex && iter->second<baseIndex+size)
iter->second = newOrder.at (iter->second-baseIndex)+baseIndex;
}
return true;
}
template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::cloneRecord(const std::string& origin,
const std::string& destination,
const UniversalId::Type type)
{
Record<ESXRecordT> copy;
copy.mModified = getRecord(origin).get();
copy.mState = RecordBase::State_ModifiedOnly;
copy.get().mId = destination;
insertRecord(copy, getAppendIndex(destination, type));
}
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
Collection<ESXRecordT, IdAccessorT>::Collection() Collection<ESXRecordT, IdAccessorT>::Collection()
{} {}
@ -142,8 +234,7 @@ namespace CSMWorld
record2.mState = Record<ESXRecordT>::State_ModifiedOnly; record2.mState = Record<ESXRecordT>::State_ModifiedOnly;
record2.mModified = record; record2.mModified = record;
mRecords.push_back (record2); insertRecord (record2, getAppendIndex (id));
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), mRecords.size()-1));
} }
else else
{ {
@ -260,7 +351,12 @@ namespace CSMWorld
ESXRecordT record; ESXRecordT record;
IdAccessorT().getId (record) = id; IdAccessorT().getId (record) = id;
record.blank(); record.blank();
add (record);
Record<ESXRecordT> record2;
record2.mState = Record<ESXRecordT>::State_ModifiedOnly;
record2.mModified = record;
insertRecord (record2, getAppendIndex (id, type), type);
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
@ -286,14 +382,14 @@ namespace CSMWorld
void Collection<ESXRecordT, IdAccessorT>::appendRecord (const RecordBase& record, void Collection<ESXRecordT, IdAccessorT>::appendRecord (const RecordBase& record,
UniversalId::Type type) UniversalId::Type type)
{ {
mRecords.push_back (dynamic_cast<const Record<ESXRecordT>&> (record)); insertRecord (record,
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( getAppendIndex (IdAccessorT().getId (
dynamic_cast<const Record<ESXRecordT>&> (record).get())), dynamic_cast<const Record<ESXRecordT>&> (record).get()), type), type);
mRecords.size()-1));
} }
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
int Collection<ESXRecordT, IdAccessorT>::getAppendIndex (UniversalId::Type type) const int Collection<ESXRecordT, IdAccessorT>::getAppendIndex (const std::string& id,
UniversalId::Type type) const
{ {
return static_cast<int> (mRecords.size()); return static_cast<int> (mRecords.size());
} }
@ -326,6 +422,29 @@ namespace CSMWorld
return mRecords.at (index); return mRecords.at (index);
} }
template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::insertRecord (const RecordBase& record, int index,
UniversalId::Type type)
{
if (index<0 || index>static_cast<int> (mRecords.size()))
throw std::runtime_error ("index out of range");
const Record<ESXRecordT>& record2 = dynamic_cast<const Record<ESXRecordT>&> (record);
mRecords.insert (mRecords.begin()+index, record2);
if (index<static_cast<int> (mRecords.size())-1)
{
for (std::map<std::string, int>::iterator iter (mIndex.begin()); iter!=mIndex.end();
++iter)
if (iter->second>=index)
++(iter->second);
}
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId (
record2.get())), index));
}
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::setRecord (int index, const Record<ESXRecordT>& record) void Collection<ESXRecordT, IdAccessorT>::setRecord (int index, const Record<ESXRecordT>& record)
{ {
@ -334,6 +453,12 @@ namespace CSMWorld
mRecords.at (index) = record; mRecords.at (index) = record;
} }
template<typename ESXRecordT, typename IdAccessorT>
bool Collection<ESXRecordT, IdAccessorT>::reorderRows (int baseIndex, const std::vector<int>& newOrder)
{
return false;
}
} }
#endif #endif

@ -1,6 +1,31 @@
#include "collectionbase.hpp" #include "collectionbase.hpp"
#include <stdexcept>
#include "columnbase.hpp"
CSMWorld::CollectionBase::CollectionBase() {} CSMWorld::CollectionBase::CollectionBase() {}
CSMWorld::CollectionBase::~CollectionBase() {} CSMWorld::CollectionBase::~CollectionBase() {}
int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const
{
int columns = getColumns();
for (int i=0; i<columns; ++i)
if (getColumn (i).mColumnId==id)
return i;
return -1;
}
int CSMWorld::CollectionBase::findColumnIndex (Columns::ColumnId id) const
{
int index = searchColumnIndex (id);
if (index==-1)
throw std::logic_error ("invalid column index");
return index;
}

@ -2,8 +2,10 @@
#define CSM_WOLRD_COLLECTIONBASE_H #define CSM_WOLRD_COLLECTIONBASE_H
#include <string> #include <string>
#include <vector>
#include "universalid.hpp" #include "universalid.hpp"
#include "columns.hpp"
class QVariant; class QVariant;
@ -72,17 +74,35 @@ namespace CSMWorld
UniversalId::Type type = UniversalId::Type_None) = 0; UniversalId::Type type = UniversalId::Type_None) = 0;
///< If the record type does not match, an exception is thrown. ///< If the record type does not match, an exception is thrown.
virtual void cloneRecord(const std::string& origin,
const std::string& destination,
const UniversalId::Type type) = 0;
virtual const RecordBase& getRecord (const std::string& id) const = 0; virtual const RecordBase& getRecord (const std::string& id) const = 0;
virtual const RecordBase& getRecord (int index) const = 0; virtual const RecordBase& getRecord (int index) const = 0;
virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const = 0; virtual int getAppendIndex (const std::string& id,
UniversalId::Type type = UniversalId::Type_None) const = 0;
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
virtual std::vector<std::string> getIds (bool listDeleted = true) const = 0; virtual std::vector<std::string> getIds (bool listDeleted = true) const = 0;
///< Return a sorted collection of all IDs ///< Return a sorted collection of all IDs
/// ///
/// \param listDeleted include deleted record in the list /// \param listDeleted include deleted record in the list
virtual bool reorderRows (int baseIndex, const std::vector<int>& newOrder) = 0;
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
/// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
///
/// \return Success?
int searchColumnIndex (Columns::ColumnId id) const;
///< Return index of column with the given \a id. If no such column exists, -1 is returned.
int findColumnIndex (Columns::ColumnId id) const;
///< Return index of column with the given \a id. If no such column exists, an exception is
/// thrown.
}; };
} }

@ -43,7 +43,10 @@ namespace CSMWorld
Display_CreatureType, Display_CreatureType,
Display_WeaponType, Display_WeaponType,
Display_RecordState, Display_RecordState,
Display_RefRecordType Display_RefRecordType,
Display_DialogueType,
Display_QuestStatusType,
Display_Gender
}; };
int mColumnId; int mColumnId;

@ -9,6 +9,7 @@
#include "columnbase.hpp" #include "columnbase.hpp"
#include "columns.hpp" #include "columns.hpp"
#include "info.hpp"
namespace CSMWorld namespace CSMWorld
{ {
@ -814,14 +815,14 @@ namespace CSMWorld
virtual QVariant get (const Record<ESXRecordT>& record) const virtual QVariant get (const Record<ESXRecordT>& record) const
{ {
return QString::fromUtf8 (record.get().mCellId.c_str()); return QString::fromUtf8 (record.get().mCell.c_str());
} }
virtual void set (Record<ESXRecordT>& record, const QVariant& data) virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{ {
ESXRecordT record2 = record.get(); ESXRecordT record2 = record.get();
record2.mCellId = data.toString().toUtf8().constData(); record2.mCell = data.toString().toUtf8().constData();
record.setModified (record2); record.setModified (record2);
} }
@ -1217,6 +1218,37 @@ namespace CSMWorld
} }
}; };
template<typename ESXRecordT>
struct ScopeColumn : public Column<ESXRecordT>
{
ScopeColumn()
: Column<ESXRecordT> (Columns::ColumnId_Scope, ColumnBase::Display_Integer, 0)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return static_cast<int> (record.get().mScope);
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mScope = static_cast<CSMFilter::Filter::Scope> (data.toInt());
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
virtual bool isUserEditable() const
{
return false;
}
};
template<typename ESXRecordT> template<typename ESXRecordT>
struct PosColumn : public Column<ESXRecordT> struct PosColumn : public Column<ESXRecordT>
{ {
@ -1284,6 +1316,373 @@ namespace CSMWorld
return true; return true;
} }
}; };
template<typename ESXRecordT>
struct DialogueTypeColumn : public Column<ESXRecordT>
{
DialogueTypeColumn (bool hidden = false)
: Column<ESXRecordT> (Columns::ColumnId_DialogueType, ColumnBase::Display_DialogueType,
hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return static_cast<int> (record.get().mType);
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mType = data.toInt();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
virtual bool isUserEditable() const
{
return false;
}
};
template<typename ESXRecordT>
struct QuestStatusTypeColumn : public Column<ESXRecordT>
{
QuestStatusTypeColumn()
: Column<ESXRecordT> (Columns::ColumnId_QuestStatusType, ColumnBase::Display_QuestStatusType)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return static_cast<int> (record.get().mQuestStatus);
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mQuestStatus = static_cast<Info::QuestStatus> (data.toInt());
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct QuestDescriptionColumn : public Column<ESXRecordT>
{
QuestDescriptionColumn() : Column<ESXRecordT> (Columns::ColumnId_QuestDescription, ColumnBase::Display_String) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return QString::fromUtf8 (record.get().mResponse.c_str());
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mResponse = data.toString().toUtf8().constData();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct QuestIndexColumn : public Column<ESXRecordT>
{
QuestIndexColumn()
: Column<ESXRecordT> (Columns::ColumnId_QuestIndex, ColumnBase::Display_Integer)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return record.get().mData.mDisposition;
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mData.mDisposition = data.toInt();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct TopicColumn : public Column<ESXRecordT>
{
TopicColumn (bool journal) : Column<ESXRecordT> (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, ColumnBase::Display_String) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return QString::fromUtf8 (record.get().mTopicId.c_str());
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mTopicId = data.toString().toUtf8().constData();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
virtual bool isUserEditable() const
{
return false;
}
};
template<typename ESXRecordT>
struct ActorColumn : public Column<ESXRecordT>
{
ActorColumn() : Column<ESXRecordT> (Columns::ColumnId_Actor, ColumnBase::Display_String) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return QString::fromUtf8 (record.get().mActor.c_str());
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mActor = data.toString().toUtf8().constData();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct RaceColumn : public Column<ESXRecordT>
{
RaceColumn() : Column<ESXRecordT> (Columns::ColumnId_Race, ColumnBase::Display_String) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return QString::fromUtf8 (record.get().mRace.c_str());
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mRace = data.toString().toUtf8().constData();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct ClassColumn : public Column<ESXRecordT>
{
ClassColumn() : Column<ESXRecordT> (Columns::ColumnId_Class, ColumnBase::Display_String) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return QString::fromUtf8 (record.get().mClass.c_str());
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mClass = data.toString().toUtf8().constData();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct PcFactionColumn : public Column<ESXRecordT>
{
PcFactionColumn() : Column<ESXRecordT> (Columns::ColumnId_PcFaction, ColumnBase::Display_String) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return QString::fromUtf8 (record.get().mPcFaction.c_str());
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mPcFaction = data.toString().toUtf8().constData();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct ResponseColumn : public Column<ESXRecordT>
{
ResponseColumn() : Column<ESXRecordT> (Columns::ColumnId_Response, ColumnBase::Display_String) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return QString::fromUtf8 (record.get().mResponse.c_str());
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mResponse = data.toString().toUtf8().constData();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct DispositionColumn : public Column<ESXRecordT>
{
DispositionColumn()
: Column<ESXRecordT> (Columns::ColumnId_Disposition, ColumnBase::Display_Integer)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return record.get().mData.mDisposition;
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mData.mDisposition = data.toInt();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct RankColumn : public Column<ESXRecordT>
{
RankColumn()
: Column<ESXRecordT> (Columns::ColumnId_Rank, ColumnBase::Display_Integer)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return static_cast<int> (record.get().mData.mRank);
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mData.mRank = static_cast<signed char> (data.toInt());
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct PcRankColumn : public Column<ESXRecordT>
{
PcRankColumn()
: Column<ESXRecordT> (Columns::ColumnId_PcRank, ColumnBase::Display_Integer)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return static_cast<int> (record.get().mData.mPCrank);
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mData.mPCrank = static_cast<signed char> (data.toInt());
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct GenderColumn : public Column<ESXRecordT>
{
GenderColumn()
: Column<ESXRecordT> (Columns::ColumnId_Gender, ColumnBase::Display_Gender)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return static_cast<int> (record.get().mData.mGender);
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mData.mGender = data.toInt();
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
} }
#endif #endif

@ -159,6 +159,20 @@ namespace CSMWorld
{ ColumnId_DoorPositionXRot, "Teleport Rot X" }, { ColumnId_DoorPositionXRot, "Teleport Rot X" },
{ ColumnId_DoorPositionYRot, "Teleport Rot Y" }, { ColumnId_DoorPositionYRot, "Teleport Rot Y" },
{ ColumnId_DoorPositionZRot, "Teleport Rot Z" }, { ColumnId_DoorPositionZRot, "Teleport Rot Z" },
{ ColumnId_DialogueType, "Dialogue Type" },
{ ColumnId_QuestIndex, "Quest Index" },
{ ColumnId_QuestStatusType, "Quest Status" },
{ ColumnId_QuestDescription, "Quest Description" },
{ ColumnId_Topic, "Topic" },
{ ColumnId_Journal, "Journal" },
{ ColumnId_Actor, "Actor" },
{ ColumnId_PcFaction, "PC Faction" },
{ ColumnId_Response, "Response" },
{ ColumnId_Disposition, "Disposition" },
{ ColumnId_Rank, "Rank" },
{ ColumnId_Gender, "Gender" },
{ ColumnId_PcRank, "PC Rank" },
{ ColumnId_Scope, "Scope", },
{ ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue1, "Use value 1" },
{ ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue2, "Use value 2" },
@ -206,7 +220,7 @@ int CSMWorld::Columns::getId (const std::string& name)
std::string name2 = Misc::StringUtils::lowerCase (name); std::string name2 = Misc::StringUtils::lowerCase (name);
for (int i=0; sNames[i].mName; ++i) for (int i=0; sNames[i].mName; ++i)
if (name2==Misc::StringUtils::lowerCase (sNames[i].mName)) if (Misc::StringUtils::ciEqual(sNames[i].mName, name2))
return sNames[i].mId; return sNames[i].mId;
return -1; return -1;
@ -249,7 +263,7 @@ namespace
static const char *sCreatureTypes[] = static const char *sCreatureTypes[] =
{ {
"Creature", "Deadra", "Undead", "Humanoid", 0 "Creature", "Daedra", "Undead", "Humanoid", 0
}; };
static const char *sWeaponTypes[] = static const char *sWeaponTypes[] =
@ -269,6 +283,21 @@ namespace
"unknown", "none", "short", "integer", "long", "float", "string", 0 "unknown", "none", "short", "integer", "long", "float", "string", 0
}; };
static const char *sDialogueTypeEnums[] =
{
"Topic", "Voice", "Greeting", "Persuasion", 0
};
static const char *sQuestStatusTypes[] =
{
"None", "Name", "Finished", "Restart", 0
};
static const char *sGenderEnums[] =
{
"Male", "Female", 0
};
const char **getEnumNames (CSMWorld::Columns::ColumnId column) const char **getEnumNames (CSMWorld::Columns::ColumnId column)
{ {
switch (column) switch (column)
@ -283,6 +312,9 @@ namespace
case CSMWorld::Columns::ColumnId_WeaponType: return sWeaponTypes; case CSMWorld::Columns::ColumnId_WeaponType: return sWeaponTypes;
case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums; case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums;
case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums; case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums;
case CSMWorld::Columns::ColumnId_DialogueType: return sDialogueTypeEnums;
case CSMWorld::Columns::ColumnId_QuestStatusType: return sQuestStatusTypes;
case CSMWorld::Columns::ColumnId_Gender: return sGenderEnums;
default: return 0; default: return 0;
} }
@ -310,4 +342,4 @@ std::vector<std::string> CSMWorld::Columns::getEnums (ColumnId column)
} }
return enums; return enums;
} }

@ -152,6 +152,20 @@ namespace CSMWorld
ColumnId_DoorPositionXRot = 139, ColumnId_DoorPositionXRot = 139,
ColumnId_DoorPositionYRot = 140, ColumnId_DoorPositionYRot = 140,
ColumnId_DoorPositionZRot = 141, ColumnId_DoorPositionZRot = 141,
ColumnId_DialogueType = 142,
ColumnId_QuestIndex = 143,
ColumnId_QuestStatusType = 144,
ColumnId_QuestDescription = 145,
ColumnId_Topic = 146,
ColumnId_Journal = 147,
ColumnId_Actor = 148,
ColumnId_PcFaction = 149,
ColumnId_Response = 150,
ColumnId_Disposition = 151,
ColumnId_Rank = 152,
ColumnId_Gender = 153,
ColumnId_PcRank = 154,
ColumnId_Scope = 155,
// 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.

@ -4,10 +4,10 @@
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include "idtable.hpp" #include "idtable.hpp"
#include "idtable.hpp" #include <components/misc/stringops.hpp>
CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index,
const QVariant& new_, QUndoCommand *parent) const QVariant& new_, QUndoCommand* parent)
: QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_) : QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_)
{ {
mOld = mModel.data (mIndex, Qt::EditRole); mOld = mModel.data (mIndex, Qt::EditRole);
@ -25,7 +25,7 @@ void CSMWorld::ModifyCommand::undo()
mModel.setData (mIndex, mOld); mModel.setData (mIndex, mOld);
} }
CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent) CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent)
: QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None) : QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None)
{ {
setText (("Create record " + id).c_str()); setText (("Create record " + id).c_str());
@ -54,7 +54,7 @@ void CSMWorld::CreateCommand::undo()
mModel.removeRow (mModel.getModelIndex (mId, 0).row()); mModel.removeRow (mModel.getModelIndex (mId, 0).row());
} }
CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent) CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent)
: QUndoCommand (parent), mModel (model), mId (id), mOld (0) : QUndoCommand (parent), mModel (model), mId (id), mOld (0)
{ {
setText (("Revert record " + id).c_str()); setText (("Revert record " + id).c_str());
@ -89,7 +89,7 @@ void CSMWorld::RevertCommand::undo()
mModel.setRecord (mId, *mOld); mModel.setRecord (mId, *mOld);
} }
CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent) CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand* parent)
: QUndoCommand (parent), mModel (model), mId (id), mOld (0) : QUndoCommand (parent), mModel (model), mId (id), mOld (0)
{ {
setText (("Delete record " + id).c_str()); setText (("Delete record " + id).c_str());
@ -122,4 +122,53 @@ void CSMWorld::DeleteCommand::redo()
void CSMWorld::DeleteCommand::undo() void CSMWorld::DeleteCommand::undo()
{ {
mModel.setRecord (mId, *mOld); mModel.setRecord (mId, *mOld);
}
CSMWorld::ReorderRowsCommand::ReorderRowsCommand (IdTable& model, int baseIndex,
const std::vector<int>& newOrder)
: mModel (model), mBaseIndex (baseIndex), mNewOrder (newOrder)
{}
void CSMWorld::ReorderRowsCommand::redo()
{
mModel.reorderRows (mBaseIndex, mNewOrder);
}
void CSMWorld::ReorderRowsCommand::undo()
{
int size = static_cast<int> (mNewOrder.size());
std::vector<int> reverse (size);
for (int i=0; i< size; ++i)
reverse.at (mNewOrder[i]) = i;
mModel.reorderRows (mBaseIndex, reverse);
}
CSMWorld::CloneCommand::CloneCommand (CSMWorld::IdTable& model,
const std::string& idOrigin,
const std::string& IdDestination,
const CSMWorld::UniversalId::Type type,
QUndoCommand* parent) :
QUndoCommand (parent),
mModel (model),
mIdOrigin (idOrigin),
mIdDestination (Misc::StringUtils::lowerCase (IdDestination)),
mType (type)
{
setText ( ("Clone record " + idOrigin + " to the " + IdDestination).c_str());
}
void CSMWorld::CloneCommand::redo()
{
mModel.cloneRecord (mIdOrigin, mIdDestination, mType);
for (std::map<int, QVariant>::const_iterator iter (mValues.begin()); iter != mValues.end(); ++iter)
mModel.setData (mModel.getModelIndex (mIdDestination, iter->first), iter->second);
}
void CSMWorld::CloneCommand::undo()
{
mModel.removeRow (mModel.getModelIndex (mIdDestination, 0).row());
} }

@ -5,6 +5,7 @@
#include <string> #include <string>
#include <map> #include <map>
#include <vector>
#include <QVariant> #include <QVariant>
#include <QUndoCommand> #include <QUndoCommand>
@ -38,6 +39,26 @@ namespace CSMWorld
virtual void undo(); virtual void undo();
}; };
class CloneCommand : public QUndoCommand
{
IdTable& mModel;
std::string mIdOrigin;
std::string mIdDestination;
UniversalId::Type mType;
std::map<int, QVariant> mValues;
public:
CloneCommand (IdTable& model, const std::string& idOrigin,
const std::string& IdDestination,
const UniversalId::Type type,
QUndoCommand* parent = 0);
virtual void redo();
virtual void undo();
};
class CreateCommand : public QUndoCommand class CreateCommand : public QUndoCommand
{ {
IdTable& mModel; IdTable& mModel;
@ -99,6 +120,21 @@ namespace CSMWorld
virtual void undo(); virtual void undo();
}; };
class ReorderRowsCommand : public QUndoCommand
{
IdTable& mModel;
int mBaseIndex;
std::vector<int> mNewOrder;
public:
ReorderRowsCommand (IdTable& model, int baseIndex, const std::vector<int>& newOrder);
virtual void redo();
virtual void undo();
};
} }
#endif #endif

@ -44,6 +44,17 @@ void CSMWorld::Data::appendIds (std::vector<std::string>& ids, const CollectionB
ids.insert (ids.end(), ids2.begin(), ids2.end()); ids.insert (ids.end(), ids2.begin(), ids2.end());
} }
int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collection)
{
int number = 0;
for (int i=0; i<collection.getSize(); ++i)
if (collection.getRecord (i).mState==state)
++number;
return number;
}
CSMWorld::Data::Data() : mRefs (mCells) CSMWorld::Data::Data() : mRefs (mCells)
{ {
mGlobals.addColumn (new StringIdColumn<ESM::Global>); mGlobals.addColumn (new StringIdColumn<ESM::Global>);
@ -141,6 +152,41 @@ CSMWorld::Data::Data() : mRefs (mCells)
mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_StarterSpell, 0x2)); mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_StarterSpell, 0x2));
mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_AlwaysSucceeds, 0x4)); mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_AlwaysSucceeds, 0x4));
mTopics.addColumn (new StringIdColumn<ESM::Dialogue>);
mTopics.addColumn (new RecordStateColumn<ESM::Dialogue>);
mTopics.addColumn (new FixedRecordTypeColumn<ESM::Dialogue> (UniversalId::Type_Topic));
mTopics.addColumn (new DialogueTypeColumn<ESM::Dialogue>);
mJournals.addColumn (new StringIdColumn<ESM::Dialogue>);
mJournals.addColumn (new RecordStateColumn<ESM::Dialogue>);
mJournals.addColumn (new FixedRecordTypeColumn<ESM::Dialogue> (UniversalId::Type_Journal));
mJournals.addColumn (new DialogueTypeColumn<ESM::Dialogue> (true));
mTopicInfos.addColumn (new StringIdColumn<Info> (true));
mTopicInfos.addColumn (new RecordStateColumn<Info>);
mTopicInfos.addColumn (new FixedRecordTypeColumn<Info> (UniversalId::Type_TopicInfo));
mTopicInfos.addColumn (new TopicColumn<Info> (false));
mTopicInfos.addColumn (new ActorColumn<Info>);
mTopicInfos.addColumn (new RaceColumn<Info>);
mTopicInfos.addColumn (new ClassColumn<Info>);
mTopicInfos.addColumn (new FactionColumn<Info>);
mTopicInfos.addColumn (new CellColumn<Info>);
mTopicInfos.addColumn (new DispositionColumn<Info>);
mTopicInfos.addColumn (new RankColumn<Info>);
mTopicInfos.addColumn (new GenderColumn<Info>);
mTopicInfos.addColumn (new PcFactionColumn<Info>);
mTopicInfos.addColumn (new PcRankColumn<Info>);
mTopicInfos.addColumn (new SoundFileColumn<Info>);
mTopicInfos.addColumn (new ResponseColumn<Info>);
mJournalInfos.addColumn (new StringIdColumn<Info> (true));
mJournalInfos.addColumn (new RecordStateColumn<Info>);
mJournalInfos.addColumn (new FixedRecordTypeColumn<Info> (UniversalId::Type_Journal));
mJournalInfos.addColumn (new TopicColumn<Info> (true));
mJournalInfos.addColumn (new QuestStatusTypeColumn<Info>);
mJournalInfos.addColumn (new QuestIndexColumn<Info>);
mJournalInfos.addColumn (new QuestDescriptionColumn<Info>);
mCells.addColumn (new StringIdColumn<Cell>); mCells.addColumn (new StringIdColumn<Cell>);
mCells.addColumn (new RecordStateColumn<Cell>); mCells.addColumn (new RecordStateColumn<Cell>);
mCells.addColumn (new FixedRecordTypeColumn<Cell> (UniversalId::Type_Cell)); mCells.addColumn (new FixedRecordTypeColumn<Cell> (UniversalId::Type_Cell));
@ -152,6 +198,7 @@ CSMWorld::Data::Data() : mRefs (mCells)
mRefs.addColumn (new StringIdColumn<CellRef> (true)); mRefs.addColumn (new StringIdColumn<CellRef> (true));
mRefs.addColumn (new RecordStateColumn<CellRef>); mRefs.addColumn (new RecordStateColumn<CellRef>);
mRefs.addColumn (new FixedRecordTypeColumn<CellRef> (UniversalId::Type_Reference));
mRefs.addColumn (new CellColumn<CellRef>); mRefs.addColumn (new CellColumn<CellRef>);
mRefs.addColumn (new IdColumn<CellRef>); mRefs.addColumn (new IdColumn<CellRef>);
mRefs.addColumn (new PosColumn<CellRef> (&CellRef::mPos, 0, false)); mRefs.addColumn (new PosColumn<CellRef> (&CellRef::mPos, 0, false));
@ -182,8 +229,10 @@ CSMWorld::Data::Data() : mRefs (mCells)
mFilters.addColumn (new StringIdColumn<CSMFilter::Filter>); mFilters.addColumn (new StringIdColumn<CSMFilter::Filter>);
mFilters.addColumn (new RecordStateColumn<CSMFilter::Filter>); mFilters.addColumn (new RecordStateColumn<CSMFilter::Filter>);
mFilters.addColumn (new FixedRecordTypeColumn<CSMFilter::Filter> (UniversalId::Type_Filter));
mFilters.addColumn (new FilterColumn<CSMFilter::Filter>); mFilters.addColumn (new FilterColumn<CSMFilter::Filter>);
mFilters.addColumn (new DescriptionColumn<CSMFilter::Filter>); mFilters.addColumn (new DescriptionColumn<CSMFilter::Filter>);
mFilters.addColumn (new ScopeColumn<CSMFilter::Filter>);
addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global);
addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst);
@ -196,6 +245,10 @@ CSMWorld::Data::Data() : mRefs (mCells)
addModel (new IdTable (&mRegions), UniversalId::Type_Regions, UniversalId::Type_Region); addModel (new IdTable (&mRegions), UniversalId::Type_Regions, UniversalId::Type_Region);
addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsigns, UniversalId::Type_Birthsign); addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsigns, UniversalId::Type_Birthsign);
addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell); addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell);
addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic);
addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal);
addModel (new IdTable (&mTopicInfos), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo);
addModel (new IdTable (&mJournalInfos), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo);
addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell); addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell);
addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables, addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables,
UniversalId::Type_Referenceable); UniversalId::Type_Referenceable);
@ -319,6 +372,47 @@ CSMWorld::IdCollection<ESM::Spell>& CSMWorld::Data::getSpells()
return mSpells; return mSpells;
} }
const CSMWorld::IdCollection<ESM::Dialogue>& CSMWorld::Data::getTopics() const
{
return mTopics;
}
CSMWorld::IdCollection<ESM::Dialogue>& CSMWorld::Data::getTopics()
{
return mTopics;
}
const CSMWorld::IdCollection<ESM::Dialogue>& CSMWorld::Data::getJournals() const
{
return mJournals;
}
CSMWorld::IdCollection<ESM::Dialogue>& CSMWorld::Data::getJournals()
{
return mJournals;
}
const CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos() const
{
return mTopicInfos;
}
CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos()
{
return mTopicInfos;
}
const CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos() const
{
return mJournalInfos;
}
CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos()
{
return mJournalInfos;
}
const CSMWorld::IdCollection<CSMWorld::Cell>& CSMWorld::Data::getCells() const const CSMWorld::IdCollection<CSMWorld::Cell>& CSMWorld::Data::getCells() const
{ {
return mCells; return mCells;
@ -387,7 +481,7 @@ void CSMWorld::Data::merge()
mGlobals.merge(); mGlobals.merge();
} }
void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base, bool project)
{ {
ESM::ESMReader reader; ESM::ESMReader reader;
@ -397,6 +491,11 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base)
reader.open (path.string()); reader.open (path.string());
const ESM::Dialogue *dialogue = 0;
mAuthor = reader.getAuthor();
mDescription = reader.getDesc();
// Note: We do not need to send update signals here, because at this point the model is not connected // Note: We do not need to send update signals here, because at this point the model is not connected
// to any view. // to any view.
while (reader.hasMoreRecs()) while (reader.hasMoreRecs())
@ -447,9 +546,80 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base)
case ESM::REC_STAT: mReferenceables.load (reader, base, UniversalId::Type_Static); break; case ESM::REC_STAT: mReferenceables.load (reader, base, UniversalId::Type_Static); break;
case ESM::REC_WEAP: mReferenceables.load (reader, base, UniversalId::Type_Weapon); break; case ESM::REC_WEAP: mReferenceables.load (reader, base, UniversalId::Type_Weapon); break;
case ESM::REC_DIAL:
{
std::string id = reader.getHNOString ("NAME");
ESM::Dialogue record;
record.mId = id;
record.load (reader);
if (record.mType==ESM::Dialogue::Journal)
{
mJournals.load (record, base);
dialogue = &mJournals.getRecord (id).get();
}
else if (record.mType==ESM::Dialogue::Deleted)
{
dialogue = 0; // record vector can be shuffled around which would make pointer
// to record invalid
if (mJournals.tryDelete (id))
{
/// \todo handle info records
}
else if (mTopics.tryDelete (id))
{
/// \todo handle info records
}
else
{
/// \todo report deletion of non-existing record
}
}
else
{
mTopics.load (record, base);
dialogue = &mTopics.getRecord (id).get();
}
break;
}
case ESM::REC_INFO:
{
if (!dialogue)
{
/// \todo INFO record without matching DIAL record -> report to user
reader.skipRecord();
break;
}
if (dialogue->mType==ESM::Dialogue::Journal)
mJournalInfos.load (reader, base, *dialogue);
else
mTopicInfos.load (reader, base, *dialogue);
break;
}
case ESM::REC_FILT:
if (project)
{
mFilters.load (reader, base);
mFilters.setData (mFilters.getSize()-1,
mFilters.findColumnIndex (CSMWorld::Columns::ColumnId_Scope),
static_cast<int> (CSMFilter::Filter::Scope_Project));
break;
}
// fall through (filter record in a content file is an error with format 0)
default: default:
/// \todo throw an exception instead, once all records are implemented /// \todo throw an exception instead, once all records are implemented
/// or maybe report error and continue?
reader.skipRecord(); reader.skipRecord();
} }
} }
@ -469,10 +639,50 @@ bool CSMWorld::Data::hasId (const std::string& id) const
getRegions().searchId (id)!=-1 || getRegions().searchId (id)!=-1 ||
getBirthsigns().searchId (id)!=-1 || getBirthsigns().searchId (id)!=-1 ||
getSpells().searchId (id)!=-1 || getSpells().searchId (id)!=-1 ||
getTopics().searchId (id)!=-1 ||
getJournals().searchId (id)!=-1 ||
getCells().searchId (id)!=-1 || getCells().searchId (id)!=-1 ||
getReferenceables().searchId (id)!=-1; getReferenceables().searchId (id)!=-1;
} }
int CSMWorld::Data::count (RecordBase::State state) const
{
return
count (state, mGlobals) +
count (state, mGmsts) +
count (state, mSkills) +
count (state, mClasses) +
count (state, mFactions) +
count (state, mRaces) +
count (state, mSounds) +
count (state, mScripts) +
count (state, mRegions) +
count (state, mBirthsigns) +
count (state, mSpells) +
count (state, mCells) +
count (state, mReferenceables);
}
void CSMWorld::Data::setDescription (const std::string& description)
{
mDescription = description;
}
std::string CSMWorld::Data::getDescription() const
{
return mDescription;
}
void CSMWorld::Data::setAuthor (const std::string& author)
{
mAuthor = author;
}
std::string CSMWorld::Data::getAuthor() const
{
return mAuthor;
}
std::vector<std::string> CSMWorld::Data::getIds (bool listDeleted) const std::vector<std::string> CSMWorld::Data::getIds (bool listDeleted) const
{ {
std::vector<std::string> ids; std::vector<std::string> ids;
@ -487,6 +697,8 @@ std::vector<std::string> CSMWorld::Data::getIds (bool listDeleted) const
appendIds (ids, mRegions, listDeleted); appendIds (ids, mRegions, listDeleted);
appendIds (ids, mBirthsigns, listDeleted); appendIds (ids, mBirthsigns, listDeleted);
appendIds (ids, mSpells, listDeleted); appendIds (ids, mSpells, listDeleted);
appendIds (ids, mTopics, listDeleted);
appendIds (ids, mJournals, listDeleted);
appendIds (ids, mCells, listDeleted); appendIds (ids, mCells, listDeleted);
appendIds (ids, mReferenceables, listDeleted); appendIds (ids, mReferenceables, listDeleted);

@ -20,6 +20,7 @@
#include <components/esm/loadregn.hpp> #include <components/esm/loadregn.hpp>
#include <components/esm/loadbsgn.hpp> #include <components/esm/loadbsgn.hpp>
#include <components/esm/loadspel.hpp> #include <components/esm/loadspel.hpp>
#include <components/esm/loaddial.hpp>
#include "../filter/filter.hpp" #include "../filter/filter.hpp"
@ -28,6 +29,7 @@
#include "cell.hpp" #include "cell.hpp"
#include "refidcollection.hpp" #include "refidcollection.hpp"
#include "refcollection.hpp" #include "refcollection.hpp"
#include "infocollection.hpp"
class QAbstractItemModel; class QAbstractItemModel;
@ -48,12 +50,18 @@ namespace CSMWorld
IdCollection<ESM::Region> mRegions; IdCollection<ESM::Region> mRegions;
IdCollection<ESM::BirthSign> mBirthsigns; IdCollection<ESM::BirthSign> mBirthsigns;
IdCollection<ESM::Spell> mSpells; IdCollection<ESM::Spell> mSpells;
IdCollection<ESM::Dialogue> mTopics;
IdCollection<ESM::Dialogue> mJournals;
InfoCollection mTopicInfos;
InfoCollection mJournalInfos;
IdCollection<Cell> mCells; IdCollection<Cell> mCells;
RefIdCollection mReferenceables; RefIdCollection mReferenceables;
RefCollection mRefs; RefCollection mRefs;
IdCollection<CSMFilter::Filter> mFilters; IdCollection<CSMFilter::Filter> mFilters;
std::vector<QAbstractItemModel *> mModels; std::vector<QAbstractItemModel *> mModels;
std::map<UniversalId::Type, QAbstractItemModel *> mModelIndex; std::map<UniversalId::Type, QAbstractItemModel *> mModelIndex;
std::string mAuthor;
std::string mDescription;
// not implemented // not implemented
Data (const Data&); Data (const Data&);
@ -66,6 +74,8 @@ namespace CSMWorld
bool listDeleted); bool listDeleted);
///< Append all IDs from collection to \a ids. ///< Append all IDs from collection to \a ids.
static int count (RecordBase::State state, const CollectionBase& collection);
public: public:
Data(); Data();
@ -116,6 +126,22 @@ namespace CSMWorld
IdCollection<ESM::Spell>& getSpells(); IdCollection<ESM::Spell>& getSpells();
const IdCollection<ESM::Dialogue>& getTopics() const;
IdCollection<ESM::Dialogue>& getTopics();
const IdCollection<ESM::Dialogue>& getJournals() const;
IdCollection<ESM::Dialogue>& getJournals();
const InfoCollection& getTopicInfos() const;
InfoCollection& getTopicInfos();
const InfoCollection& getJournalInfos() const;
InfoCollection& getJournalInfos();
const IdCollection<Cell>& getCells() const; const IdCollection<Cell>& getCells() const;
IdCollection<Cell>& getCells(); IdCollection<Cell>& getCells();
@ -141,8 +167,10 @@ namespace CSMWorld
void merge(); void merge();
///< Merge modified into base. ///< Merge modified into base.
void loadFile (const boost::filesystem::path& path, bool base); void loadFile (const boost::filesystem::path& path, bool base, bool project);
///< Merging content of a file into base or modified. ///< Merging content of a file into base or modified.
///
/// \param project load project file instead of content file
bool hasId (const std::string& id) const; bool hasId (const std::string& id) const;
@ -151,6 +179,17 @@ namespace CSMWorld
/// ///
/// \param listDeleted include deleted record in the list /// \param listDeleted include deleted record in the list
int count (RecordBase::State state) const;
///< Return number of top-level records with the given \a state.
void setDescription (const std::string& description);
std::string getDescription() const;
void setAuthor (const std::string& author);
std::string getAuthor() const;
signals: signals:
void idListChanged(); void idListChanged();

@ -7,21 +7,24 @@
namespace CSMWorld namespace CSMWorld
{ {
/// \brief Single type collection of top level records /// \brief Single type collection of top level records
template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> > template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
class IdCollection : public Collection<ESXRecordT, IdAccessorT> class IdCollection : public Collection<ESXRecordT, IdAccessorT>
{ {
public: public:
void load (ESM::ESMReader& reader, bool base, void load (ESM::ESMReader& reader, bool base);
UniversalId::Type type = UniversalId::Type_None);
///< \param type Will be ignored, unless the collection supports multiple record types void load (const ESXRecordT& record, bool base);
bool tryDelete (const std::string& id);
///< Try deleting \a id. If the id does not exist or can't be deleted the call is ignored.
///
/// \return Has the ID been deleted?
}; };
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void IdCollection<ESXRecordT, IdAccessorT>::load (ESM::ESMReader& reader, bool base, void IdCollection<ESXRecordT, IdAccessorT>::load (ESM::ESMReader& reader, bool base)
UniversalId::Type type)
{ {
std::string id = reader.getHNOString ("NAME"); std::string id = reader.getHNOString ("NAME");
@ -56,30 +59,62 @@ namespace CSMWorld
IdAccessorT().getId (record) = id; IdAccessorT().getId (record) = id;
record.load (reader); record.load (reader);
int index = this->searchId (IdAccessorT().getId (record)); load (record, base);
}
}
if (index==-1) template<typename ESXRecordT, typename IdAccessorT>
{ void IdCollection<ESXRecordT, IdAccessorT>::load (const ESXRecordT& record, bool base)
// new record {
Record<ESXRecordT> record2; int index = this->searchId (IdAccessorT().getId (record));
record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record2.mBase : record2.mModified) = record;
this->appendRecord (record2); if (index==-1)
} {
// new record
Record<ESXRecordT> record2;
record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record2.mBase : record2.mModified) = record;
this->appendRecord (record2);
}
else
{
// old record
Record<ESXRecordT> record2 = Collection<ESXRecordT, IdAccessorT>::getRecord (index);
if (base)
record2.mBase = record;
else else
{ record2.setModified (record);
// old record
Record<ESXRecordT> record2 = Collection<ESXRecordT, IdAccessorT>::getRecord (index);
if (base) this->setRecord (index, record2);
record2.mBase = record; }
else }
record2.setModified (record);
this->setRecord (index, record2); template<typename ESXRecordT, typename IdAccessorT>
} bool IdCollection<ESXRecordT, IdAccessorT>::tryDelete (const std::string& id)
{
int index = this->searchId (id);
if (index==-1)
return false;
Record<ESXRecordT> record = Collection<ESXRecordT, IdAccessorT>::getRecord (index);
if (record.isDeleted())
return false;
if (record.mState==RecordBase::State_ModifiedOnly)
{
Collection<ESXRecordT, IdAccessorT>::removeRows (index, 1);
} }
else
{
record.mState = RecordBase::State_Deleted;
this->setRecord (index, record);
}
return true;
} }
} }

@ -4,15 +4,12 @@
#include "collectionbase.hpp" #include "collectionbase.hpp"
#include "columnbase.hpp" #include "columnbase.hpp"
CSMWorld::IdTable::IdTable (CollectionBase *idCollection) : mIdCollection (idCollection) CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering)
{ : mIdCollection (idCollection), mReordering (reordering)
{}
}
CSMWorld::IdTable::~IdTable() CSMWorld::IdTable::~IdTable()
{ {}
}
int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const
{ {
@ -118,7 +115,7 @@ QModelIndex CSMWorld::IdTable::parent (const QModelIndex& index) const
void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type) void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type)
{ {
int index = mIdCollection->getAppendIndex(); int index = mIdCollection->getAppendIndex (id, type);
beginInsertRows (QModelIndex(), index, index); beginInsertRows (QModelIndex(), index, index);
@ -127,6 +124,17 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type
endInsertRows(); endInsertRows();
} }
void CSMWorld::IdTable::cloneRecord(const std::string& origin,
const std::string& destination,
CSMWorld::UniversalId::Type type)
{
int index = mIdCollection->getAppendIndex (destination);
beginInsertRows (QModelIndex(), index, index);
mIdCollection->cloneRecord(origin, destination, type);
endInsertRows();
}
QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const
{ {
return index (mIdCollection->getIndex (id), column); return index (mIdCollection->getIndex (id), column);
@ -138,7 +146,7 @@ void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& reco
if (index==-1) if (index==-1)
{ {
int index = mIdCollection->getAppendIndex(); int index = mIdCollection->getAppendIndex (id);
beginInsertRows (QModelIndex(), index, index); beginInsertRows (QModelIndex(), index, index);
@ -161,21 +169,23 @@ const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id)
int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const
{ {
int columns = mIdCollection->getColumns(); return mIdCollection->searchColumnIndex (id);
for (int i=0; i<columns; ++i)
if (mIdCollection->getColumn (i).mColumnId==id)
return i;
return -1;
} }
int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const
{ {
int index = searchColumnIndex (id); return mIdCollection->findColumnIndex (id);
}
if (index==-1) void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector<int>& newOrder)
throw std::logic_error ("invalid column index"); {
if (!newOrder.empty())
if (mIdCollection->reorderRows (baseIndex, newOrder))
emit dataChanged (index (baseIndex, 0),
index (baseIndex+newOrder.size()-1, mIdCollection->getColumns()-1));
}
return index; CSMWorld::IdTable::Reordering CSMWorld::IdTable::getReordering() const
{
return mReordering;
} }

@ -1,6 +1,8 @@
#ifndef CSM_WOLRD_IDTABLE_H #ifndef CSM_WOLRD_IDTABLE_H
#define CSM_WOLRD_IDTABLE_H #define CSM_WOLRD_IDTABLE_H
#include <vector>
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include "universalid.hpp" #include "universalid.hpp"
@ -15,7 +17,18 @@ namespace CSMWorld
{ {
Q_OBJECT Q_OBJECT
public:
enum Reordering
{
Reordering_None,
Reordering_WithinTopic
};
private:
CollectionBase *mIdCollection; CollectionBase *mIdCollection;
Reordering mReordering;
// not implemented // not implemented
IdTable (const IdTable&); IdTable (const IdTable&);
@ -23,7 +36,7 @@ namespace CSMWorld
public: public:
IdTable (CollectionBase *idCollection); IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_WithinTopic);
///< The ownership of \a idCollection is not transferred. ///< The ownership of \a idCollection is not transferred.
virtual ~IdTable(); virtual ~IdTable();
@ -50,6 +63,10 @@ namespace CSMWorld
void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None);
///< \param type Will be ignored, unless the collection supports multiple record types ///< \param type Will be ignored, unless the collection supports multiple record types
void cloneRecord(const std::string& origin,
const std::string& destination,
UniversalId::Type type = UniversalId::Type_None);
QModelIndex getModelIndex (const std::string& id, int column) const; QModelIndex getModelIndex (const std::string& id, int column) const;
void setRecord (const std::string& id, const RecordBase& record); void setRecord (const std::string& id, const RecordBase& record);
@ -63,6 +80,12 @@ namespace CSMWorld
int findColumnIndex (Columns::ColumnId id) const; int findColumnIndex (Columns::ColumnId id) const;
///< Return index of column with the given \a id. If no such column exists, an exception is ///< Return index of column with the given \a id. If no such column exists, an exception is
/// thrown. /// thrown.
void reorderRows (int baseIndex, const std::vector<int>& newOrder);
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
/// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
Reordering getReordering() const;
}; };
} }

@ -33,7 +33,9 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI
CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent)
: QSortFilterProxyModel (parent) : QSortFilterProxyModel (parent)
{} {
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
{ {

@ -0,0 +1,14 @@
#ifndef CSM_WOLRD_INFO_H
#define CSM_WOLRD_INFO_H
#include <components/esm/loadinfo.hpp>
namespace CSMWorld
{
struct Info : public ESM::DialInfo
{
std::string mTopicId;
};
}
#endif

@ -0,0 +1,184 @@
#include "infocollection.hpp"
#include <stdexcept>
#include <iterator>
#include <components/esm/esmreader.hpp>
#include <components/esm/loaddial.hpp>
#include <components/misc/stringops.hpp>
void CSMWorld::InfoCollection::load (const Info& record, bool base)
{
int index = searchId (record.mId);
if (index==-1)
{
// new record
Record<Info> record2;
record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record2.mBase : record2.mModified) = record;
int index = -1;
std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId);
if (!record2.get().mPrev.empty())
{
index = getIndex (record2.get().mPrev, topic);
if (index!=-1)
++index;
}
if (index==-1 && !record2.get().mNext.empty())
{
index = getIndex (record2.get().mNext, topic);
}
if (index==-1)
{
Range range = getTopicRange (topic);
index = std::distance (getRecords().begin(), range.second);
}
insertRecord (record2, index);
}
else
{
// old record
Record<Info> record2 = getRecord (index);
if (base)
record2.mBase = record;
else
record2.setModified (record);
setRecord (index, record2);
}
}
int CSMWorld::InfoCollection::getIndex (const std::string& id, const std::string& topic) const
{
std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id;
std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange (topic);
for (; range.first!=range.second; ++range.first)
if (Misc::StringUtils::ciEqual(range.first->get().mId, fullId))
return std::distance (getRecords().begin(), range.first);
return -1;
}
int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const
{
std::string::size_type separator = id.find_last_of ('#');
if (separator==std::string::npos)
throw std::runtime_error ("invalid info ID: " + id);
std::pair<RecordConstIterator, RecordConstIterator> range = getTopicRange (id.substr (0, separator));
if (range.first==range.second)
return Collection<Info, IdAccessor<Info> >::getAppendIndex (id, type);
return std::distance (getRecords().begin(), range.second);
}
bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector<int>& newOrder)
{
// check if the range is valid
int lastIndex = baseIndex + newOrder.size() -1;
if (lastIndex>=getSize())
return false;
// Check that topics match
if (getRecord (baseIndex).get().mTopicId!=getRecord (lastIndex).get().mTopicId)
return false;
// reorder
return reorderRowsImp (baseIndex, newOrder);
}
void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue)
{
std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" +
reader.getHNOString ("INAM");
if (reader.isNextSub ("DELE"))
{
int index = searchId (id);
reader.skipRecord();
if (index==-1)
{
// deleting a record that does not exist
// ignore it for now
/// \todo report the problem to the user
}
else if (base)
{
removeRows (index, 1);
}
else
{
Record<Info> record = getRecord (index);
record.mState = RecordBase::State_Deleted;
setRecord (index, record);
}
}
else
{
Info record;
record.mTopicId = dialogue.mId;
record.mId = id;
record.load (reader);
load (record, base);
}
}
CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic)
const
{
std::string topic2 = Misc::StringUtils::lowerCase (topic);
std::map<std::string, int>::const_iterator iter = getIdMap().lower_bound (topic2);
// Skip invalid records: The beginning of a topic string could be identical to another topic
// string.
for (; iter!=getIdMap().end(); ++iter)
{
std::string testTopicId =
Misc::StringUtils::lowerCase (getRecord (iter->second).get().mTopicId);
if (testTopicId==topic2)
break;
std::size_t size = topic2.size();
if (testTopicId.size()<size || testTopicId.substr (0, size)!=topic2)
return Range (getRecords().end(), getRecords().end());
}
if (iter==getIdMap().end())
return Range (getRecords().end(), getRecords().end());
RecordConstIterator begin = getRecords().begin()+iter->second;
// Find end
RecordConstIterator end = begin;
for (; end!=getRecords().end(); ++end)
if (!Misc::StringUtils::ciEqual(end->get().mTopicId, topic2))
break;
return Range (begin, end);
}

@ -0,0 +1,50 @@
#ifndef CSM_WOLRD_INFOCOLLECTION_H
#define CSM_WOLRD_INFOCOLLECTION_H
#include "collection.hpp"
#include "info.hpp"
namespace ESM
{
class Dialogue;
}
namespace CSMWorld
{
class InfoCollection : public Collection<Info, IdAccessor<Info> >
{
public:
typedef std::vector<Record<Info> >::const_iterator RecordConstIterator;
typedef std::pair<RecordConstIterator, RecordConstIterator> Range;
private:
void load (const Info& record, bool base);
int getIndex (const std::string& id, const std::string& topic) const;
///< Return index for record \a id or -1 (if not present; deleted records are considered)
///
/// \param id info ID without topic prefix
public:
virtual int getAppendIndex (const std::string& id,
UniversalId::Type type = UniversalId::Type_None) const;
///< \param type Will be ignored, unless the collection supports multiple record types
virtual bool reorderRows (int baseIndex, const std::vector<int>& newOrder);
///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices
/// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex).
///
/// \return Success?
void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue);
Range getTopicRange (const std::string& topic) const;
///< Return iterators that point to the beginning and past the end of the range for
/// the given topic.
};
}
#endif

@ -6,8 +6,7 @@
void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string& id) void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string& id)
{ {
mId = id; mId = id;
mCellId = cell.mId; mCell = cell.mId;
if (!mDeleted) cell.addRef (mId);
cell.addRef (mId);
} }

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

Loading…
Cancel
Save