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
*~
Doxygen
prebuilt
apps/openmw/config.hpp
Docs/mainpage.hpp
## make
CMakeFiles
*/CMakeFiles
CMakeCache.txt
moc_*.cxx
cmake_install.cmake
*.[ao]
Makefile
makefile
data
build
prebuilt
## doxygen
Doxygen
## ides/editors
*~
*.kdev4
CMakeLists.txt.user
*.swp
*.swo
*.kate-swp
.cproject
.project
.settings/
.settings
.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:
only:
- master
- next
- /openmw-.*$/
before_install:
- 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 apt-add-repository ppa:openmw/deps
- echo "yes" | sudo apt-add-repository ppa:openmw/openmw
- sudo apt-get update -qq
- sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev
- sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev
- sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev
- sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev
- sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock
- sudo apt-get install -qq libqt4-dev
- sudo apt-get install -qq libopenal-dev
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
- sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev
- sudo mkdir /usr/src/gtest/build
- cd /usr/src/gtest/build
- sudo cmake .. -DBUILD_SHARED_LIBS=1
@ -26,7 +26,7 @@ before_script:
- cd -
- mkdir 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:
- make -j4
after_script:
@ -37,3 +37,9 @@ notifications:
email:
on_success: change
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/)
include (OpenMWMacros)
include(OpenMWMacros)
# Version
set (OPENMW_VERSION_MAJOR 0)
set (OPENMW_VERSION_MINOR 26)
set (OPENMW_VERSION_RELEASE 0)
include(GetGitRevisionDescription)
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
@ -50,7 +65,12 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON)
# OS X deployment
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
if (APPLE)
@ -79,7 +99,6 @@ set(OENGINE_OGRE
${LIBDIR}/openengine/ogre/renderer.cpp
${LIBDIR}/openengine/ogre/fader.cpp
${LIBDIR}/openengine/ogre/lights.cpp
${LIBDIR}/openengine/ogre/particles.cpp
${LIBDIR}/openengine/ogre/selectionbuffer.cpp
${LIBDIR}/openengine/ogre/imagerotate.cpp
)
@ -89,14 +108,10 @@ set(OENGINE_GUI
)
set(OENGINE_BULLET
${LIBDIR}/openengine/bullet/btKinematicCharacterController.cpp
${LIBDIR}/openengine/bullet/btKinematicCharacterController.h
${LIBDIR}/openengine/bullet/BtOgre.cpp
${LIBDIR}/openengine/bullet/BtOgreExtras.h
${LIBDIR}/openengine/bullet/BtOgreGP.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.hpp
${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp
@ -181,10 +196,11 @@ if (WIN32)
set(Boost_USE_STATIC_LIBS ON)
set(PLATFORM_INCLUDE_DIR "platform")
add_definitions(-DBOOST_ALL_NO_LIB)
# Suppress WinMain(), provided by SDL
add_definitions(-DSDL_MAIN_HANDLED)
else (WIN32)
set(PLATFORM_INCLUDE_DIR "")
find_path (UUID_INCLUDE_DIR uuid/uuid.h)
include_directories(${UUID_INCLUDE_DIR})
endif (WIN32)
if (MSVC10)
set(PLATFORM_INCLUDE_DIR "")
@ -208,7 +224,7 @@ if (HAVE_UNORDERED_MAP)
endif ()
set(BOOST_COMPONENTS system filesystem program_options thread date_time wave)
set(BOOST_COMPONENTS system filesystem program_options)
IF(BOOST_STATIC)
set(Boost_USE_STATIC_LIBS ON)
@ -236,7 +252,6 @@ include_directories("."
${MYGUI_INCLUDE_DIRS}
${MYGUI_PLATFORM_INCLUDE_DIRS}
${OPENAL_INCLUDE_DIR}
${UUID_INCLUDE_DIR}
${LIBDIR}
)
@ -318,6 +333,9 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg
configure_file(${OpenMW_SOURCE_DIR}/files/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)
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop
"${OpenMW_BINARY_DIR}/openmw.desktop")
@ -378,7 +396,6 @@ IF(NOT WIN32 AND NOT APPLE)
# Install licenses
INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "Daedric Font License.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" )
ENDIF (DPKG_PROGRAM)
@ -402,46 +419,6 @@ IF(NOT WIN32 AND NOT APPLE)
# Install 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")
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)
if(WIN32)
@ -453,13 +430,22 @@ if(WIN32)
"${OpenMW_SOURCE_DIR}/GPL3.txt"
"${OpenMW_SOURCE_DIR}/OFL.txt"
"${OpenMW_SOURCE_DIR}/DejaVu Font License.txt"
"${OpenMW_SOURCE_DIR}/Daedric Font License.txt"
"${OpenMW_BINARY_DIR}/settings-default.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"
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 ".")
SET(CPACK_GENERATOR "NSIS")
@ -469,7 +455,13 @@ if(WIN32)
SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR})
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_DELETE_ICONS_EXTRA "
!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_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 "")
foreach (PLUGIN ${USED_OGRE_PLUGINS})
@ -725,7 +720,8 @@ if (APPLE)
cmake_policy(SET CMP0009 OLD)
set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities)
fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\")
fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\")
fixup_bundle(\"${OPENCS_APP}\" \"\" \"${DIRS}\")
" COMPONENT Runtime)
include(CPack)
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
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)
{

@ -236,7 +236,9 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
// Loop through all the references
ESM::CellRef ref;
if(!quiet) std::cout << " References:\n";
while(cell.getNextRef(esm, ref))
bool deleted = false;
while(cell.getNextRef(esm, ref, deleted))
{
if (save) {
info.data.mCellRefs[&cell].push_back(ref);
@ -244,13 +246,14 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
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 << " Owner: '" << ref.mOwner << "'\n";
std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n";
std::cout << " Uses/health: '" << ref.mCharge << "'\n";
std::cout << " Gold value: '" << ref.mGoldValue << "'\n";
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.description = esm.getDesc();
info.data.masters = esm.getMasters();
info.data.masters = esm.getGameFiles();
if (!quiet)
{
std::cout << "Author: " << esm.getAuthor() << std::endl
<< "Description: " << esm.getDesc() << 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())
{
std::cout << "Masters:" << std::endl;
@ -339,6 +342,8 @@ int load(Arguments& info)
}
std::string id = esm.getHNOString("NAME");
if (id.empty())
id = esm.getHNOString("INAM");
if(!quiet && interested)
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::Swims) properties += "Swims ";
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::Weapon) properties += "Weapon ";
if (flags & ESM::Creature::Skeleton) properties += "Skeleton ";
@ -691,7 +691,7 @@ std::string creatureFlags(int flags)
ESM::Creature::Walks|
ESM::Creature::Swims|
ESM::Creature::Flies|
ESM::Creature::Biped|
ESM::Creature::Bipedal|
ESM::Creature::Respawn|
ESM::Creature::Weapon|
ESM::Creature::Skeleton|
@ -717,16 +717,26 @@ std::string landFlags(int flags)
return properties;
}
std::string leveledListFlags(int flags)
std::string itemListFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
if (flags & ESM::LeveledListBase::AllLevels) properties += "AllLevels ";
// This flag apparently not present on creature lists...
if (flags & ESM::LeveledListBase::Each) properties += "Each ";
if (flags & ESM::ItemLevList::AllLevels) properties += "AllLevels ";
if (flags & ESM::ItemLevList::Each) properties += "Each ";
int unused = (0xFFFFFFFF ^
(ESM::LeveledListBase::AllLevels|
ESM::LeveledListBase::Each));
(ESM::ItemLevList::AllLevels|
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 ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;
@ -764,34 +774,19 @@ std::string magicEffectFlags(int flags)
{
std::string properties = "";
if (flags == 0) properties += "[None] ";
// Enchanting & SpellMaking occur on the same list of effects.
// "EXTRA SPELL" appears in the construction set under both the
// 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::TargetAttribute) properties += "TargetAttribute ";
if (flags & ESM::MagicEffect::TargetSkill) properties += "TargetSkill ";
if (flags & ESM::MagicEffect::NoDuration) properties += "NoDuration ";
if (flags & 0x00000008) properties += "NoMagnitude ";
if (flags & 0x00000010) properties += "Negative ";
if (flags & 0x00000020) properties += "Unknown1 ";
// ESM componet says 0x800 is negative, but none of the magic
// effects have this flags set.
if (flags & ESM::MagicEffect::Negative) properties += "Unused ";
// Since only Chameleon has this flag it could be anything
// that uniquely distinguishes Chameleon.
if (flags & 0x00002000) properties += "Chameleon ";
if (flags & 0x00004000) properties += "Bound ";
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 & ESM::MagicEffect::NoMagnitude) properties += "NoMagnitude ";
if (flags & ESM::MagicEffect::Harmful) properties += "Harmful ";
if (flags & ESM::MagicEffect::ContinuousVfx) properties += "ContinuousVFX ";
if (flags & ESM::MagicEffect::CastSelf) properties += "CastSelf ";
if (flags & ESM::MagicEffect::CastTouch) properties += "CastTouch ";
if (flags & ESM::MagicEffect::CastTarget) properties += "CastTarget ";
if (flags & ESM::MagicEffect::UncappedDamage) properties += "UncappedDamage ";
if (flags & ESM::MagicEffect::NonRecastable) properties += "NonRecastable ";
if (flags & ESM::MagicEffect::Unreflectable) properties += "Unreflectable ";
if (flags & ESM::MagicEffect::CasterLinked) properties += "CasterLinked ";
if (flags & 0xFFFC0000) properties += "Invalid ";
properties += str(boost::format("(0x%08X)") % flags);
return properties;

@ -50,7 +50,8 @@ std::string cellFlags(int flags);
std::string containerFlags(int flags);
std::string creatureFlags(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 magicEffectFlags(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 << " Duration: " << p.mWander.mDuration << std::endl;
std::cout << " Time of Day: " << (int)p.mWander.mTimeOfDay << std::endl;
if (p.mWander.mUnk != 1)
std::cout << " Unknown: " << (int)p.mWander.mUnk << std::endl;
if (p.mWander.mShouldRepeat != 1)
std::cout << " Should repeat: " << (bool)p.mWander.mShouldRepeat << std::endl;
std::cout << " Idle: ";
for (int i = 0; i != 8; i++)
@ -740,8 +740,8 @@ void Record<ESM::DialInfo>::print()
if (mData.mClass != "")
std::cout << " Class: " << mData.mClass << std::endl;
std::cout << " Factionless: " << mData.mFactionLess << std::endl;
if (mData.mNpcFaction != "")
std::cout << " NPC Faction: " << mData.mNpcFaction << std::endl;
if (mData.mFaction != "")
std::cout << " NPC Faction: " << mData.mFaction << std::endl;
if (mData.mData.mRank != -1)
std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl;
if (mData.mPcFaction != "")
@ -834,7 +834,7 @@ template<>
void Record<ESM::CreatureLevList>::print()
{
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::vector<ESM::LeveledListBase::LevelItem>::iterator iit;
for (iit = mData.mList.begin(); iit != mData.mList.end(); iit++)
@ -846,11 +846,11 @@ template<>
void Record<ESM::ItemLevList>::print()
{
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::vector<ESM::LeveledListBase::LevelItem>::iterator 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;
}
@ -958,7 +958,7 @@ void Record<ESM::MagicEffect>::print()
std::cout << " RGB Color: " << "("
<< mData.mData.mRed << ","
<< mData.mData.mGreen << ","
<< mData.mData.mGreen << ")" << std::endl;
<< mData.mData.mBlue << ")" << std::endl;
}
template<>
@ -989,8 +989,7 @@ void Record<ESM::NPC>::print()
std::cout << " Faction: " << mData.mFaction << std::endl;
std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl;
// Seriously?
if (mData.mNpdt52.mGold == -10)
if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
std::cout << " Level: " << mData.mNpdt12.mLevel << 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 << " Skills:" << std::endl;
for (int i = 0; i != 27; i++)
for (int i = 0; i != ESM::Skill::Length; i++)
std::cout << " " << skillLabel(i) << ": "
<< (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl;

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

@ -11,7 +11,9 @@ set(LAUNCHER
settings/launchersettings.cpp
utils/checkablemessagebox.cpp
utils/profilescombobox.cpp
utils/textinputdialog.cpp
utils/lineedit.cpp
${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc
)
@ -32,8 +34,9 @@ set(LAUNCHER_HEADER
settings/settingsbase.hpp
utils/checkablemessagebox.hpp
utils/profilescombobox.hpp
utils/textinputdialog.hpp
utils/lineedit.hpp
)
if(NOT WIN32)
LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp)
@ -48,8 +51,11 @@ set(LAUNCHER_HEADER_MOC
playpage.hpp
textslotmsgbox.hpp
utils/checkablemessagebox.hpp
utils/textinputdialog.hpp
utils/checkablemessagebox.hpp
utils/profilescombobox.hpp
utils/lineedit.hpp
)
if(NOT WIN32)
@ -62,6 +68,7 @@ set(LAUNCHER_UI
${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui
${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui
${CMAKE_SOURCE_DIR}/files/ui/playpage.ui
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
)
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
@ -130,8 +137,3 @@ if (BUILD_WITH_CODE_COVERAGE)
target_link_libraries(omwlauncher gcov)
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 <QCheckBox>
#include <QMenu>
#include <QSortFilterProxyModel>
#include <components/files/configurationmanager.hpp>
#include <components/fileorderlist/model/datafilesmodel.hpp>
#include <components/fileorderlist/model/pluginsproxymodel.hpp>
#include <components/fileorderlist/model/esm/esmfile.hpp>
#include <components/contentselector/model/esmfile.hpp>
#include <components/fileorderlist/utils/lineedit.hpp>
#include <components/fileorderlist/utils/naturalsort.hpp>
#include <components/fileorderlist/utils/profilescombobox.hpp>
#include <components/contentselector/model/naturalsort.hpp>
#include "utils/textinputdialog.hpp"
#include "utils/profilescombobox.hpp"
#include "settings/gamesettings.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)
, mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings)
, QWidget(parent)
{
setupUi(this);
// Models
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)));
ui.setupUi (this);
setObjectName ("DataFilesPage");
mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget);
connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter()));
createActions();
buildView();
setupDataFiles();
}
void DataFilesPage::createActions()
{
// 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()
void Launcher::DataFilesPage::loadSettings()
{
// 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();
paths.insert (0, mDataLocal);
PathIterator pathIterator (paths);
foreach (const QString &path, paths) {
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"));
QString profileName = ui.profilesComboBox->currentText();
if (profile.isEmpty())
return;
QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName, Qt::MatchExactly);
mDataFilesModel->uncheckAll();
QStringList filepaths;
QStringList masters = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly);
QStringList plugins = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly);
foreach (const QString &file, files)
{
QString filepath = pathIterator.findFirstPath (file);
foreach (const QString &master, masters) {
QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(master));
if (index.isValid())
mDataFilesModel->setCheckState(index, Qt::Checked);
if (!filepath.isEmpty())
filepaths << filepath;
}
foreach (const QString &plugin, plugins) {
QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(plugin));
if (index.isValid())
mDataFilesModel->setCheckState(index, Qt::Checked);
}
mSelector->setProfileContent (filepaths);
}
void DataFilesPage::saveSettings()
void Launcher::DataFilesPage::saveSettings(const QString &profile)
{
if (mDataFilesModel->rowCount() < 1)
return;
QString profileName = profile;
QString profile = mLauncherSettings.value(QString("Profiles/currentprofile"));
if (profile.isEmpty()) {
profile = profilesComboBox->currentText();
mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile);
}
if (profileName.isEmpty())
profileName = ui.profilesComboBox->currentText();
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master"));
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin"));
//retrieve the files selected for the profile
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
mGameSettings.remove(QString("master"));
mGameSettings.remove(QString("plugin"));
removeProfile (profileName);
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)) {
mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item);
mGameSettings.setMultiValue(QString("master"), item);
} else if (item.endsWith(QString(".esp"), Qt::CaseInsensitive)) {
mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item);
mGameSettings.setMultiValue(QString("plugin"), item);
}
foreach(const ContentSelectorModel::EsmFile *item, items) {
mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName());
mGameSettings.setMultiValue(QString("content"), item->fileName());
}
}
void DataFilesPage::updateOkButton(const QString &text)
void Launcher::DataFilesPage::buildView()
{
// We do this here because we need the profiles combobox text
if (text.isEmpty()) {
mNewProfileDialog->setOkButtonEnabled(false);
return;
}
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
(profilesComboBox->findText(text) == -1)
? mNewProfileDialog->setOkButtonEnabled(true)
: mNewProfileDialog->setOkButtonEnabled(false);
}
//tool buttons
ui.newProfileButton->setToolTip ("Create a new profile");
ui.deleteProfileButton->setToolTip ("Delete an existing profile");
void DataFilesPage::updateSplitter()
{
// Sigh, update the saved splitter size in settings only when moved
// Since getting mSplitter->sizes() if page is hidden returns invalid values
QList<int> sizes = splitter->sizes();
//combo box
ui.profilesComboBox->addItem ("Default");
ui.profilesComboBox->setPlaceholderText (QString("Select a profile..."));
mLauncherSettings.setValue(QString("General/MastersTable/width"), QString::number(sizes.at(0)));
mLauncherSettings.setValue(QString("General/PluginsTable/width"), QString::number(sizes.at(1)));
}
// Add the actions to the toolbuttons
ui.newProfileButton->setDefaultAction (ui.newProfileAction);
ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction);
void DataFilesPage::updateViews()
{
// Ensure the columns are hidden because sort() re-enables them
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);
}
//establish connections
connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)),
this, SLOT (slotProfileChanged(int)));
void DataFilesPage::setProfilesComboBoxIndex(int index)
{
profilesComboBox->setCurrentIndex(index);
connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)),
this, SLOT (slotProfileRenamed(QString, QString)));
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) {
QString profile = mNewProfileDialog->lineEdit()->text();
profilesComboBox->addItem(profile);
profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile));
if (index >= -1 && index < ui.profilesComboBox->count())
{
QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex());
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();
if (profile.isEmpty())
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));
//abort if no change (poss. duplicate signal)
if (previous == current)
return;
QAbstractButton *deleteButton =
msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);
if (!previous.isEmpty() && savePrevious)
saveSettings (previous);
msgBox.exec();
ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current));
if (msgBox.clickedButton() == deleteButton) {
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master"));
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin"));
loadSettings();
// Remove the profile from the combobox
profilesComboBox->removeItem(profilesComboBox->findText(profile));
}
checkForDefaultProfile();
}
void DataFilesPage::on_checkAction_triggered()
void Launcher::DataFilesPage::slotProfileDeleted (const QString &item)
{
if (pluginsTable->hasFocus())
setPluginsCheckstates(Qt::Checked);
if (mastersTable->hasFocus())
setMastersCheckstates(Qt::Checked);
removeProfile (item);
}
void DataFilesPage::on_uncheckAction_triggered()
void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString &current)
{
if (pluginsTable->hasFocus())
setPluginsCheckstates(Qt::Unchecked);
if (mastersTable->hasFocus())
setMastersCheckstates(Qt::Unchecked);
setProfile(previous, current, true);
emit signalProfileChanged (ui.profilesComboBox->findText(current));
}
void DataFilesPage::setMastersCheckstates(Qt::CheckState state)
void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const QString &current)
{
if (!mastersTable->selectionModel()->hasSelection()) {
if (previous.isEmpty())
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())
return;
// Remove the old one
removeProfile (previous);
mDataFilesModel->setCheckState(sourceIndex, state);
}
loadSettings();
}
void DataFilesPage::setPluginsCheckstates(Qt::CheckState state)
void Launcher::DataFilesPage::slotProfileChanged(int index)
{
if (!pluginsTable->selectionModel()->hasSelection()) {
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);
}
setProfile (index, true);
}
void DataFilesPage::setCheckState(QModelIndex index)
void Launcher::DataFilesPage::setupDataFiles()
{
if (!index.isValid())
return;
QStringList paths = mGameSettings.getDataDirs();
QObject *object = QObject::sender();
foreach (const QString &path, paths)
mSelector->addFiles(path);
// Not a signal-slot call
if (!object)
return;
mDataLocal = mGameSettings.getDataLocal();
if (!mDataLocal.isEmpty())
mSelector->addFiles(mDataLocal);
if (object->objectName() == QLatin1String("PluginsTable")) {
QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
mFilterProxyModel->mapToSource(index));
QStringList profiles;
QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile");
if (sourceIndex.isValid()) {
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
: mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
foreach (QString key, mLauncherSettings.getSettings().keys())
{
if (key.contains("Profiles/"))
{
QString profile = key.mid (9);
if (profile != "currentprofile")
{
if (!profiles.contains(profile))
profiles << profile;
}
}
}
if (object->objectName() == QLatin1String("MastersTable")) {
QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index);
if (sourceIndex.isValid()) {
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked)
: mDataFilesModel->setCheckState(sourceIndex, Qt::Checked);
}
}
foreach (const QString &item, profiles)
addProfile (item, false);
return;
}
setProfile (ui.profilesComboBox->findText(currentProfile), false);
void DataFilesPage::filterChanged(const QString filter)
{
QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString);
mFilterProxyModel->setFilterRegExp(regExp);
loadSettings();
}
void DataFilesPage::profileChanged(const QString &previous, const QString &current)
void Launcher::DataFilesPage::on_newProfileAction_triggered()
{
// Prevent the deletion of the default profile
if (current == QLatin1String("Default")) {
deleteProfileAction->setEnabled(false);
profilesComboBox->setEditEnabled(false);
} else {
deleteProfileAction->setEnabled(true);
profilesComboBox->setEditEnabled(true);
}
TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this);
if (previous.isEmpty())
if (newDialog.exec() != QDialog::Accepted)
return;
if (profilesComboBox->findText(previous) == -1)
return; // Profile was deleted
// Store the previous profile
mLauncherSettings.setValue(QString("Profiles/currentprofile"), previous);
saveSettings();
mLauncherSettings.setValue(QString("Profiles/currentprofile"), current);
loadSettings();
}
QString profile = newDialog.getText();
void DataFilesPage::profileRenamed(const QString &previous, const QString &current)
{
if (previous.isEmpty())
return;
if (profile.isEmpty())
return;
// Save the new profile name
mLauncherSettings.setValue(QString("Profiles/currentprofile"), current);
saveSettings();
// Remove the old one
mLauncherSettings.remove(QString("Profiles/") + previous + QString("/master"));
mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin"));
mSelector->clearCheckStates();
// Remove the profile from the combobox
profilesComboBox->removeItem(profilesComboBox->findText(previous));
addProfile(profile, true);
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 (!object)
if (ui.profilesComboBox->findText (profile) != -1)
return;
if (object->objectName() == QLatin1String("PluginsTable")) {
if (!pluginsTable->selectionModel()->hasSelection())
return;
ui.profilesComboBox->addItem (profile);
QPoint globalPos = pluginsTable->mapToGlobal(point);
QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes();
if (setAsCurrent)
setProfile (ui.profilesComboBox->findText (profile), false);
}
// Show the check/uncheck actions depending on the state of the selected items
uncheckAction->setEnabled(false);
checkAction->setEnabled(false);
void Launcher::DataFilesPage::on_deleteProfileAction_triggered()
{
QString profile = ui.profilesComboBox->currentText();
foreach (const QModelIndex &index, indexes)
{
if (!index.isValid())
return;
if (profile.isEmpty())
return;
QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(
mFilterProxyModel->mapToSource(index));
if (!showDeleteMessageBox (profile))
return;
if (!sourceIndex.isValid())
return;
// Remove the profile from the combobox
ui.profilesComboBox->removeItem (ui.profilesComboBox->findText (profile));
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? uncheckAction->setEnabled(true)
: checkAction->setEnabled(true);
}
removeProfile(profile);
// Show menu
mContextMenu->exec(globalPos);
}
saveSettings();
if (object->objectName() == QLatin1String("MastersTable")) {
if (!mastersTable->selectionModel()->hasSelection())
return;
loadSettings();
QPoint globalPos = mastersTable->mapToGlobal(point);
QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes();
checkForDefaultProfile();
}
// Show the check/uncheck actions depending on the state of the selected items
uncheckAction->setEnabled(false);
checkAction->setEnabled(false);
void Launcher::DataFilesPage::checkForDefaultProfile()
{
//don't allow deleting "Default" profile
bool success = (ui.profilesComboBox->currentText() != "Default");
foreach (const QModelIndex &index, indexes)
{
if (!index.isValid())
return;
ui.deleteProfileAction->setEnabled (success);
ui.profilesComboBox->setEditEnabled (success);
}
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())
return;
QAbstractButton *deleteButton =
msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);
(mDataFilesModel->checkState(sourceIndex) == Qt::Checked)
? uncheckAction->setEnabled(true)
: checkAction->setEnabled(true);
}
msgBox.exec();
mContextMenu->exec(globalPos);
}
return (msgBox.clickedButton() == deleteButton);
}

@ -1,88 +1,136 @@
#ifndef DATAFILESPAGE_H
#define DATAFILESPAGE_H
#include "ui_datafilespage.h"
#include <QWidget>
#include <QModelIndex>
#include "ui_datafilespage.h"
#include <QDir>
#include <QFile>
class QSortFilterProxyModel;
class QAbstractItemModel;
class QAction;
class QMenu;
class DataFilesModel;
class TextInputDialog;
class GameSettings;
class LauncherSettings;
class PluginsProxyModel;
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:
DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0);
void on_newProfileAction_triggered();
void on_deleteProfileAction_triggered();
QAbstractItemModel* profilesComboBoxModel();
int profilesComboBoxIndex();
private:
void writeConfig(QString profile = QString());
void saveSettings();
QMenu *mContextMenu;
signals:
void profileChanged(int index);
Files::ConfigurationManager &mCfgMgr;
public slots:
void setCheckState(QModelIndex index);
void setProfilesComboBoxIndex(int index);
GameSettings &mGameSettings;
LauncherSettings &mLauncherSettings;
void filterChanged(const QString filter);
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();
QString mDataLocal;
// Action slots
void on_newProfileAction_triggered();
void on_deleteProfileAction_triggered();
void on_checkAction_triggered();
void on_uncheckAction_triggered();
void setPluginsCheckstates(Qt::CheckState state);
private slots:
void slotCurrentIndexChanged(int index);
void buildView();
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:
DataFilesModel *mDataFilesModel;
class PathIterator
{
QStringList::ConstIterator mCitEnd;
QStringList::ConstIterator mCitCurrent;
QStringList::ConstIterator mCitBegin;
QString mFile;
QString mFilePath;
PluginsProxyModel *mPluginsProxyModel;
QSortFilterProxyModel *mMastersProxyModel;
public:
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;
LauncherSettings &mLauncherSettings;
QString path ()
{
bool success = false;
QDir dir;
QFileInfo file;
TextInputDialog *mNewProfileDialog;
while (!success)
{
if (mCitCurrent == mCitEnd)
break;
void setMastersCheckstates(Qt::CheckState state);
void setPluginsCheckstates(Qt::CheckState state);
dir.setPath (*(mCitCurrent++));
file.setFile (dir.absoluteFilePath (mFile));
void createActions();
void setupDataFiles();
void setupConfig();
void readConfig();
success = file.exists();
}
void loadSettings();
if (success)
return file.absoluteFilePath();
};
return "";
}
};
};
}
#endif

@ -4,20 +4,19 @@
#include <QMessageBox>
#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
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#endif
#include <SDL.h>
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
#include <cstdlib>
#include <SDL.h>
#include <boost/math/common_factor.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"
@ -33,11 +32,12 @@ QString getAspect(int x, int y)
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)
, mGraphicsSettings(graphicsSetting)
, QWidget(parent)
{
setObjectName ("GraphicsPage");
setupUi(this);
// 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
{
mOgre = new Ogre::Root("", "", "./launcherOgre.log");
mOgre = mOgreInit.init(mCfgMgr.getLogPath().string() + "/launcherOgre.log");
}
catch(Ogre::Exception &ex)
{
@ -78,40 +74,6 @@ bool GraphicsPage::setupOgre()
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
const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers();
@ -133,7 +95,7 @@ bool GraphicsPage::setupOgre()
msgBox.setIcon(QMessageBox::Critical);
msgBox.setStandardButtons(QMessageBox::Ok);
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();
return false;
}
@ -156,7 +118,7 @@ bool GraphicsPage::setupOgre()
return true;
}
bool GraphicsPage::setupSDL()
bool Launcher::GraphicsPage::setupSDL()
{
int displays = SDL_GetNumVideoDisplays();
@ -179,7 +141,7 @@ bool GraphicsPage::setupSDL()
return true;
}
bool GraphicsPage::loadSettings()
bool Launcher::GraphicsPage::loadSettings()
{
if (!setupSDL())
return false;
@ -218,7 +180,7 @@ bool GraphicsPage::loadSettings()
return true;
}
void GraphicsPage::saveSettings()
void Launcher::GraphicsPage::saveSettings()
{
vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true"))
: mGraphicsSettings.setValue(QString("Video/vsync"), QString("false"));
@ -245,7 +207,7 @@ void GraphicsPage::saveSettings()
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;
@ -278,7 +240,7 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy
return result;
}
QStringList GraphicsPage::getAvailableResolutions(int screen)
QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
{
QStringList result;
SDL_DisplayMode mode;
@ -325,7 +287,7 @@ QStringList GraphicsPage::getAvailableResolutions(int screen)
return result;
}
QRect GraphicsPage::getMaximumResolution()
QRect Launcher::GraphicsPage::getMaximumResolution()
{
QRect max;
int screens = QApplication::desktop()->screenCount();
@ -340,7 +302,7 @@ QRect GraphicsPage::getMaximumResolution()
return max;
}
void GraphicsPage::rendererChanged(const QString &renderer)
void Launcher::GraphicsPage::rendererChanged(const QString &renderer)
{
mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString());
@ -349,7 +311,7 @@ void GraphicsPage::rendererChanged(const QString &renderer)
antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
}
void GraphicsPage::screenChanged(int screen)
void Launcher::GraphicsPage::screenChanged(int screen)
{
if (screen >= 0) {
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) {
standardRadioButton->toggle();
@ -371,7 +333,7 @@ void GraphicsPage::slotFullScreenChanged(int state)
}
}
void GraphicsPage::slotStandardToggled(bool checked)
void Launcher::GraphicsPage::slotStandardToggled(bool checked)
{
if (checked) {
resolutionComboBox->setEnabled(true);

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

@ -3,19 +3,20 @@
#include <QDir>
#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
#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 "maindialog.hpp"
// SDL workaround
#include "graphicspage.hpp"
int main(int argc, char *argv[])
{
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
SDL_SetMainReady();
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError());
@ -49,7 +50,7 @@ int main(int argc, char *argv[])
// Support non-latin characters
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
MainDialog mainWin;
Launcher::MainDialog mainWin;
if (mainWin.setup()) {
mainWin.show();
@ -61,4 +62,3 @@ int main(int argc, char *argv[])
SDL_Quit();
return returnValue;
}

@ -1,5 +1,11 @@
#include "maindialog.hpp"
#include <components/version/version.hpp>
#include <QLabel>
#include <QDate>
#include <QTime>
#include <QPushButton>
#include <QFontDatabase>
#include <QInputDialog>
#include <QFileDialog>
@ -23,8 +29,8 @@
#include "graphicspage.hpp"
#include "datafilespage.hpp"
MainDialog::MainDialog()
: mGameSettings(mCfgMgr)
Launcher::MainDialog::MainDialog(QWidget *parent)
: mGameSettings(mCfgMgr), QMainWindow (parent)
{
// Install the stylesheet font
QFile file;
@ -66,10 +72,26 @@ MainDialog::MainDialog()
// Remove what's this? button
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();
}
void MainDialog::createIcons()
void Launcher::MainDialog::createIcons()
{
if (!QIcon::hasThemeIcon("document-new"))
QIcon::setThemeName("tango");
@ -101,15 +123,15 @@ void MainDialog::createIcons()
}
void MainDialog::createPages()
void Launcher::MainDialog::createPages()
{
mPlayPage = new PlayPage(this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this);
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
// Set the combobox of the play page to imitate the combobox on the datafilespage
mPlayPage->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel());
mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex());
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
mPlayPage->setProfilesIndex(mDataFilesPage->profilesIndex());
// Add the pages to the stacked widget
pagesWidget->addWidget(mPlayPage);
@ -121,12 +143,12 @@ void MainDialog::createPages()
connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play()));
connect(mPlayPage, SIGNAL(profileChanged(int)), mDataFilesPage, SLOT(setProfilesComboBoxIndex(int)));
connect(mDataFilesPage, SIGNAL(profileChanged(int)), mPlayPage, SLOT(setProfilesComboBoxIndex(int)));
connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int)));
connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int)));
}
bool MainDialog::showFirstRunDialog()
bool Launcher::MainDialog::showFirstRunDialog()
{
QStringList iniPaths;
@ -218,7 +240,7 @@ bool MainDialog::showFirstRunDialog()
}
// 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);
if (!file.exists()) {
@ -261,19 +283,11 @@ bool MainDialog::showFirstRunDialog()
// Add a new profile
if (msgBox.isChecked()) {
mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported"));
mLauncherSettings.remove(QString("Profiles/Imported/content"));
mLauncherSettings.remove(QString("Profiles/Imported/master"));
mLauncherSettings.remove(QString("Profiles/Imported/plugin"));
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);
QStringList contents = mGameSettings.values(QString("content"));
foreach (const QString &content, contents) {
mLauncherSettings.setMultiValue(QString("Profiles/Imported/content"), content);
}
}
@ -282,7 +296,7 @@ bool MainDialog::showFirstRunDialog()
return true;
}
bool MainDialog::setup()
bool Launcher::MainDialog::setup()
{
if (!setupLauncherSettings())
return false;
@ -311,19 +325,37 @@ bool MainDialog::setup()
return true;
}
void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous)
void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous)
{
if (!current)
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);
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QStringList paths;
paths.append(QString("launcher.cfg"));
@ -356,7 +388,7 @@ bool MainDialog::setupLauncherSettings()
}
#ifndef WIN32
bool expansions(UnshieldThread& cd)
bool Launcher::expansions(Launcher::UnshieldThread& cd)
{
if(cd.BloodmoonDone())
{
@ -367,7 +399,7 @@ bool expansions(UnshieldThread& cd)
QMessageBox expansionsBox;
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>"));
QAbstractButton* tribunalButton = NULL;
if(!cd.TribunalDone())
tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole);
@ -386,7 +418,7 @@ bool expansions(UnshieldThread& cd)
{
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(close()), &cdbox, SLOT(reject()));
@ -405,7 +437,7 @@ bool expansions(UnshieldThread& cd)
{
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(close()), &cdbox, SLOT(reject()));
@ -427,11 +459,37 @@ bool expansions(UnshieldThread& cd)
}
#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());
// 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;
paths.append(userPath + QString("openmw.cfg"));
paths.append(QString("openmw.cfg"));
@ -467,7 +525,7 @@ bool MainDialog::setupGameSettings()
foreach (const QString path, mGameSettings.getDataDirs()) {
QDir dir(path);
QStringList filters;
filters << "*.esp" << "*.esm";
filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon";
if (!dir.entryList(filters).isEmpty())
dataDirs.append(path);
@ -485,12 +543,12 @@ bool MainDialog::setupGameSettings()
QAbstractButton *dirSelectButton =
msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole);
#ifndef WIN32
QAbstractButton *cdSelectButton =
QAbstractButton *cdSelectButton =
msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole);
#endif
msgBox.exec();
@ -505,14 +563,14 @@ bool MainDialog::setupGameSettings()
#ifndef WIN32
else if(msgBox.clickedButton() == cdSelectButton) {
UnshieldThread cd;
{
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(close()), &cdbox, SLOT(reject()));
cd.SetMorrowindPath(
QFileDialog::getOpenFileName(
NULL,
@ -526,11 +584,11 @@ bool MainDialog::setupGameSettings()
QObject::tr("Select where to extract files to"),
QDir::currentPath(),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData());
cd.start();
cdbox.exec();
}
while(expansions(cd));
selectedFile = QString::fromStdString(cd.GetMWEsmPath());
@ -550,11 +608,11 @@ bool MainDialog::setupGameSettings()
return true;
}
bool MainDialog::setupGraphicsSettings()
bool Launcher::MainDialog::setupGraphicsSettings()
{
mGraphicsSettings.setMultiValueEnabled(false);
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string());
QFile localDefault(QString("settings-default.cfg"));
@ -604,7 +662,7 @@ bool MainDialog::setupGraphicsSettings()
return true;
}
void MainDialog::loadSettings()
void Launcher::MainDialog::loadSettings()
{
int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt();
int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt();
@ -616,7 +674,7 @@ void MainDialog::loadSettings()
move(posX, posY);
}
void MainDialog::saveSettings()
void Launcher::MainDialog::saveSettings()
{
QString width = QString::number(this->width());
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
saveSettings();
mGraphicsPage->saveSettings();
mDataFilesPage->saveSettings();
QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string());
QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string());
QDir dir(userPath);
if (!dir.exists()) {
@ -727,13 +785,13 @@ bool MainDialog::writeSettings()
return true;
}
void MainDialog::closeEvent(QCloseEvent *event)
void Launcher::MainDialog::closeEvent(QCloseEvent *event)
{
writeSettings();
event->accept();
}
void MainDialog::play()
void Launcher::MainDialog::play()
{
if (!writeSettings()) {
qApp->quit();
@ -742,11 +800,11 @@ void MainDialog::play()
if(!mGameSettings.hasMaster()) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("No master file selected"));
msgBox.setWindowTitle(tr("No game file selected"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setText(tr("<br><b>You do not have any master files selected.</b><br><br> \
OpenMW will not start without a master file selected.<br>"));
msgBox.setText(tr("<br><b>You do not have no game file selected.</b><br><br> \
OpenMW will not start without a game file selected.<br>"));
msgBox.exec();
return;
}
@ -756,7 +814,7 @@ void MainDialog::play()
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;
#ifdef Q_OS_WIN

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

@ -6,8 +6,9 @@
#include <QPlastiqueStyle>
#endif
PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
{
setObjectName ("PlayPage");
setupUi(this);
// Hacks to get the stylesheet look properly
@ -17,27 +18,22 @@ PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
#endif
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()));
}
void PlayPage::setProfilesComboBoxModel(QAbstractItemModel *model)
void Launcher::PlayPage::setProfilesModel(QAbstractItemModel *model)
{
profilesComboBox->setModel(model);
}
void PlayPage::setProfilesComboBoxIndex(int index)
void Launcher::PlayPage::setProfilesIndex(int index)
{
profilesComboBox->setCurrentIndex(index);
}
void PlayPage::slotCurrentIndexChanged(int index)
{
emit profileChanged(index);
}
void PlayPage::slotPlayClicked()
void Launcher::PlayPage::slotPlayClicked()
{
emit playButtonClicked();
}

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

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

@ -5,15 +5,15 @@
#include <QRegExp>
#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;
QRegExp sectionRe("([^/]+)/(.+)$");

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

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

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

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

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

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

@ -235,7 +235,7 @@ namespace
{
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();
}
}
@ -243,7 +243,7 @@ namespace
{
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();
}
}
@ -255,7 +255,7 @@ namespace
{
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;
}
@ -292,30 +292,30 @@ namespace
}
bool UnshieldThread::SetMorrowindPath(const std::string& path)
bool Launcher::UnshieldThread::SetMorrowindPath(const std::string& path)
{
mMorrowindPath = path;
return true;
}
bool UnshieldThread::SetTribunalPath(const std::string& path)
bool Launcher::UnshieldThread::SetTribunalPath(const std::string& path)
{
mTribunalPath = path;
return true;
}
bool UnshieldThread::SetBloodmoonPath(const std::string& path)
bool Launcher::UnshieldThread::SetBloodmoonPath(const std::string& path)
{
mBloodmoonPath = path;
return true;
}
void UnshieldThread::SetOutputPath(const std::string& path)
void Launcher::UnshieldThread::SetOutputPath(const std::string& 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;
bfs::path dirname;
@ -349,7 +349,7 @@ bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, cons
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_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;
outputDataFilesDir /= "Data Files";
@ -475,7 +475,7 @@ bool UnshieldThread::extract()
return true;
}
void UnshieldThread::Done()
void Launcher::UnshieldThread::Done()
{
// Get rid of unnecessary files
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"));
}
std::string UnshieldThread::GetMWEsmPath()
std::string Launcher::UnshieldThread::GetMWEsmPath()
{
return findFile(mOutputPath / "Data Files", "morrowind.esm").string();
}
bool UnshieldThread::TribunalDone()
bool Launcher::UnshieldThread::TribunalDone()
{
return mTribunalDone;
}
bool UnshieldThread::BloodmoonDone()
bool Launcher::UnshieldThread::BloodmoonDone()
{
return mBloodmoonDone;
}
void UnshieldThread::run()
void Launcher::UnshieldThread::run()
{
extract();
emit close();
}
UnshieldThread::UnshieldThread()
Launcher::UnshieldThread::UnshieldThread()
{
unshield_set_log_level(0);
mMorrowindDone = false;

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

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

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

@ -1,10 +1,12 @@
#include <QToolButton>
#include <QStyle>
#include "lineedit.hpp"
LineEdit::LineEdit(QWidget *parent)
: QLineEdit(parent)
{
setupClearButton();
}
void LineEdit::setupClearButton()
{
mClearButton = new QToolButton(this);
QPixmap pixmap(":images/clear.png");
@ -15,13 +17,6 @@ LineEdit::LineEdit(QWidget *parent)
mClearButton->hide();
connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear()));
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 *)

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

@ -5,18 +5,12 @@
#include <QKeyEvent>
#include "profilescombobox.hpp"
#include "comboboxlineedit.hpp"
ProfilesComboBox::ProfilesComboBox(QWidget *parent) :
QComboBox(parent)
ContentSelectorView::ComboBox(parent)
{
mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore
setEditEnabled(true);
setValidator(mValidator);
setCompleter(0);
connect(this, SIGNAL(currentIndexChanged(int)), this,
SLOT(slotIndexChanged(int)));
connect(this, SIGNAL(activated(int)), this,
SLOT(slotIndexChangedByUser(int)));
setInsertPolicy(QComboBox::NoInsert);
}
@ -37,6 +31,7 @@ void ProfilesComboBox::setEditEnabled(bool editable)
setValidator(mValidator);
ComboBoxLineEdit *edit = new ComboBoxLineEdit(this);
setLineEdit(edit);
setCompleter(0);
@ -45,6 +40,9 @@ void ProfilesComboBox::setEditEnabled(bool editable)
connect(lineEdit(), SIGNAL(textChanged(QString)), this,
SLOT(slotTextChanged(QString)));
connect (lineEdit(), SIGNAL(textChanged(QString)), this,
SIGNAL (signalProfileTextChanged (QString)));
}
void ProfilesComboBox::slotTextChanged(const QString &text)
@ -82,11 +80,20 @@ void ProfilesComboBox::slotEditingFinished()
emit(profileRenamed(previous, current));
}
void ProfilesComboBox::slotIndexChanged(int index)
void ProfilesComboBox::slotIndexChangedByUser(int index)
{
if (index == -1)
return;
emit(profileChanged(mOldProfile, currentText()));
mOldProfile = itemText(index);
emit (signalProfileChanged(mOldProfile, currentText()));
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 <QLabel>
#include <components/fileorderlist/utils/lineedit.hpp>
TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) :
Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) :
QDialog(parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
mButtonBox = new QDialogButtonBox(this);
mButtonBox->addButton(QDialogButtonBox::Ok);
mButtonBox->addButton(QDialogButtonBox::Cancel);
mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false);
// Line edit
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->setCompleter(0);
@ -38,34 +37,51 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid
Q_UNUSED(title);
#endif
setOkButtonEnabled(false);
setModal(true);
connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept()));
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->setFocus();
return QDialog::exec();
}
void TextInputDialog::setOkButtonEnabled(bool enabled)
QString Launcher::TextInputDialog::getText() const
{
QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok);
okButton->setEnabled(enabled);
return mLineEdit->text();
}
QPalette *palette = new QPalette();
palette->setColor(QPalette::Text,Qt::red);
void Launcher::TextInputDialog::slotUpdateOkButton(QString text)
{
bool enabled = !(text.isEmpty());
mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled);
if (enabled) {
if (enabled)
mLineEdit->setPalette(QApplication::palette());
} else {
else
{
// Existing profile name, make the text red
QPalette *palette = new QPalette();
palette->setColor(QPalette::Text,Qt::red);
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
#include <QDialog>
//#include "lineedit.hpp"
#include "lineedit.hpp"
class QDialogButtonBox;
class LineEdit;
class TextInputDialog : public QDialog
namespace Launcher
{
Q_OBJECT
public:
explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0);
inline LineEdit *lineEdit() { return mLineEdit; }
void setOkButtonEnabled(bool enabled);
class TextInputDialog : public QDialog
{
Q_OBJECT
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:
QDialogButtonBox *mButtonBox;
private slots:
void slotUpdateOkButton(QString text);
};
};
}
#endif // TEXTINPUTDIALOG_HPP

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

@ -5,11 +5,11 @@ opencs_units (. editor)
set (CMAKE_BUILD_TYPE DEBUG)
opencs_units (model/doc
document
document operation saving
)
opencs_units_noqt (model/doc
documentmanager
documentmanager stage savingstate savingstages
)
opencs_hdrs_noqt (model/doc
@ -24,27 +24,27 @@ opencs_units (model/world
opencs_units_noqt (model/world
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
columnimp idcollection collection
columnimp idcollection collection info
)
opencs_units (model/tools
tools operation reportmodel
tools reportmodel
)
opencs_units_noqt (model/tools
stage verifier mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck referenceablecheck
)
opencs_units (view/doc
viewmanager view operations operation subview startup filedialog newgame filewidget
adjusterwidget
viewmanager view operations operation subview startup filedialog newgame
filewidget adjusterwidget
)
@ -60,13 +60,17 @@ opencs_hdrs_noqt (view/doc
opencs_units (view/world
table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator
cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool
scenetoolmode
scenetoolmode infocreator
)
opencs_units (view/render
scenewidget
)
opencs_units_noqt (view/world
dialoguesubview subviews
enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate
scripthighlighter idvalidator
scripthighlighter idvalidator dialoguecreator
)
@ -124,11 +128,13 @@ opencs_units (view/filter
set (OPENCS_US
)
set (OPENCS_RES ../../files/opencs/resources.qrc
../../files/launcher/launcher.qrc
set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.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})
@ -137,7 +143,7 @@ if(WIN32)
set(QT_USE_QTMAIN TRUE)
endif(WIN32)
find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork QtXml QtXmlPatterns REQUIRED)
find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED)
include(${QT_USE_FILE})
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})
if(APPLE)
set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns)
else()
set (OPENCS_MAC_ICON "")
endif(APPLE)
add_executable(opencs
MACOSX_BUNDLE
${OPENCS_SRC}
${OPENCS_UI_HDR}
${OPENCS_MOC_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
${Boost_LIBRARIES}
${QT_LIBRARIES}
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 <QMessageBox>
#include <OgreRoot.h>
#include <OgreRenderWindow.h>
#include "model/doc/document.hpp"
#include "model/world/data.hpp"
CS::Editor::Editor() : mViewManager (mDocumentManager)
CS::Editor::Editor()
: mDocumentManager (mCfgMgr), mViewManager (mDocumentManager)
{
mIpcServerName = "org.openmw.OpenCS";
setupDataFiles();
mNewGame.setLocalData (mLocal);
mFileDialog.setLocalData (mLocal);
connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ()));
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 (editConfig()), this, SLOT (showSettings ()));
connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles()));
connect (&mFileDialog, SIGNAL(createNewFile()), this, SLOT(createNewFile()));
connect (&mFileDialog, SIGNAL(signalOpenFiles (const boost::filesystem::path&)),
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&)),
this, SLOT (createNewGame (const boost::filesystem::path&)));
this, SLOT (createNewGame (const boost::filesystem::path&)));
}
void CS::Editor::setupDataFiles()
{
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()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken())
("data-local", boost::program_options::value<std::string>()->default_value(""))
("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);
@ -78,14 +86,13 @@ void CS::Editor::setupDataFiles()
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());
mDocumentManager.setResourceDir (variables["resources"].as<std::string>());
for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter)
{
QString path = QString::fromStdString(iter->string());
mFileDialog.addFiles(path);
}
@ -109,48 +116,39 @@ void CS::Editor::createGame()
void CS::Editor::createAddon()
{
mStartup.hide();
mFileDialog.newFile();
mFileDialog.showDialog (CSVDoc::ContentAction_New);
}
void CS::Editor::loadDocument()
{
mStartup.hide();
mFileDialog.openFile();
mFileDialog.showDialog (CSVDoc::ContentAction_Edit);
}
void CS::Editor::openFiles()
void CS::Editor::openFiles (const boost::filesystem::path &savePath)
{
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());
}
/// \todo Get the save path from the file dialogue
CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), false);
CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, false);
mViewManager.addView (document);
mFileDialog.hide();
}
void CS::Editor::createNewFile()
void CS::Editor::createNewFile (const boost::filesystem::path &savePath)
{
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(mFileDialog.fileName().toStdString());
/// \todo Get the save path from the file dialogue.
files.push_back(mFileDialog.filename().toStdString());
CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), true);
CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, true);
mViewManager.addView (document);
mFileDialog.hide();
@ -212,6 +210,20 @@ int CS::Editor::run()
if (mLocal.empty())
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();
QApplication::setQuitOnLastWindowClosed (true);

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

@ -7,6 +7,12 @@
#include <QApplication>
#include <QIcon>
#include <components/ogreinit/ogreinit.hpp>
#ifdef Q_OS_MAC
#include <QDir>
#endif
class Application : public QApplication
{
private:
@ -33,7 +39,32 @@ class Application : public QApplication
int main(int argc, char *argv[])
{
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);
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"));
@ -42,7 +73,7 @@ int main(int argc, char *argv[])
if(!editor.makeIPCServer())
{
editor.connectToIPCServer();
return 0;
// return 0;
}
return editor.run();

@ -1,8 +1,15 @@
#include "document.hpp"
#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,
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);
@ -12,10 +19,10 @@ void CSMDoc::Document::load (const std::vector<boost::filesystem::path>::const_i
--end2;
for (std::vector<boost::filesystem::path>::const_iterator iter (begin); iter!=end2; ++iter)
getData().loadFile (*iter, true);
getData().loadFile (*iter, true, false);
if (lastAsModified)
getData().loadFile (*end2, false);
getData().loadFile (*end2, false, false);
}
void CSMDoc::Document::addGmsts()
@ -2058,9 +2065,9 @@ void CSMDoc::Document::addOptionalGlobals()
{
static const char *sGlobals[] =
{
"dayspassed",
"pcwerewolf",
"pcyear",
"DaysPassed",
"PCWerewolf",
"PCYear",
0
};
@ -2137,11 +2144,86 @@ void CSMDoc::Document::createBase()
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,
const boost::filesystem::path& savePath, bool new_)
: mSavePath (savePath), mTools (mData)
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_)
: mSavePath (savePath), mContentFiles (files), mTools (mData), mResDir(resDir),
mProjectPath ((configuration.getUserDataPath() / "projects") /
(savePath.filename().string() + ".project")),
mSaving (*this, mProjectPath)
{
if (files.empty())
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_);
}
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();
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 (done (int)), this, SLOT (operationDone (int)));
// dummy implementation -> remove when proper save is implemented.
mSaveCount = 0;
connect (&mSaveTimer, SIGNAL(timeout()), this, SLOT (saving()));
connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
connect (&mSaving, SIGNAL (done (int)), this, SLOT (operationDone (int)));
connect (&mSaving, SIGNAL (reportMessage (const QString&, int)),
this, SLOT (reportMessage (const QString&, int)));
}
CSMDoc::Document::~Document()
@ -2187,7 +2308,7 @@ int CSMDoc::Document::getState() const
if (!mUndoStack.isClean())
state |= State_Modified;
if (mSaveCount)
if (mSaving.isRunning())
state |= State_Locked | State_Saving | State_Operation;
if (int operations = mTools.getRunningOperations())
@ -2201,12 +2322,20 @@ const boost::filesystem::path& CSMDoc::Document::getSavePath() const
return mSavePath;
}
const std::vector<boost::filesystem::path>& CSMDoc::Document::getContentFiles() const
{
return mContentFiles;
}
void CSMDoc::Document::save()
{
mSaveCount = 1;
mSaveTimer.start (500);
if (mSaving.isRunning())
throw std::logic_error (
"Failed to initiate save, because a save operation is already running.");
mSaving.start();
emit stateChanged (getState(), this);
emit progress (1, 16, State_Saving, 1, this);
}
CSMWorld::UniversalId CSMDoc::Document::verify()
@ -2218,44 +2347,26 @@ CSMWorld::UniversalId CSMDoc::Document::verify()
void CSMDoc::Document::abortOperation (int type)
{
mTools.abortOperation (type);
if (type==State_Saving)
{
mSaveCount=0;
mSaveTimer.stop();
emit stateChanged (getState(), this);
}
mSaving.abort();
else
mTools.abortOperation (type);
}
void CSMDoc::Document::modificationStateChanged (bool clean)
{
emit stateChanged (getState(), this);
}
void CSMDoc::Document::operationDone (int type)
void CSMDoc::Document::reportMessage (const QString& message, 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 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);
}
emit stateChanged (getState(), this);
}
const CSMWorld::Data& CSMDoc::Document::getData() const

@ -14,6 +14,7 @@
#include "../tools/tools.hpp"
#include "state.hpp"
#include "saving.hpp"
class QAbstractItemModel;
@ -23,6 +24,11 @@ namespace ESM
struct Global;
}
namespace Files
{
class ConfigurationManager;
}
namespace CSMDoc
{
class Document : public QObject
@ -32,16 +38,17 @@ namespace CSMDoc
private:
boost::filesystem::path mSavePath;
std::vector<boost::filesystem::path> mContentFiles;
CSMWorld::Data mData;
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
// using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late.
QUndoStack mUndoStack;
int mSaveCount; ///< dummy implementation -> remove when proper save is implemented.
QTimer mSaveTimer; ///< dummy implementation -> remove when proper save is implemented.
// not implemented
Document (const Document&);
Document& operator= (const Document&);
@ -64,8 +71,7 @@ namespace CSMDoc
public:
Document (const std::vector<boost::filesystem::path>& files,
const boost::filesystem::path& savePath, bool new_);
Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_);
~Document();
@ -75,6 +81,10 @@ namespace CSMDoc
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();
CSMWorld::UniversalId verify();
@ -98,10 +108,9 @@ namespace CSMDoc
void modificationStateChanged (bool clean);
void operationDone (int type);
void reportMessage (const QString& message, int type);
void saving();
///< dummy implementation -> remove when proper save is implemented.
void operationDone (int type);
public slots:
@ -110,3 +119,4 @@ namespace CSMDoc
}
#endif

@ -4,9 +4,22 @@
#include <algorithm>
#include <stdexcept>
#include <boost/filesystem.hpp>
#ifndef Q_MOC_RUN
#include <components/files/configurationmanager.hpp>
#endif
#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()
{
@ -17,7 +30,7 @@ CSMDoc::DocumentManager::~DocumentManager()
CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files, const boost::filesystem::path& savePath,
bool new_)
{
Document *document = new Document (files, savePath, new_);
Document *document = new Document (mConfiguration, files, savePath, mResDir, new_);
mDocuments.push_back (document);
@ -35,4 +48,9 @@ bool CSMDoc::DocumentManager::removeDocument (Document *document)
delete document;
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>
namespace Files
{
class ConfigurationManager;
}
namespace CSMDoc
{
class Document;
@ -13,18 +18,18 @@ namespace CSMDoc
class DocumentManager
{
std::vector<Document *> mDocuments;
const Files::ConfigurationManager& mConfiguration;
DocumentManager (const DocumentManager&);
DocumentManager& operator= (const DocumentManager&);
public:
DocumentManager();
DocumentManager (const Files::ConfigurationManager& configuration);
~DocumentManager();
Document *addDocument (const std::vector<boost::filesystem::path>& files,
const boost::filesystem::path& savePath, bool new_);
Document *addDocument (const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_);
///< 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
@ -32,6 +37,10 @@ namespace CSMDoc
bool removeDocument (Document *document);
///< \return last document removed?
void setResourceDir (const boost::filesystem::path& parResDir);
private:
boost::filesystem::path mResDir;
};
}

@ -6,16 +6,16 @@
#include <QTimer>
#include "../doc/state.hpp"
#include "state.hpp"
#include "stage.hpp"
void CSMTools::Operation::prepareStages()
void CSMDoc::Operation::prepareStages()
{
mCurrentStage = mStages.begin();
mCurrentStep = 0;
mCurrentStepTotal = 0;
mTotalSteps = 0;
mError = false;
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)
delete iter->first;
}
void CSMTools::Operation::run()
void CSMDoc::Operation::run()
{
prepareStages();
QTimer timer;
timer.connect (&timer, SIGNAL (timeout()), this, SLOT (verify()));
timer.connect (&timer, SIGNAL (timeout()), this, SLOT (executeStage()));
timer.start (0);
exec();
}
void CSMTools::Operation::appendStage (Stage *stage)
void CSMDoc::Operation::appendStage (Stage *stage)
{
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;
@ -68,7 +91,16 @@ void CSMTools::Operation::verify()
}
else
{
mCurrentStage->first->perform (mCurrentStep++, messages);
try
{
mCurrentStage->first->perform (mCurrentStep++, messages);
}
catch (const std::exception& e)
{
emit reportMessage (e.what(), mType);
abort();
}
++mCurrentStepTotal;
break;
}
@ -81,4 +113,9 @@ void CSMTools::Operation::verify()
if (mCurrentStage==mStages.end())
exit();
}
void CSMDoc::Operation::operationDone()
{
emit done (mType);
}

@ -1,11 +1,11 @@
#ifndef CSM_TOOLS_OPERATION_H
#define CSM_TOOLS_OPERATION_H
#ifndef CSM_DOC_OPERATION_H
#define CSM_DOC_OPERATION_H
#include <vector>
#include <QThread>
namespace CSMTools
namespace CSMDoc
{
class Stage;
@ -19,12 +19,17 @@ namespace CSMTools
int mCurrentStep;
int mCurrentStepTotal;
int mTotalSteps;
int mOrdered;
bool mFinalAlways;
bool mError;
void prepareStages();
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();
@ -35,19 +40,25 @@ namespace CSMTools
///
/// \attention Do no call this function while this Operation is running.
bool hasError() const;
signals:
void progress (int current, int max, int type);
void reportMessage (const QString& message, int type);
void done (int type);
public slots:
void abort();
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
#define CSM_TOOLS_STAGE_H
#ifndef CSM_DOC_STAGE_H
#define CSM_DOC_STAGE_H
#include <vector>
#include <string>
namespace CSMTools
namespace CSMDoc
{
class Stage
{
@ -16,7 +16,7 @@ namespace CSMTools
///< \return number of steps
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
{
bool mAnd;
public:
AndNode (const std::vector<boost::shared_ptr<Node> >& nodes);

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

@ -61,11 +61,11 @@ namespace CSMFilter
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) {}
@ -614,4 +614,4 @@ boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::getFilter() const
throw std::logic_error ("No filter available");
return mFilter;
}
}

@ -44,7 +44,11 @@ namespace CSMSettings
inline QStringPair *getValuePair() { return mValuePair; }
/// 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; }

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

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp"
#include "stage.hpp"
#include "../doc/stage.hpp"
namespace CSMTools
{
/// \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;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp"
#include "stage.hpp"
#include "../doc/stage.hpp"
namespace CSMTools
{
/// \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;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp"
#include "stage.hpp"
#include "../doc/stage.hpp"
namespace CSMTools
{
/// \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;

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

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp"
#include "stage.hpp"
#include "../doc/stage.hpp"
namespace CSMTools
{
/// \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;
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 "stage.hpp"
#include "../doc/stage.hpp"
namespace CSMTools
{
/// \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;

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

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp"
#include "stage.hpp"
#include "../doc/stage.hpp"
namespace CSMTools
{
/// \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;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp"
#include "stage.hpp"
#include "../doc/stage.hpp"
namespace CSMTools
{
/// \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;

@ -5,12 +5,12 @@
#include "../world/idcollection.hpp"
#include "stage.hpp"
#include "../doc/stage.hpp"
namespace CSMTools
{
/// \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;

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

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

@ -11,10 +11,13 @@ namespace CSMWorld
class UniversalId;
}
namespace CSMTools
namespace CSMDoc
{
class Verifier;
class Operation;
}
namespace CSMTools
{
class ReportModel;
class Tools : public QObject
@ -22,7 +25,7 @@ namespace CSMTools
Q_OBJECT
CSMWorld::Data& mData;
Verifier *mVerifier;
CSMDoc::Operation *mVerifier;
std::map<int, ReportModel *> mReports;
int mNextReportNumber;
std::map<int, int> mActiveReports; // type, report number
@ -31,12 +34,12 @@ namespace CSMTools
Tools (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.
const Operation *get (int type) const;
const CSMDoc::Operation *get (int type) const;
///< Returns a 0-pointer, if operation hasn't been used yet.
public:
@ -58,8 +61,6 @@ namespace CSMTools
private slots:
void verifierDone();
void verifierMessage (const QString& message, int type);
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& 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:
Collection();
@ -86,6 +98,10 @@ namespace CSMWorld
UniversalId::Type type = UniversalId::Type_None);
///< \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;
////< Search record with \a id.
/// \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 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
virtual std::vector<std::string> getIds (bool listDeleted = true) const;
@ -112,12 +129,87 @@ namespace CSMWorld
///
/// \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 setRecord (int index, const Record<ESXRecordT>& record);
///< \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>
Collection<ESXRecordT, IdAccessorT>::Collection()
{}
@ -142,8 +234,7 @@ namespace CSMWorld
record2.mState = Record<ESXRecordT>::State_ModifiedOnly;
record2.mModified = record;
mRecords.push_back (record2);
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), mRecords.size()-1));
insertRecord (record2, getAppendIndex (id));
}
else
{
@ -260,7 +351,12 @@ namespace CSMWorld
ESXRecordT record;
IdAccessorT().getId (record) = id;
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>
@ -286,14 +382,14 @@ namespace CSMWorld
void Collection<ESXRecordT, IdAccessorT>::appendRecord (const RecordBase& record,
UniversalId::Type type)
{
mRecords.push_back (dynamic_cast<const Record<ESXRecordT>&> (record));
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId (
dynamic_cast<const Record<ESXRecordT>&> (record).get())),
mRecords.size()-1));
insertRecord (record,
getAppendIndex (IdAccessorT().getId (
dynamic_cast<const Record<ESXRecordT>&> (record).get()), type), type);
}
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());
}
@ -326,6 +422,29 @@ namespace CSMWorld
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>
void Collection<ESXRecordT, IdAccessorT>::setRecord (int index, const Record<ESXRecordT>& record)
{
@ -334,6 +453,12 @@ namespace CSMWorld
mRecords.at (index) = record;
}
template<typename ESXRecordT, typename IdAccessorT>
bool Collection<ESXRecordT, IdAccessorT>::reorderRows (int baseIndex, const std::vector<int>& newOrder)
{
return false;
}
}
#endif

@ -1,6 +1,31 @@
#include "collectionbase.hpp"
#include <stdexcept>
#include "columnbase.hpp"
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
#include <string>
#include <vector>
#include "universalid.hpp"
#include "columns.hpp"
class QVariant;
@ -72,17 +74,35 @@ namespace CSMWorld
UniversalId::Type type = UniversalId::Type_None) = 0;
///< 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 (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
virtual std::vector<std::string> getIds (bool listDeleted = true) const = 0;
///< Return a sorted collection of all IDs
///
/// \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_WeaponType,
Display_RecordState,
Display_RefRecordType
Display_RefRecordType,
Display_DialogueType,
Display_QuestStatusType,
Display_Gender
};
int mColumnId;

@ -9,6 +9,7 @@
#include "columnbase.hpp"
#include "columns.hpp"
#include "info.hpp"
namespace CSMWorld
{
@ -814,14 +815,14 @@ namespace CSMWorld
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)
{
ESXRecordT record2 = record.get();
record2.mCellId = data.toString().toUtf8().constData();
record2.mCell = data.toString().toUtf8().constData();
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>
struct PosColumn : public Column<ESXRecordT>
{
@ -1284,6 +1316,373 @@ namespace CSMWorld
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

@ -159,6 +159,20 @@ namespace CSMWorld
{ ColumnId_DoorPositionXRot, "Teleport Rot X" },
{ ColumnId_DoorPositionYRot, "Teleport Rot Y" },
{ 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_UseValue2, "Use value 2" },
@ -206,7 +220,7 @@ int CSMWorld::Columns::getId (const std::string& name)
std::string name2 = Misc::StringUtils::lowerCase (name);
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 -1;
@ -249,7 +263,7 @@ namespace
static const char *sCreatureTypes[] =
{
"Creature", "Deadra", "Undead", "Humanoid", 0
"Creature", "Daedra", "Undead", "Humanoid", 0
};
static const char *sWeaponTypes[] =
@ -269,6 +283,21 @@ namespace
"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)
{
switch (column)
@ -283,6 +312,9 @@ namespace
case CSMWorld::Columns::ColumnId_WeaponType: return sWeaponTypes;
case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums;
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;
}
@ -310,4 +342,4 @@ std::vector<std::string> CSMWorld::Columns::getEnums (ColumnId column)
}
return enums;
}
}

@ -152,6 +152,20 @@ namespace CSMWorld
ColumnId_DoorPositionXRot = 139,
ColumnId_DoorPositionYRot = 140,
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
// to extend the number of use values.

@ -4,10 +4,10 @@
#include <QAbstractItemModel>
#include "idtable.hpp"
#include "idtable.hpp"
#include <components/misc/stringops.hpp>
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_)
{
mOld = mModel.data (mIndex, Qt::EditRole);
@ -25,7 +25,7 @@ void CSMWorld::ModifyCommand::undo()
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)
{
setText (("Create record " + id).c_str());
@ -54,7 +54,7 @@ void CSMWorld::CreateCommand::undo()
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)
{
setText (("Revert record " + id).c_str());
@ -89,7 +89,7 @@ void CSMWorld::RevertCommand::undo()
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)
{
setText (("Delete record " + id).c_str());
@ -122,4 +122,53 @@ void CSMWorld::DeleteCommand::redo()
void CSMWorld::DeleteCommand::undo()
{
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 <map>
#include <vector>
#include <QVariant>
#include <QUndoCommand>
@ -38,6 +39,26 @@ namespace CSMWorld
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
{
IdTable& mModel;
@ -99,6 +120,21 @@ namespace CSMWorld
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

@ -44,6 +44,17 @@ void CSMWorld::Data::appendIds (std::vector<std::string>& ids, const CollectionB
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)
{
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_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 RecordStateColumn<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 RecordStateColumn<CellRef>);
mRefs.addColumn (new FixedRecordTypeColumn<CellRef> (UniversalId::Type_Reference));
mRefs.addColumn (new CellColumn<CellRef>);
mRefs.addColumn (new IdColumn<CellRef>);
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 RecordStateColumn<CSMFilter::Filter>);
mFilters.addColumn (new FixedRecordTypeColumn<CSMFilter::Filter> (UniversalId::Type_Filter));
mFilters.addColumn (new FilterColumn<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 (&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 (&mBirthsigns), UniversalId::Type_Birthsigns, UniversalId::Type_Birthsign);
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 (&mReferenceables), UniversalId::Type_Referenceables,
UniversalId::Type_Referenceable);
@ -319,6 +372,47 @@ CSMWorld::IdCollection<ESM::Spell>& CSMWorld::Data::getSpells()
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
{
return mCells;
@ -387,7 +481,7 @@ void CSMWorld::Data::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;
@ -397,6 +491,11 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base)
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
// to any view.
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_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:
/// \todo throw an exception instead, once all records are implemented
/// or maybe report error and continue?
reader.skipRecord();
}
}
@ -469,10 +639,50 @@ bool CSMWorld::Data::hasId (const std::string& id) const
getRegions().searchId (id)!=-1 ||
getBirthsigns().searchId (id)!=-1 ||
getSpells().searchId (id)!=-1 ||
getTopics().searchId (id)!=-1 ||
getJournals().searchId (id)!=-1 ||
getCells().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> ids;
@ -487,6 +697,8 @@ std::vector<std::string> CSMWorld::Data::getIds (bool listDeleted) const
appendIds (ids, mRegions, listDeleted);
appendIds (ids, mBirthsigns, listDeleted);
appendIds (ids, mSpells, listDeleted);
appendIds (ids, mTopics, listDeleted);
appendIds (ids, mJournals, listDeleted);
appendIds (ids, mCells, listDeleted);
appendIds (ids, mReferenceables, listDeleted);

@ -20,6 +20,7 @@
#include <components/esm/loadregn.hpp>
#include <components/esm/loadbsgn.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loaddial.hpp>
#include "../filter/filter.hpp"
@ -28,6 +29,7 @@
#include "cell.hpp"
#include "refidcollection.hpp"
#include "refcollection.hpp"
#include "infocollection.hpp"
class QAbstractItemModel;
@ -48,12 +50,18 @@ namespace CSMWorld
IdCollection<ESM::Region> mRegions;
IdCollection<ESM::BirthSign> mBirthsigns;
IdCollection<ESM::Spell> mSpells;
IdCollection<ESM::Dialogue> mTopics;
IdCollection<ESM::Dialogue> mJournals;
InfoCollection mTopicInfos;
InfoCollection mJournalInfos;
IdCollection<Cell> mCells;
RefIdCollection mReferenceables;
RefCollection mRefs;
IdCollection<CSMFilter::Filter> mFilters;
std::vector<QAbstractItemModel *> mModels;
std::map<UniversalId::Type, QAbstractItemModel *> mModelIndex;
std::string mAuthor;
std::string mDescription;
// not implemented
Data (const Data&);
@ -66,6 +74,8 @@ namespace CSMWorld
bool listDeleted);
///< Append all IDs from collection to \a ids.
static int count (RecordBase::State state, const CollectionBase& collection);
public:
Data();
@ -116,6 +126,22 @@ namespace CSMWorld
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;
IdCollection<Cell>& getCells();
@ -141,8 +167,10 @@ namespace CSMWorld
void merge();
///< 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.
///
/// \param project load project file instead of content file
bool hasId (const std::string& id) const;
@ -151,6 +179,17 @@ namespace CSMWorld
///
/// \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:
void idListChanged();

@ -7,21 +7,24 @@
namespace CSMWorld
{
/// \brief Single type collection of top level records
template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
class IdCollection : public Collection<ESXRecordT, IdAccessorT>
{
public:
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 (ESM::ESMReader& reader, bool base);
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>
void IdCollection<ESXRecordT, IdAccessorT>::load (ESM::ESMReader& reader, bool base,
UniversalId::Type type)
void IdCollection<ESXRecordT, IdAccessorT>::load (ESM::ESMReader& reader, bool base)
{
std::string id = reader.getHNOString ("NAME");
@ -56,30 +59,62 @@ namespace CSMWorld
IdAccessorT().getId (record) = id;
record.load (reader);
int index = this->searchId (IdAccessorT().getId (record));
load (record, base);
}
}
if (index==-1)
{
// new record
Record<ESXRecordT> record2;
record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record2.mBase : record2.mModified) = record;
template<typename ESXRecordT, typename IdAccessorT>
void IdCollection<ESXRecordT, IdAccessorT>::load (const ESXRecordT& record, bool base)
{
int index = this->searchId (IdAccessorT().getId (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
{
// old record
Record<ESXRecordT> record2 = Collection<ESXRecordT, IdAccessorT>::getRecord (index);
record2.setModified (record);
if (base)
record2.mBase = record;
else
record2.setModified (record);
this->setRecord (index, record2);
}
}
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 "columnbase.hpp"
CSMWorld::IdTable::IdTable (CollectionBase *idCollection) : mIdCollection (idCollection)
{
}
CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering)
: mIdCollection (idCollection), mReordering (reordering)
{}
CSMWorld::IdTable::~IdTable()
{
}
{}
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)
{
int index = mIdCollection->getAppendIndex();
int index = mIdCollection->getAppendIndex (id, type);
beginInsertRows (QModelIndex(), index, index);
@ -127,6 +124,17 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type
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
{
return index (mIdCollection->getIndex (id), column);
@ -138,7 +146,7 @@ void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& reco
if (index==-1)
{
int index = mIdCollection->getAppendIndex();
int index = mIdCollection->getAppendIndex (id);
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 columns = mIdCollection->getColumns();
for (int i=0; i<columns; ++i)
if (mIdCollection->getColumn (i).mColumnId==id)
return i;
return -1;
return mIdCollection->searchColumnIndex (id);
}
int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const
{
int index = searchColumnIndex (id);
return mIdCollection->findColumnIndex (id);
}
if (index==-1)
throw std::logic_error ("invalid column index");
void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector<int>& newOrder)
{
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
#define CSM_WOLRD_IDTABLE_H
#include <vector>
#include <QAbstractItemModel>
#include "universalid.hpp"
@ -15,7 +17,18 @@ namespace CSMWorld
{
Q_OBJECT
public:
enum Reordering
{
Reordering_None,
Reordering_WithinTopic
};
private:
CollectionBase *mIdCollection;
Reordering mReordering;
// not implemented
IdTable (const IdTable&);
@ -23,7 +36,7 @@ namespace CSMWorld
public:
IdTable (CollectionBase *idCollection);
IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_WithinTopic);
///< The ownership of \a idCollection is not transferred.
virtual ~IdTable();
@ -50,6 +63,10 @@ namespace CSMWorld
void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None);
///< \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;
void setRecord (const std::string& id, const RecordBase& record);
@ -63,6 +80,12 @@ namespace CSMWorld
int findColumnIndex (Columns::ColumnId id) const;
///< Return index of column with the given \a id. If no such column exists, an exception is
/// 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)
: QSortFilterProxyModel (parent)
{}
{
setSortCaseSensitivity (Qt::CaseInsensitive);
}
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)
{
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