diff --git a/.travis.yml b/.travis.yml index 1fc85dca3..bd4aefbdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,10 +38,10 @@ before_script: - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_script.osx.sh; fi script: - cd ./build - - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j4; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi -after_script: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi notifications: recipients: - corrmage+travis-ci@gmail.com diff --git a/AUTHORS.md b/AUTHORS.md index ce76c104e..565d5a703 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -19,6 +19,7 @@ Programmers Alexander Nadeau (wareya) Alexander Olofsson (Ace) Artem Kotsynyak (greye) + artemutin Arthur Moore (EmperorArthur) athile Bret Curtis (psi29a) @@ -59,6 +60,7 @@ Programmers Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) Kevin Poitra (PuppyKevin) + Koncord Lars Söderberg (Lazaroth) lazydev Leon Saunders (emoose) @@ -109,6 +111,7 @@ Programmers viadanna Vincent Heuken vocollapse + zelurker Manual ------ diff --git a/CI/check_tabs.sh b/CI/check_tabs.sh new file mode 100755 index 000000000..91f81e2a1 --- /dev/null +++ b/CI/check_tabs.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} apps components) + +if [[ $OUTPUT ]] ; then + echo "Error: Tab characters found!" + echo $OUTPUT + exit 1 +fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fefc673e..fb1ac83cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,25 +98,29 @@ endif() cmake_minimum_required(VERSION 2.6) # Sound setup -set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE AVRESAMPLE) unset(FFMPEG_LIBRARIES CACHE) -find_package(FFmpeg) + +find_package(FFmpeg REQUIRED) + +set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARY}) + if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND ) message(FATAL_ERROR "FFmpeg component required, but not found!") endif() set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIRS}) -set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) if( SWRESAMPLE_FOUND ) add_definitions(-DHAVE_LIBSWRESAMPLE) - set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES}) + set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES}) else() if( AVRESAMPLE_FOUND ) - set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES}) + set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES}) else() message(FATAL_ERROR "Install either libswresample (FFmpeg) or libavresample (Libav).") endif() endif() +set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES}) + # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) if(USE_SYSTEM_TINYXML) @@ -134,9 +138,11 @@ endif() # Platform specific if (WIN32) + if(NOT MINGW) set(Boost_USE_STATIC_LIBS ON) set(PLATFORM_INCLUDE_DIR "platform") add_definitions(-DBOOST_ALL_NO_LIB) + endif(NOT MINGW) # Suppress WinMain(), provided by SDL add_definitions(-DSDL_MAIN_HANDLED) @@ -149,6 +155,19 @@ endif() # Dependencies +set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") +message(STATUS "Using Qt${DESIRED_QT_VERSION}") + +if (DESIRED_QT_VERSION MATCHES 4) + find_package(Qt4 REQUIRED COMPONENTS QtCore QtGui QtNetwork) +else() + find_package(Qt5Widgets REQUIRED) + find_package(Qt5Core REQUIRED) + find_package(Qt5Network REQUIRED) + # Instruct CMake to run moc automatically when needed. + #set(CMAKE_AUTOMOC ON) +endif() + # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) find_package (Threads) @@ -169,7 +188,7 @@ if (HAVE_UNORDERED_MAP) endif () -set(BOOST_COMPONENTS system filesystem program_options) +set(BOOST_COMPONENTS system filesystem program_options thread wave) if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) endif(WIN32) @@ -345,6 +364,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml + "${OpenMW_BINARY_DIR}/openmw.appdata.xml") configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop "${OpenMW_BINARY_DIR}/openmw-cs.desktop") endif() @@ -402,6 +423,9 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_ESMTOOL) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESMTOOL) + IF(BUILD_NIFTEST) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_NIFTEST) IF(BUILD_MWINIIMPORTER) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_MWINIIMPORTER) @@ -428,6 +452,7 @@ IF(NOT WIN32 AND NOT APPLE) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/appdata" COMPONENT "openmw") IF(BUILD_OPENCS) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs") @@ -598,6 +623,10 @@ if (BUILD_WIZARD) add_subdirectory(apps/wizard) endif() +if (BUILD_NIFTEST) + add_subdirectory(apps/niftest) +endif(BUILD_NIFTEST) + # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..a9cd6a690 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +Description +=========== + +Your pull request description should include (if applicable): + +* A link back to the bug report or forum discussion that prompted the change +* Summary of the changes made +* Reasoning / motivation behind the change +* What testing you have carried out to verify the change + +Other notes +=========== + +* Separate your work into multiple pull requests whenever possible. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time. +* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title. +* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards). diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt index 3f1988a70..27baff815 100644 --- a/apps/bsatool/CMakeLists.txt +++ b/apps/bsatool/CMakeLists.txt @@ -9,7 +9,8 @@ add_executable(bsatool ) target_link_libraries(bsatool - ${Boost_LIBRARIES} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} components ) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index c0a6dcc81..d87abc285 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 1d0026215..1d5e662d8 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -13,7 +13,7 @@ add_executable(esmtool ) target_link_libraries(esmtool - ${Boost_LIBRARIES} + ${Boost_PROGRAM_OPTIONS_LIBRARY} components ) diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 88e188df0..883a9e728 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -119,7 +119,7 @@ std::string clothingTypeLabel(int idx) } std::string armorTypeLabel(int idx) -{ +{ if (idx >= 0 && idx <= 10) { static const char *armorTypeLabels[] = { @@ -645,7 +645,7 @@ std::string ruleFunction(int idx) else return "Invalid"; } - + // The "unused flag bits" should probably be defined alongside the // defined bits in the ESM component. The names of the flag bits are // very inconsistent. @@ -653,7 +653,7 @@ std::string ruleFunction(int idx) std::string bodyPartFlags(int flags) { std::string properties = ""; - if (flags == 0) properties += "[None] "; + if (flags == 0) properties += "[None] "; if (flags & ESM::BodyPart::BPF_Female) properties += "Female "; if (flags & ESM::BodyPart::BPF_NotPlayable) properties += "NotPlayable "; int unused = (0xFFFFFFFF ^ @@ -667,7 +667,7 @@ std::string bodyPartFlags(int flags) std::string cellFlags(int flags) { std::string properties = ""; - if (flags == 0) properties += "[None] "; + if (flags == 0) properties += "[None] "; if (flags & ESM::Cell::HasWater) properties += "HasWater "; if (flags & ESM::Cell::Interior) properties += "Interior "; if (flags & ESM::Cell::NoSleep) properties += "NoSleep "; @@ -830,12 +830,12 @@ std::string npcFlags(int flags) std::string properties = ""; if (flags == 0) properties += "[None] "; // Mythicmods and the ESM component differ. Mythicmods says - // 0x8=None and 0x10=AutoCalc, while our code defines 0x8 as - // AutoCalc. The former seems to be correct. All Bethesda - // records have bit 0x8 set. A suspiciously large portion of - // females have autocalc turned off. - if (flags & ESM::NPC::Autocalc) properties += "Unknown "; - if (flags & 0x00000010) properties += "Autocalc "; + // 0x8=None and 0x10=AutoCalc, while our code previously defined + // 0x8 as AutoCalc. The former seems to be correct. All Bethesda + // records have bit 0x8 set. Previously, suspiciously large portion + // of females had autocalc turned off. + if (flags & 0x00000008) properties += "Unknown "; + if (flags & ESM::NPC::Autocalc) properties += "Autocalc "; if (flags & ESM::NPC::Female) properties += "Female "; if (flags & ESM::NPC::Respawn) properties += "Respawn "; if (flags & ESM::NPC::Essential) properties += "Essential "; @@ -847,8 +847,8 @@ std::string npcFlags(int flags) // however the only unknown bit occurs on ALL records, and // relatively few NPCs have this bit set. int unused = (0xFFFFFFFF ^ - (ESM::NPC::Autocalc| - 0x00000010| + (0x00000008| + ESM::NPC::Autocalc| ESM::NPC::Female| ESM::NPC::Respawn| ESM::NPC::Essential| diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 2ee6c54bb..5fe6471b0 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -493,14 +493,14 @@ void Record::print() std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; if (mPrintPlain) { - std::cout << " Text:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mText << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Text:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mText << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Text: [skipped]" << std::endl; + std::cout << " Text: [skipped]" << std::endl; } } @@ -799,14 +799,14 @@ void Record::print() { if (mPrintPlain) { - std::cout << " Result Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mResultScript << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Result Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mResultScript << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Result Script: [skipped]" << std::endl; + std::cout << " Result Script: [skipped]" << std::endl; } } } @@ -841,19 +841,13 @@ void Record::print() std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; std::cout << " DataTypes: " << mData.mDataTypes << std::endl; - // Seems like this should done with reference counting in the - // loader to me. But I'm not really knowledgable about this - // record type yet. --Cory - bool wasLoaded = (mData.mDataLoaded != 0); - if (mData.mDataTypes) mData.loadData(mData.mDataTypes); - if (mData.mDataLoaded) + if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes)) { - std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl; + std::cout << " Height Offset: " << data->mHeightOffset << std::endl; // Lots of missing members. - std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl; - std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl; + std::cout << " Unknown1: " << data->mUnk1 << std::endl; + std::cout << " Unknown2: " << data->mUnk2 << std::endl; } - if (!wasLoaded) mData.unloadData(); } template<> @@ -1207,14 +1201,14 @@ void Record::print() if (mPrintPlain) { - std::cout << " Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mScriptText << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mScriptText << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Script: [skipped]" << std::endl; + std::cout << " Script: [skipped]" << std::endl; } } diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index c1b90ac2b..5e03c64db 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -53,7 +53,7 @@ namespace EsmTool } void setPrintPlain(bool plain) { - mPrintPlain = plain; + mPrintPlain = plain; } virtual void load(ESM::ESMReader &esm) = 0; diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index 72ef364ee..84e31dad9 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -33,7 +33,8 @@ add_executable(openmw-essimporter ) target_link_libraries(openmw-essimporter - ${Boost_LIBRARIES} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} components ) diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp index 55a20ec3d..8f090b3fc 100644 --- a/apps/essimporter/convertacdt.cpp +++ b/apps/essimporter/convertacdt.cpp @@ -41,9 +41,9 @@ namespace ESSImport { for (int i=0; i + #include #include +#include #include #include @@ -292,7 +296,7 @@ namespace ESSImport ESM::ESMWriter writer; - writer.setFormat (ESM::Header::CurrentFormat); + writer.setFormat (ESM::SavedGame::sCurrentFormat); std::ofstream stream(mOutFile.c_str(), std::ios::binary); // all unused diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 0de79f8f6..caf96054e 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -57,7 +57,6 @@ set(LAUNCHER_UI source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) -find_package(Qt4 REQUIRED) set(QT_USE_QTGUI 1) # Set some platform specific settings @@ -66,12 +65,17 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) -QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) -QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) +if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) + QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) + QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) +else() + QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) + QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) + QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) +endif() - -include(${QT_USE_FILE}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(NOT WIN32) include_directories(${LIBUNSHIELD_INCLUDE_DIR}) @@ -88,17 +92,27 @@ add_executable(openmw-launcher ) target_link_libraries(openmw-launcher - ${Boost_LIBRARIES} ${OGRE_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SDL2_LIBRARY_ONLY} - ${QT_LIBRARIES} components ) +if (DESIRED_QT_VERSION MATCHES 4) + target_link_libraries(openmw-launcher ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY}) + if(WIN32) + target_link_libraries(openmw-launcher ${QT_QTMAIN_LIBRARY}) + endif(WIN32) +else() + qt5_use_modules(openmw-launcher Widgets Core) + if (WIN32) + target_link_libraries(Qt5::WinMain) + endif() +endif() if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-launcher gcov) endif() + diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index ba0686110..32fe8c93a 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -54,9 +54,6 @@ int main(int argc, char *argv[]) QDir::setCurrent(dir.absolutePath()); - // Support non-latin characters - QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); - Launcher::MainDialog mainWin; Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index fd36993bf..304bf45ea 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -309,11 +309,11 @@ bool Launcher::MainDialog::setupGameSettings() mGameSettings.readUserFile(stream); } - // Now the rest + // Now the rest - priority: user > local > global QStringList paths; - paths.append(userPath + QString("openmw.cfg")); - paths.append(QString("openmw.cfg")); paths.append(globalPath + QString("openmw.cfg")); + paths.append(QString("openmw.cfg")); + paths.append(userPath + QString("openmw.cfg")); foreach (const QString &path, paths) { qDebug() << "Loading config file:" << qPrintable(path); @@ -490,7 +490,7 @@ bool Launcher::MainDialog::writeSettings() // Game settings QFile file(userPath + QString("openmw.cfg")); - if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { // File cannot be opened or created QMessageBox msgBox; msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); @@ -503,10 +503,8 @@ bool Launcher::MainDialog::writeSettings() return false; } - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - mGameSettings.writeFile(stream); + mGameSettings.writeFileWithComments(file); file.close(); // Graphics settings @@ -525,6 +523,7 @@ bool Launcher::MainDialog::writeSettings() return false; } + QTextStream stream(&file); stream.setDevice(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 790d47dc4..4024c0b42 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -14,10 +14,20 @@ add_executable(openmw-iniimporter ) target_link_libraries(openmw-iniimporter - ${Boost_LIBRARIES} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} components ) +if (WIN32) + target_link_libraries(openmw-iniimporter + ${Boost_LOCALE_LIBRARY}) +endif() + +if (MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") +endif() + if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-iniimporter gcov) diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt new file mode 100644 index 000000000..d7f0200d2 --- /dev/null +++ b/apps/niftest/CMakeLists.txt @@ -0,0 +1,19 @@ +set(NIFTEST + niftest.cpp +) +source_group(components\\nif\\tests FILES ${NIFTEST}) + +# Main executable +add_executable(niftest + ${NIFTEST} +) + +target_link_libraries(niftest + ${Boost_FILESYSTEM_LIBRARY} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(niftest gcov) +endif() diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp new file mode 100644 index 000000000..72393db40 --- /dev/null +++ b/apps/niftest/niftest.cpp @@ -0,0 +1,165 @@ +///Program to test .nif files both on the FileSystem and in BSA archives. + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +// Create local aliases for brevity +namespace bpo = boost::program_options; +namespace bfs = boost::filesystem; + +///See if the file has the named extension +bool hasExtension(std::string filename, std::string extensionToFind) +{ + std::string extension = filename.substr(filename.find_last_of(".")+1); + + //Convert strings to lower case for comparison + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower); + + if(extension == extensionToFind) + return true; + else + return false; +} + +///See if the file has the "nif" extension. +bool isNIF(std::string filename) +{ + return hasExtension(filename,"nif"); +} +///See if the file has the "bsa" extension. +bool isBSA(std::string filename) +{ + return hasExtension(filename,"bsa"); +} + +/// Check all the nif files in a given VFS::Archive +/// \note Takes ownership! +/// \note Can not read a bsa file inside of a bsa file. +void readVFS(VFS::Archive* anArchive,std::string archivePath = "") +{ + VFS::Manager myManager(true); + myManager.addArchive(anArchive); + myManager.buildIndex(); + + std::map files=myManager.getIndex(); + for(std::map::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::string name = it->first; + + try{ + if(isNIF(name)) + { + // std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile temp_nif(myManager.get(name),archivePath+name); + } + else if(isBSA(name)) + { + if(!archivePath.empty() && !isBSA(archivePath)) + { +// std::cout << "Reading BSA File: " << name << std::endl; + readVFS(new VFS::BsaArchive(archivePath+name),archivePath+name+"/"); +// std::cout << "Done with BSA File: " << name << std::endl; + } + } + } + catch (std::exception& e) + { + std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + } + } +} + +std::vector parseOptions (int argc, char** argv) +{ + bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n" + "Usages:\n" + " niftool \n" + " Scan the file or directories for nif errors.\n\n" + "Allowed options"); + desc.add_options() + ("help,h", "print help message.") + ("input-file", bpo::value< std::vector >(), "input file") + ; + + //Default option if none provided + bpo::positional_options_description p; + p.add("input-file", -1); + + bpo::variables_map variables; + try + { + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv). + options(desc).positional(p).run(); + bpo::store(valid_opts, variables); + } + catch(std::exception &e) + { + std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" + << desc << std::endl; + exit(1); + } + + bpo::notify(variables); + if (variables.count ("help")) + { + std::cout << desc << std::endl; + exit(1); + } + if (variables.count("input-file")) + { + return variables["input-file"].as< std::vector >(); + } + + std::cout << "No input files or directories specified!" << std::endl; + std::cout << desc << std::endl; + exit(1); +} + +int main(int argc, char **argv) +{ + std::vector files = parseOptions (argc, argv); + +// std::cout << "Reading Files" << std::endl; + for(std::vector::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::string name = *it; + + try{ + if(isNIF(name)) + { + //std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name.c_str()),name); + } + else if(isBSA(name)) + { +// std::cout << "Reading BSA File: " << name << std::endl; + readVFS(new VFS::BsaArchive(name)); + } + else if(bfs::is_directory(bfs::path(name))) + { +// std::cout << "Reading All Files in: " << name << std::endl; + readVFS(new VFS::FileSystemArchive(name),name); + } + else + { + std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl; + } + } + catch (std::exception& e) + { + std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + } + } + return 0; +} diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 7723b15f5..cd75db705 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -18,14 +18,15 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world - idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree + idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel ) opencs_units_noqt (model/world - universalid record commands columnbase scriptcontext cell refidcollection + universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection + idcompletionmanager npcstats metadata ) opencs_hdrs_noqt (model/world @@ -34,13 +35,18 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck - startscriptcheck search searchoperation searchstage pathgridcheck + startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck + mergestages + ) + +opencs_hdrs_noqt (model/tools + mergestate ) @@ -61,19 +67,20 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator - cellcreator referenceablecreator referencecreator scenesubview + cellcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable - dialoguespinbox + dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator ) opencs_units_noqt (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate - scripthighlighter idvalidator dialoguecreator physicssystem + scripthighlighter idvalidator dialoguecreator physicssystem idcompletiondelegate + colordelegate dragdroputils ) opencs_units (view/widget scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton - scenetooltoggle2 + scenetooltoggle2 completerpopup coloreditor colorpickerpopup droplineedit ) opencs_units (view/render @@ -92,7 +99,7 @@ opencs_hdrs_noqt (view/render opencs_units (view/tools - reportsubview reporttable searchsubview searchbox + reportsubview reporttable searchsubview searchbox merge ) opencs_units_noqt (view/tools @@ -152,19 +159,16 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -set(BOOST_COMPONENTS system filesystem program_options thread wave) -if(WIN32) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) -endif(WIN32) - -find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) - -find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED) -include(${QT_USE_FILE}) - -qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) -qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) -qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) +if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) + qt4_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) + qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) +else() + qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) + qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) + qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) +endif() # for compiled .ui files include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -205,12 +209,36 @@ target_link_libraries(openmw-cs ${OGRE_Overlay_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} - ${Boost_LIBRARIES} + ${Boost_SYSTEM_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_WAVE_LIBRARY} ${BULLET_LIBRARIES} - ${QT_LIBRARIES} components ) +if (DESIRED_QT_VERSION MATCHES 4) + target_link_libraries(openmw-cs + ${QT_QTGUI_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${QT_QTNETWORK_LIBRARY}) + + if (WIN32) + target_link_libraries(openmw-cs ${QT_QTMAIN_LIBRARY}) + endif() + +else() + qt5_use_modules(openmw-cs Widgets Core Network) + if (WIN32) + target_link_libraries(Qt5::WinMain) + endif() +endif() + +if (WIN32) + target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) +endif() + + if(APPLE) INSTALL(TARGETS openmw-cs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) endif() diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 53a9e9e83..3721936ce 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -1,4 +1,3 @@ - #include "editor.hpp" #include @@ -7,6 +6,9 @@ #include #include #include +#include +#include +#include #include #include @@ -24,7 +26,8 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) : mUserSettings (mCfgMgr), mOverlaySystem (0), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPid(""), - mLock(), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) + mLock(), mMerge (mDocumentManager), + mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { std::pair > config = readConfig(); @@ -46,9 +49,12 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); + mMerge.setLocalData (mLocal); connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)), this, SLOT (documentAdded (CSMDoc::Document *))); + connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)), + this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *))); connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()), this, SLOT (lastDocumentDeleted())); @@ -56,6 +62,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ())); + connect (&mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *))); connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ())); connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ())); @@ -67,9 +74,11 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) connect (&mFileDialog, SIGNAL(signalCreateNewFile (const boost::filesystem::path&)), this, SLOT(createNewFile (const boost::filesystem::path&))); + connect (&mFileDialog, SIGNAL (rejected()), this, SLOT (cancelFileDialog ())); connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), this, SLOT (createNewGame (const boost::filesystem::path&))); + connect (&mNewGame, SIGNAL (cancelCreateGame()), this, SLOT (cancelCreateGame ())); } CS::Editor::~Editor () @@ -93,7 +102,7 @@ void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs) } } -std::pair > CS::Editor::readConfig() +std::pair > CS::Editor::readConfig(bool quiet) { boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); @@ -113,7 +122,7 @@ std::pair > CS::Editor::readConfi boost::program_options::notify(variables); - mCfgMgr.readConfiguration(variables, desc); + mCfgMgr.readConfiguration(variables, desc, quiet); mDocumentManager.setEncoding ( ToUTF8::calculateEncoding (variables["encoding"].as())); @@ -154,15 +163,23 @@ std::pair > CS::Editor::readConfi } dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + Files::PathContainer canonicalPaths; //iterate the data directories and add them to the file dialog for loading for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { + boost::filesystem::path p = boost::filesystem::canonical(*iter); + Files::PathContainer::iterator it = std::find(canonicalPaths.begin(), canonicalPaths.end(), p); + if (it == canonicalPaths.end()) + canonicalPaths.push_back(p); + else + continue; + QString path = QString::fromUtf8 (iter->string().c_str()); mFileDialog.addFiles(path); } - return std::make_pair (dataDirs, variables["fallback-archive"].as >()); + return std::make_pair (canonicalPaths, variables["fallback-archive"].as >()); } void CS::Editor::createGame() @@ -176,15 +193,53 @@ void CS::Editor::createGame() mNewGame.activateWindow(); } +void CS::Editor::cancelCreateGame() +{ + if (!mDocumentManager.isEmpty()) + return; + + mNewGame.hide(); + + if (mStartup.isHidden()) + mStartup.show(); + + mStartup.raise(); + mStartup.activateWindow(); +} + void CS::Editor::createAddon() { mStartup.hide(); + + mFileDialog.clearFiles(); + std::pair > config = readConfig(/*quiet*/true); + setupDataFiles (config.first); + mFileDialog.showDialog (CSVDoc::ContentAction_New); } +void CS::Editor::cancelFileDialog() +{ + if (!mDocumentManager.isEmpty()) + return; + + mFileDialog.hide(); + + if (mStartup.isHidden()) + mStartup.show(); + + mStartup.raise(); + mStartup.activateWindow(); +} + void CS::Editor::loadDocument() { mStartup.hide(); + + mFileDialog.clearFiles(); + std::pair > config = readConfig(/*quiet*/true); + setupDataFiles (config.first); + mFileDialog.showDialog (CSVDoc::ContentAction_Edit); } @@ -213,6 +268,7 @@ void CS::Editor::createNewFile (const boost::filesystem::path &savePath) mDocumentManager.addDocument (files, savePath, true); mFileDialog.hide(); + showSplashMessage(); } void CS::Editor::createNewGame (const boost::filesystem::path& file) @@ -224,6 +280,7 @@ void CS::Editor::createNewGame (const boost::filesystem::path& file) mDocumentManager.addDocument (files, file, true); mNewGame.hide(); + showSplashMessage(); } void CS::Editor::showStartup() @@ -277,12 +334,12 @@ bool CS::Editor::makeIPCServer() mServer->close(); fullPath.remove(QRegExp("dummy$")); fullPath += mIpcServerName; - if(boost::filesystem::exists(fullPath.toStdString().c_str())) + if(boost::filesystem::exists(fullPath.toUtf8().constData())) { // TODO: compare pid of the current process with that in the file std::cout << "Detected unclean shutdown." << std::endl; // delete the stale file - if(remove(fullPath.toStdString().c_str())) + if(remove(fullPath.toUtf8().constData())) std::cerr << "ERROR removing stale connection file" << std::endl; } } @@ -435,9 +492,65 @@ std::auto_ptr CS::Editor::setupGraphics() void CS::Editor::documentAdded (CSMDoc::Document *document) { mViewManager.addView (document); + showSplashMessage(); +} + +void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document) +{ + if (mMerge.getDocument()==document) + mMerge.cancel(); } void CS::Editor::lastDocumentDeleted() { QApplication::quit(); } + +void CS::Editor::showSplashMessage() +{ + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + if(settings.settingValue ("filter/project-added") == "true" || + settings.settingValue ("filter/project-modified") == "true") + { + QPixmap img(QPixmap(":./openmw-cs.png")); + + QString msgTop("You have active global filters."); + QString msgBottom("Some rows may be hidden!"); + QFont splashFont(QFont("Arial", 16, QFont::Bold)); // TODO: use system font? + //splashFont.setStretch(125); + + QFontMetrics fm(splashFont); + int msgWidth = std::max(fm.width(msgTop), fm.width(msgBottom)); + img.scaledToWidth(msgWidth); + + QSplashScreen *splash = new QSplashScreen(img, Qt::WindowStaysOnTopHint); + splash->setFont(splashFont); + splash->resize(msgWidth+20, splash->height()+fm.lineSpacing()*2+20); // add some margin + + // try to center the message near the center of the saved window + QWidget dummy; + bool xWorkaround = settings.settingValue ("window/x-save-state-workaround").toStdString() == "true"; + if (settings.settingValue ("window/save-state").toStdString() == "true" && + !(xWorkaround && settings.settingValue ("window/maximized").toStdString() == "true")) + { + dummy.restoreGeometry(settings.value("window/geometry").toByteArray()); + splash->move(dummy.x()+std::max(0, (dummy.width()-msgWidth)/2), + dummy.y()+std::max(0, (dummy.height()-splash->height())/2)); + } + else + splash->move(std::max(0, splash->x()-msgWidth/2), splash->y()); + + splash->show(); + splash->showMessage(msgTop+"\n"+msgBottom, Qt::AlignHCenter|Qt::AlignBottom, Qt::red); + QTimer::singleShot(4000, splash, SLOT(close())); // 4 seconds should be enough + splash->raise(); // for X windows + } +} + +void CS::Editor::mergeDocument (CSMDoc::Document *document) +{ + mMerge.configure (document); + mMerge.show(); + mMerge.raise(); + mMerge.activateWindow(); +} diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 273f0825b..33f5fd3b3 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -32,11 +32,18 @@ #include "view/settings/dialog.hpp" #include "view/render/overlaysystem.hpp" +#include "view/tools/merge.hpp" + namespace OgreInit { class OgreInit; } +namespace CSMDoc +{ + class Document; +} + namespace CS { class Editor : public QObject @@ -59,10 +66,13 @@ namespace CS boost::interprocess::file_lock mLock; boost::filesystem::ofstream mPidFile; bool mFsStrict; + CSVTools::Merge mMerge; + + void showSplashMessage(); void setupDataFiles (const Files::PathContainer& dataDirs); - std::pair > readConfig(); + std::pair > readConfig(bool quiet=false); ///< \return data paths // not implemented @@ -87,6 +97,8 @@ namespace CS void createGame(); void createAddon(); + void cancelCreateGame(); + void cancelFileDialog(); void loadDocument(); void openFiles (const boost::filesystem::path &path); @@ -99,8 +111,12 @@ namespace CS void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved (CSMDoc::Document *document); + void lastDocumentDeleted(); + void mergeDocument (CSMDoc::Document *document); + private: QString mIpcServerName; diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index b11561c13..db6531dc6 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -1,4 +1,3 @@ - #include "editor.hpp" #include @@ -13,6 +12,8 @@ #include +#include "model/doc/messages.hpp" + #include "model/world/universalid.hpp" #ifdef Q_OS_MAC @@ -52,6 +53,7 @@ int main(int argc, char *argv[]) qRegisterMetaType ("std::string"); qRegisterMetaType ("CSMWorld::UniversalId"); + qRegisterMetaType ("CSMDoc::Message"); OgreInit::OgreInit ogreInit; diff --git a/apps/opencs/model/doc/blacklist.cpp b/apps/opencs/model/doc/blacklist.cpp index 083726412..b1d402c69 100644 --- a/apps/opencs/model/doc/blacklist.cpp +++ b/apps/opencs/model/doc/blacklist.cpp @@ -1,4 +1,3 @@ - #include "blacklist.hpp" #include diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index a73201ec0..925eef659 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -799,9 +799,9 @@ void CSMDoc::Document::addGmsts() "sBookSkillMessage", "sBounty", "sBreath", - "sBribe", - "sBribe", - "sBribe", + "sBribe 10 Gold", + "sBribe 100 Gold", + "sBribe 1000 Gold", "sBribeFail", "sBribeSuccess", "sBuy", @@ -2251,13 +2251,14 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts) : mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), - mTools (*this), + mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSavingOperation (*this, mProjectPath, encoding), mSaving (&mSavingOperation), mResDir(resDir), - mRunner (mProjectPath), mPhysics(boost::shared_ptr()) + mRunner (mProjectPath), mPhysics(boost::shared_ptr()), + mDirty (false), mIdCompletionManager(mData) { if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2271,7 +2272,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, if (boost::filesystem::exists (customFiltersPath)) { - destination << std::ifstream(customFiltersPath.c_str(), std::ios::binary).rdbuf(); + destination << std::ifstream(customFiltersPath.string().c_str(), std::ios::binary).rdbuf(); } else { @@ -2281,9 +2282,6 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, if (mNew) { - mData.setDescription (""); - mData.setAuthor (""); - if (mContentFiles.size()==1) createBase(); } @@ -2298,13 +2296,15 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); + connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SIGNAL (mergeDone (CSMDoc::Document*))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); connect ( - &mSaving, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), - this, SLOT (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); + &mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)), + this, SLOT (reportMessage (const CSMDoc::Message&, int))); connect (&mRunner, SIGNAL (runStateChanged()), this, SLOT (runStateChanged())); } @@ -2322,7 +2322,7 @@ int CSMDoc::Document::getState() const { int state = 0; - if (!mUndoStack.isClean()) + if (!mUndoStack.isClean() || mDirty) state |= State_Modified; if (mSaving.isRunning()) @@ -2368,9 +2368,9 @@ void CSMDoc::Document::save() emit stateChanged (getState(), this); } -CSMWorld::UniversalId CSMDoc::Document::verify() +CSMWorld::UniversalId CSMDoc::Document::verify (const CSMWorld::UniversalId& reportId) { - CSMWorld::UniversalId id = mTools.runVerifier(); + CSMWorld::UniversalId id = mTools.runVerifier (reportId); emit stateChanged (getState(), this); return id; } @@ -2387,6 +2387,12 @@ void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const C emit stateChanged (getState(), this); } +void CSMDoc::Document::runMerge (std::auto_ptr target) +{ + mTools.runMerge (target); + emit stateChanged (getState(), this); +} + void CSMDoc::Document::abortOperation (int type) { if (type==State_Saving) @@ -2400,15 +2406,17 @@ void CSMDoc::Document::modificationStateChanged (bool clean) emit stateChanged (getState(), this); } -void CSMDoc::Document::reportMessage (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, int type) +void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) { /// \todo find a better way to get these messages to the user. - std::cout << message << std::endl; + std::cout << message.mMessage << std::endl; } void CSMDoc::Document::operationDone (int type, bool failed) { + if (type==CSMDoc::State_Saving && !failed) + mDirty = false; + emit stateChanged (getState(), this); } @@ -2488,3 +2496,13 @@ boost::shared_ptr CSMDoc::Document::getPhysics () return mPhysics; } + +CSMWorld::IdCompletionManager &CSMDoc::Document::getIdCompletionManager() +{ + return mIdCompletionManager; +} + +void CSMDoc::Document::flagAsDirty() +{ + mDirty = true; +} diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 6b1a1fc1e..e737bb1c4 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -13,6 +13,7 @@ #include #include "../world/data.hpp" +#include "../world/idcompletionmanager.hpp" #include "../tools/tools.hpp" @@ -66,6 +67,9 @@ namespace CSMDoc Blacklist mBlacklist; Runner mRunner; boost::shared_ptr mPhysics; + bool mDirty; + + CSMWorld::IdCompletionManager mIdCompletionManager; // 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. @@ -118,12 +122,14 @@ namespace CSMDoc void save(); - CSMWorld::UniversalId verify(); + CSMWorld::UniversalId verify (const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); - + + void runMerge (std::auto_ptr target); + void abortOperation (int type); const CSMWorld::Data& getData() const; @@ -144,18 +150,25 @@ namespace CSMDoc boost::shared_ptr getPhysics(); + CSMWorld::IdCompletionManager &getIdCompletionManager(); + + void flagAsDirty(); + signals: void stateChanged (int state, CSMDoc::Document *document); void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); + private slots: void modificationStateChanged (bool clean); - void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, int type); + void reportMessage (const CSMDoc::Message& message, int type); void operationDone (int type, bool failed); @@ -168,4 +181,3 @@ namespace CSMDoc } #endif - diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 2d444f245..af10dc9d6 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -1,4 +1,3 @@ - #include "documentmanager.hpp" #include @@ -49,13 +48,32 @@ CSMDoc::DocumentManager::~DocumentManager() delete *iter; } +bool CSMDoc::DocumentManager::isEmpty() +{ + return mDocuments.empty(); +} + void CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); + Document *document = makeDocument (files, savePath, new_); + insertDocument (document); +} +CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( + const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_) +{ + return new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); +} + +void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) +{ mDocuments.push_back (document); + connect (document, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SLOT (insertDocument (CSMDoc::Document*))); + emit loadRequest (document); mLoader.hasThingsToDo().wakeAll(); @@ -68,6 +86,8 @@ void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document) if (iter==mDocuments.end()) throw std::runtime_error ("removing invalid document"); + emit documentAboutToBeRemoved (document); + mDocuments.erase (iter); document->deleteLater(); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 0ae73e70c..1e8492828 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -50,6 +50,15 @@ namespace CSMDoc ///< \param new_ Do not load the last content file in \a files and instead create in an /// appropriate way. + /// Create a new document. The ownership of the created document is transferred to + /// the calling function. The DocumentManager does not manage it. Loading has not + /// taken place at the point when the document is returned. + /// + /// \param new_ Do not load the last content file in \a files and instead create in an + /// appropriate way. + Document *makeDocument (const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_); + void setResourceDir (const boost::filesystem::path& parResDir); void setEncoding (ToUTF8::FromType encoding); @@ -59,6 +68,8 @@ namespace CSMDoc /// Ask OGRE for a list of available resources. void listResources(); + bool isEmpty(); + private: boost::filesystem::path mResDir; @@ -77,10 +88,16 @@ namespace CSMDoc void removeDocument (CSMDoc::Document *document); ///< Emits the lastDocumentDeleted signal, if applicable. + /// Hand over document to *this. The ownership is transferred. The DocumentManager + /// will initiate the load procedure, if necessary + void insertDocument (CSMDoc::Document *document); + signals: void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved (CSMDoc::Document *document); + void loadRequest (CSMDoc::Document *document); void lastDocumentDeleted(); diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 43f3b850e..cb3ff2cd0 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -1,4 +1,3 @@ - #include "loader.hpp" #include @@ -52,7 +51,7 @@ void CSMDoc::Loader::load() { if (iter->second.mRecordsLeft) { - CSMDoc::Messages messages; + Messages messages (Message::Severity_Error); for (int i=0; igetData().continueLoading (messages)) { @@ -68,7 +67,7 @@ void CSMDoc::Loader::load() for (CSMDoc::Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) { - document->getReport (log)->add (iter->mId, iter->mMessage); + document->getReport (log)->add (*iter); emit loadMessage (document, iter->mMessage); } } diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index 9b295fb28..86e96a88d 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -1,15 +1,38 @@ - #include "messages.hpp" -void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint) -{ - Message data; - data.mId = id; - data.mMessage = message; - data.mHint = hint; +CSMDoc::Message::Message() {} - mMessages.push_back (data); +CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint, Severity severity) +: mId (id), mMessage (message), mHint (hint), mSeverity (severity) +{} + +std::string CSMDoc::Message::toString (Severity severity) +{ + switch (severity) + { + case CSMDoc::Message::Severity_Info: return "Information"; + case CSMDoc::Message::Severity_Warning: return "Warning"; + case CSMDoc::Message::Severity_Error: return "Error"; + case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; + case CSMDoc::Message::Severity_Default: break; + } + + return ""; +} + + +CSMDoc::Messages::Messages (Message::Severity default_) +: mDefault (default_) +{} + +void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint, Message::Severity severity) +{ + if (severity==Message::Severity_Default) + severity = mDefault; + + mMessages.push_back (Message (id, message, hint, severity)); } void CSMDoc::Messages::push_back (const std::pair& data) diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp index 0f36c73a7..429feae4e 100644 --- a/apps/opencs/model/doc/messages.hpp +++ b/apps/opencs/model/doc/messages.hpp @@ -4,20 +4,43 @@ #include #include +#include + #include "../world/universalid.hpp" namespace CSMDoc { + struct Message + { + enum Severity + { + Severity_Info = 0, // no problem + Severity_Warning = 1, // a potential problem, but we are probably fine + Severity_Error = 2, // an error; we are not fine + Severity_SeriousError = 3, // an error so bad we can't even be sure if we are + // reporting it correctly + Severity_Default = 4 + }; + + CSMWorld::UniversalId mId; + std::string mMessage; + std::string mHint; + Severity mSeverity; + + Message(); + + Message (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint, Severity severity); + + static std::string toString (Severity severity); + }; + class Messages { public: - struct Message - { - CSMWorld::UniversalId mId; - std::string mMessage; - std::string mHint; - }; + // \deprecated Use CSMDoc::Message directly instead. + typedef CSMDoc::Message Message; typedef std::vector Collection; @@ -26,11 +49,15 @@ namespace CSMDoc private: Collection mMessages; + Message::Severity mDefault; public: + Messages (Message::Severity default_); + void add (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint = ""); + const std::string& hint = "", + Message::Severity severity = Message::Severity_Default); /// \deprecated Use add instead. void push_back (const std::pair& data); @@ -41,4 +68,6 @@ namespace CSMDoc }; } +Q_DECLARE_METATYPE (CSMDoc::Message) + #endif diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 3f1674f50..cb9b7ec29 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -1,4 +1,3 @@ - #include "operation.hpp" #include @@ -7,6 +6,7 @@ #include #include "../world/universalid.hpp" +#include "../settings/usersettings.hpp" #include "state.hpp" #include "stage.hpp" @@ -23,13 +23,17 @@ void CSMDoc::Operation::prepareStages() { iter->second = iter->first->setup(); mTotalSteps += iter->second; + + for (std::map::const_iterator iter2 (mSettings.begin()); iter2!=mSettings.end(); ++iter2) + iter->first->updateUserSetting (iter2->first, iter2->second); } } CSMDoc::Operation::Operation (int type, bool ordered, bool finalAlways) : mType (type), mStages(std::vector >()), mCurrentStage(mStages.begin()), mCurrentStep(0), mCurrentStepTotal(0), mTotalSteps(0), mOrdered (ordered), - mFinalAlways (finalAlways), mError(false), mConnected (false) + mFinalAlways (finalAlways), mError(false), mConnected (false), mPrepared (false), + mDefaultSeverity (Message::Severity_Error) { mTimer = new QTimer (this); } @@ -49,8 +53,8 @@ void CSMDoc::Operation::run() connect (mTimer, SIGNAL (timeout()), this, SLOT (executeStage())); mConnected = true; } - - prepareStages(); + + mPrepared = false; mTimer->start (0); } @@ -60,6 +64,19 @@ void CSMDoc::Operation::appendStage (Stage *stage) mStages.push_back (std::make_pair (stage, 0)); } +void CSMDoc::Operation::configureSettings (const std::vector& settings) +{ + for (std::vector::const_iterator iter (settings.begin()); iter!=settings.end(); ++iter) + { + mSettings.insert (std::make_pair (*iter, CSMSettings::UserSettings::instance().definitions (*iter))); + } +} + +void CSMDoc::Operation::setDefaultSeverity (Message::Severity severity) +{ + mDefaultSeverity = severity; +} + bool CSMDoc::Operation::hasError() const { return mError; @@ -84,9 +101,23 @@ void CSMDoc::Operation::abort() mCurrentStage = mStages.end(); } +void CSMDoc::Operation::updateUserSetting (const QString& name, const QStringList& value) +{ + std::map::iterator iter = mSettings.find (name); + + if (iter!=mSettings.end()) + iter->second = value; +} + void CSMDoc::Operation::executeStage() { - Messages messages; + if (!mPrepared) + { + prepareStages(); + mPrepared = true; + } + + Messages messages (mDefaultSeverity); while (mCurrentStage!=mStages.end()) { @@ -103,7 +134,7 @@ void CSMDoc::Operation::executeStage() } catch (const std::exception& e) { - emit reportMessage (CSMWorld::UniversalId(), e.what(), "", mType); + emit reportMessage (Message (CSMWorld::UniversalId(), e.what(), "", Message::Severity_SeriousError), mType); abort(); } @@ -115,7 +146,7 @@ void CSMDoc::Operation::executeStage() emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); for (Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) - emit reportMessage (iter->mId, iter->mMessage, iter->mHint, mType); + emit reportMessage (*iter, mType); if (mCurrentStage==mStages.end()) operationDone(); diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index a6217fe2d..3c86a6e23 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -2,9 +2,13 @@ #define CSM_DOC_OPERATION_H #include +#include #include #include +#include + +#include "messages.hpp" namespace CSMWorld { @@ -30,6 +34,9 @@ namespace CSMDoc bool mError; bool mConnected; QTimer *mTimer; + std::map mSettings; + bool mPrepared; + Message::Severity mDefaultSeverity; void prepareStages(); @@ -46,14 +53,21 @@ namespace CSMDoc /// /// \attention Do no call this function while this Operation is running. + /// Specify settings to be passed on to stages. + /// + /// \attention Do no call this function while this Operation is running. + void configureSettings (const std::vector& settings); + + /// \attention Do no call this function while this Operation is running. + void setDefaultSeverity (Message::Severity severity); + bool hasError() const; signals: void progress (int current, int max, int type); - void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, int type); + void reportMessage (const CSMDoc::Message& message, int type); void done (int type, bool failed); @@ -63,11 +77,15 @@ namespace CSMDoc void run(); + void updateUserSetting (const QString& name, const QStringList& value); + private slots: void executeStage(); - void operationDone(); + protected slots: + + virtual void operationDone(); }; } diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index d79e14023..db0d1a9a4 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -1,6 +1,7 @@ - #include "operationholder.hpp" +#include "../settings/usersettings.hpp" + #include "operation.hpp" CSMDoc::OperationHolder::OperationHolder (Operation *operation) : mRunning (false) @@ -19,8 +20,8 @@ void CSMDoc::OperationHolder::setOperation (Operation *operation) this, SIGNAL (progress (int, int, int))); connect ( - mOperation, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), - this, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); + mOperation, SIGNAL (reportMessage (const CSMDoc::Message&, int)), + this, SIGNAL (reportMessage (const CSMDoc::Message&, int))); connect ( mOperation, SIGNAL (done (int, bool)), @@ -29,6 +30,9 @@ void CSMDoc::OperationHolder::setOperation (Operation *operation) connect (this, SIGNAL (abortSignal()), mOperation, SLOT (abort())); connect (&mThread, SIGNAL (started()), mOperation, SLOT (run())); + + connect (&CSMSettings::UserSettings::instance(), SIGNAL (userSettingUpdated (const QString&, const QStringList&)), + mOperation, SLOT (updateUserSetting (const QString&, const QStringList&))); } bool CSMDoc::OperationHolder::isRunning() const diff --git a/apps/opencs/model/doc/operationholder.hpp b/apps/opencs/model/doc/operationholder.hpp index 6fe6df053..b73d61dab 100644 --- a/apps/opencs/model/doc/operationholder.hpp +++ b/apps/opencs/model/doc/operationholder.hpp @@ -4,6 +4,8 @@ #include #include +#include "messages.hpp" + namespace CSMWorld { class UniversalId; @@ -44,8 +46,7 @@ namespace CSMDoc void progress (int current, int max, int type); - void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, int type); + void reportMessage (const CSMDoc::Message& message, int type); void done (int type, bool failed); diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 14fe0cda8..5a0bc39be 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -1,4 +1,3 @@ - #include "runner.hpp" #include diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 9f6e469b8..95a2feaf2 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -1,4 +1,3 @@ - #include "saving.hpp" #include "../world/data.hpp" @@ -81,22 +80,25 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteCollectionStage > (mDocument.getData().getStartScripts(), mState)); - appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); - - appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); - appendStage (new WriteRefIdCollectionStage (mDocument, mState)); appendStage (new CollectionReferencesStage (mDocument, mState)); appendStage (new WriteCellCollectionStage (mDocument, mState)); + // Dialogue can reference objects and cells so must be written after these records for vanilla-compatible files + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); + appendStage (new WritePathgridCollectionStage (mDocument, mState)); - appendStage (new WriteLandCollectionStage (mDocument, mState)); - appendStage (new WriteLandTextureCollectionStage (mDocument, mState)); + // references Land Textures + appendStage (new WriteLandCollectionStage (mDocument, mState)); + // close file and clean up appendStage (new CloseSaveStage (mState)); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index d6258da6a..3fba2cd85 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -1,4 +1,3 @@ - #include "savingstages.hpp" #include @@ -53,18 +52,16 @@ void CSMDoc::WriteHeaderStage::perform (int stage, Messages& messages) mState.getWriter().clearMaster(); - mState.getWriter().setFormat (0); - if (mSimple) { mState.getWriter().setAuthor (""); mState.getWriter().setDescription (""); mState.getWriter().setRecordCount (0); + mState.getWriter().setFormat (ESM::Header::CurrentFormat); } else { - mState.getWriter().setAuthor (mDocument.getData().getAuthor()); - mState.getWriter().setDescription (mDocument.getData().getDescription()); + mDocument.getData().getMetaData().save (mState.getWriter()); mState.getWriter().setRecordCount ( mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + @@ -137,23 +134,34 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& message 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); + if (infoModified && state != CSMWorld::RecordBase::State_Modified + && state != CSMWorld::RecordBase::State_ModifiedOnly) + { + mState.getWriter().startRecord (topic.mBase.sRecordId); + mState.getWriter().writeHNCString ("NAME", topic.mBase.mId); + topic.mBase.save (mState.getWriter()); + mState.getWriter().endRecord (topic.mBase.sRecordId); + } + else + { + 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; + CSMWorld::RecordBase::State infoState = iter->mState; - if (state==CSMWorld::RecordBase::State_Deleted) + if (infoState==CSMWorld::RecordBase::State_Deleted) { /// \todo wrote record with delete flag } - else if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + else if (infoState==CSMWorld::RecordBase::State_Modified || + infoState==CSMWorld::RecordBase::State_ModifiedOnly) { ESM::DialInfo info = iter->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); @@ -418,15 +426,16 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) if (land.mState==CSMWorld::RecordBase::State_Modified || land.mState==CSMWorld::RecordBase::State_ModifiedOnly) { - CSMWorld::Land record = land.get(); + const CSMWorld::Land& record = land.get(); - mState.getWriter().startRecord (record.mLand->sRecordId); + mState.getWriter().startRecord (record.sRecordId); - record.mLand->save (mState.getWriter()); - if(record.mLand->mLandData) - record.mLand->mLandData->save (mState.getWriter()); + record.save (mState.getWriter()); - mState.getWriter().endRecord (record.mLand->sRecordId); + if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) + data->save (mState.getWriter()); + + mState.getWriter().endRecord (record.sRecordId); } else if (land.mState==CSMWorld::RecordBase::State_Deleted) { @@ -457,6 +466,8 @@ void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& mess mState.getWriter().startRecord (record.sRecordId); + mState.getWriter().writeHNString("NAME", record.mId); + record.save (mState.getWriter()); mState.getWriter().endRecord (record.sRecordId); diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp index e7ad551b2..10539c1b5 100644 --- a/apps/opencs/model/doc/savingstate.cpp +++ b/apps/opencs/model/doc/savingstate.cpp @@ -1,4 +1,3 @@ - #include "savingstate.hpp" #include "operation.hpp" diff --git a/apps/opencs/model/doc/stage.cpp b/apps/opencs/model/doc/stage.cpp index 1a2c5c721..c8da86069 100644 --- a/apps/opencs/model/doc/stage.cpp +++ b/apps/opencs/model/doc/stage.cpp @@ -1,4 +1,5 @@ - #include "stage.hpp" CSMDoc::Stage::~Stage() {} + +void CSMDoc::Stage::updateUserSetting (const QString& name, const QStringList& value) {} diff --git a/apps/opencs/model/doc/stage.hpp b/apps/opencs/model/doc/stage.hpp index 126823ae9..e0328a91a 100644 --- a/apps/opencs/model/doc/stage.hpp +++ b/apps/opencs/model/doc/stage.hpp @@ -8,6 +8,8 @@ #include "messages.hpp" +class QString; + namespace CSMDoc { class Stage @@ -21,6 +23,9 @@ namespace CSMDoc virtual void perform (int stage, Messages& messages) = 0; ///< Messages resulting from this stage will be appended to \a messages. + + /// Default-implementation: ignore + virtual void updateUserSetting (const QString& name, const QStringList& value); }; } diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp index 78f468101..7352b4b99 100644 --- a/apps/opencs/model/doc/state.hpp +++ b/apps/opencs/model/doc/state.hpp @@ -12,7 +12,7 @@ namespace CSMDoc State_Saving = 16, State_Verifying = 32, - State_Compiling = 64, // not implemented yet + State_Merging = 64, State_Searching = 128, State_Loading = 256 // pseudo-state; can not be encountered in a loaded document }; diff --git a/apps/opencs/model/filter/andnode.cpp b/apps/opencs/model/filter/andnode.cpp index 4249fc228..908662799 100644 --- a/apps/opencs/model/filter/andnode.cpp +++ b/apps/opencs/model/filter/andnode.cpp @@ -1,4 +1,3 @@ - #include "andnode.hpp" #include diff --git a/apps/opencs/model/filter/booleannode.cpp b/apps/opencs/model/filter/booleannode.cpp index 35fc98e08..ee7ddc1c0 100644 --- a/apps/opencs/model/filter/booleannode.cpp +++ b/apps/opencs/model/filter/booleannode.cpp @@ -1,4 +1,3 @@ - #include "booleannode.hpp" CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {} diff --git a/apps/opencs/model/filter/leafnode.cpp b/apps/opencs/model/filter/leafnode.cpp index 055a1747c..6745e165e 100644 --- a/apps/opencs/model/filter/leafnode.cpp +++ b/apps/opencs/model/filter/leafnode.cpp @@ -1,4 +1,3 @@ - #include "leafnode.hpp" std::vector CSMFilter::LeafNode::getReferencedColumns() const diff --git a/apps/opencs/model/filter/narynode.cpp b/apps/opencs/model/filter/narynode.cpp index 98f706c87..f2e0e5cb2 100644 --- a/apps/opencs/model/filter/narynode.cpp +++ b/apps/opencs/model/filter/narynode.cpp @@ -1,4 +1,3 @@ - #include "narynode.hpp" #include diff --git a/apps/opencs/model/filter/node.cpp b/apps/opencs/model/filter/node.cpp index 091dc4698..e25610675 100644 --- a/apps/opencs/model/filter/node.cpp +++ b/apps/opencs/model/filter/node.cpp @@ -1,4 +1,3 @@ - #include "node.hpp" CSMFilter::Node::Node() {} diff --git a/apps/opencs/model/filter/notnode.cpp b/apps/opencs/model/filter/notnode.cpp index b5d9da7b7..ba5302bbe 100644 --- a/apps/opencs/model/filter/notnode.cpp +++ b/apps/opencs/model/filter/notnode.cpp @@ -1,4 +1,3 @@ - #include "notnode.hpp" CSMFilter::NotNode::NotNode (boost::shared_ptr child) : UnaryNode (child, "not") {} diff --git a/apps/opencs/model/filter/ornode.cpp b/apps/opencs/model/filter/ornode.cpp index c5d15a384..41ec7b5cf 100644 --- a/apps/opencs/model/filter/ornode.cpp +++ b/apps/opencs/model/filter/ornode.cpp @@ -1,4 +1,3 @@ - #include "ornode.hpp" #include diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 51338dfc9..7936a1ae2 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -1,4 +1,3 @@ - #include "parser.hpp" #include diff --git a/apps/opencs/model/filter/textnode.cpp b/apps/opencs/model/filter/textnode.cpp index 73c378f11..246ebae24 100644 --- a/apps/opencs/model/filter/textnode.cpp +++ b/apps/opencs/model/filter/textnode.cpp @@ -1,4 +1,3 @@ - #include "textnode.hpp" #include diff --git a/apps/opencs/model/filter/unarynode.cpp b/apps/opencs/model/filter/unarynode.cpp index c40d191b6..cbdadf6fc 100644 --- a/apps/opencs/model/filter/unarynode.cpp +++ b/apps/opencs/model/filter/unarynode.cpp @@ -1,4 +1,3 @@ - #include "unarynode.hpp" CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr child, const std::string& name) diff --git a/apps/opencs/model/filter/valuenode.cpp b/apps/opencs/model/filter/valuenode.cpp index 6fdb5cb02..85c5a8e27 100644 --- a/apps/opencs/model/filter/valuenode.cpp +++ b/apps/opencs/model/filter/valuenode.cpp @@ -1,4 +1,3 @@ - #include "valuenode.hpp" #include diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index ea002c5ed..a40a790b7 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -161,6 +161,16 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() grow->setToolTip ("When \"Grow then Scroll\" option is selected, the window size grows to" " the width of the virtual desktop. \nIf this option is selected the the window growth" "is limited to the current screen."); + + Setting *saveState = createSetting (Type_CheckBox, "save-state", "Save window size and position"); + saveState->setDefaultValue ("true"); + saveState->setToolTip ("Remember window size and position between editing sessions."); + + Setting *saveX = createSetting (Type_CheckBox, "x-save-state-workaround", "X windows workaround"); + saveX->setDefaultValue ("false"); + saveX->setToolTip ("Some X window managers don't remember the windows state before being" + " maximized. In such environments exiting while maximized will correctly start in a maximized" + " window, but restoring back to the normal size won't work. Try this workaround."); } declareSection ("records", "Records"); @@ -179,7 +189,7 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() ritd->setDeclaredValues (values); } - declareSection ("table-input", "Table Input"); + declareSection ("table-input", "ID Tables"); { QString inPlaceEdit ("Edit in Place"); QString editRecord ("Edit Record"); @@ -232,6 +242,62 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "Jump to the added or cloned record."); jumpToAdded->setDefaultValue (defaultValue); jumpToAdded->setDeclaredValues (jumpValues); + + Setting *extendedConfig = createSetting (Type_CheckBox, "extended-config", + "Manually specify affected record types for an extended delete/revert"); + extendedConfig->setDefaultValue("false"); + extendedConfig->setToolTip("Delete and revert commands have an extended form that also affects " + "associated records.\n\n" + "If this option is enabled, types of affected records are selected " + "manually before a command execution.\nOtherwise, all associated " + "records are deleted/reverted immediately."); + } + + declareSection ("dialogues", "ID Dialogues"); + { + Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); + toolbar->setDefaultValue ("true"); + } + + declareSection ("report-input", "Reports"); + { + QString none ("None"); + QString edit ("Edit"); + QString remove ("Remove"); + QString editAndRemove ("Edit And Remove"); + + QStringList values; + values << none << edit << remove << editAndRemove; + + QString toolTip = "
    " + "
  • None
  • " + "
  • Edit: Open a table or dialogue suitable for addressing the listed report
  • " + "
  • Remove: Remove the report from the report table
  • " + "
  • Edit and Remove: Open a table or dialogue suitable for addressing the listed report, then remove the report from the report table
  • " + "
"; + + Setting *doubleClick = createSetting (Type_ComboBox, "double", "Double Click"); + doubleClick->setDeclaredValues (values); + doubleClick->setDefaultValue (edit); + doubleClick->setToolTip ("Action on double click in report table:

" + toolTip); + + Setting *shiftDoubleClick = createSetting (Type_ComboBox, "double-s", + "Shift Double Click"); + shiftDoubleClick->setDeclaredValues (values); + shiftDoubleClick->setDefaultValue (remove); + shiftDoubleClick->setToolTip ("Action on shift double click in report table:

" + toolTip); + + Setting *ctrlDoubleClick = createSetting (Type_ComboBox, "double-c", + "Control Double Click"); + ctrlDoubleClick->setDeclaredValues (values); + ctrlDoubleClick->setDefaultValue (editAndRemove); + ctrlDoubleClick->setToolTip ("Action on control double click in report table:

" + toolTip); + + Setting *shiftCtrlDoubleClick = createSetting (Type_ComboBox, "double-sc", + "Shift Control Double Click"); + shiftCtrlDoubleClick->setDeclaredValues (values); + shiftCtrlDoubleClick->setDefaultValue (none); + shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in report table:

" + toolTip); } declareSection ("search", "Search & Replace"); @@ -252,16 +318,97 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() autoDelete->setDefaultValue ("true"); } - declareSection ("script-editor", "Script Editor"); + declareSection ("script-editor", "Scripts"); { Setting *lineNum = createSetting (Type_CheckBox, "show-linenum", "Show Line Numbers"); lineNum->setDefaultValue ("true"); lineNum->setToolTip ("Show line numbers to the left of the script editor window." - "The current row and column numbers of the text cursor are shown at the bottom."); + " The current row and column numbers of the text cursor are shown at the bottom."); Setting *monoFont = createSetting (Type_CheckBox, "mono-font", "Use monospace font"); monoFont->setDefaultValue ("true"); monoFont->setToolTip ("Whether to use monospaced fonts on script edit subview."); + + QString tooltip = + "\n#RGB (each of R, G, and B is a single hex digit)" + "\n#RRGGBB" + "\n#RRRGGGBBB" + "\n#RRRRGGGGBBBB" + "\nA name from the list of colors defined in the list of SVG color keyword names." + "\nX11 color names may also work."; + + QString modeNormal ("Normal"); + + QStringList modes; + modes << "Ignore" << modeNormal << "Strict"; + + Setting *warnings = createSetting (Type_ComboBox, "warnings", + "Warning Mode"); + warnings->setDeclaredValues (modes); + warnings->setDefaultValue (modeNormal); + warnings->setToolTip ("

    How to handle warning messages during compilation:

    " + "

  • Ignore: Do not report warning
  • " + "
  • Normal: Report warning as a warning
  • " + "
  • Strict: Promote warning to an error
  • " + "
"); + + Setting *toolbar = createSetting (Type_CheckBox, "toolbar", "Show toolbar"); + toolbar->setDefaultValue ("true"); + + Setting *delay = createSetting (Type_SpinBox, "compile-delay", + "Delay between updating of source errors"); + delay->setDefaultValue (100); + delay->setRange (0, 10000); + delay->setToolTip ("Delay in milliseconds"); + + Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); + formatInt->setDefaultValues (QStringList() << "Dark magenta"); + formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); + + Setting *formatFloat = createSetting (Type_LineEdit, "colour-float", "Highlight Colour: Float"); + formatFloat->setDefaultValues (QStringList() << "Magenta"); + formatFloat->setToolTip ("(Default: Magenta) Use one of the following formats:" + tooltip); + + Setting *formatName = createSetting (Type_LineEdit, "colour-name", "Highlight Colour: Name"); + formatName->setDefaultValues (QStringList() << "Gray"); + formatName->setToolTip ("(Default: Gray) Use one of the following formats:" + tooltip); + + Setting *formatKeyword = createSetting (Type_LineEdit, "colour-keyword", "Highlight Colour: Keyword"); + formatKeyword->setDefaultValues (QStringList() << "Red"); + formatKeyword->setToolTip ("(Default: Red) Use one of the following formats:" + tooltip); + + Setting *formatSpecial = createSetting (Type_LineEdit, "colour-special", "Highlight Colour: Special"); + formatSpecial->setDefaultValues (QStringList() << "Dark yellow"); + formatSpecial->setToolTip ("(Default: Dark yellow) Use one of the following formats:" + tooltip); + + Setting *formatComment = createSetting (Type_LineEdit, "colour-comment", "Highlight Colour: Comment"); + formatComment->setDefaultValues (QStringList() << "Green"); + formatComment->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); + + Setting *formatId = createSetting (Type_LineEdit, "colour-id", "Highlight Colour: Id"); + formatId->setDefaultValues (QStringList() << "Blue"); + formatId->setToolTip ("(Default: Blue) Use one of the following formats:" + tooltip); + } + + declareSection ("filter", "Global Filter"); + { + Setting *projAdded = createSetting (Type_CheckBox, "project-added", "Project::added initial value"); + projAdded->setDefaultValue ("false"); + projAdded->setToolTip ("Show records added by the project when opening a table." + " Other records are filterd out."); + + Setting *projModified = createSetting (Type_CheckBox, "project-modified", "Project::modified initial value"); + projModified->setDefaultValue ("false"); + projModified->setToolTip ("Show records modified by the project when opening a table." + " Other records are filterd out."); + } + + declareSection ("general-input", "General Input"); + { + Setting *cycle = createSetting (Type_CheckBox, "cycle", "Cyclic next/previous"); + cycle->setDefaultValue ("false"); + cycle->setToolTip ("When using next/previous functions at the last/first item of a " + "list go to the first/last item"); } { @@ -484,6 +631,21 @@ QString CSMSettings::UserSettings::setting(const QString &viewKey, const QString return QString(); } +QVariant CSMSettings::UserSettings::value(const QString &viewKey, const QVariant &value) +{ + if(value != QVariant()) + { + mSettingDefinitions->setValue (viewKey, value); + return value; + } + else if(mSettingDefinitions->contains(viewKey)) + { + return mSettingDefinitions->value (viewKey); + } + + return QVariant(); +} + bool CSMSettings::UserSettings::hasSettingDefinitions (const QString &viewKey) const { return (mSettingDefinitions->contains (viewKey)); diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp index 5188a9842..c4c5ea583 100644 --- a/apps/opencs/model/settings/usersettings.hpp +++ b/apps/opencs/model/settings/usersettings.hpp @@ -82,6 +82,8 @@ namespace CSMSettings { QString setting(const QString &viewKey, const QString &value = QString()); + QVariant value(const QString &viewKey, const QVariant &value = QVariant()); + private: void buildSettingModelDefaults(); diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp index 4e6da4631..9898352f1 100644 --- a/apps/opencs/model/tools/birthsigncheck.cpp +++ b/apps/opencs/model/tools/birthsigncheck.cpp @@ -1,4 +1,3 @@ - #include "birthsigncheck.hpp" #include diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index be57a3729..e4964d4e3 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -1,4 +1,3 @@ - #include "classcheck.hpp" #include diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 0dfdee775..621b28070 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -1,4 +1,3 @@ - #include "factioncheck.hpp" #include diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp new file mode 100644 index 000000000..5435881b3 --- /dev/null +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -0,0 +1,133 @@ +#include "magiceffectcheck.hpp" + +#include + +#include "../world/resources.hpp" +#include "../world/data.hpp" + +namespace +{ + void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string text) + { + if (!text.empty()) + { + messages.push_back(std::make_pair(id, text)); + } + } +} + +bool CSMTools::MagicEffectCheckStage::isTextureExists(const std::string &texture, bool isIcon) const +{ + const CSMWorld::Resources &textures = isIcon ? mIcons : mTextures; + bool exists = false; + + if (textures.searchId(texture) != -1) + { + exists = true; + } + else + { + std::string ddsTexture = texture; + if (Misc::ResourceHelpers::changeExtensionToDds(ddsTexture) && textures.searchId(ddsTexture) != -1) + { + exists = true; + } + } + + return exists; +} + +std::string CSMTools::MagicEffectCheckStage::checkReferenceable(const std::string &id, + const CSMWorld::UniversalId &type, + const std::string &column) const +{ + std::string error; + if (!id.empty()) + { + CSMWorld::RefIdData::LocalIndex index = mReferenceables.getDataSet().searchId(id); + if (index.first == -1) + { + error = "No such " + column + " '" + id + "'"; + } + else if (index.second != type.getType()) + { + error = column + " is not of type " + type.getTypeName(); + } + } + return error; +} + +std::string CSMTools::MagicEffectCheckStage::checkSound(const std::string &id, const std::string &column) const +{ + std::string error; + if (!id.empty() && mSounds.searchId(id) == -1) + { + error = "No such " + column + " '" + id + "'"; + } + return error; +} + +CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection &effects, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::Resources &icons, + const CSMWorld::Resources &textures) + : mMagicEffects(effects), + mSounds(sounds), + mReferenceables(referenceables), + mIcons(icons), + mTextures(textures) +{} + +int CSMTools::MagicEffectCheckStage::setup() +{ + return mMagicEffects.getSize(); +} + +void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages) +{ + ESM::MagicEffect effect = mMagicEffects.getRecord(stage).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); + + if (effect.mData.mBaseCost < 0.0f) + { + messages.push_back(std::make_pair(id, "Base Cost is negative")); + } + + if (effect.mIcon.empty()) + { + messages.push_back(std::make_pair(id, "Icon is not specified")); + } + else if (!isTextureExists(effect.mIcon, true)) + { + messages.push_back(std::make_pair(id, "No such Icon '" + effect.mIcon + "'")); + } + + if (!effect.mParticle.empty() && !isTextureExists(effect.mParticle, false)) + { + messages.push_back(std::make_pair(id, "No such Particle '" + effect.mParticle + "'")); + } + + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt Object")); + + addMessageIfNotEmpty(messages, id, checkSound(effect.mCastSound, "Casting Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mHitSound, "Hit Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mAreaSound, "Area Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mBoltSound, "Bolt Sound")); + + if (effect.mDescription.empty()) + { + messages.push_back(std::make_pair(id, "Description is empty")); + } +} diff --git a/apps/opencs/model/tools/magiceffectcheck.hpp b/apps/opencs/model/tools/magiceffectcheck.hpp new file mode 100644 index 000000000..0ad6760d3 --- /dev/null +++ b/apps/opencs/model/tools/magiceffectcheck.hpp @@ -0,0 +1,50 @@ +#ifndef CSM_TOOLS_MAGICEFFECTCHECK_HPP +#define CSM_TOOLS_MAGICEFFECTCHECK_HPP + +#include +#include + +#include "../world/idcollection.hpp" +#include "../world/refidcollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMWorld +{ + class Resources; +} + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that magic effect records are internally consistent + class MagicEffectCheckStage : public CSMDoc::Stage + { + const CSMWorld::IdCollection &mMagicEffects; + const CSMWorld::IdCollection &mSounds; + const CSMWorld::RefIdCollection &mReferenceables; + const CSMWorld::Resources &mIcons; + const CSMWorld::Resources &mTextures; + + private: + bool isTextureExists(const std::string &texture, bool isIcon) const; + + std::string checkReferenceable(const std::string &id, + const CSMWorld::UniversalId &type, + const std::string &column) const; + std::string checkSound(const std::string &id, const std::string &column) const; + + public: + MagicEffectCheckStage(const CSMWorld::IdCollection &effects, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::Resources &icons, + const CSMWorld::Resources &textures); + + virtual int setup(); + ///< \return number of steps + virtual void perform (int stage, CSMDoc::Messages &messages); + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp index 4c97d2266..23adb9d37 100644 --- a/apps/opencs/model/tools/mandatoryid.cpp +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -1,4 +1,3 @@ - #include "mandatoryid.hpp" #include "../world/collectionbase.hpp" diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp new file mode 100644 index 000000000..907d742ed --- /dev/null +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -0,0 +1,59 @@ + +#include "mergeoperation.hpp" + +#include "../doc/state.hpp" +#include "../doc/document.hpp" + +#include "mergestages.hpp" + +CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding) +: CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) +{ + appendStage (new StartMergeStage (mState)); + + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGlobals)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGmsts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSkills)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getClasses)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFactions)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRaces)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSounds)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getScripts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRegions)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBirthsigns)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSpells)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopics)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournals)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getCells)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFilters)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getEnchantments)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBodyParts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getDebugProfiles)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSoundGens)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getMagicEffects)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getStartScripts)); + appendStage (new MergeIdCollectionStage > (mState, &CSMWorld::Data::getPathgrids)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopicInfos)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); + appendStage (new MergeRefIdsStage (mState)); + appendStage (new MergeReferencesStage (mState)); + appendStage (new MergeReferencesStage (mState)); + appendStage (new ListLandTexturesMergeStage (mState)); + appendStage (new MergeLandTexturesStage (mState)); + appendStage (new MergeLandStage (mState)); + + appendStage (new FinishMergedDocumentStage (mState, encoding)); +} + +void CSMTools::MergeOperation::setTarget (std::auto_ptr document) +{ + mState.mTarget = document; +} + +void CSMTools::MergeOperation::operationDone() +{ + CSMDoc::Operation::operationDone(); + + if (mState.mCompleted) + emit mergeDone (mState.mTarget.release()); +} diff --git a/apps/opencs/model/tools/mergeoperation.hpp b/apps/opencs/model/tools/mergeoperation.hpp new file mode 100644 index 000000000..bdaeb2ccd --- /dev/null +++ b/apps/opencs/model/tools/mergeoperation.hpp @@ -0,0 +1,45 @@ +#ifndef CSM_TOOLS_MERGEOPERATION_H +#define CSM_TOOLS_MERGEOPERATION_H + +#include + +#include + +#include "../doc/operation.hpp" + +#include "mergestate.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMTools +{ + class MergeOperation : public CSMDoc::Operation + { + Q_OBJECT + + MergeState mState; + + public: + + MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding); + + /// \attention Do not call this function while a merge is running. + void setTarget (std::auto_ptr document); + + protected slots: + + virtual void operationDone(); + + signals: + + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); + + }; +} + +#endif diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp new file mode 100644 index 000000000..52e1e6964 --- /dev/null +++ b/apps/opencs/model/tools/mergestages.cpp @@ -0,0 +1,258 @@ + +#include "mergestages.hpp" + +#include + +#include + +#include "mergestate.hpp" + +#include "../doc/document.hpp" +#include "../world/data.hpp" + + +CSMTools::StartMergeStage::StartMergeStage (MergeState& state) +: mState (state) +{} + +int CSMTools::StartMergeStage::setup() +{ + return 1; +} + +void CSMTools::StartMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mCompleted = false; + mState.mTextureIndices.clear(); +} + + +CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding) +: mState (state), mEncoder (encoding) +{} + +int CSMTools::FinishMergedDocumentStage::setup() +{ + return 1; +} + +void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages) +{ + // We know that the content file list contains at least two entries and that the first one + // does exist on disc (otherwise it would have been impossible to initiate a merge on that + // document). + boost::filesystem::path path = mState.mSource.getContentFiles()[0]; + + ESM::ESMReader reader; + reader.setEncoder (&mEncoder); + reader.open (path.string()); + + CSMWorld::MetaData source; + source.mId = "sys::meta"; + source.load (reader); + + CSMWorld::MetaData target = mState.mTarget->getData().getMetaData(); + + target.mAuthor = source.mAuthor; + target.mDescription = source.mDescription; + + mState.mTarget->getData().setMetaData (target); + + mState.mCompleted = true; +} + + +CSMTools::MergeRefIdsStage::MergeRefIdsStage (MergeState& state) : mState (state) {} + +int CSMTools::MergeRefIdsStage::setup() +{ + return mState.mSource.getData().getReferenceables().getSize(); +} + +void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mSource.getData().getReferenceables().copyTo ( + stage, mState.mTarget->getData().getReferenceables()); +} + + +CSMTools::MergeReferencesStage::MergeReferencesStage (MergeState& state) +: mState (state) +{} + +int CSMTools::MergeReferencesStage::setup() +{ + mIndex.clear(); + return mState.mSource.getData().getReferences().getSize(); +} + +void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getReferences().getRecord (stage); + + if (!record.isDeleted()) + { + CSMWorld::CellRef ref = record.get(); + + ref.mOriginalCell = ref.mCell; + + ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; + ref.mRefNum.mContentFile = 0; + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref); + + mState.mTarget->getData().getReferences().appendRecord (newRecord); + } +} + + +CSMTools::ListLandTexturesMergeStage::ListLandTexturesMergeStage (MergeState& state) +: mState (state) +{} + +int CSMTools::ListLandTexturesMergeStage::setup() +{ + return mState.mSource.getData().getLand().getSize(); +} + +void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); + + if (!record.isDeleted()) + { + const CSMWorld::Land& land = record.get(); + + // make sure record is loaded + land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + + if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX)) + { + // list texture indices + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + + mState.mTextureIndices[key] = -1; + } + } + } +} + + +CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state) +: mState (state), mNext (mState.mTextureIndices.end()) +{} + +int CSMTools::MergeLandTexturesStage::setup() +{ + // Should use the size of mState.mTextureIndices instead, but that is not available at this + // point. Unless there are any errors in the land and land texture records this will not + // make a difference. + return mState.mSource.getData().getLandTextures().getSize(); +} + +void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages) +{ + if (stage==0) + mNext = mState.mTextureIndices.begin(); + + bool found = false; + + do + { + if (mNext==mState.mTextureIndices.end()) + return; + + mNext->second = stage+1; + + std::ostringstream stream; + stream << mNext->first.first-1 << "_" << mNext->first.second; + + int index = mState.mSource.getData().getLandTextures().searchId (stream.str()); + + if (index!=-1) + { + CSMWorld::LandTexture texture = + mState.mSource.getData().getLandTextures().getRecord (index).get(); + + std::ostringstream stream; + stream << mNext->second-1 << "_0"; + + texture.mIndex = mNext->second-1; + texture.mId = stream.str(); + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); + + mState.mTarget->getData().getLandTextures().appendRecord (newRecord); + + found = true; + } + + ++mNext; + } + while (!found); +} + + +CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) {} + +int CSMTools::MergeLandStage::setup() +{ + return mState.mSource.getData().getLand().getSize(); +} + +void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); + + if (!record.isDeleted()) + { + const CSMWorld::Land& land = record.get(); + + land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + + CSMWorld::Land newLand (land); + + newLand.mEsm = 0; // avoid potential dangling pointer (ESMReader isn't needed anyway, + // because record is already fully loaded) + newLand.mPlugin = 0; + + if (land.mDataTypes & ESM::Land::DATA_VTEX) + { + // adjust land texture references + if (ESM::Land::LandData *data = newLand.getLandData()) + { + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + std::map, int>::const_iterator iter = + mState.mTextureIndices.find (key); + + if (iter!=mState.mTextureIndices.end()) + data->mTextures[i] = iter->second; + else + data->mTextures[i] = 0; + } + } + } + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand); + + mState.mTarget->getData().getLand().appendRecord (newRecord); + } +} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp new file mode 100644 index 000000000..f88f5be9f --- /dev/null +++ b/apps/opencs/model/tools/mergestages.hpp @@ -0,0 +1,166 @@ +#ifndef CSM_TOOLS_MERGESTAGES_H +#define CSM_TOOLS_MERGESTAGES_H + +#include +#include + +#include + +#include "../doc/stage.hpp" + +#include "../world/data.hpp" + +#include "mergestate.hpp" + +namespace CSMTools +{ + class StartMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + StartMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class FinishMergedDocumentStage : public CSMDoc::Stage + { + MergeState& mState; + ToUTF8::Utf8Encoder mEncoder; + + public: + + FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template > + class MergeIdCollectionStage : public CSMDoc::Stage + { + MergeState& mState; + Collection& (CSMWorld::Data::*mAccessor)(); + + public: + + MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template + MergeIdCollectionStage::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()) + : mState (state), mAccessor (accessor) + {} + + template + int MergeIdCollectionStage::setup() + { + return (mState.mSource.getData().*mAccessor)().getSize(); + } + + template + void MergeIdCollectionStage::perform (int stage, CSMDoc::Messages& messages) + { + const Collection& source = (mState.mSource.getData().*mAccessor)(); + Collection& target = (mState.mTarget->getData().*mAccessor)(); + + const CSMWorld::Record& record = source.getRecord (stage); + + if (!record.isDeleted()) + target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get())); + } + + class MergeRefIdsStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + MergeRefIdsStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeReferencesStage : public CSMDoc::Stage + { + MergeState& mState; + std::map mIndex; + + public: + + MergeReferencesStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class ListLandTexturesMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + ListLandTexturesMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeLandTexturesStage : public CSMDoc::Stage + { + MergeState& mState; + std::map, int>::iterator mNext; + + public: + + MergeLandTexturesStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeLandStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + MergeLandStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/mergestate.hpp b/apps/opencs/model/tools/mergestate.hpp new file mode 100644 index 000000000..29e1bbda7 --- /dev/null +++ b/apps/opencs/model/tools/mergestate.hpp @@ -0,0 +1,24 @@ +#ifndef CSM_TOOLS_MERGESTATE_H +#define CSM_TOOLS_MERGESTATE_H + +#include + +#include +#include + +#include "../doc/document.hpp" + +namespace CSMTools +{ + struct MergeState + { + std::auto_ptr mTarget; + CSMDoc::Document& mSource; + bool mCompleted; + std::map, int> mTextureIndices; // (texture, content file) -> new texture + + MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {} + }; +} + +#endif diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index 8f22cc8cd..69ee5a809 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -8,16 +8,6 @@ #include "../world/subcellcollection.hpp" #include "../world/pathgrid.hpp" -namespace -{ - struct Point - { - unsigned char mConnectionNum; - std::vector mOtherIndex; - Point() : mConnectionNum(0), mOtherIndex(0) {} - }; -} - CSMTools::PathgridCheckStage::PathgridCheckStage (const CSMWorld::SubCellCollection& pathgrids) : mPathgrids (pathgrids) {} @@ -40,11 +30,11 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message // check the number of pathgrid points if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) - messages.push_back (std::make_pair (id, pathgrid.mId + " has less points than expected")); + messages.add (id, pathgrid.mId + " has less points than expected", "", CSMDoc::Message::Severity_Error); else if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) - messages.push_back (std::make_pair (id, pathgrid.mId + " has more points than expected")); + messages.add (id, pathgrid.mId + " has more points than expected", "", CSMDoc::Message::Severity_Error); - std::vector pointList(pathgrid.mPoints.size()); + std::vector pointList(pathgrid.mPoints.size()); std::vector duplList; for (unsigned int i = 0; i < pathgrid.mEdges.size(); ++i) @@ -61,7 +51,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message std::ostringstream ss; ss << "has a duplicate edge between points" << pathgrid.mEdges[i].mV0 << " and " << pathgrid.mEdges[i].mV1; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); break; } } @@ -74,7 +64,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message { std::ostringstream ss; ss << " has an edge connecting a non-existent point " << pathgrid.mEdges[i].mV0; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); } } @@ -85,13 +75,13 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message { std::ostringstream ss; ss << " has has less edges than expected for point " << i; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); } else if (pathgrid.mPoints[i].mConnectionNum < pointList[i].mConnectionNum) { std::ostringstream ss; ss << " has has more edges than expected for point " << i; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); } // check that edges are bidirectional @@ -111,7 +101,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message { std::ostringstream ss; ss << " has a missing edge between points " << i << " and " << pointList[i].mOtherIndex[j]; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); } } @@ -134,7 +124,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message << ") x=" << pathgrid.mPoints[i].mX << ", y=" << pathgrid.mPoints[i].mY << ", z=" << pathgrid.mPoints[i].mZ; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Warning); duplList.push_back(i); break; @@ -153,7 +143,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message << ") x=" << pathgrid.mPoints[i].mX << ", y=" << pathgrid.mPoints[i].mY << ", z=" << pathgrid.mPoints[i].mZ; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Warning); } } diff --git a/apps/opencs/model/tools/pathgridcheck.hpp b/apps/opencs/model/tools/pathgridcheck.hpp index c90dbc8ed..f45b5bc93 100644 --- a/apps/opencs/model/tools/pathgridcheck.hpp +++ b/apps/opencs/model/tools/pathgridcheck.hpp @@ -14,17 +14,26 @@ namespace CSMWorld namespace CSMTools { + struct Point + { + unsigned char mConnectionNum; + std::vector mOtherIndex; + Point() : mConnectionNum(0), mOtherIndex(0) {} + }; + class PathgridCheckStage : public CSMDoc::Stage { - const CSMWorld::SubCellCollection >& mPathgrids; + const CSMWorld::SubCellCollection >& mPathgrids; - public: + public: - PathgridCheckStage (const CSMWorld::SubCellCollection >& pathgrids); + PathgridCheckStage (const CSMWorld::SubCellCollection >& pathgrids); - virtual int setup(); + virtual int setup(); - virtual void perform (int stage, CSMDoc::Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); }; } diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 3b2c8d290..b30088620 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -1,4 +1,3 @@ - #include "racecheck.hpp" #include diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 548fcd36f..336a5e713 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -468,6 +468,9 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( if (creature.mData.mGold < 0) //It seems that this is for gold in merchant creatures messages.push_back (std::make_pair (id, creature.mId + " has negative gold ")); + if (creature.mScale == 0) + messages.push_back (std::make_pair (id, creature.mId + " has zero scale value")); + // Check that mentioned scripts exist scriptCheck(creature, messages, id.toString()); } @@ -648,7 +651,7 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated { - if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0008 = autocalculated flag + if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag { messages.push_back (std::make_pair (id, npc.mId + " mNpdtType or flags mismatch!")); //should not happend? return; diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 42abc35c9..2fdff5f38 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -1,4 +1,3 @@ - #include "regioncheck.hpp" #include diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 1248e202b..77a14de84 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -1,4 +1,3 @@ - #include "reportmodel.hpp" #include @@ -6,24 +5,18 @@ #include "../world/columns.hpp" -CSMTools::ReportModel::Line::Line (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint) -: mId (id), mMessage (message), mHint (hint) -{} - -CSMTools::ReportModel::ReportModel (bool fieldColumn) +CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn) +: mColumnField (-1), mColumnSeverity (-1) { - if (fieldColumn) - { - mColumnField = 3; - mColumnDescription = 4; - } - else - { - mColumnDescription = 3; + int index = 3; - mColumnField = -1; - } + if (severityColumn) + mColumnSeverity = index++; + + if (fieldColumn) + mColumnField = index++; + + mColumnDescription = index; } int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const @@ -52,7 +45,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const case Column_Type: return static_cast (mRows.at (index.row()).mId.getType()); - + case Column_Id: { CSMWorld::UniversalId id = mRows.at (index.row()).mId; @@ -62,7 +55,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const return QString ("-"); } - + case Column_Hint: return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str()); @@ -88,7 +81,13 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const return QString::fromUtf8 (field.c_str()); } - + + if (index.column()==mColumnSeverity) + { + return QString::fromUtf8 ( + CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str()); + } + return QVariant(); } @@ -112,6 +111,9 @@ QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orienta if (section==mColumnField) return "Field"; + if (section==mColumnSeverity) + return "Severity"; + return "-"; } @@ -132,19 +134,18 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p return true; } -void CSMTools::ReportModel::add (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint) +void CSMTools::ReportModel::add (const CSMDoc::Message& message) { beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); - - mRows.push_back (Line (id, message, hint)); + + mRows.push_back (message); endInsertRows(); } void CSMTools::ReportModel::flagAsReplaced (int index) { - Line& line = mRows.at (index); + CSMDoc::Message& line = mRows.at (index); std::string hint = line.mHint; if (hint.empty() || hint[0]!='R') @@ -176,3 +177,16 @@ void CSMTools::ReportModel::clear() endRemoveRows(); } } + +int CSMTools::ReportModel::countErrors() const +{ + int count = 0; + + for (std::vector::const_iterator iter (mRows.begin()); + iter!=mRows.end(); ++iter) + if (iter->mSeverity==CSMDoc::Message::Severity_Error || + iter->mSeverity==CSMDoc::Message::Severity_SeriousError) + ++count; + + return count; +} diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp index 4d2d0542f..5704970f5 100644 --- a/apps/opencs/model/tools/reportmodel.hpp +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -6,6 +6,8 @@ #include +#include "../doc/messages.hpp" + #include "../world/universalid.hpp" namespace CSMTools @@ -14,17 +16,7 @@ namespace CSMTools { Q_OBJECT - struct Line - { - Line (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint); - - CSMWorld::UniversalId mId; - std::string mMessage; - std::string mHint; - }; - - std::vector mRows; + std::vector mRows; // Fixed columns enum Columns @@ -35,10 +27,11 @@ namespace CSMTools // Configurable columns int mColumnDescription; int mColumnField; + int mColumnSeverity; public: - ReportModel (bool fieldColumn = false); + ReportModel (bool fieldColumn = false, bool severityColumn = true); virtual int rowCount (const QModelIndex & parent = QModelIndex()) const; @@ -50,8 +43,7 @@ namespace CSMTools virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); - void add (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint = ""); + void add (const CSMDoc::Message& message); void flagAsReplaced (int index); @@ -60,6 +52,9 @@ namespace CSMTools std::string getHint (int row) const; void clear(); + + // Return number of messages with Error or SeriousError severity. + int countErrors() const; }; } diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index a70ee2ae4..d7c41cfcf 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -1,4 +1,3 @@ - #include "scriptcheck.hpp" #include @@ -11,6 +10,17 @@ #include "../world/data.hpp" +CSMDoc::Message::Severity CSMTools::ScriptCheckStage::getSeverity (Type type) +{ + switch (type) + { + case WarningMessage: return CSMDoc::Message::Severity_Warning; + case ErrorMessage: return CSMDoc::Message::Severity_Error; + } + + return CSMDoc::Message::Severity_SeriousError; +} + void CSMTools::ScriptCheckStage::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) { @@ -18,11 +28,6 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, const Compi CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); - if (type==ErrorMessage) - stream << "error "; - else - stream << "warning "; - stream << "script " << mFile << ", line " << loc.mLine << ", column " << loc.mColumn @@ -32,19 +37,21 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, const Compi hintStream << "l:" << loc.mLine << " " << loc.mColumn; - mMessages->add (id, stream.str(), hintStream.str()); + mMessages->add (id, stream.str(), hintStream.str(), getSeverity (type)); } void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); - mMessages->push_back (std::make_pair (id, - (type==ErrorMessage ? "error: " : "warning: ") + message)); + std::ostringstream stream; + stream << "script " << mFile << ": " << message; + + mMessages->add (id, stream.str(), "", getSeverity (type)); } CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document) -: mDocument (document), mContext (document.getData()), mMessages (0) +: mDocument (document), mContext (document.getData()), mMessages (0), mWarningMode (Mode_Ignore) { /// \todo add an option to configure warning mode setWarningsMode (0); @@ -58,6 +65,7 @@ int CSMTools::ScriptCheckStage::setup() mContext.clear(); mMessages = 0; mId.clear(); + Compiler::ErrorHandler::reset(); return mDocument.getData().getScripts().getSize(); } @@ -72,6 +80,13 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) mMessages = &messages; + switch (mWarningMode) + { + case Mode_Ignore: setWarningsMode (0); break; + case Mode_Normal: setWarningsMode (1); break; + case Mode_Strict: setWarningsMode (2); break; + } + try { const CSMWorld::Data& data = mDocument.getData(); @@ -93,9 +108,24 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); - messages.push_back (std::make_pair (id, - std::string ("Critical compile error: ") + error.what())); + std::ostringstream stream; + stream << "script " << mFile << ": " << error.what(); + + messages.add (id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); } mMessages = 0; } + +void CSMTools::ScriptCheckStage::updateUserSetting (const QString& name, const QStringList& value) +{ + if (name=="script-editor/warnings" && !value.isEmpty()) + { + if (value.at (0)=="Ignore") + mWarningMode = Mode_Ignore; + else if (value.at (0)=="Normal") + mWarningMode = Mode_Normal; + else if (value.at (0)=="Strict") + mWarningMode = Mode_Strict; + } +} diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp index 3fe12fc9a..3f0276652 100644 --- a/apps/opencs/model/tools/scriptcheck.hpp +++ b/apps/opencs/model/tools/scriptcheck.hpp @@ -18,13 +18,23 @@ namespace CSMTools /// \brief VerifyStage: make sure that scripts compile class ScriptCheckStage : public CSMDoc::Stage, private Compiler::ErrorHandler { + enum WarningMode + { + Mode_Ignore, + Mode_Normal, + Mode_Strict + }; + const CSMDoc::Document& mDocument; Compiler::Extensions mExtensions; CSMWorld::ScriptContext mContext; std::string mId; std::string mFile; CSMDoc::Messages *mMessages; + WarningMode mWarningMode; + CSMDoc::Message::Severity getSeverity (Type type); + virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); ///< Report error to the user. @@ -40,6 +50,8 @@ namespace CSMTools virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. + + virtual void updateUserSetting (const QString& name, const QStringList& value); }; } diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 7eb531161..0409199af 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -1,4 +1,3 @@ - #include "search.hpp" #include @@ -280,7 +279,7 @@ void CSMTools::Search::replace (CSMDoc::Document& document, CSMWorld::IdTableBas bool CSMTools::Search::verify (CSMDoc::Document& document, CSMWorld::IdTableBase *model, const CSMWorld::UniversalId& id, const std::string& messageHint) const { - CSMDoc::Messages messages; + CSMDoc::Messages messages (CSMDoc::Message::Severity_Info); int row = model->getModelIndex (id.getId(), model->findColumnIndex (CSMWorld::Columns::ColumnId_Id)).row(); diff --git a/apps/opencs/model/tools/searchoperation.cpp b/apps/opencs/model/tools/searchoperation.cpp index 4512de582..8fba1cc1e 100644 --- a/apps/opencs/model/tools/searchoperation.cpp +++ b/apps/opencs/model/tools/searchoperation.cpp @@ -1,4 +1,3 @@ - #include "searchoperation.hpp" #include "../doc/state.hpp" @@ -21,6 +20,8 @@ CSMTools::SearchOperation::SearchOperation (CSMDoc::Document& document) iter!=types.end(); ++iter) appendStage (new SearchStage (&dynamic_cast ( *document.getData().getTableModel (*iter)))); + + setDefaultSeverity (CSMDoc::Message::Severity_Info); } void CSMTools::SearchOperation::configure (const Search& search) diff --git a/apps/opencs/model/tools/searchstage.cpp b/apps/opencs/model/tools/searchstage.cpp index 17859d930..3db10b0c3 100644 --- a/apps/opencs/model/tools/searchstage.cpp +++ b/apps/opencs/model/tools/searchstage.cpp @@ -1,4 +1,3 @@ - #include "searchstage.hpp" #include "../world/idtablebase.hpp" diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp index 2b55526e0..77ba8d4a2 100644 --- a/apps/opencs/model/tools/skillcheck.cpp +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -1,4 +1,3 @@ - #include "skillcheck.hpp" #include diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index f78932a32..6a059bee2 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -1,4 +1,3 @@ - #include "soundcheck.hpp" #include diff --git a/apps/opencs/model/tools/soundgencheck.cpp b/apps/opencs/model/tools/soundgencheck.cpp new file mode 100644 index 000000000..bdf89f19d --- /dev/null +++ b/apps/opencs/model/tools/soundgencheck.cpp @@ -0,0 +1,53 @@ +#include "soundgencheck.hpp" + +#include + +#include "../world/refiddata.hpp" +#include "../world/universalid.hpp" + +CSMTools::SoundGenCheckStage::SoundGenCheckStage(const CSMWorld::IdCollection &soundGens, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables) + : mSoundGens(soundGens), + mSounds(sounds), + mReferenceables(referenceables) +{} + +int CSMTools::SoundGenCheckStage::setup() +{ + return mSoundGens.getSize(); +} + +void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages) +{ + const CSMWorld::Record &record = mSoundGens.getRecord(stage); + if (record.isDeleted()) + { + return; + } + + const ESM::SoundGenerator soundGen = record.get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_SoundGen, soundGen.mId); + + if (!soundGen.mCreature.empty()) + { + CSMWorld::RefIdData::LocalIndex creatureIndex = mReferenceables.getDataSet().searchId(soundGen.mCreature); + if (creatureIndex.first == -1) + { + messages.push_back(std::make_pair(id, "No such creature '" + soundGen.mCreature + "'")); + } + else if (creatureIndex.second != CSMWorld::UniversalId::Type_Creature) + { + messages.push_back(std::make_pair(id, "'" + soundGen.mCreature + "' is not a creature")); + } + } + + if (soundGen.mSound.empty()) + { + messages.push_back(std::make_pair(id, "Sound is not specified")); + } + else if (mSounds.searchId(soundGen.mSound) == -1) + { + messages.push_back(std::make_pair(id, "No such sound '" + soundGen.mSound + "'")); + } +} diff --git a/apps/opencs/model/tools/soundgencheck.hpp b/apps/opencs/model/tools/soundgencheck.hpp new file mode 100644 index 000000000..91b08f979 --- /dev/null +++ b/apps/opencs/model/tools/soundgencheck.hpp @@ -0,0 +1,30 @@ +#ifndef CSM_TOOLS_SOUNDGENCHECK_HPP +#define CSM_TOOLS_SOUNDGENCHECK_HPP + +#include "../world/data.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that sound gen records are internally consistent + class SoundGenCheckStage : public CSMDoc::Stage + { + const CSMWorld::IdCollection &mSoundGens; + const CSMWorld::IdCollection &mSounds; + const CSMWorld::RefIdCollection &mReferenceables; + + public: + SoundGenCheckStage(const CSMWorld::IdCollection &soundGens, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int stage, CSMDoc::Messages &messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index bd076d2a5..91aed37ed 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -1,4 +1,3 @@ - #include "spellcheck.hpp" #include diff --git a/apps/opencs/model/tools/startscriptcheck.cpp b/apps/opencs/model/tools/startscriptcheck.cpp index e3c01368b..220751797 100644 --- a/apps/opencs/model/tools/startscriptcheck.cpp +++ b/apps/opencs/model/tools/startscriptcheck.cpp @@ -1,4 +1,3 @@ - #include "startscriptcheck.hpp" #include diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 8d93a9433..fdbf406f1 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -1,4 +1,3 @@ - #include "tools.hpp" #include @@ -27,6 +26,9 @@ #include "startscriptcheck.hpp" #include "searchoperation.hpp" #include "pathgridcheck.hpp" +#include "soundgencheck.hpp" +#include "magiceffectcheck.hpp" +#include "mergeoperation.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -34,6 +36,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { case CSMDoc::State_Verifying: return &mVerifier; case CSMDoc::State_Searching: return &mSearch; + case CSMDoc::State_Merging: return &mMerge; } return 0; @@ -50,11 +53,15 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() { mVerifierOperation = new CSMDoc::Operation (CSMDoc::State_Verifying, false); + std::vector settings; + settings.push_back ("script-editor/warnings"); + + mVerifierOperation->configureSettings (settings); + connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (&mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); - connect (&mVerifier, - SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), - this, SLOT (verifierMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); + connect (&mVerifier, SIGNAL (reportMessage (const CSMDoc::Message&, int)), + this, SLOT (verifierMessage (const CSMDoc::Message&, int))); std::vector mandatoryIds; // I want C++11, damn it! mandatoryIds.push_back ("Day"); @@ -99,15 +106,25 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mVerifierOperation->appendStage (new PathgridCheckStage (mData.getPathgrids())); + mVerifierOperation->appendStage (new SoundGenCheckStage (mData.getSoundGens(), + mData.getSounds(), + mData.getReferenceables())); + + mVerifierOperation->appendStage (new MagicEffectCheckStage (mData.getMagicEffects(), + mData.getSounds(), + mData.getReferenceables(), + mData.getResources (CSMWorld::UniversalId::Type_Icons), + mData.getResources (CSMWorld::UniversalId::Type_Textures))); + mVerifier.setOperation (mVerifierOperation); } return &mVerifier; } -CSMTools::Tools::Tools (CSMDoc::Document& document) +CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding) : mDocument (document), mData (document.getData()), mVerifierOperation (0), - mSearchOperation (0), mNextReportNumber (0) + mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0), mEncoding (encoding) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); @@ -115,9 +132,12 @@ CSMTools::Tools::Tools (CSMDoc::Document& document) connect (&mSearch, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); - connect (&mSearch, - SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), - this, SLOT (verifierMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); + connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)), + this, SLOT (verifierMessage (const CSMDoc::Message&, int))); + + connect (&mMerge, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); + connect (&mMerge, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); + // don't need to connect report message, since there are no messages for merge } CSMTools::Tools::~Tools() @@ -134,23 +154,34 @@ CSMTools::Tools::~Tools() delete mSearchOperation; } + if (mMergeOperation) + { + mMerge.abortAndWait(); + delete mMergeOperation; + } + for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) delete iter->second; } -CSMWorld::UniversalId CSMTools::Tools::runVerifier() +CSMWorld::UniversalId CSMTools::Tools::runVerifier (const CSMWorld::UniversalId& reportId) { - mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); - mActiveReports[CSMDoc::State_Verifying] = mNextReportNumber-1; + int reportNumber = reportId.getType()==CSMWorld::UniversalId::Type_VerificationResults ? + reportId.getIndex() : mNextReportNumber++; + + if (mReports.find (reportNumber)==mReports.end()) + mReports.insert (std::make_pair (reportNumber, new ReportModel)); + + mActiveReports[CSMDoc::State_Verifying] = reportNumber; getVerifier()->start(); - return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, mNextReportNumber-1); + return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, reportNumber); } CSMWorld::UniversalId CSMTools::Tools::newSearch() { - mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel (true))); + mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel (true, false))); return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Search, mNextReportNumber-1); } @@ -170,6 +201,25 @@ void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Se mSearch.start(); } +void CSMTools::Tools::runMerge (std::auto_ptr target) +{ + // not setting an active report, because merge does not produce messages + + if (!mMergeOperation) + { + mMergeOperation = new MergeOperation (mDocument, mEncoding); + mMerge.setOperation (mMergeOperation); + connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SIGNAL (mergeDone (CSMDoc::Document*))); + } + + target->flagAsDirty(); + + mMergeOperation->setTarget (target); + + mMerge.start(); +} + void CSMTools::Tools::abortOperation (int type) { if (CSMDoc::OperationHolder *operation = get (type)) @@ -182,6 +232,7 @@ int CSMTools::Tools::getRunningOperations() const { CSMDoc::State_Verifying, CSMDoc::State_Searching, + CSMDoc::State_Merging, -1 }; @@ -205,12 +256,10 @@ CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& return mReports.at (id.getIndex()); } -void CSMTools::Tools::verifierMessage (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, int type) +void CSMTools::Tools::verifierMessage (const CSMDoc::Message& message, int type) { std::map::iterator iter = mActiveReports.find (type); if (iter!=mActiveReports.end()) - mReports[iter->second]->add (id, message, hint); + mReports[iter->second]->add (message); } - diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 0f9e57044..e16a3854c 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -1,9 +1,14 @@ #ifndef CSM_TOOLS_TOOLS_H #define CSM_TOOLS_TOOLS_H +#include +#include + +#include + #include -#include +#include #include "../doc/operationholder.hpp" @@ -24,6 +29,7 @@ namespace CSMTools class ReportModel; class Search; class SearchOperation; + class MergeOperation; class Tools : public QObject { @@ -35,9 +41,12 @@ namespace CSMTools CSMDoc::OperationHolder mVerifier; SearchOperation *mSearchOperation; CSMDoc::OperationHolder mSearch; + MergeOperation *mMergeOperation; + CSMDoc::OperationHolder mMerge; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number + ToUTF8::FromType mEncoding; // not implemented Tools (const Tools&); @@ -53,18 +62,23 @@ namespace CSMTools public: - Tools (CSMDoc::Document& document); + Tools (CSMDoc::Document& document, ToUTF8::FromType encoding); virtual ~Tools(); - CSMWorld::UniversalId runVerifier(); - ///< \return ID of the report for this verification run + /// \param reportId If a valid VerificationResults ID, run verifier for the + /// specified report instead of creating a new one. + /// + /// \return ID of the report for this verification run + CSMWorld::UniversalId runVerifier (const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); /// Return ID of the report for this search. CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); - + + void runMerge (std::auto_ptr target); + void abortOperation (int type); ///< \attention The operation is not aborted immediately. @@ -75,14 +89,17 @@ namespace CSMTools private slots: - void verifierMessage (const CSMWorld::UniversalId& id, const std::string& message, - const std::string& hint, int type); + void verifierMessage (const CSMDoc::Message& message, int type); signals: void progress (int current, int max, int type); void done (int type, bool failed); + + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); }; } diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 40520a9ba..91becdb74 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -1,4 +1,3 @@ - #include "cell.hpp" #include diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index b1c8441e6..95e206e2d 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -1,4 +1,3 @@ - #include "cellcoordinates.hpp" #include diff --git a/apps/opencs/model/world/cellselection.cpp b/apps/opencs/model/world/cellselection.cpp index 73b5196f1..c6988e488 100644 --- a/apps/opencs/model/world/cellselection.cpp +++ b/apps/opencs/model/world/cellselection.cpp @@ -1,4 +1,3 @@ - #include "cellselection.hpp" #include diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp index b8eed4192..6134dc172 100644 --- a/apps/opencs/model/world/collectionbase.cpp +++ b/apps/opencs/model/world/collectionbase.cpp @@ -1,4 +1,3 @@ - #include "collectionbase.hpp" #include diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 3d13538c0..1f16c9695 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -65,6 +65,8 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_JournalInfo, Display_Scene, Display_GlobalVariable, + Display_BodyPart, + Display_Enchantment, Display_Script, Display_Mesh, @@ -75,16 +77,18 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_Video, Display_Id, - Display_SkillImpact, + Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, Display_AiPackageType, - Display_YesNo, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, - Display_RaceSkill, + + Display_EffectSkill, + Display_EffectAttribute, + Display_IngredEffectId, Display_None }; @@ -98,7 +102,8 @@ bool CSMWorld::ColumnBase::isId (Display display) bool CSMWorld::ColumnBase::isText (Display display) { - return display==Display_String || display==Display_LongString; + return display==Display_String || display==Display_LongString || + display==Display_String32 || display==Display_LongString256; } bool CSMWorld::ColumnBase::isScript (Display display) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index bf8378e37..c75a3c2a1 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -14,6 +14,13 @@ namespace CSMWorld { struct ColumnBase { + enum TableEditModes + { + TableEdit_None, // no editing + TableEdit_Full, // edit cells and add/remove rows + TableEdit_FixedRows // edit cells only + }; + enum Roles { Role_Flags = Qt::UserRole, @@ -74,6 +81,8 @@ namespace CSMWorld Display_JournalInfo, Display_Scene, Display_GlobalVariable, + Display_BodyPart, + Display_Enchantment, //CONCRETE TYPES ENDS HERE Display_Integer, @@ -111,16 +120,20 @@ namespace CSMWorld Display_SoundGeneratorType, Display_School, Display_Id, - Display_SkillImpact, + Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, Display_AiPackageType, - Display_YesNo, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, - Display_RaceSkill, + Display_String32, + Display_LongString256, + + Display_EffectSkill, // must display at least one, unlike Display_Skill + Display_EffectAttribute, // must display at least one, unlike Display_Attribute + Display_IngredEffectId, // display none allowed, unlike Display_EffectId //top level columns that nest other columns Display_NestedHeader @@ -184,19 +197,32 @@ namespace CSMWorld template struct NestedParentColumn : public Column { - NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue) : Column (id, - ColumnBase::Display_NestedHeader, flags) + NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue, bool fixedRows = false) + : Column (id, ColumnBase::Display_NestedHeader, flags), mFixedRows(fixedRows) {} + virtual void set (Record& record, const QVariant& data) + { + // There is nothing to do here. + // This prevents exceptions from parent's implementation + } + virtual QVariant get (const Record& record) const { - return true; // required by IdTree::hasChildren() + // by default editable; also see IdTree::hasChildren() + if (mFixedRows) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); + else + return QVariant::fromValue(ColumnBase::TableEdit_Full); } virtual bool isEditable() const { return true; } + + private: + bool mFixedRows; }; struct NestedChildColumn : public NestableColumn @@ -211,4 +237,6 @@ namespace CSMWorld }; } +Q_DECLARE_METATYPE(CSMWorld::ColumnBase::TableEditModes) + #endif diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp new file mode 100644 index 000000000..dc3d39edb --- /dev/null +++ b/apps/opencs/model/world/columnimp.cpp @@ -0,0 +1,28 @@ +#include "columnimp.hpp" + +CSMWorld::BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn *meshType) + : mMeshType(meshType) +{} + +QVariant CSMWorld::BodyPartRaceColumn::get(const Record &record) const +{ + if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin) + { + return QString::fromUtf8(record.get().mRace.c_str()); + } + return QVariant(QVariant::UserType); +} + +void CSMWorld::BodyPartRaceColumn::set(Record &record, const QVariant &data) +{ + ESM::BodyPart record2 = record.get(); + + record2.mRace = data.toString().toUtf8().constData(); + + record.setModified(record2); +} + +bool CSMWorld::BodyPartRaceColumn::isEditable() const +{ + return true; +} diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 6b496e0ca..4e608dbbd 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -9,6 +9,10 @@ #include +#include +#include +#include + #include "columnbase.hpp" #include "columns.hpp" #include "info.hpp" @@ -694,7 +698,7 @@ namespace CSMWorld QColor colour = data.value(); - record2.mMapColor = colour.rgb() & 0xffffff; + record2.mMapColor = (colour.blue() << 16) | (colour.green() << 8) | colour.red(); record.setModified (record2); } @@ -709,7 +713,7 @@ namespace CSMWorld struct SleepListColumn : public Column { SleepListColumn() - : Column (Columns::ColumnId_SleepEncounter, ColumnBase::Display_String) + : Column (Columns::ColumnId_SleepEncounter, ColumnBase::Display_CreatureLevelledList) {} virtual QVariant get (const Record& record) const @@ -735,7 +739,7 @@ namespace CSMWorld template struct TextureColumn : public Column { - TextureColumn() : Column (Columns::ColumnId_Texture, ColumnBase::Display_String) {} + TextureColumn() : Column (Columns::ColumnId_Texture, ColumnBase::Display_Texture) {} virtual QVariant get (const Record& record) const { @@ -1269,7 +1273,7 @@ namespace CSMWorld template struct TrapColumn : public Column { - TrapColumn() : Column (Columns::ColumnId_Trap, ColumnBase::Display_String) {} + TrapColumn() : Column (Columns::ColumnId_Trap, ColumnBase::Display_Spell) {} virtual QVariant get (const Record& record) const { @@ -1294,7 +1298,7 @@ namespace CSMWorld template struct FilterColumn : public Column { - FilterColumn() : Column (Columns::ColumnId_Filter, ColumnBase::Display_String) {} + FilterColumn() : Column (Columns::ColumnId_Filter, ColumnBase::Display_Filter) {} virtual QVariant get (const Record& record) const { @@ -1497,7 +1501,10 @@ namespace CSMWorld template struct TopicColumn : public Column { - TopicColumn (bool journal) : Column (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, ColumnBase::Display_String) {} + TopicColumn (bool journal) + : Column (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, + journal ? ColumnBase::Display_Journal : ColumnBase::Display_Topic) + {} virtual QVariant get (const Record& record) const { @@ -1527,7 +1534,7 @@ namespace CSMWorld template struct ActorColumn : public Column { - ActorColumn() : Column (Columns::ColumnId_Actor, ColumnBase::Display_String) {} + ActorColumn() : Column (Columns::ColumnId_Actor, ColumnBase::Display_Npc) {} virtual QVariant get (const Record& record) const { @@ -1830,7 +1837,7 @@ namespace CSMWorld template struct ModelColumn : public Column { - ModelColumn() : Column (Columns::ColumnId_Model, ColumnBase::Display_String) {} + ModelColumn() : Column (Columns::ColumnId_Model, ColumnBase::Display_Mesh) {} virtual QVariant get (const Record& record) const { @@ -1908,8 +1915,8 @@ namespace CSMWorld template struct MeshTypeColumn : public Column { - MeshTypeColumn() - : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType) + MeshTypeColumn(int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) + : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType, flags) {} virtual QVariant get (const Record& record) const @@ -2158,7 +2165,9 @@ namespace CSMWorld struct EffectTextureColumn : public Column { EffectTextureColumn (Columns::ColumnId columnId) - : Column (columnId, ColumnBase::Display_Texture) + : Column (columnId, + columnId == Columns::ColumnId_Particle ? ColumnBase::Display_Texture + : ColumnBase::Display_Icon) { assert (this->mColumnId==Columns::ColumnId_Icon || this->mColumnId==Columns::ColumnId_Particle); @@ -2303,6 +2312,89 @@ namespace CSMWorld return true; } }; + + template + struct FormatColumn : public Column + { + FormatColumn() + : Column (Columns::ColumnId_FileFormat, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return record.get().mFormat; + } + + virtual bool isEditable() const + { + return false; + } + }; + + template + struct AuthorColumn : public Column + { + AuthorColumn() + : Column (Columns::ColumnId_Author, ColumnBase::Display_String32) + {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mAuthor.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mAuthor = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct FileDescriptionColumn : public Column + { + FileDescriptionColumn() + : Column (Columns::ColumnId_FileDescription, ColumnBase::Display_LongString256) + {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mDescription.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mDescription = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + struct BodyPartRaceColumn : public RaceColumn + { + const MeshTypeColumn *mMeshType; + + BodyPartRaceColumn(const MeshTypeColumn *meshType); + + virtual QVariant get(const Record &record) const; + virtual void set(Record &record, const QVariant &data); + virtual bool isEditable() const; + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 9491c3246..c06cc2618 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -1,4 +1,3 @@ - #include "columns.hpp" #include @@ -35,6 +34,8 @@ namespace CSMWorld { ColumnId_Volume, "Volume" }, { ColumnId_MinRange, "Min Range" }, { ColumnId_MaxRange, "Max Range" }, + { ColumnId_MinMagnitude, "Min Magnitude" }, + { ColumnId_MaxMagnitude, "Max Magnitude" }, { ColumnId_SoundFile, "Sound File" }, { ColumnId_MapColour, "Map Colour" }, { ColumnId_SleepEncounter, "Sleep Encounter" }, @@ -70,7 +71,6 @@ namespace CSMWorld { ColumnId_Weight, "Weight" }, { ColumnId_EnchantmentPoints, "Enchantment Points" }, { ColumnId_Quality, "Quality" }, - { ColumnId_Ai, "AI" }, { ColumnId_AiHello, "AI Hello" }, { ColumnId_AiFlee, "AI Flee" }, { ColumnId_AiFight, "AI Fight" }, @@ -92,7 +92,7 @@ namespace CSMWorld { ColumnId_Trainer, "Trainer" }, { ColumnId_Spellmaking, "Spellmaking" }, { ColumnId_EnchantingService, "Enchanting Service" }, - { ColumnId_RepairService, "Repair Serivce" }, + { ColumnId_RepairService, "Repair Service" }, { ColumnId_ApparatusType, "Apparatus Type" }, { ColumnId_ArmorType, "Armor Type" }, { ColumnId_Health, "Health" }, @@ -107,7 +107,6 @@ namespace CSMWorld { ColumnId_OriginalCreature, "Original Creature" }, { ColumnId_Biped, "Biped" }, { ColumnId_HasWeapon, "Has Weapon" }, - { ColumnId_NoMovement, "No Movement" }, { ColumnId_Swims, "Swims" }, { ColumnId_Flies, "Flies" }, { ColumnId_Walks, "Walks" }, @@ -199,8 +198,6 @@ namespace CSMWorld { ColumnId_RotY, "Rotation Y"}, { ColumnId_RotZ, "Rotation Z"}, - { ColumnId_Skill, "Skill" }, - { ColumnId_OwnerGlobal, "Owner Global" }, { ColumnId_DefaultProfile, "Default Profile" }, { ColumnId_BypassNewGame, "Bypass New Game" }, @@ -252,7 +249,7 @@ namespace CSMWorld { ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiWanderToD, "Wander ToD" }, - { ColumnId_AiWanderIdle, "Wander Idle" }, + //{ ColumnId_AiWanderIdle, "Wander Idle" }, { ColumnId_AiWanderRepeat, "Wander Repeat" }, { ColumnId_AiActivateName, "Activate" }, { ColumnId_AiTargetId, "Target ID" }, @@ -265,13 +262,13 @@ namespace CSMWorld { ColumnId_LevelledList,"Levelled List" }, { ColumnId_LevelledItemId,"Levelled Item" }, - { ColumnId_LevelledItemLevel,"Level" }, + { ColumnId_LevelledItemLevel,"Item Level" }, { ColumnId_LevelledItemType, "Calculate all levels <= player" }, { ColumnId_LevelledItemTypeEach, "Select a new item each instance" }, { ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_PowerList, "Powers" }, - { ColumnId_SkillImpact, "Skills" }, + { ColumnId_Skill, "Skill" }, { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, @@ -281,26 +278,24 @@ namespace CSMWorld { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, - { ColumnId_NpcAttributes, "Attributes" }, - { ColumnId_NpcSkills, "Skills" }, + { ColumnId_NpcAttributes, "NPC Attributes" }, + { ColumnId_NpcSkills, "NPC Skill" }, { ColumnId_UChar, "Value [0..255]" }, - { ColumnId_NpcMisc, "Misc" }, - { ColumnId_NpcLevel, "Level" }, + { ColumnId_NpcMisc, "NPC Misc" }, + { ColumnId_Level, "Level" }, { ColumnId_NpcFactionID, "Faction ID" }, - { ColumnId_NpcHealth, "Health" }, - { ColumnId_NpcMana, "Mana" }, - { ColumnId_NpcFatigue, "Fatigue" }, - { ColumnId_NpcDisposition, "Disposition" }, + + { ColumnId_Mana, "Mana" }, + { ColumnId_Fatigue, "Fatigue" }, + { ColumnId_NpcDisposition, "NPC Disposition" }, { ColumnId_NpcReputation, "Reputation" }, - { ColumnId_NpcRank, "Rank" }, - { ColumnId_NpcGold, "Gold" }, + { ColumnId_NpcRank, "NPC Rank" }, + { ColumnId_Gold, "Gold" }, { ColumnId_NpcPersistence, "Persistent" }, - { ColumnId_RaceAttributes, "Attributes" }, - { ColumnId_RaceMaleValue, "Male" }, - { ColumnId_RaceFemaleValue, "Female" }, + { ColumnId_RaceAttributes, "Race Attributes" }, + { ColumnId_Male, "Male" }, { ColumnId_RaceSkillBonus, "Skill Bonus" }, - { ColumnId_RaceSkill, "Skills" }, { ColumnId_RaceBonus, "Bonus" }, { ColumnId_Interior, "Interior" }, @@ -311,6 +306,30 @@ namespace CSMWorld { ColumnId_WaterLevel, "Water Level" }, { ColumnId_MapColor, "Map Color" }, + { ColumnId_FileFormat, "File Format" }, + { ColumnId_FileDescription, "File Description" }, + { ColumnId_Author, "Author" }, + + { ColumnId_CreatureAttributes, "Creature Attributes" }, + { ColumnId_AttributeValue, "Attrib Value" }, + { ColumnId_CreatureAttack, "Creature Attack" }, + { ColumnId_MinAttack, "Min Attack" }, + { ColumnId_MaxAttack, "Max Attack" }, + { ColumnId_CreatureMisc, "Creature Misc" }, + + { ColumnId_Idle1, "Idle 1" }, + { ColumnId_Idle2, "Idle 2" }, + { ColumnId_Idle3, "Idle 3" }, + { ColumnId_Idle4, "Idle 4" }, + { ColumnId_Idle5, "Idle 5" }, + { ColumnId_Idle6, "Idle 6" }, + { ColumnId_Idle7, "Idle 7" }, + { ColumnId_Idle8, "Idle 8" }, + + { ColumnId_SpellSrc, "From Race" }, + { ColumnId_SpellCost, "Cast Cost" }, + { ColumnId_SpellChance, "Cast Chance" }, + { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, @@ -532,11 +551,6 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sAiWanderRepeat[] = - { - "No", "Yes", 0 - }; - static const char *sInfoCondFunc[] = { " ", "Function", "Global", "Local", "Journal", @@ -571,17 +585,15 @@ namespace case CSMWorld::Columns::ColumnId_MeshType: return sMeshTypes; case CSMWorld::Columns::ColumnId_SoundGeneratorType: return sSoundGeneratorType; case CSMWorld::Columns::ColumnId_School: return sSchools; - case CSMWorld::Columns::ColumnId_SkillImpact: return sSkills; + case CSMWorld::Columns::ColumnId_Skill: return sSkills; case CSMWorld::Columns::ColumnId_EffectRange: return sEffectRange; case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_AiWanderRepeat: return sAiWanderRepeat; case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; // FIXME: don't have dynamic value enum delegate, use Display_String for now //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; - case CSMWorld::Columns::ColumnId_RaceSkill: return sSkills; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 191bbdea8..d30b939e0 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -65,7 +65,7 @@ namespace CSMWorld ColumnId_Weight = 50, ColumnId_EnchantmentPoints = 51, ColumnId_Quality = 52, - ColumnId_Ai = 53, + // unused ColumnId_AiHello = 54, ColumnId_AiFlee = 55, ColumnId_AiFight = 56, @@ -102,7 +102,7 @@ namespace CSMWorld ColumnId_OriginalCreature = 87, ColumnId_Biped = 88, ColumnId_HasWeapon = 89, - ColumnId_NoMovement = 90, + // used for SpellSrc ColumnId_Swims = 91, ColumnId_Flies = 92, ColumnId_Walks = 93, @@ -189,7 +189,7 @@ namespace CSMWorld ColumnId_RotX = 174, ColumnId_RotY = 175, ColumnId_RotZ = 176, - ColumnId_Skill = 177, + // used for SpellCost ColumnId_OwnerGlobal = 178, ColumnId_DefaultProfile = 179, ColumnId_BypassNewGame = 180, @@ -241,7 +241,7 @@ namespace CSMWorld ColumnId_AiWanderDist = 221, ColumnId_AiDuration = 222, ColumnId_AiWanderToD = 223, - ColumnId_AiWanderIdle = 224, + // unused ColumnId_AiWanderRepeat = 225, ColumnId_AiActivateName = 226, // use ColumnId_PosX, etc for AI destinations @@ -261,7 +261,7 @@ namespace CSMWorld ColumnId_LevelledItemChanceNone = 238, ColumnId_PowerList = 239, - ColumnId_SkillImpact = 240, // impact from magic effects + ColumnId_Skill = 240, ColumnId_InfoList = 241, ColumnId_InfoCondition = 242, @@ -276,22 +276,22 @@ namespace CSMWorld ColumnId_NpcSkills = 249, ColumnId_UChar = 250, ColumnId_NpcMisc = 251, - ColumnId_NpcLevel = 252, + ColumnId_Level = 252, ColumnId_NpcFactionID = 253, - ColumnId_NpcHealth = 254, - ColumnId_NpcMana = 255, - ColumnId_NpcFatigue = 256, + // used for SpellChance + ColumnId_Mana = 255, + ColumnId_Fatigue = 256, ColumnId_NpcDisposition = 257, ColumnId_NpcReputation = 258, ColumnId_NpcRank = 259, - ColumnId_NpcGold = 260, + ColumnId_Gold = 260, ColumnId_NpcPersistence = 261, ColumnId_RaceAttributes = 262, - ColumnId_RaceMaleValue = 263, - ColumnId_RaceFemaleValue = 264, + ColumnId_Male = 263, + // unused ColumnId_RaceSkillBonus = 265, - ColumnId_RaceSkill = 266, + // unused ColumnId_RaceBonus = 267, ColumnId_Interior = 268, @@ -302,6 +302,33 @@ namespace CSMWorld ColumnId_WaterLevel = 273, ColumnId_MapColor = 274, + ColumnId_FileFormat = 275, + ColumnId_FileDescription = 276, + ColumnId_Author = 277, + + ColumnId_MinMagnitude = 278, + ColumnId_MaxMagnitude = 279, + + ColumnId_CreatureAttributes = 280, + ColumnId_AttributeValue = 281, + ColumnId_CreatureAttack = 282, + ColumnId_MinAttack = 283, + ColumnId_MaxAttack = 284, + ColumnId_CreatureMisc = 285, + + ColumnId_Idle1 = 286, + ColumnId_Idle2 = 287, + ColumnId_Idle3 = 288, + ColumnId_Idle4 = 289, + ColumnId_Idle5 = 290, + ColumnId_Idle6 = 291, + ColumnId_Idle7 = 292, + ColumnId_Idle8 = 293, + + ColumnId_SpellSrc = 90, + ColumnId_SpellCost = 177, + ColumnId_SpellChance = 254, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index b9d5bd7fe..0b1af0e84 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -1,4 +1,3 @@ - #include "commanddispatcher.hpp" #include diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index a44d8770f..d510cd103 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -21,19 +21,31 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI // Replace proxy with actual model mIndex = proxy->mapToSource (index); mModel = proxy->sourceModel(); + } + if (mIndex.parent().isValid()) + { setText ("Modify " + dynamic_cast(mModel)->nestedHeaderData ( mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); } else + { setText ("Modify " + mModel->headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + } // Remember record state before the modification if (CSMWorld::IdTable *table = dynamic_cast(mModel)) { mHasRecordState = true; int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification); - mRecordStateIndex = table->index(mIndex.row(), stateColumnIndex); + + int rowIndex = mIndex.row(); + if (mIndex.parent().isValid()) + { + rowIndex = mIndex.parent().row(); + } + + mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mOldRecordState = static_cast(table->data(mRecordStateIndex).toInt()); } } @@ -58,6 +70,25 @@ void CSMWorld::CreateCommand::applyModifications() { for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); + + if (!mNestedValues.empty()) + { + CSMWorld::IdTree *tree = dynamic_cast(&mModel); + if (tree == NULL) + { + throw std::logic_error("CSMWorld::CreateCommand: Attempt to add nested values to the non-nested model"); + } + + std::map >::const_iterator current = mNestedValues.begin(); + std::map >::const_iterator end = mNestedValues.end(); + for (; current != end; ++current) + { + QModelIndex index = tree->index(0, + current->second.first, + tree->getNestedModelIndex(mId, current->first)); + tree->setData(index, current->second.second); + } + } } CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent) @@ -71,6 +102,11 @@ void CSMWorld::CreateCommand::addValue (int column, const QVariant& value) mValues[column] = value; } +void CSMWorld::CreateCommand::addNestedValue(int parentColumn, int nestedColumn, const QVariant &value) +{ + mNestedValues[parentColumn] = std::make_pair(nestedColumn, value); +} + void CSMWorld::CreateCommand::setType (UniversalId::Type type) { mType = type; @@ -258,21 +294,24 @@ CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model, std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Delete row in " + title + " sub-table of " + mId).c_str()); + + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); + mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::DeleteNestedCommand::redo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.removeRows (mNestedRow, 1, parentIndex); + mModifyParentCommand->redo(); } void CSMWorld::DeleteNestedCommand::undo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); + mModifyParentCommand->undo(); } CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) @@ -286,20 +325,23 @@ CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& i std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Add row in " + title + " sub-table of " + mId).c_str()); + + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); + mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::AddNestedCommand::redo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.addNestedRow (parentIndex, mNewRow); + mModifyParentCommand->redo(); } void CSMWorld::AddNestedCommand::undo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); + mModifyParentCommand->undo(); } CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index cdd398153..23ffccbd7 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -48,6 +48,9 @@ namespace CSMWorld class CreateCommand : public QUndoCommand { std::map mValues; + std::map > mNestedValues; + ///< Parameter order: a parent column, a nested column, a data. + ///< A nested row has index of 0. protected: @@ -68,6 +71,8 @@ namespace CSMWorld void addValue (int column, const QVariant& value); + void addNestedValue(int parentColumn, int nestedColumn, const QVariant &value); + virtual void redo(); virtual void undo(); @@ -195,6 +200,9 @@ namespace CSMWorld int mNestedRow; + // The command to redo/undo the Modified status of a record + ModifyCommand *mModifyParentCommand; + public: DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); @@ -214,6 +222,9 @@ namespace CSMWorld int mParentColumn; + // The command to redo/undo the Modified status of a record + ModifyCommand *mModifyParentCommand; + public: AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index c27c068f1..dfb2d99d3 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1,4 +1,3 @@ - #include "data.hpp" #include @@ -11,6 +10,10 @@ #include #include +#include +#include +#include + #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" @@ -19,6 +22,71 @@ #include "resourcesmanager.hpp" #include "resourcetable.hpp" #include "nestedcoladapterimp.hpp" +#include "npcstats.hpp" + +namespace +{ + class CSStore : public AutoCalc::StoreCommon + { + const CSMWorld::IdCollection& mGmstTable; + const CSMWorld::IdCollection& mSkillTable; + const CSMWorld::IdCollection& mMagicEffectTable; + const CSMWorld::NestedIdCollection& mSpells; + + public: + + CSStore(const CSMWorld::IdCollection& gmst, + const CSMWorld::IdCollection& skills, + const CSMWorld::IdCollection& magicEffects, + const CSMWorld::NestedIdCollection& spells) + : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(spells) + { } + + ~CSStore() {} + + virtual int findGmstInt(const std::string& name) const + { + return mGmstTable.getRecord(name).get().getInt(); + } + + virtual float findGmstFloat(const std::string& name) const + { + return mGmstTable.getRecord(name).get().getFloat(); + } + + virtual const ESM::Skill *findSkill(int index) const + { + // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) + return &mSkillTable.getRecord(ESM::Skill::indexToId(index)).get(); + } + + virtual const ESM::MagicEffect* findMagicEffect(int id) const + { + // if the magic effect does not exist, throws std::runtime_error ("invalid ID: " + id) + return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); + } + + virtual void getSpells(std::vector& spells) + { + // prepare data in a format used by OpenMW store + for (int index = 0; index < mSpells.getSize(); ++index) + spells.push_back(const_cast(&mSpells.getRecord(index).get())); + } + }; + + unsigned short autoCalculateMana(AutoCalc::StatsBase& stats) + { + return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2; + } + + unsigned short autoCalculateFatigue(AutoCalc::StatsBase& stats) + { + return stats.getBaseAttribute(ESM::Attribute::Strength) + + stats.getBaseAttribute(ESM::Attribute::Willpower) + + stats.getBaseAttribute(ESM::Attribute::Agility) + + stats.getBaseAttribute(ESM::Attribute::Endurance); + } +} void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) { @@ -61,7 +129,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec } CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) -: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), +: mEncoder (encoding), mPathgrids (mCells), mReferenceables(self()), mRefs (mCells), mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) { int index = 0; @@ -115,7 +183,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc index = mFactions.getColumns()-1; mFactions.addAdapter (std::make_pair(&mFactions.getColumn(index), new FactionReactionsAdapter ())); mFactions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Faction, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_Faction, ColumnBase::Display_Faction)); mFactions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_FactionReaction, ColumnBase::Display_Integer)); @@ -135,24 +203,26 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new SpellListAdapter ())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); // Race attributes - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes)); + mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes, + ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceAttributes, ColumnBase::Display_String, + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, ColumnBase::Flag_Dialogue, false)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceMaleValue, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_Male, ColumnBase::Display_Integer)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceFemaleValue, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_Female, ColumnBase::Display_Integer)); // Race skill bonus - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus)); + mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus, + ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceSkill, ColumnBase::Display_RaceSkill)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); @@ -180,7 +250,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc index = mRegions.getColumns()-1; mRegions.addAdapter (std::make_pair(&mRegions.getColumn(index), new RegionSoundListAdapter ())); mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SoundName, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SoundChance, ColumnBase::Display_Integer)); @@ -196,13 +266,13 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mBirthsigns.addAdapter (std::make_pair(&mBirthsigns.getColumn(index), new SpellListAdapter ())); mBirthsigns.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); mSpells.addColumn (new StringIdColumn); mSpells.addColumn (new RecordStateColumn); mSpells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Spell)); mSpells.addColumn (new NameColumn); - mSpells.addColumn (new SpellTypeColumn); + mSpells.addColumn (new SpellTypeColumn); // ColumnId_SpellType mSpells.addColumn (new CostColumn); mSpells.addColumn (new FlagColumn (Columns::ColumnId_AutoCalc, 0x1)); mSpells.addColumn (new FlagColumn (Columns::ColumnId_StarterSpell, 0x2)); @@ -214,9 +284,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mSpells.getNestableColumn(index)->addColumn( @@ -224,9 +294,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mTopics.addColumn (new StringIdColumn); mTopics.addColumn (new RecordStateColumn); @@ -330,9 +400,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mEnchantments.getNestableColumn(index)->addColumn( @@ -340,9 +410,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mBodyParts.addColumn (new StringIdColumn); mBodyParts.addColumn (new RecordStateColumn); @@ -352,9 +422,12 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Female, ESM::BodyPart::BPF_Female)); mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Playable, ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); - mBodyParts.addColumn (new MeshTypeColumn); + + int meshTypeFlags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh; + MeshTypeColumn *meshTypeColumn = new MeshTypeColumn(meshTypeFlags); + mBodyParts.addColumn (meshTypeColumn); mBodyParts.addColumn (new ModelColumn); - mBodyParts.addColumn (new RaceColumn); + mBodyParts.addColumn (new BodyPartRaceColumn(meshTypeColumn)); mSoundGens.addColumn (new StringIdColumn); mSoundGens.addColumn (new RecordStateColumn); @@ -475,6 +548,23 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mDebugProfiles.addColumn (new ScriptColumn ( ScriptColumn::Type_Lines)); + mMetaData.appendBlankRecord ("sys::meta"); + + mMetaData.addColumn (new StringIdColumn (true)); + mMetaData.addColumn (new RecordStateColumn); + mMetaData.addColumn (new FixedRecordTypeColumn (UniversalId::Type_MetaData)); + mMetaData.addColumn (new FormatColumn); + mMetaData.addColumn (new AuthorColumn); + mMetaData.addColumn (new FileDescriptionColumn); + + mLandTextures.addColumn (new StringIdColumn); + mLandTextures.addColumn (new RecordStateColumn); + mLandTextures.addColumn (new FixedRecordTypeColumn (UniversalId::Type_LandTexture)); + + mLand.addColumn (new StringIdColumn); + mLand.addColumn (new RecordStateColumn); + mLand.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Land)); + addModel (new IdTable (&mGlobals), UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); addModel (new IdTable (&mSkills), UniversalId::Type_Skill); @@ -515,12 +605,44 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc UniversalId::Type_Texture); addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)), UniversalId::Type_Video); + addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData); + addModel (new IdTable (&mLand), UniversalId::Type_Land); + addModel (new IdTable (&mLandTextures), UniversalId::Type_LandTexture); + + // for autocalc updates when gmst/race/class/skils tables change + CSMWorld::IdTable *gmsts = + static_cast(getTableModel(UniversalId::Type_Gmst)); + CSMWorld::IdTable *skills = + static_cast(getTableModel(UniversalId::Type_Skill)); + CSMWorld::IdTable *classes = + static_cast(getTableModel(UniversalId::Type_Class)); + CSMWorld::IdTree *races = + static_cast(getTableModel(UniversalId::Type_Race)); + CSMWorld::IdTree *objects = + static_cast(getTableModel(UniversalId::Type_Referenceable)); + + connect (gmsts, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (gmstDataChanged (const QModelIndex&, const QModelIndex&))); + connect (skills, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (skillDataChanged (const QModelIndex&, const QModelIndex&))); + connect (classes, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (classDataChanged (const QModelIndex&, const QModelIndex&))); + connect (races, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (raceDataChanged (const QModelIndex&, const QModelIndex&))); + connect (objects, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (npcDataChanged (const QModelIndex&, const QModelIndex&))); + connect (this, SIGNAL (updateNpcAutocalc (int, const std::string&)), + objects, SLOT (updateNpcAutocalc (int, const std::string&))); + connect (this, SIGNAL (cacheNpcStats (const std::string&, NpcStats*)), + this, SLOT (cacheNpcStatsEvent (const std::string&, NpcStats*))); mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } CSMWorld::Data::~Data() { + clearNpcStatsCache(); + for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) delete *iter; @@ -753,11 +875,21 @@ const CSMWorld::IdCollection& CSMWorld::Data::getLand() const return mLand; } +CSMWorld::IdCollection& CSMWorld::Data::getLand() +{ + return mLand; +} + const CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() const { return mLandTextures; } +CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() +{ + return mLandTextures; +} + const CSMWorld::IdCollection& CSMWorld::Data::getSoundGens() const { return mSoundGens; @@ -803,6 +935,17 @@ const CSMWorld::Resources& CSMWorld::Data::getResources (const UniversalId& id) return mResourcesManager.get (id.getType()); } +const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const +{ + return mMetaData.getRecord (0).get(); +} + +void CSMWorld::Data::setMetaData (const MetaData& metaData) +{ + Record record (RecordBase::State_ModifiedOnly, 0, &metaData); + mMetaData.setRecord (0, record); +} + QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); @@ -847,8 +990,14 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mBase = base; mProject = project; - mAuthor = mReader->getAuthor(); - mDescription = mReader->getDesc(); + if (!mProject && !mBase) + { + MetaData metaData; + metaData.mId = "sys::meta"; + metaData.load (*mReader); + + mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); + } return mReader->getRecordCount(); } @@ -908,8 +1057,10 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { int index = mLand.load(*mReader, mBase); - if (index!=-1 && !mBase) - mLand.getRecord (index).mModified.mLand->loadData ( + // Load all land data for now. A future optimisation may only load non-base data + // if a suitable mechanism for avoiding race conditions can be established. + if (index!=-1/* && !mBase*/) + mLand.getRecord (index).get().loadData ( ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); @@ -923,7 +1074,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { // log an error and continue loading the refs to the last loaded cell CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_None); - messages.add (id, "Logic error: cell index out of bounds"); + messages.add (id, "Logic error: cell index out of bounds", "", CSMDoc::Message::Severity_Error); index = mCells.getSize()-1; } std::string cellId = Misc::StringUtils::lowerCase (mCells.getId (index)); @@ -984,7 +1135,8 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) else { messages.add (UniversalId::Type_None, - "Trying to delete dialogue record " + id + " which does not exist"); + "Trying to delete dialogue record " + id + " which does not exist", + "", CSMDoc::Message::Severity_Warning); } } else @@ -1001,7 +1153,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) if (!mDialogue) { messages.add (UniversalId::Type_None, - "Found info record not following a dialogue record"); + "Found info record not following a dialogue record", "", CSMDoc::Message::Severity_Error); mReader->skipRecord(); break; @@ -1044,7 +1196,8 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) if (unhandledRecord) { - messages.add (UniversalId::Type_None, "Unsupported record type: " + n.toString()); + messages.add (UniversalId::Type_None, "Unsupported record type: " + n.toString(), "", + CSMDoc::Message::Severity_Error); mReader->skipRecord(); } @@ -1101,26 +1254,6 @@ int CSMWorld::Data::count (RecordBase::State state) const count (state, mPathgrids); } -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 CSMWorld::Data::getIds (bool listDeleted) const { std::vector ids; @@ -1159,3 +1292,276 @@ void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) { emit idListChanged(); } + +const CSMWorld::Data& CSMWorld::Data::self () +{ + return *this; +} + +void CSMWorld::Data::skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // mData.mAttribute (affects attributes skill bonus autocalc) + // mData.mSpecialization (affects skills autocalc) + CSMWorld::IdTable *skillModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Skill)); + + int attributeColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute); + int specialisationColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); + + if ((topLeft.column() <= attributeColumn && attributeColumn <= bottomRight.column()) + || (topLeft.column() <= specialisationColumn && specialisationColumn <= bottomRight.column())) + { + clearNpcStatsCache(); + + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); + } +} + +void CSMWorld::Data::classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // update autocalculated attributes/skills of every NPC with matching class + // - mData.mAttribute[2] + // - mData.mSkills[5][2] + // - mData.mSpecialization + CSMWorld::IdTable *classModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Class)); + + int attribute1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute1); // +1 + int majorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MajorSkill1); // +4 + int minorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MinorSkill1); // +4 + int specialisationColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); + + if ((topLeft.column() > attribute1Column+1 || attribute1Column > bottomRight.column()) + && (topLeft.column() > majorSkill1Column+4 || majorSkill1Column > bottomRight.column()) + && (topLeft.column() > minorSkill1Column+4 || minorSkill1Column > bottomRight.column()) + && (topLeft.column() > specialisationColumn || specialisationColumn > bottomRight.column())) + { + return; + } + + // get the affected class + int idColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + for (int classRow = topLeft.row(); classRow <= bottomRight.row(); ++classRow) + { + clearNpcStatsCache(); + + std::string classId = + classModel->data(classModel->index(classRow, idColumn)).toString().toUtf8().constData(); + emit updateNpcAutocalc(1/*class*/, classId); + } +} + +void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // affects racial bonus attributes & skills + // - mData.mAttributeValues[] + // - mData.mBonus[].mBonus + // - mPowers.mList[] + CSMWorld::IdTree *raceModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Race)); + + int attrColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes); + int bonusColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus); + int powersColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_PowerList); + + bool match = false; + int raceRow = topLeft.row(); + int raceEnd = bottomRight.row(); + if (topLeft.parent().isValid() && bottomRight.parent().isValid()) + { + if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column()) + || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column()) + || (topLeft.parent().column() <= powersColumn && powersColumn <= bottomRight.parent().column())) + { + match = true; // TODO: check for specific nested column? + raceRow = topLeft.parent().row(); + raceEnd = bottomRight.parent().row(); + } + } + else + { + if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column()) + || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column()) + || (topLeft.column() <= powersColumn && powersColumn <= bottomRight.column())) + { + match = true; // maybe the whole table changed + } + } + + if (!match) + return; + + // update autocalculated attributes/skills of every NPC with matching race + int idColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + for (; raceRow <= raceEnd; ++raceRow) + { + clearNpcStatsCache(); + + std::string raceId = + raceModel->data(raceModel->index(raceRow, idColumn)).toString().toUtf8().constData(); + emit updateNpcAutocalc(2/*race*/, raceId); + } +} + +void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // TODO: for now always recalculate + clearNpcStatsCache(); +} + +void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + static const QStringList gmsts(QStringList()<< "fNPCbaseMagickaMult" << "fAutoSpellChance" + << "fEffectCostMult" << "iAutoSpellAlterationMax" << "iAutoSpellConjurationMax" + << "iAutoSpellDestructionMax" << "iAutoSpellIllusionMax" << "iAutoSpellMysticismMax" + << "iAutoSpellRestorationMax" << "iAutoSpellTimesCanCast" << "iAutoSpellAttSkillMin"); + + bool match = false; + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) + { + if (gmsts.contains(mGmsts.getRecord(row).get().mId.c_str())) + { + match = true; + break; + } + } + + if (!match) + return; + + clearNpcStatsCache(); + + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); +} + +void CSMWorld::Data::clearNpcStatsCache () +{ + for (std::map::iterator it (mNpcStatCache.begin()); + it != mNpcStatCache.end(); ++it) + delete it->second; + + mNpcStatCache.clear(); +} + +CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const +{ + CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId); + if (cachedStats) + return cachedStats; + + int raceIndex = mRaces.searchId(npc.mRace); + int classIndex = mClasses.searchId(npc.mClass); + // this can happen when creating a new game from scratch + if (raceIndex == -1 || classIndex == -1) + return 0; + + const ESM::Race *race = &mRaces.getRecord(raceIndex).get(); + const ESM::Class *class_ = &mClasses.getRecord(classIndex).get(); + + bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + short level = npc.mNpdt52.mLevel; + if (autoCalc) + level = npc.mNpdt12.mLevel; + + std::auto_ptr stats (new CSMWorld::NpcStats()); + + CSStore store(mGmsts, mSkills, mMagicEffects, mSpells); + + if (autoCalc) + { + AutoCalc::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); + + stats->setHealth(autoCalculateHealth(level, class_, *stats)); + stats->setMana(autoCalculateMana(*stats)); + stats->setFatigue(autoCalculateFatigue(*stats)); + + AutoCalc::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); + + AutoCalc::autoCalculateSpells(race, *stats, &store); + } + else + { + for (std::vector::const_iterator it = npc.mSpells.mList.begin(); + it != npc.mSpells.mList.end(); ++it) + { + stats->addSpell(*it); + } + } + + // update spell info + const std::vector &racePowers = race->mPowers.mList; + for (unsigned int i = 0; i < racePowers.size(); ++i) + { + int type = -1; + int spellIndex = mSpells.searchId(racePowers[i]); + if (spellIndex != -1) + type = mSpells.getRecord(spellIndex).get().mData.mType; + stats->addPowers(racePowers[i], type); + } + // cost/chance + int skills[ESM::Skill::Length]; + if (autoCalc) + for (int i = 0; i< ESM::Skill::Length; ++i) + skills[i] = stats->getBaseSkill(i); + else + for (int i = 0; i< ESM::Skill::Length; ++i) + skills[i] = npc.mNpdt52.mSkills[i]; + + int attributes[ESM::Attribute::Length]; + if (autoCalc) + for (int i = 0; i< ESM::Attribute::Length; ++i) + attributes[i] = stats->getBaseAttribute(i); + else + { + attributes[ESM::Attribute::Strength] = npc.mNpdt52.mStrength; + attributes[ESM::Attribute::Willpower] = npc.mNpdt52.mWillpower; + attributes[ESM::Attribute::Agility] = npc.mNpdt52.mAgility; + attributes[ESM::Attribute::Speed] = npc.mNpdt52.mSpeed; + attributes[ESM::Attribute::Endurance] = npc.mNpdt52.mEndurance; + attributes[ESM::Attribute::Personality] = npc.mNpdt52.mPersonality; + attributes[ESM::Attribute::Luck] = npc.mNpdt52.mLuck; + } + + const std::vector& spells = stats->spells(); + for (std::vector::const_iterator it = spells.begin(); it != spells.end(); ++it) + { + int cost = -1; + int spellIndex = mSpells.searchId((*it).mName); + const ESM::Spell* spell = 0; + if (spellIndex != -1) + { + spell = &mSpells.getRecord(spellIndex).get(); + cost = spell->mData.mCost; + + int school; + float skillTerm; + AutoCalc::calcWeakestSchool(spell, skills, school, skillTerm, &store); + float chance = calcAutoCastChance(spell, skills, attributes, school, &store); + + stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent + } + } + + if (stats.get() == 0) + return 0; + + CSMWorld::NpcStats *result = stats.release(); + emit cacheNpcStats (npc.mId, result); + return result; +} + +void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) +{ + mNpcStatCache[id] = stats; +} + +CSMWorld::NpcStats* CSMWorld::Data::getCachedNpcData (const std::string& id) const +{ + std::map::const_iterator it = mNpcStatCache.find(id); + if (it != mNpcStatCache.end()) + return it->second; + else + return 0; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 060e47bd9..944a636f0 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -44,6 +44,7 @@ #include "infocollection.hpp" #include "nestedinfocollection.hpp" #include "pathgrid.hpp" +#include "metadata.hpp" #ifndef Q_MOC_RUN #include "subcellcollection.hpp" #endif @@ -60,6 +61,7 @@ namespace CSMWorld { class ResourcesManager; class Resources; + class NpcStats; class Data : public QObject { @@ -94,11 +96,10 @@ namespace CSMWorld RefIdCollection mReferenceables; RefCollection mRefs; IdCollection mFilters; + Collection mMetaData; const ResourcesManager& mResourcesManager; std::vector mModels; std::map mModelIndex; - std::string mAuthor; - std::string mDescription; ESM::ESMReader *mReader; const ESM::Dialogue *mDialogue; // last loaded dialogue bool mBase; @@ -108,6 +109,8 @@ namespace CSMWorld std::vector > mReaders; + std::map mNpcStatCache; + // not implemented Data (const Data&); Data& operator= (const Data&); @@ -121,6 +124,10 @@ namespace CSMWorld static int count (RecordBase::State state, const CollectionBase& collection); + const Data& self (); + + void clearNpcStatsCache (); + public: Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager); @@ -217,8 +224,12 @@ namespace CSMWorld const IdCollection& getLand() const; + IdCollection& getLand(); + const IdCollection& getLandTextures() const; + IdCollection& getLandTextures(); + const IdCollection& getSoundGens() const; IdCollection& getSoundGens(); @@ -238,6 +249,10 @@ namespace CSMWorld /// Throws an exception, if \a id does not match a resources list. const Resources& getResources (const UniversalId& id) const; + const MetaData& getMetaData() const; + + void setMetaData (const MetaData& metaData); + QAbstractItemModel *getTableModel (const UniversalId& id); ///< If no table model is available for \a id, an exception is thrown. /// @@ -267,23 +282,37 @@ namespace CSMWorld int count (RecordBase::State state) const; ///< Return number of top-level records with the given \a state. - void setDescription (const std::string& description); + NpcStats* npcAutoCalculate (const ESM::NPC& npc) const; - std::string getDescription() const; - - void setAuthor (const std::string& author); - - std::string getAuthor() const; + NpcStats* getCachedNpcData (const std::string& id) const; signals: void idListChanged(); + // refresh NPC dialogue subviews via object table model + void updateNpcAutocalc (int type, const std::string& id); + + void cacheNpcStats (const std::string& id, NpcStats *stats) const; + private slots: void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsChanged (const QModelIndex& parent, int start, int end); + + // for autocalc updates when gmst/race/class/skils tables change + void gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void cacheNpcStatsEvent (const std::string& id, NpcStats *stats); }; } diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp new file mode 100644 index 000000000..20cd8652c --- /dev/null +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -0,0 +1,112 @@ +#include "idcompletionmanager.hpp" + +#include + +#include + +#include "../../view/widget/completerpopup.hpp" + +#include "data.hpp" +#include "idtablebase.hpp" + +namespace +{ + std::map generateModelTypes() + { + std::map types; + + types[CSMWorld::ColumnBase::Display_BodyPart ] = CSMWorld::UniversalId::Type_BodyPart; + types[CSMWorld::ColumnBase::Display_Cell ] = CSMWorld::UniversalId::Type_Cell; + types[CSMWorld::ColumnBase::Display_Class ] = CSMWorld::UniversalId::Type_Class; + types[CSMWorld::ColumnBase::Display_CreatureLevelledList] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Creature ] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Enchantment ] = CSMWorld::UniversalId::Type_Enchantment; + types[CSMWorld::ColumnBase::Display_Faction ] = CSMWorld::UniversalId::Type_Faction; + types[CSMWorld::ColumnBase::Display_GlobalVariable ] = CSMWorld::UniversalId::Type_Global; + types[CSMWorld::ColumnBase::Display_Icon ] = CSMWorld::UniversalId::Type_Icon; + types[CSMWorld::ColumnBase::Display_Journal ] = CSMWorld::UniversalId::Type_Journal; + types[CSMWorld::ColumnBase::Display_Mesh ] = CSMWorld::UniversalId::Type_Mesh; + types[CSMWorld::ColumnBase::Display_Miscellaneous ] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Npc ] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Race ] = CSMWorld::UniversalId::Type_Race; + types[CSMWorld::ColumnBase::Display_Region ] = CSMWorld::UniversalId::Type_Region; + types[CSMWorld::ColumnBase::Display_Referenceable ] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Script ] = CSMWorld::UniversalId::Type_Script; + types[CSMWorld::ColumnBase::Display_Skill ] = CSMWorld::UniversalId::Type_Skill; + types[CSMWorld::ColumnBase::Display_Sound ] = CSMWorld::UniversalId::Type_Sound; + types[CSMWorld::ColumnBase::Display_SoundRes ] = CSMWorld::UniversalId::Type_SoundRes; + types[CSMWorld::ColumnBase::Display_Spell ] = CSMWorld::UniversalId::Type_Spell; + types[CSMWorld::ColumnBase::Display_Static ] = CSMWorld::UniversalId::Type_Referenceable; + types[CSMWorld::ColumnBase::Display_Texture ] = CSMWorld::UniversalId::Type_Texture; + types[CSMWorld::ColumnBase::Display_Topic ] = CSMWorld::UniversalId::Type_Topic; + types[CSMWorld::ColumnBase::Display_Weapon ] = CSMWorld::UniversalId::Type_Referenceable; + + return types; + } + + typedef std::map::const_iterator ModelTypeConstIterator; +} + +const std::map + CSMWorld::IdCompletionManager::sCompleterModelTypes = generateModelTypes(); + +std::vector CSMWorld::IdCompletionManager::getDisplayTypes() +{ + std::vector types; + ModelTypeConstIterator current = sCompleterModelTypes.begin(); + ModelTypeConstIterator end = sCompleterModelTypes.end(); + for (; current != end; ++current) + { + types.push_back(current->first); + } + return types; +} + +CSMWorld::IdCompletionManager::IdCompletionManager(CSMWorld::Data &data) +{ + generateCompleters(data); +} + +bool CSMWorld::IdCompletionManager::hasCompleterFor(CSMWorld::ColumnBase::Display display) const +{ + return mCompleters.find(display) != mCompleters.end(); +} + +boost::shared_ptr CSMWorld::IdCompletionManager::getCompleter(CSMWorld::ColumnBase::Display display) +{ + if (!hasCompleterFor(display)) + { + throw std::logic_error("This column doesn't have an ID completer"); + } + return mCompleters[display]; +} + +void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) +{ + ModelTypeConstIterator current = sCompleterModelTypes.begin(); + ModelTypeConstIterator end = sCompleterModelTypes.end(); + for (; current != end; ++current) + { + QAbstractItemModel *model = data.getTableModel(current->second); + CSMWorld::IdTableBase *table = dynamic_cast(model); + if (table != NULL) + { + int idColumn = table->searchColumnIndex(CSMWorld::Columns::ColumnId_Id); + if (idColumn != -1) + { + boost::shared_ptr completer = boost::make_shared(table); + completer->setCompletionColumn(idColumn); + // The completion role must be Qt::DisplayRole to get the ID values from the model + completer->setCompletionRole(Qt::DisplayRole); + completer->setCaseSensitivity(Qt::CaseInsensitive); + + QAbstractItemView *popup = new CSVWidget::CompleterPopup(); + completer->setPopup(popup); // The completer takes ownership of the popup + completer->setMaxVisibleItems(10); + + mCompleters[current->first] = completer; + } + } + } +} diff --git a/apps/opencs/model/world/idcompletionmanager.hpp b/apps/opencs/model/world/idcompletionmanager.hpp new file mode 100644 index 000000000..7944e6777 --- /dev/null +++ b/apps/opencs/model/world/idcompletionmanager.hpp @@ -0,0 +1,41 @@ +#ifndef CSM_WORLD_IDCOMPLETIONMANAGER_HPP +#define CSM_WORLD_IDCOMPLETIONMANAGER_HPP + +#include +#include + +#include + +#include "columnbase.hpp" +#include "universalid.hpp" + +class QCompleter; + +namespace CSMWorld +{ + class Data; + + /// \brief Creates and stores all ID completers + class IdCompletionManager + { + static const std::map sCompleterModelTypes; + + std::map > mCompleters; + + // Don't allow copying + IdCompletionManager(const IdCompletionManager &); + IdCompletionManager &operator = (const IdCompletionManager &); + + void generateCompleters(Data &data); + + public: + static std::vector getDisplayTypes(); + + IdCompletionManager(Data &data); + + bool hasCompleterFor(ColumnBase::Display display) const; + boost::shared_ptr getCompleter(ColumnBase::Display display); + }; +} + +#endif diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 1f1e35b60..7f80eaa8e 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -33,6 +33,9 @@ QVariant CSMWorld::IdTable::data (const QModelIndex & index, int role) const if (index.row() < 0 || index.column() < 0) return QVariant(); + if (role==ColumnBase::Role_Display) + return QVariant(mIdCollection->getColumn(index.column()).mDisplayType); + if (role==ColumnBase::Role_ColumnId) return QVariant (getColumnId (index.column())); @@ -73,8 +76,15 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) { mIdCollection->setData (index.row(), index.column(), value); + emit dataChanged(index, index); - emit dataChanged (index, index); + // Modifying a value can also change the Modified status of a record. + int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); + if (stateColumn != -1) + { + QModelIndex stateIndex = this->index(index.row(), stateColumn); + emit dataChanged(stateIndex, stateIndex); + } return true; } @@ -84,6 +94,9 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const { + if (!index.isValid()) + return 0; + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (mIdCollection->getColumn (index.column()).isUserEditable()) diff --git a/apps/opencs/model/world/idtablebase.cpp b/apps/opencs/model/world/idtablebase.cpp index 389f5396e..274446b79 100644 --- a/apps/opencs/model/world/idtablebase.cpp +++ b/apps/opencs/model/world/idtablebase.cpp @@ -1,4 +1,3 @@ - #include "idtablebase.hpp" CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {} diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 5c621cec1..0a2ef1230 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -1,59 +1,124 @@ - #include "idtableproxymodel.hpp" #include #include "idtablebase.hpp" +namespace +{ + std::string getEnumValue(const std::vector &values, int index) + { + if (index < 0 || index >= static_cast(values.size())) + { + return ""; + } + return values[index]; + } +} + void CSMWorld::IdTableProxyModel::updateColumnMap() { - mColumnMap.clear(); + Q_ASSERT(mSourceModel != NULL); + mColumnMap.clear(); if (mFilter) { std::vector columns = mFilter->getReferencedColumns(); - - const IdTableBase& table = dynamic_cast (*sourceModel()); - for (std::vector::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter) - mColumnMap.insert (std::make_pair (*iter, - table.searchColumnIndex (static_cast (*iter)))); + mColumnMap.insert (std::make_pair (*iter, + mSourceModel->searchColumnIndex (static_cast (*iter)))); } } bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const { + Q_ASSERT(mSourceModel != NULL); + + // It is not possible to use filterAcceptsColumn() and check for + // sourceModel()->headerData (sourceColumn, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags) + // because the sourceColumn parameter excludes the hidden columns, i.e. wrong columns can + // be rejected. Workaround by disallowing tree branches (nested columns), which are not meant + // to be visible, from the filter. + if (sourceParent.isValid()) + return false; + if (!mFilter) return true; - return mFilter->test ( - dynamic_cast (*sourceModel()), sourceRow, mColumnMap); + return mFilter->test (*mSourceModel, sourceRow, mColumnMap); } CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) -: QSortFilterProxyModel (parent) + : QSortFilterProxyModel (parent), + mSourceModel(NULL) { setSortCaseSensitivity (Qt::CaseInsensitive); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { - return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); + Q_ASSERT(mSourceModel != NULL); + + return mapFromSource(mSourceModel->getModelIndex (id, column)); +} + +void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model) +{ + QSortFilterProxyModel::setSourceModel(model); + + mSourceModel = dynamic_cast(sourceModel()); + connect(mSourceModel, + SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsInserted(const QModelIndex &, int, int))); + connect(mSourceModel, + SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); + connect(mSourceModel, + SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + this, + SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &))); } void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr& filter) { + beginResetModel(); mFilter = filter; updateColumnMap(); - reset(); + endResetModel(); } bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { + Columns::ColumnId id = static_cast(left.data(ColumnBase::Role_ColumnId).toInt()); + EnumColumnCache::const_iterator valuesIt = mEnumColumnCache.find(id); + if (valuesIt == mEnumColumnCache.end()) + { + if (Columns::hasEnums(id)) + { + valuesIt = mEnumColumnCache.insert(std::make_pair(id, Columns::getEnums(id))).first; + } + } + + if (valuesIt != mEnumColumnCache.end()) + { + std::string first = getEnumValue(valuesIt->second, left.data().toInt()); + std::string second = getEnumValue(valuesIt->second, right.data().toInt()); + return first < second; + } return QSortFilterProxyModel::lessThan(left, right); } +QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const +{ + Q_ASSERT(mSourceModel != NULL); + + int idColumn = mSourceModel->findColumnIndex(Columns::ColumnId_Id); + return mSourceModel->data(mSourceModel->index(sourceRow, idColumn)).toString(); +} + void CSMWorld::IdTableProxyModel::refreshFilter() { if (mFilter) @@ -62,3 +127,22 @@ void CSMWorld::IdTableProxyModel::refreshFilter() invalidateFilter(); } } + +void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) +{ + refreshFilter(); + if (!parent.isValid()) + { + emit rowAdded(getRecordId(end).toUtf8().constData()); + } +} + +void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +{ + refreshFilter(); +} + +void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/) +{ + refreshFilter(); +} diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 8683c2b9e..cf31b5c11 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -11,6 +11,8 @@ #include "../filter/node.hpp" +#include "columns.hpp" + namespace CSMWorld { class IdTableProxyModel : public QSortFilterProxyModel @@ -20,25 +22,50 @@ namespace CSMWorld boost::shared_ptr mFilter; std::map mColumnMap; // column ID, column index in this model (or -1) + // Cache of enum values for enum columns (e.g. Modified, Record Type). + // Used to speed up comparisons during the sort by such columns. + typedef std::map > EnumColumnCache; + mutable EnumColumnCache mEnumColumnCache; + + protected: + + IdTableBase *mSourceModel; + private: void updateColumnMap(); - bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; - public: IdTableProxyModel (QObject *parent = 0); virtual QModelIndex getModelIndex (const std::string& id, int column) const; + virtual void setSourceModel(QAbstractItemModel *model); + void setFilter (const boost::shared_ptr& filter); void refreshFilter(); protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + + virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; + + QString getRecordId(int sourceRow) const; + + protected slots: + + virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); + + virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + + virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + + signals: + + void rowAdded(const std::string &id); }; } diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 7351c03a7..56d83d9ed 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -35,28 +35,29 @@ QVariant CSMWorld::IdTree::data (const QModelIndex & index, int role) const if (!index.isValid()) return QVariant(); - if ((role!=Qt::DisplayRole && role!=Qt::EditRole) || index.row() < 0 || index.column() < 0) - return QVariant(); - if (index.internalId() != 0) { std::pair parentAddress(unfoldIndexAddress(index.internalId())); + const NestableColumn *parentColumn = mNestedCollection->getNestableColumn(parentAddress.second); - if (role == Qt::EditRole && - !mNestedCollection->getNestableColumn(parentAddress.second)->nestedColumn(index.column()).isEditable()) - { + if (role == ColumnBase::Role_Display) + return parentColumn->nestedColumn(index.column()).mDisplayType; + + if (role == ColumnBase::Role_ColumnId) + return parentColumn->nestedColumn(index.column()).mColumnId; + + if (role == Qt::EditRole && !parentColumn->nestedColumn(index.column()).isEditable()) + return QVariant(); + + if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant(); - } return mNestedCollection->getNestedData(parentAddress.first, parentAddress.second, index.row(), index.column()); } else { - if (role==Qt::EditRole && !idCollection()->getColumn (index.column()).isEditable()) - return QVariant(); - - return idCollection()->getData (index.row(), index.column()); + return IdTable::data(index, role); } } @@ -79,6 +80,9 @@ QVariant CSMWorld::IdTree::nestedHeaderData(int section, int subSection, Qt::Ori if (role==ColumnBase::Role_Display) return parentColumn->nestedColumn(subSection).mDisplayType; + if (role==ColumnBase::Role_ColumnId) + return parentColumn->nestedColumn(subSection).mColumnId; + return QVariant(); } @@ -91,8 +95,15 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, const std::pair& parentAddress(unfoldIndexAddress(index.internalId())); mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column()); + emit dataChanged(index, index); - emit dataChanged (index, index); + // Modifying a value can also change the Modified status of a record. + int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); + if (stateColumn != -1) + { + QModelIndex stateIndex = this->index(index.parent().row(), stateColumn); + emit dataChanged(stateIndex, stateIndex); + } return true; } @@ -167,10 +178,10 @@ QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& par encodedId = this->foldIndexAddress(parent); } - if (row<0 || row>=idCollection()->getSize()) + if (row < 0 || row >= rowCount(parent)) return QModelIndex(); - if (column<0 || column>=idCollection()->getColumns()) + if (column < 0 || column >= columnCount(parent)) return QModelIndex(); return createIndex(row, column, encodedId); // store internal id @@ -257,3 +268,18 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelInde return mNestedCollection->nestedTable(index.row(), index.column()); } + +void CSMWorld::IdTree::updateNpcAutocalc (int type, const std::string& id) +{ + emit refreshNpcDialogue (type, id); +} + +int CSMWorld::IdTree::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + return mNestedCollection->searchNestedColumnIndex(parentColumn, id); +} + +int CSMWorld::IdTree::findNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + return mNestedCollection->findNestedColumnIndex(parentColumn, id); +} diff --git a/apps/opencs/model/world/idtree.hpp b/apps/opencs/model/world/idtree.hpp index 5337ed82b..8e464dd95 100644 --- a/apps/opencs/model/world/idtree.hpp +++ b/apps/opencs/model/world/idtree.hpp @@ -73,11 +73,23 @@ namespace CSMWorld virtual bool hasChildren (const QModelIndex& index) const; - signals: + virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or -1 if the requested column wasn't found. - void resetStart(const QString& id); + virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or throws an exception if the requested column wasn't found. - void resetEnd(const QString& id); + signals: + + void resetStart(const QString& id); + + void resetEnd(const QString& id); + + void refreshNpcDialogue (int type, const std::string& id); + + public slots: + + void updateNpcAutocalc (int type, const std::string& id); }; } diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index a508d28f3..60c613041 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -1,4 +1,3 @@ - #include "infocollection.hpp" #include @@ -97,7 +96,8 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector + +#include "idtablebase.hpp" +#include "columns.hpp" + +namespace +{ + QString toLower(const QString &str) + { + return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toUtf8().constData()).c_str()); + } +} + +CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent) + : IdTableProxyModel(parent), + mType(type), + mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : + Columns::ColumnId_Journal), + mInfoColumnIndex(-1), + mLastAddedSourceRow(-1) +{ + Q_ASSERT(type == UniversalId::Type_TopicInfos || type == UniversalId::Type_JournalInfos); +} + +void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + IdTableProxyModel::setSourceModel(sourceModel); + + if (mSourceModel != NULL) + { + mInfoColumnIndex = mSourceModel->findColumnIndex(mInfoColumnId); + mFirstRowCache.clear(); + } +} + +bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + Q_ASSERT(mSourceModel != NULL); + + QModelIndex first = mSourceModel->index(getFirstInfoRow(left.row()), left.column()); + QModelIndex second = mSourceModel->index(getFirstInfoRow(right.row()), right.column()); + + // If both indexes are belonged to the same Topic/Journal, compare their original rows only + if (first.row() == second.row()) + { + return sortOrder() == Qt::AscendingOrder ? left.row() < right.row() : right.row() < left.row(); + } + return IdTableProxyModel::lessThan(first, second); +} + +int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const +{ + Q_ASSERT(mSourceModel != NULL); + + int row = currentRow; + int column = mInfoColumnIndex; + QString info = toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()); + + if (mFirstRowCache.contains(info)) + { + return mFirstRowCache[info]; + } + + while (--row >= 0 && + toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()) == info); + ++row; + + mFirstRowCache[info] = row; + return row; +} + +void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +{ + refreshFilter(); + mFirstRowCache.clear(); +} + +void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) +{ + refreshFilter(); + + if (!parent.isValid()) + { + mFirstRowCache.clear(); + // We can't re-sort the model here, because the topic of the added row isn't set yet. + // Store the row index for using in the first dataChanged() after this row insertion. + mLastAddedSourceRow = end; + } +} + +void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + refreshFilter(); + + if (mLastAddedSourceRow != -1 && + topLeft.row() <= mLastAddedSourceRow && bottomRight.row() >= mLastAddedSourceRow) + { + // Now the topic of the last added row is set, + // so we can re-sort the model to ensure the corrent position of this row + int column = sortColumn(); + Qt::SortOrder order = sortOrder(); + sort(mInfoColumnIndex); // Restore the correct position of an added row + sort(column, order); // Restore the original sort order + emit rowAdded(getRecordId(mLastAddedSourceRow).toUtf8().constData()); + + // Make sure that we perform a re-sorting only in the first dataChanged() after a row insertion + mLastAddedSourceRow = -1; + } +} diff --git a/apps/opencs/model/world/infotableproxymodel.hpp b/apps/opencs/model/world/infotableproxymodel.hpp new file mode 100644 index 000000000..51d93f9a1 --- /dev/null +++ b/apps/opencs/model/world/infotableproxymodel.hpp @@ -0,0 +1,45 @@ +#ifndef CSM_WORLD_INFOTABLEPROXYMODEL_HPP +#define CSM_WORLD_INFOTABLEPROXYMODEL_HPP + +#include + +#include "idtableproxymodel.hpp" +#include "columns.hpp" +#include "universalid.hpp" + +namespace CSMWorld +{ + class IdTableBase; + + class InfoTableProxyModel : public IdTableProxyModel + { + Q_OBJECT + + UniversalId::Type mType; + Columns::ColumnId mInfoColumnId; + ///< Contains ID for Topic or Journal ID + int mInfoColumnIndex; + int mLastAddedSourceRow; + + mutable QHash mFirstRowCache; + + int getFirstInfoRow(int currentRow) const; + ///< Finds the first row with the same topic (journal entry) as in \a currentRow + ///< \a currentRow is a row of the source model. + + public: + InfoTableProxyModel(UniversalId::Type type, QObject *parent = 0); + + virtual void setSourceModel(QAbstractItemModel *sourceModel); + + protected: + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + + protected slots: + virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); + virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + }; +} + +#endif diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index 119e18761..222f9bc02 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -4,25 +4,13 @@ namespace CSMWorld { - - Land::Land() - { - mLand.reset(new ESM::Land()); - } - void Land::load(ESM::ESMReader &esm) { - mLand->load(esm); + ESM::Land::load(esm); std::ostringstream stream; - stream << "#" << mLand->mX << " " << mLand->mY; + stream << "#" << mX << " " << mY; mId = stream.str(); } - - void Land::blank() - { - /// \todo - } - } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index e97a2d7dd..22cedb56d 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -2,7 +2,7 @@ #define CSM_WORLD_LAND_H #include -#include + #include namespace CSMWorld @@ -11,18 +11,12 @@ namespace CSMWorld /// /// \todo Add worldspace support to the Land record. /// \todo Add a proper copy constructor (currently worked around using shared_ptr) - struct Land + struct Land : public ESM::Land { - Land(); - - boost::shared_ptr mLand; - std::string mId; /// Loads the metadata and ID void load (ESM::ESMReader &esm); - - void blank(); }; } diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 4725866a5..e7772129c 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -9,13 +9,7 @@ namespace CSMWorld { ESM::LandTexture::load(esm); - int plugin = esm.getIndex(); - - std::ostringstream stream; - - stream << mIndex << "_" << plugin; - - mId = stream.str(); + mPluginIndex = esm.getIndex(); } } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index b13903186..c0b6eeba9 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -7,13 +7,10 @@ namespace CSMWorld { - /// \brief Wrapper for LandTexture record. Encodes mIndex and the plugin index (obtained from ESMReader) - /// in the ID. - /// - /// \attention The mId field of the ESM::LandTexture struct is not used. + /// \brief Wrapper for LandTexture record, providing info which plugin the LandTexture was loaded from. struct LandTexture : public ESM::LandTexture { - std::string mId; + int mPluginIndex; void load (ESM::ESMReader &esm); }; diff --git a/apps/opencs/model/world/metadata.cpp b/apps/opencs/model/world/metadata.cpp new file mode 100644 index 000000000..960fdc9e4 --- /dev/null +++ b/apps/opencs/model/world/metadata.cpp @@ -0,0 +1,26 @@ +#include "metadata.hpp" + +#include +#include +#include + +void CSMWorld::MetaData::blank() +{ + mFormat = ESM::Header::CurrentFormat; + mAuthor.clear(); + mDescription.clear(); +} + +void CSMWorld::MetaData::load (ESM::ESMReader& esm) +{ + mFormat = esm.getHeader().mFormat; + mAuthor = esm.getHeader().mData.author.toString(); + mDescription = esm.getHeader().mData.desc.toString(); +} + +void CSMWorld::MetaData::save (ESM::ESMWriter& esm) const +{ + esm.setFormat (mFormat); + esm.setAuthor (mAuthor); + esm.setDescription (mDescription); +} diff --git a/apps/opencs/model/world/metadata.hpp b/apps/opencs/model/world/metadata.hpp new file mode 100644 index 000000000..f8df2690e --- /dev/null +++ b/apps/opencs/model/world/metadata.hpp @@ -0,0 +1,29 @@ +#ifndef CSM_WOLRD_METADATA_H +#define CSM_WOLRD_METADATA_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + +namespace CSMWorld +{ + struct MetaData + { + std::string mId; + + int mFormat; + std::string mAuthor; + std::string mDescription; + + void blank(); + + void load (ESM::ESMReader& esm); + void save (ESM::ESMWriter& esm) const; + }; +} + +#endif diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index b7d09777d..3ebc637b5 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "usertype.hpp" namespace CSMWorld { @@ -922,7 +923,7 @@ namespace CSMWorld switch (subColIndex) { - case 0: return QString(ESM::Attribute::sAttributeNames[subRowIndex].c_str()); + case 0: return subRowIndex; case 1: return race.mData.mAttributeValues[subRowIndex].mMale; case 2: return race.mData.mAttributeValues[subRowIndex].mFemale; default: throw std::runtime_error("Race Attribute subcolumn index out of range"); @@ -1069,23 +1070,66 @@ namespace CSMWorld switch (subColIndex) { case 0: return isInterior; - case 1: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mAmbient : QVariant(QVariant::UserType); - case 2: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mSunlight : QVariant(QVariant::UserType); - case 3: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mFog : QVariant(QVariant::UserType); - case 4: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); + case 1: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mAmbient; + else + { + UserInt i(cell.mAmbi.mAmbient); + return QVariant(QVariant::fromValue(i)); + } + } + case 2: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mSunlight; + else + { + UserInt i(cell.mAmbi.mSunlight); + return QVariant(QVariant::fromValue(i)); + } + } + case 3: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mFog; + else + { + UserInt i(cell.mAmbi.mFog); + return QVariant(QVariant::fromValue(i)); + } + } + case 4: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mFogDensity; + else + { + UserFloat f(cell.mAmbi.mFogDensity); + return QVariant(QVariant::fromValue(f)); + } + } case 5: { if (isInterior && !behaveLikeExterior && interiorWater) return cell.mWater; else - return QVariant(QVariant::UserType); + { + UserFloat f(cell.mWater); + return QVariant(QVariant::fromValue(f)); + } + } + case 6: + { + if (isInterior) + { + UserInt i(cell.mMapColor); + return QVariant(QVariant::fromValue(i)); + } + else + return cell.mMapColor; // TODO: how to select? } - case 6: return isInterior ? - QVariant(QVariant::UserType) : cell.mMapColor; // TODO: how to select? //case 7: return isInterior ? //behaveLikeExterior : QVariant(QVariant::UserType); default: throw std::runtime_error("Cell subcolumn index out of range"); diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 81c52588b..2fd569bd0 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -317,8 +317,34 @@ namespace CSMWorld else throw std::runtime_error("Magic effects ID unexpected value"); } - case 1: return effect.mSkill; - case 2: return effect.mAttribute; + case 1: + { + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + return effect.mSkill; + default: + return QVariant(); + } + } + case 2: + { + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + return effect.mAttribute; + default: + return QVariant(); + } + } case 3: { if (effect.mRange >=0 && effect.mRange <=2) diff --git a/apps/opencs/model/world/nestedcollection.cpp b/apps/opencs/model/world/nestedcollection.cpp index 937ad6ad6..850d8c385 100644 --- a/apps/opencs/model/world/nestedcollection.cpp +++ b/apps/opencs/model/world/nestedcollection.cpp @@ -15,3 +15,28 @@ int CSMWorld::NestedCollection::getNestedColumnsCount(int row, int column) const { return 0; } + +int CSMWorld::NestedCollection::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + // Assumed that the parentColumn is always a valid index + const NestableColumn *parent = getNestableColumn(parentColumn); + int nestedColumnCount = getNestedColumnsCount(0, parentColumn); + for (int i = 0; i < nestedColumnCount; ++i) + { + if (parent->nestedColumn(i).mColumnId == id) + { + return i; + } + } + return -1; +} + +int CSMWorld::NestedCollection::findNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + int index = searchNestedColumnIndex(parentColumn, id); + if (index == -1) + { + throw std::logic_error("CSMWorld::NestedCollection: No such nested column"); + } + return index; +} diff --git a/apps/opencs/model/world/nestedcollection.hpp b/apps/opencs/model/world/nestedcollection.hpp index b075f53c4..4548cfb2b 100644 --- a/apps/opencs/model/world/nestedcollection.hpp +++ b/apps/opencs/model/world/nestedcollection.hpp @@ -1,6 +1,8 @@ #ifndef CSM_WOLRD_NESTEDCOLLECTION_H #define CSM_WOLRD_NESTEDCOLLECTION_H +#include "columns.hpp" + class QVariant; namespace CSMWorld @@ -33,6 +35,12 @@ namespace CSMWorld virtual int getNestedColumnsCount(int row, int column) const; virtual NestableColumn *getNestableColumn(int column) = 0; + + virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or -1 if the requested column wasn't found. + + virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or throws an exception if the requested column wasn't found. }; } diff --git a/apps/opencs/model/world/nestedidcollection.hpp b/apps/opencs/model/world/nestedidcollection.hpp index 792a13b7d..56b112365 100644 --- a/apps/opencs/model/world/nestedidcollection.hpp +++ b/apps/opencs/model/world/nestedidcollection.hpp @@ -161,8 +161,19 @@ namespace CSMWorld template int NestedIdCollection::getNestedColumnsCount(int row, int column) const { - return getAdapter(Collection::getColumn(column)).getColumnsCount( - Collection::getRecord(row)); + const ColumnBase &nestedColumn = Collection::getColumn(column); + int numRecords = Collection::getSize(); + if (row >= 0 && row < numRecords) + { + const Record& record = Collection::getRecord(row); + return getAdapter(nestedColumn).getColumnsCount(record); + } + else + { + // If the row is invalid (or there no records), retrieve the column count using a blank record + const Record record; + return getAdapter(nestedColumn).getColumnsCount(record); + } } template diff --git a/apps/opencs/model/world/nestedtableproxymodel.cpp b/apps/opencs/model/world/nestedtableproxymodel.cpp index acf197716..edcc7a070 100644 --- a/apps/opencs/model/world/nestedtableproxymodel.cpp +++ b/apps/opencs/model/world/nestedtableproxymodel.cpp @@ -75,10 +75,10 @@ QModelIndex CSMWorld::NestedTableProxyModel::index(int row, int column, const QM { assert (!parent.isValid()); - int rows = mMainModel->rowCount(parent); - int columns = mMainModel->columnCount(parent); + int numRows = rowCount(parent); + int numColumns = columnCount(parent); - if (row < 0 || row >= rows || column < 0 || column >= columns) + if (row < 0 || row >= numRows || column < 0 || column >= numColumns) return QModelIndex(); return createIndex(row, column); @@ -192,4 +192,8 @@ void CSMWorld::NestedTableProxyModel::forwardDataChanged (const QModelIndex& top emit dataChanged(index(0,0), index(mMainModel->rowCount(parent)-1, mMainModel->columnCount(parent)-1)); } + else if (topLeft.parent() == parent && bottomRight.parent() == parent) + { + emit dataChanged(index(topLeft.row(), topLeft.column()), index(bottomRight.row(), bottomRight.column())); + } } diff --git a/apps/opencs/model/world/npcstats.cpp b/apps/opencs/model/world/npcstats.cpp new file mode 100644 index 000000000..5b8484934 --- /dev/null +++ b/apps/opencs/model/world/npcstats.cpp @@ -0,0 +1,138 @@ +#include "npcstats.hpp" + +#include +#include + +namespace CSMWorld +{ + NpcStats::NpcStats() : mHealth(0), mMana(0), mFatigue(0) + { + for (int i = 0; i < ESM::Skill::Length; ++i) + mSkill[i] = 0; + } + + NpcStats::NpcStats(const NpcStats &other) + { + for (int i = 0; i < ESM::Attribute::Length; ++i) + mAttr[i] = other.mAttr[i]; + + mSpells = other.mSpells; + + for (int i = 0; i < ESM::Skill::Length; ++i) + mSkill[i] = 0; + + mHealth = other.mHealth; + mMana = other.mMana; + mFatigue = other.mFatigue; + } + + NpcStats::~NpcStats() + {} + + unsigned char NpcStats::getBaseAttribute(int index) const + { + if (index < 0 || index >= ESM::Attribute::Length) + throw std::runtime_error("attrib index out of bounds"); + + return mAttr[index]; + } + + void NpcStats::setAttribute(int index, unsigned char value) + { + if (index < 0 || index >= ESM::Attribute::Length) + throw std::runtime_error("attrib index out of bounds"); + + mAttr[index] = value; + } + + void NpcStats::addSpell(const std::string& id) + { + struct SpellInfo info; + info.mName = id; + info.mType = ESM::Spell::ST_Spell; // default type from autocalc + info.mFromRace = false; + info.mCost = 0; + info.mChance = 0; + + mSpells.insert(mSpells.begin(), info); + } + + void NpcStats::addPowers(const std::string& id, int type) + { + struct SpellInfo info; + info.mName = id; + info.mType = type; + info.mFromRace = true; + info.mCost = 0; + info.mChance = 0; + + mSpells.push_back(info); + } + + void NpcStats::addCostAndChance(const std::string& id, int cost, int chance) + { + // usually only a few spells, so simply iterate through rather than keeping a separate + // lookup index or map + for (std::vector::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + if ((*it).mName == id) + { + (*it).mCost = cost; + (*it).mChance = chance; + return; + } + } + } + + const std::vector& NpcStats::spells() const + { + return mSpells; + } + + unsigned char NpcStats::getBaseSkill(int index) const + { + if (index < 0 || index >= ESM::Skill::Length) + throw std::runtime_error("skill index out of bounds"); + + return mSkill[index]; + } + + void NpcStats::setBaseSkill(int index, unsigned char value) + { + if (index < 0 || index >= ESM::Skill::Length) + throw std::runtime_error("skill index out of bounds"); + + mSkill[index] = value; + } + + unsigned short NpcStats::getHealth() + { + return mHealth; + } + + void NpcStats::setHealth(unsigned short health) + { + mHealth = health; + } + + unsigned short NpcStats::getMana() + { + return mMana; + } + + void NpcStats::setMana(unsigned short mana) + { + mMana = mana; + } + + unsigned short NpcStats::getFatigue() + { + return mFatigue; + } + + void NpcStats::setFatigue(unsigned short fatigue) + { + mFatigue = fatigue; + } +} + diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp new file mode 100644 index 000000000..8cefe586f --- /dev/null +++ b/apps/opencs/model/world/npcstats.hpp @@ -0,0 +1,74 @@ +#ifndef CSM_WORLD_NPCSTATS_H +#define CSM_WORLD_NPCSTATS_H + +#include + +#include + +#include +#include +#include + +namespace CSMWorld +{ + struct SpellInfo + { + std::string mName; + int mType; + bool mFromRace; + int mCost; + int mChance; + }; + + class NpcStats : public AutoCalc::StatsBase + { + + int mAttr[ESM::Attribute::Length]; + std::vector mSpells; + int mSkill[ESM::Skill::Length]; + + unsigned short mHealth; + unsigned short mMana; + unsigned short mFatigue; + + public: + + NpcStats(); + + NpcStats(const NpcStats &other); + + ~NpcStats(); + + virtual unsigned char getBaseAttribute(int index) const; + + virtual void setAttribute(int index, unsigned char value); + + virtual void addSpell(const std::string& id); + + void addPowers(const std::string& id, int type); + + void addCostAndChance(const std::string& id, int cost, int chance); + + const std::vector& spells() const; + + virtual unsigned char getBaseSkill(int index) const; + + virtual void setBaseSkill(int index, unsigned char value); + + unsigned short getHealth(); + + void setHealth(unsigned short health); + + unsigned short getMana(); + + void setMana(unsigned short mana); + + unsigned short getFatigue(); + + void setFatigue(unsigned short fatigue); + }; +} + +Q_DECLARE_METATYPE(CSMWorld::NpcStats*) + +#endif // CSM_WORLD_NPCSTATS_H diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp index ef2f4d320..f13a36afc 100644 --- a/apps/opencs/model/world/record.cpp +++ b/apps/opencs/model/world/record.cpp @@ -1,4 +1,3 @@ - #include "record.hpp" CSMWorld::RecordBase::~RecordBase() {} diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 13706c950..638f7ec9c 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -1,4 +1,3 @@ - #include "ref.hpp" #include diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index ff30dafae..f8818807b 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,4 +1,3 @@ - #include "refcollection.hpp" #include diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index d31a9ceaa..a268cb5f0 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -5,7 +5,13 @@ #include #include +#include +#include + #include "nestedtablewrapper.hpp" +#include "usertype.hpp" +#include "idtree.hpp" +#include "npcstats.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) : InventoryColumns (columns) {} @@ -25,8 +31,9 @@ QVariant CSMWorld::PotionRefIdAdapter::getData (const RefIdColumn *column, const if (column==mAutoCalc) return record.get().mData.mAutoCalc!=0; + // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column==mColumns.mEffects) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return InventoryRefIdAdapter::getData (column, data, index); } @@ -37,10 +44,168 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Potion))); + ESM::Potion potion = record.get(); + if (column==mAutoCalc) - record.get().mData.mAutoCalc = value.toInt(); + potion.mData.mAutoCalc = value.toInt(); else + { InventoryRefIdAdapter::setData (column, data, index, value); + + return; + } + + record.setModified(potion); +} + + +CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) +: InventoryColumns (columns) {} + +CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) +: InventoryRefIdAdapter (UniversalId::Type_Ingredient, columns), + mColumns(columns) +{} + +QVariant CSMWorld::IngredientRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + if (column==mColumns.mEffects) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); + + return InventoryRefIdAdapter::getData (column, data, index); +} + +void CSMWorld::IngredientRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, + const QVariant& value) const +{ + InventoryRefIdAdapter::setData (column, data, index, value); + + return; +} + + +CSMWorld::IngredEffectRefIdAdapter::IngredEffectRefIdAdapter() +: mType(UniversalId::Type_Ingredient) +{} + +CSMWorld::IngredEffectRefIdAdapter::~IngredEffectRefIdAdapter() +{} + +void CSMWorld::IngredEffectRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::IngredEffectRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Ingredient ingredient = record.get(); + + ingredient.mData = + static_cast >&>(nestedTable).mNestedTable.at(0); + + record.setModified (ingredient); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (subRowIndex < 0 || subRowIndex >= 4) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: return record.get().mData.mEffectID[subRowIndex]; + case 1: + { + switch (record.get().mData.mEffectID[subRowIndex]) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + return record.get().mData.mSkills[subRowIndex]; + default: + return QVariant(); + } + } + case 2: + { + switch (record.get().mData.mEffectID[subRowIndex]) + { + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + return record.get().mData.mAttributes[subRowIndex]; + default: + return QVariant(); + } + } + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } +} + +void CSMWorld::IngredEffectRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + ESM::Ingredient ingredient = record.get(); + + if (subRowIndex < 0 || subRowIndex >= 4) + throw std::runtime_error ("index out of range"); + + switch(subColIndex) + { + case 0: ingredient.mData.mEffectID[subRowIndex] = value.toInt(); break; + case 1: ingredient.mData.mSkills[subRowIndex] = value.toInt(); break; + case 2: ingredient.mData.mAttributes[subRowIndex] = value.toInt(); break; + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } + + record.setModified (ingredient); +} + +int CSMWorld::IngredEffectRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 3; // effect, skill, attribute +} + +int CSMWorld::IngredEffectRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + return 4; // up to 4 effects } @@ -71,12 +236,19 @@ void CSMWorld::ApparatusRefIdAdapter::setData (const RefIdColumn *column, RefIdD Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Apparatus))); + ESM::Apparatus apparatus = record.get(); + if (column==mType) - record.get().mData.mType = value.toInt(); + apparatus.mData.mType = value.toInt(); else if (column==mQuality) - record.get().mData.mQuality = value.toFloat(); + apparatus.mData.mQuality = value.toFloat(); else + { InventoryRefIdAdapter::setData (column, data, index, value); + + return; + } + record.setModified(apparatus); } @@ -103,7 +275,7 @@ QVariant CSMWorld::ArmorRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mArmor; if (column==mPartRef) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -114,14 +286,22 @@ void CSMWorld::ArmorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Armor))); + ESM::Armor armor = record.get(); + if (column==mType) - record.get().mData.mType = value.toInt(); + armor.mData.mType = value.toInt(); else if (column==mHealth) - record.get().mData.mHealth = value.toInt(); + armor.mData.mHealth = value.toInt(); else if (column==mArmor) - record.get().mData.mArmor = value.toInt(); + armor.mData.mArmor = value.toInt(); else + { EnchantableRefIdAdapter::setData (column, data, index, value); + + return; + } + + record.setModified(armor); } CSMWorld::BookRefIdAdapter::BookRefIdAdapter (const EnchantableColumns& columns, @@ -151,12 +331,20 @@ void CSMWorld::BookRefIdAdapter::setData (const RefIdColumn *column, RefIdData& Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Book))); + ESM::Book book = record.get(); + if (column==mScroll) - record.get().mData.mIsScroll = value.toInt(); + book.mData.mIsScroll = value.toInt(); else if (column==mSkill) - record.get().mData.mSkillID = value.toInt(); + book.mData.mSkillID = value.toInt(); else + { EnchantableRefIdAdapter::setData (column, data, index, value); + + return; + } + + record.setModified(book); } CSMWorld::ClothingRefIdAdapter::ClothingRefIdAdapter (const EnchantableColumns& columns, @@ -175,7 +363,7 @@ QVariant CSMWorld::ClothingRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mType; if (column==mPartRef) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -186,10 +374,18 @@ void CSMWorld::ClothingRefIdAdapter::setData (const RefIdColumn *column, RefIdDa Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Clothing))); + ESM::Clothing clothing = record.get(); + if (column==mType) - record.get().mData.mType = value.toInt(); + clothing.mData.mType = value.toInt(); else + { EnchantableRefIdAdapter::setData (column, data, index, value); + + return; + } + + record.setModified(clothing); } CSMWorld::ContainerRefIdAdapter::ContainerRefIdAdapter (const NameColumns& columns, @@ -215,7 +411,7 @@ QVariant CSMWorld::ContainerRefIdAdapter::getData (const RefIdColumn *column, return (record.get().mFlags & ESM::Container::Respawn)!=0; if (column==mContent) - return true; // Required to show nested tables in dialogue subview + return QVariant::fromValue(ColumnBase::TableEdit_Full); return NameRefIdAdapter::getData (column, data, index); } @@ -226,35 +422,42 @@ void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdD Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Container))); + ESM::Container container = record.get(); + if (column==mWeight) - record.get().mWeight = value.toFloat(); + container.mWeight = value.toFloat(); else if (column==mOrganic) { if (value.toInt()) - record.get().mFlags |= ESM::Container::Organic; + container.mFlags |= ESM::Container::Organic; else - record.get().mFlags &= ~ESM::Container::Organic; + container.mFlags &= ~ESM::Container::Organic; } else if (column==mRespawn) { if (value.toInt()) - record.get().mFlags |= ESM::Container::Respawn; + container.mFlags |= ESM::Container::Respawn; else - record.get().mFlags &= ~ESM::Container::Respawn; + container.mFlags &= ~ESM::Container::Respawn; } else + { NameRefIdAdapter::setData (column, data, index, value); + + return; + } + + record.setModified(container); } CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns), mType(NULL), - mSoul(NULL), mScale(NULL), mOriginal(NULL), - mCombat(NULL), - mMagic(NULL), - mStealth(NULL) + mAttributes(NULL), + mAttacks(NULL), + mMisc(NULL) {} CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) @@ -270,23 +473,20 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con if (column==mColumns.mType) return record.get().mData.mType; - if (column==mColumns.mSoul) - return record.get().mData.mSoul; - if (column==mColumns.mScale) return record.get().mScale; if (column==mColumns.mOriginal) return QString::fromUtf8 (record.get().mOriginal.c_str()); - if (column==mColumns.mCombat) - return static_cast (record.get().mData.mCombat); + if (column==mColumns.mAttributes) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); - if (column==mColumns.mMagic) - return static_cast (record.get().mData.mMagic); + if (column==mColumns.mAttacks) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); - if (column==mColumns.mStealth) - return static_cast (record.get().mData.mStealth); + if (column==mColumns.mMisc) + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -303,20 +503,14 @@ void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdDa Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + if (column==mColumns.mType) - record.get().mData.mType = value.toInt(); - else if (column==mColumns.mSoul) - record.get().mData.mSoul = value.toInt(); + creature.mData.mType = value.toInt(); else if (column==mColumns.mScale) - record.get().mScale = value.toFloat(); + creature.mScale = value.toFloat(); else if (column==mColumns.mOriginal) - record.get().mOriginal = value.toString().toUtf8().constData(); - else if (column==mColumns.mCombat) - record.get().mData.mCombat = value.toInt(); - else if (column==mColumns.mMagic) - record.get().mData.mMagic = value.toInt(); - else if (column==mColumns.mStealth) - record.get().mData.mStealth = value.toInt(); + creature.mOriginal = value.toString().toUtf8().constData(); else { std::map::const_iterator iter = @@ -325,13 +519,19 @@ void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdDa if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) - record.get().mFlags |= iter->second; + creature.mFlags |= iter->second; else - record.get().mFlags &= ~iter->second; + creature.mFlags &= ~iter->second; } else + { ActorRefIdAdapter::setData (column, data, index, value); + + return; + } } + + record.setModified(creature); } CSMWorld::DoorRefIdAdapter::DoorRefIdAdapter (const NameColumns& columns, @@ -361,12 +561,20 @@ void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Door))); + ESM::Door door = record.get(); + if (column==mOpenSound) - record.get().mOpenSound = value.toString().toUtf8().constData(); + door.mOpenSound = value.toString().toUtf8().constData(); else if (column==mCloseSound) - record.get().mCloseSound = value.toString().toUtf8().constData(); + door.mCloseSound = value.toString().toUtf8().constData(); else + { NameRefIdAdapter::setData (column, data, index, value); + + return; + } + + record.setModified(door); } CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns) @@ -409,14 +617,16 @@ void CSMWorld::LightRefIdAdapter::setData (const RefIdColumn *column, RefIdData& Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Light))); + ESM::Light light = record.get(); + if (column==mColumns.mTime) - record.get().mData.mTime = value.toInt(); + light.mData.mTime = value.toInt(); else if (column==mColumns.mRadius) - record.get().mData.mRadius = value.toInt(); + light.mData.mRadius = value.toInt(); else if (column==mColumns.mColor) - record.get().mData.mColor = value.toInt(); + light.mData.mColor = value.toInt(); else if (column==mColumns.mSound) - record.get().mSound = value.toString().toUtf8().constData(); + light.mSound = value.toString().toUtf8().constData(); else { std::map::const_iterator iter = @@ -425,13 +635,19 @@ void CSMWorld::LightRefIdAdapter::setData (const RefIdColumn *column, RefIdData& if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) - record.get().mData.mFlags |= iter->second; + light.mData.mFlags |= iter->second; else - record.get().mData.mFlags &= ~iter->second; + light.mData.mFlags &= ~iter->second; } else + { InventoryRefIdAdapter::setData (column, data, index, value); + + return; + } } + + record.setModified (light); } CSMWorld::MiscRefIdAdapter::MiscRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *key) @@ -456,10 +672,18 @@ void CSMWorld::MiscRefIdAdapter::setData (const RefIdColumn *column, RefIdData& Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Miscellaneous))); + ESM::Miscellaneous misc = record.get(); + if (column==mKey) - record.get().mData.mIsKey = value.toInt(); + misc.mData.mIsKey = value.toInt(); else + { InventoryRefIdAdapter::setData (column, data, index, value); + + return; + } + + record.setModified(misc); } CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) @@ -474,8 +698,8 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) mMisc(NULL) {} -CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) -: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) +CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, const CSMWorld::Data& data) +: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns), mData(data) {} QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) @@ -502,13 +726,13 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re if (column==mColumns.mAttributes || column==mColumns.mSkills) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) - return QVariant(QVariant::UserType); + return QVariant::fromValue(ColumnBase::TableEdit_None); else - return true; + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); } if (column==mColumns.mMisc) - return true; + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -525,16 +749,18 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); + ESM::NPC npc = record.get(); + if (column==mColumns.mRace) - record.get().mRace = value.toString().toUtf8().constData(); + npc.mRace = value.toString().toUtf8().constData(); else if (column==mColumns.mClass) - record.get().mClass = value.toString().toUtf8().constData(); + npc.mClass = value.toString().toUtf8().constData(); else if (column==mColumns.mFaction) - record.get().mFaction = value.toString().toUtf8().constData(); + npc.mFaction = value.toString().toUtf8().constData(); else if (column==mColumns.mHair) - record.get().mHair = value.toString().toUtf8().constData(); + npc.mHair = value.toString().toUtf8().constData(); else if (column==mColumns.mHead) - record.get().mHead = value.toString().toUtf8().constData(); + npc.mHead = value.toString().toUtf8().constData(); else { std::map::const_iterator iter = @@ -543,16 +769,70 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) - record.get().mFlags |= iter->second; + npc.mFlags |= iter->second; else - record.get().mFlags &= ~iter->second; + npc.mFlags &= ~iter->second; + + if (iter->second == ESM::NPC::Autocalc) + { + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + { + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + { + record.setModified (npc); + return; + } + + // update npc + npc.mNpdtType = ESM::NPC::NPC_DEFAULT; + npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; + + npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength); + npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence); + npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower); + npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility); + npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed); + npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance); + npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality); + npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck); + + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i); + } + + npc.mNpdt52.mHealth = stats->getHealth(); + npc.mNpdt52.mMana = stats->getMana(); + npc.mNpdt52.mFatigue = stats->getFatigue(); + npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; + npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; + npc.mNpdt52.mRank = npc.mNpdt12.mRank; + npc.mNpdt52.mGold = npc.mNpdt12.mGold; + + // TODO: add spells from autogenerated list like vanilla (but excluding any + // race powers or abilities) + } + else + { + npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc + mData.npcAutoCalculate(npc); + } + } } else + { ActorRefIdAdapter::setData (column, data, index, value); + + return; + } } + + record.setModified (npc); } -CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter () +CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::Data& data) : mData(data) {} void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -600,34 +880,44 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; + const ESM::NPC npc = record.get(); + const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; if (subColIndex == 0) - switch (subRowIndex) - { - case 0: return QString("Strength"); - case 1: return QString("Intelligence"); - case 2: return QString("Willpower"); - case 3: return QString("Agility"); - case 4: return QString("Speed"); - case 5: return QString("Endurance"); - case 6: return QString("Personality"); - case 7: return QString("Luck"); - default: return QVariant(); // throw an exception here? - } + return subRowIndex; else if (subColIndex == 1) - switch (subRowIndex) + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - case 0: return static_cast(npcStruct.mStrength); - case 1: return static_cast(npcStruct.mIntelligence); - case 2: return static_cast(npcStruct.mWillpower); - case 3: return static_cast(npcStruct.mAgility); - case 4: return static_cast(npcStruct.mSpeed); - case 5: return static_cast(npcStruct.mEndurance); - case 6: return static_cast(npcStruct.mPersonality); - case 7: return static_cast(npcStruct.mLuck); - default: return QVariant(); // throw an exception here? + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + return QVariant(); + + switch (subRowIndex) + { + case 0: return static_cast(stats->getBaseAttribute(ESM::Attribute::Strength)); + case 1: return static_cast(stats->getBaseAttribute(ESM::Attribute::Intelligence)); + case 2: return static_cast(stats->getBaseAttribute(ESM::Attribute::Willpower)); + case 3: return static_cast(stats->getBaseAttribute(ESM::Attribute::Agility)); + case 4: return static_cast(stats->getBaseAttribute(ESM::Attribute::Speed)); + case 5: return static_cast(stats->getBaseAttribute(ESM::Attribute::Endurance)); + case 6: return static_cast(stats->getBaseAttribute(ESM::Attribute::Personality)); + case 7: return static_cast(stats->getBaseAttribute(ESM::Attribute::Luck)); + default: return QVariant(); // throw an exception here? + } } + else + switch (subRowIndex) + { + case 0: return static_cast(npcStruct.mStrength); + case 1: return static_cast(npcStruct.mIntelligence); + case 2: return static_cast(npcStruct.mWillpower); + case 3: return static_cast(npcStruct.mAgility); + case 4: return static_cast(npcStruct.mSpeed); + case 5: return static_cast(npcStruct.mEndurance); + case 6: return static_cast(npcStruct.mPersonality); + case 7: return static_cast(npcStruct.mLuck); + default: return QVariant(); // throw an exception here? + } else return QVariant(); // throw an exception here? } @@ -670,7 +960,8 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *c return 8; } -CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter () +CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::Data& data) + : mData(data) {} void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -718,15 +1009,29 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; + const ESM::NPC npc = record.get(); if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) throw std::runtime_error ("index out of range"); if (subColIndex == 0) - return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); + return subRowIndex; else if (subColIndex == 1) - return static_cast(npcStruct.mSkills[subRowIndex]); + { + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + { + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + return QVariant(); + + return static_cast(stats->getBaseSkill(subRowIndex)); + } + else + { + const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + return static_cast(npcStruct.mSkills[subRowIndex]); + } + } else return QVariant(); // throw an exception here? } @@ -761,7 +1066,7 @@ int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *colum return ESM::Skill::Length; } -CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter () +CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::Data& data) : mData(data) {} CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter() @@ -797,16 +1102,43 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - bool autoCalc = (record.get().mFlags & ESM::NPC::Autocalc) != 0; + const ESM::NPC npc = record.get(); + + bool autoCalc = (npc.mFlags & ESM::NPC::Autocalc) != 0; if (autoCalc) + { + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + switch (subColIndex) { - case 0: return static_cast(record.get().mNpdt12.mLevel); - case 1: return QVariant(QVariant::UserType); - case 2: return QVariant(QVariant::UserType); - case 3: return QVariant(QVariant::UserType); - case 4: return QVariant(QVariant::UserType); + case 0: return static_cast(npc.mNpdt12.mLevel); + case 1: + { + UserInt i(0); // unknown + return QVariant(QVariant::fromValue(i)); + } + case 2: + { + UserInt i(0); + if (stats) + i = UserInt(stats->getHealth()); + return QVariant(QVariant::fromValue(i)); + } + case 3: + { + UserInt i(0); + if (stats) + i = UserInt(stats->getMana()); + return QVariant(QVariant::fromValue(i)); + } + case 4: + { + UserInt i(0); + if (stats) + i = UserInt(stats->getFatigue()); + return QVariant(QVariant::fromValue(i)); + } case 5: return static_cast(record.get().mNpdt12.mDisposition); case 6: return static_cast(record.get().mNpdt12.mReputation); case 7: return static_cast(record.get().mNpdt12.mRank); @@ -814,6 +1146,7 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column case 9: return record.get().mPersistent == true; default: return QVariant(); // throw an exception here? } + } else switch (subColIndex) { @@ -843,31 +1176,31 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column, if (autoCalc) switch(subColIndex) { - case 0: npc.mNpdt12.mLevel = static_cast(value.toInt()); break; + case 0: npc.mNpdt12.mLevel = static_cast(value.toInt()); break; case 1: return; case 2: return; case 3: return; case 4: return; case 5: npc.mNpdt12.mDisposition = static_cast(value.toInt()); break; - case 6: npc.mNpdt12.mReputation = static_cast(value.toInt()); break; - case 7: npc.mNpdt12.mRank = static_cast(value.toInt()); break; - case 8: npc.mNpdt12.mGold = value.toInt(); break; - case 9: npc.mPersistent = value.toBool(); break; + case 6: npc.mNpdt12.mReputation = static_cast(value.toInt()); break; + case 7: npc.mNpdt12.mRank = static_cast(value.toInt()); break; + case 8: npc.mNpdt12.mGold = value.toInt(); break; + case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } else switch(subColIndex) { - case 0: npc.mNpdt52.mLevel = static_cast(value.toInt()); break; - case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; - case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; - case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; - case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; + case 0: npc.mNpdt52.mLevel = static_cast(value.toInt()); break; + case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; + case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; + case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; + case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; case 5: npc.mNpdt52.mDisposition = static_cast(value.toInt()); break; - case 6: npc.mNpdt52.mReputation = static_cast(value.toInt()); break; - case 7: npc.mNpdt52.mRank = static_cast(value.toInt()); break; - case 8: npc.mNpdt52.mGold = value.toInt(); break; - case 9: npc.mPersistent = value.toBool(); break; + case 6: npc.mNpdt52.mReputation = static_cast(value.toInt()); break; + case 7: npc.mNpdt52.mRank = static_cast(value.toInt()); break; + case 8: npc.mNpdt52.mGold = value.toInt(); break; + case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } @@ -884,6 +1217,289 @@ int CSMWorld::NpcMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, return 1; // fixed at size 1 } +CSMWorld::CreatureAttributesRefIdAdapter::CreatureAttributesRefIdAdapter() +{} + +void CSMWorld::CreatureAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttributesRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + // store the whole struct + creature.mData = + static_cast > &>(nestedTable).mNestedTable.at(0); + + record.setModified (creature); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + if (subColIndex == 0) + return subRowIndex; + else if (subColIndex == 1) + switch (subRowIndex) + { + case 0: return creature.mData.mStrength; + case 1: return creature.mData.mIntelligence; + case 2: return creature.mData.mWillpower; + case 3: return creature.mData.mAgility; + case 4: return creature.mData.mSpeed; + case 5: return creature.mData.mEndurance; + case 6: return creature.mData.mPersonality; + case 7: return creature.mData.mLuck; + default: return QVariant(); // throw an exception here? + } + else + return QVariant(); // throw an exception here? +} + +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + if (subColIndex == 1) + switch(subRowIndex) + { + case 0: creature.mData.mStrength = value.toInt(); break; + case 1: creature.mData.mIntelligence = value.toInt(); break; + case 2: creature.mData.mWillpower = value.toInt(); break; + case 3: creature.mData.mAgility = value.toInt(); break; + case 4: creature.mData.mSpeed = value.toInt(); break; + case 5: creature.mData.mEndurance = value.toInt(); break; + case 6: creature.mData.mPersonality = value.toInt(); break; + case 7: creature.mData.mLuck = value.toInt(); break; + default: return; // throw an exception here? + } + else + return; // throw an exception here? + + record.setModified (creature); +} + +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 2; +} + +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + // There are 8 attributes + return 8; +} + +CSMWorld::CreatureAttackRefIdAdapter::CreatureAttackRefIdAdapter() +{} + +void CSMWorld::CreatureAttackRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttackRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + // store the whole struct + creature.mData = + static_cast > &>(nestedTable).mNestedTable.at(0); + + record.setModified (creature); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + if (subRowIndex < 0 || subRowIndex > 2 || subColIndex < 0 || subColIndex > 2) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 0) + return subRowIndex + 1; + else if (subColIndex < 3) // 1 or 2 + return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)]; + else + return QVariant(); // throw an exception here? +} + +void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + if (subRowIndex < 0 || subRowIndex > 2) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 1 || subColIndex == 2) + creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)] = value.toInt(); + else + return; // throw an exception here? + + record.setModified (creature); +} + +int CSMWorld::CreatureAttackRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 3; +} + +int CSMWorld::CreatureAttackRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + // There are 3 attacks + return 3; +} + +CSMWorld::CreatureMiscRefIdAdapter::CreatureMiscRefIdAdapter() +{} + +CSMWorld::CreatureMiscRefIdAdapter::~CreatureMiscRefIdAdapter() +{} + +void CSMWorld::CreatureMiscRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + throw std::logic_error ("cannot add a row to a fixed table"); +} + +void CSMWorld::CreatureMiscRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + throw std::logic_error ("cannot remove a row to a fixed table"); +} + +void CSMWorld::CreatureMiscRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + throw std::logic_error ("table operation not supported"); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureMiscRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + throw std::logic_error ("table operation not supported"); +} + +QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + switch (subColIndex) + { + case 0: return creature.mData.mLevel; + case 1: return creature.mData.mHealth; + case 2: return creature.mData.mMana; + case 3: return creature.mData.mFatigue; + case 4: return creature.mData.mSoul; + case 5: return creature.mData.mCombat; + case 6: return creature.mData.mMagic; + case 7: return creature.mData.mStealth; + case 8: return creature.mData.mGold; + default: return QVariant(); // throw an exception here? + } +} + +void CSMWorld::CreatureMiscRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + switch(subColIndex) + { + case 0: creature.mData.mLevel = value.toInt(); break; + case 1: creature.mData.mHealth = value.toInt(); break; + case 2: creature.mData.mMana = value.toInt(); break; + case 3: creature.mData.mFatigue = value.toInt(); break; + case 4: creature.mData.mSoul = value.toInt(); break; + case 5: creature.mData.mCombat = value.toInt(); break; + case 6: creature.mData.mMagic = value.toInt(); break; + case 7: creature.mData.mStealth = value.toInt(); break; + case 8: creature.mData.mGold = value.toInt(); break; + default: return; // throw an exception here? + } + + record.setModified (creature); +} + +int CSMWorld::CreatureMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 9; // Level, Health, Mana, Fatigue, Soul, Combat, Magic, Steath, Gold +} + +int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + return 1; // fixed at size 1 +} + CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) : EnchantableColumns (columns) {} @@ -972,3 +1588,330 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData EnchantableRefIdAdapter::setData (column, data, index, value); } } + +namespace CSMWorld +{ + +template<> +QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + const Record& record = static_cast&> ( + data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + + if (column==mActors.mHello) + return record.get().mAiData.mHello; + + if (column==mActors.mFlee) + return record.get().mAiData.mFlee; + + if (column==mActors.mFight) + return record.get().mAiData.mFight; + + if (column==mActors.mAlarm) + return record.get().mAiData.mAlarm; + + if (column==mActors.mInventory) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + if (column==mActors.mSpells) + { + if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) + return QVariant::fromValue(ColumnBase::TableEdit_None); + else + return QVariant::fromValue(ColumnBase::TableEdit_Full); + } + + if (column==mActors.mDestinations) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + if (column==mActors.mAiPackages) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + std::map::const_iterator iter = + mActors.mServices.find (column); + + if (iter!=mActors.mServices.end()) + return (record.get().mAiData.mServices & iter->second)!=0; + + return NameRefIdAdapter::getData (column, data, index); +} + +template <> +void NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + std::string newString; + + if (position >= (int)list.size()) + list.push_back(newString); + else + list.insert(list.begin()+position, newString); + + record.setModified (caster); +} + +template <> +void NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + // avoid race power rows + int size = 0; + int raceIndex = mData.getRaces().searchId(caster.mRace); + if (raceIndex != -1) + size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size(); + + if (rowToRemove < 0 || rowToRemove >= static_cast (list.size() + size)) + throw std::runtime_error ("index out of range"); + + if (rowToRemove >= static_cast(list.size()) && rowToRemove < static_cast(list.size() + size)) + return; // hack, assumes the race powers are added at the end + + list.erase (list.begin () + rowToRemove); + + record.setModified (caster); +} + +template <> +void NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + std::vector& list = caster.mSpells.mList; + + // avoid race power rows + int size = 0; + int raceIndex = mData.getRaces().searchId(caster.mRace); + if (raceIndex != -1) + size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size(); + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size() + size)) + throw std::runtime_error ("index out of range"); + + if (subRowIndex >= static_cast(list.size()) && subRowIndex < static_cast(list.size() + size)) + return; // hack, assumes the race powers are added at the end + + if (subColIndex == 0) + list.at(subRowIndex) = std::string(value.toString().toUtf8()); + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + + record.setModified (caster); +} + +template <> +QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get()); + if (!stats) + return QVariant(); + + const std::vector& spells = stats->spells(); + + if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: return QString::fromUtf8(spells[subRowIndex].mName.c_str()); + case 1: return spells[subRowIndex].mType; + case 2: return spells[subRowIndex].mFromRace; + case 3: return spells[subRowIndex].mCost; + case 4: return spells[subRowIndex].mChance; + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } +} + +template <> +int NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, + const RefIdData& data) const +{ + return 5; +} + +template <> +int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get()); + if (!stats) + return 0; + + return static_cast(stats->spells().size()); +} + +template<> +QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + const Record& record = static_cast&> ( + data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + + if (column==mActors.mHello) + return record.get().mAiData.mHello; + + if (column==mActors.mFlee) + return record.get().mAiData.mFlee; + + if (column==mActors.mFight) + return record.get().mAiData.mFight; + + if (column==mActors.mAlarm) + return record.get().mAiData.mAlarm; + + if (column==mActors.mInventory) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + if (column==mActors.mSpells) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + if (column==mActors.mDestinations) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + if (column==mActors.mAiPackages) + return QVariant::fromValue(ColumnBase::TableEdit_Full); + + std::map::const_iterator iter = + mActors.mServices.find (column); + + if (iter!=mActors.mServices.end()) + return (record.get().mAiData.mServices & iter->second)!=0; + + return NameRefIdAdapter::getData (column, data, index); +} + +template <> +void NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Creature caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + std::string newString; + + if (position >= (int)list.size()) + list.push_back(newString); + else + list.insert(list.begin()+position, newString); + + record.setModified (caster); +} + +template <> +void NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Creature caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + list.erase (list.begin () + rowToRemove); + + record.setModified (caster); +} + +template <> +void NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + ESM::Creature caster = record.get(); + std::vector& list = caster.mSpells.mList; + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 0) + list.at(subRowIndex) = std::string(value.toString().toUtf8()); + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + + record.setModified (caster); +} + +template<> +QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + const std::vector& list = record.get().mSpells.mList; + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + const std::string& content = list.at(subRowIndex); + + int type = -1; + int spellIndex = mData.getSpells().searchId(content); + if (spellIndex != -1) + type = mData.getSpells().getRecord(spellIndex).get().mData.mType; + + if (subColIndex == 0) + return QString::fromUtf8(content.c_str()); + else if (subColIndex == 1) + return type; + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); +} + +template <> +int NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, + const RefIdData& data) const +{ + return 2; +} + +template <> +int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + return static_cast(record.get().mSpells.mList.size()); +} + +} diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 869996da5..a2b4df3c4 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -11,12 +11,18 @@ #include #include #include +#include +#include +#include +#include "columnbase.hpp" #include "record.hpp" #include "refiddata.hpp" #include "universalid.hpp" #include "refidadapter.hpp" #include "nestedtablewrapper.hpp" +#include "idcollection.hpp" +#include "data.hpp" namespace CSMWorld { @@ -341,6 +347,66 @@ namespace CSMWorld ///< If the data type does not match an exception is thrown. }; + struct IngredientColumns : public InventoryColumns + { + const RefIdColumn *mEffects; + + IngredientColumns (const InventoryColumns& columns); + }; + + class IngredientRefIdAdapter : public InventoryRefIdAdapter + { + IngredientColumns mColumns; + + public: + + IngredientRefIdAdapter (const IngredientColumns& columns); + + virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) + const; + + virtual void setData (const RefIdColumn *column, RefIdData& data, int index, + const QVariant& value) const; + ///< If the data type does not match an exception is thrown. + }; + + class IngredEffectRefIdAdapter : public NestedRefIdAdapterBase + { + UniversalId::Type mType; + + // not implemented + IngredEffectRefIdAdapter (const IngredEffectRefIdAdapter&); + IngredEffectRefIdAdapter& operator= (const IngredEffectRefIdAdapter&); + + public: + + IngredEffectRefIdAdapter(); + + virtual ~IngredEffectRefIdAdapter(); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + struct EnchantableColumns : public InventoryColumns { const RefIdColumn *mEnchantment; @@ -480,7 +546,6 @@ namespace CSMWorld struct ActorColumns : public NameColumns { - const RefIdColumn *mHasAi; const RefIdColumn *mHello; const RefIdColumn *mFlee; const RefIdColumn *mFight; @@ -518,49 +583,6 @@ namespace CSMWorld : NameRefIdAdapter (type, columns), mActors (columns) {} - template - QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const - { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mActors.mHasAi) - return record.get().mHasAI!=0; - - if (column==mActors.mHello) - return record.get().mAiData.mHello; - - if (column==mActors.mFlee) - return record.get().mAiData.mFlee; - - if (column==mActors.mFight) - return record.get().mAiData.mFight; - - if (column==mActors.mAlarm) - return record.get().mAiData.mAlarm; - - if (column==mActors.mInventory) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mSpells) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mDestinations) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mAiPackages) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - std::map::const_iterator iter = - mActors.mServices.find (column); - - if (iter!=mActors.mServices.end()) - return (record.get().mAiData.mServices & iter->second)!=0; - - return NameRefIdAdapter::getData (column, data, index); - } - template void ActorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const @@ -569,9 +591,7 @@ namespace CSMWorld data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mActors.mHasAi) - record2.mHasAI = value.toInt(); - else if (column==mActors.mHello) + if (column==mActors.mHello) record2.mAiData.mHello = value.toInt(); else if (column==mActors.mFlee) record2.mAiData.mFlee = value.toInt(); @@ -697,12 +717,11 @@ namespace CSMWorld { std::map mFlags; const RefIdColumn *mType; - const RefIdColumn *mSoul; const RefIdColumn *mScale; const RefIdColumn *mOriginal; - const RefIdColumn *mCombat; - const RefIdColumn *mMagic; - const RefIdColumn *mStealth; + const RefIdColumn *mAttributes; + const RefIdColumn *mAttacks; + const RefIdColumn *mMisc; CreatureColumns (const ActorColumns& actorColumns); }; @@ -802,10 +821,11 @@ namespace CSMWorld class NpcRefIdAdapter : public ActorRefIdAdapter { NpcColumns mColumns; + const Data& mData; public: - NpcRefIdAdapter (const NpcColumns& columns); + NpcRefIdAdapter (const NpcColumns& columns, const Data& data); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; @@ -850,9 +870,11 @@ namespace CSMWorld class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase { + const Data& mData; + public: - NpcAttributesRefIdAdapter (); + NpcAttributesRefIdAdapter (const Data& data); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -879,9 +901,11 @@ namespace CSMWorld class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase { + const Data& mData; + public: - NpcSkillsRefIdAdapter (); + NpcSkillsRefIdAdapter (const Data& data); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -908,12 +932,14 @@ namespace CSMWorld class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase { + const Data& mData; + NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&); NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&); public: - NpcMiscRefIdAdapter (); + NpcMiscRefIdAdapter (const Data& data); virtual ~NpcMiscRefIdAdapter(); virtual void addNestedRow (const RefIdColumn *column, @@ -939,6 +965,97 @@ namespace CSMWorld virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; }; + class CreatureAttributesRefIdAdapter : public NestedRefIdAdapterBase + { + public: + + CreatureAttributesRefIdAdapter (); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + + class CreatureAttackRefIdAdapter : public NestedRefIdAdapterBase + { + public: + + CreatureAttackRefIdAdapter (); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + + class CreatureMiscRefIdAdapter : public NestedRefIdAdapterBase + { + CreatureMiscRefIdAdapter (const CreatureMiscRefIdAdapter&); + CreatureMiscRefIdAdapter& operator= (const CreatureMiscRefIdAdapter&); + + public: + + CreatureMiscRefIdAdapter (); + virtual ~CreatureMiscRefIdAdapter(); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + template class EffectsListAdapter; @@ -1161,6 +1278,7 @@ namespace CSMWorld class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; + const Data& mData; // not implemented NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&); @@ -1168,45 +1286,15 @@ namespace CSMWorld public: - NestedSpellRefIdAdapter(UniversalId::Type type) :mType(type) {} + NestedSpellRefIdAdapter(UniversalId::Type type, const Data& data) :mType(type), mData(data) {} virtual ~NestedSpellRefIdAdapter() {} virtual void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - ESXRecordT caster = record.get(); - - std::vector& list = caster.mSpells.mList; - - std::string newString; - - if (position >= (int)list.size()) - list.push_back(newString); - else - list.insert(list.begin()+position, newString); - - record.setModified (caster); - } + RefIdData& data, int index, int position) const; virtual void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - ESXRecordT caster = record.get(); - - std::vector& list = caster.mSpells.mList; - - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); - - list.erase (list.begin () + rowToRemove); - - record.setModified (caster); - } + RefIdData& data, int index, int rowToRemove) const; virtual void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const @@ -1232,55 +1320,14 @@ namespace CSMWorld } virtual QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const - { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - - const std::vector& list = record.get().mSpells.mList; - - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); - - const std::string& content = list.at(subRowIndex); - - if (subColIndex == 0) - return QString::fromUtf8(content.c_str()); - else - throw std::runtime_error("Trying to access non-existing column in the nested table!"); - } + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; virtual void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); - ESXRecordT caster = record.get(); - std::vector& list = caster.mSpells.mList; + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; - if (subColIndex == 0) - list.at(subRowIndex) = std::string(value.toString().toUtf8()); - else - throw std::runtime_error("Trying to access non-existing column in the nested table!"); - - record.setModified (caster); - } - - virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const - { - return 1; - } - - virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const - { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - - return static_cast(record.get().mSpells.mList.size()); - } + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; }; template @@ -1449,6 +1496,8 @@ namespace CSMWorld virtual ~ActorAiRefIdAdapter() {} + // FIXME: should check if the AI package type is already in the list and use a default + // that wasn't used already (in extreme case do not add anything at all? virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { @@ -1532,6 +1581,7 @@ namespace CSMWorld switch (subColIndex) { case 0: + // FIXME: should more than one AI package type be allowed? Check vanilla switch (content.mType) { case ESM::AI_Wander: return 0; @@ -1559,47 +1609,52 @@ namespace CSMWorld else return QVariant(); case 4: // wander idle + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: if (content.mType == ESM::AI_Wander) - { - return static_cast(content.mWander.mIdle[0]); // FIXME: - } + return static_cast(content.mWander.mIdle[subColIndex-4]); else return QVariant(); - case 5: // wander repeat + case 12: // wander repeat if (content.mType == ESM::AI_Wander) - return content.mWander.mShouldRepeat; + return content.mWander.mShouldRepeat != 0; else return QVariant(); - case 6: // activate name + case 13: // activate name if (content.mType == ESM::AI_Activate) return QString(content.mActivate.mName.toString().c_str()); else return QVariant(); - case 7: // target id + case 14: // target id if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString(content.mTarget.mId.toString().c_str()); else return QVariant(); - case 8: // target cell + case 15: // target cell if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString::fromUtf8(content.mCellName.c_str()); else return QVariant(); - case 9: + case 16: if (content.mType == ESM::AI_Travel) return content.mTravel.mX; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mX; else return QVariant(); - case 10: + case 17: if (content.mType == ESM::AI_Travel) return content.mTravel.mY; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mY; else return QVariant(); - case 11: + case 18: if (content.mType == ESM::AI_Travel) return content.mTravel.mZ; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) @@ -1629,11 +1684,12 @@ namespace CSMWorld case 0: // ai package type switch (value.toInt()) { - case 0: content.mType = ESM::AI_Wander; - case 1: content.mType = ESM::AI_Travel; - case 2: content.mType = ESM::AI_Follow; - case 3: content.mType = ESM::AI_Escort; - case 4: content.mType = ESM::AI_Activate; + case 0: content.mType = ESM::AI_Wander; break; + case 1: content.mType = ESM::AI_Travel; break; + case 2: content.mType = ESM::AI_Follow; break; + case 3: content.mType = ESM::AI_Escort; break; + case 4: content.mType = ESM::AI_Activate; break; + default: return; // return without saving } break; // always save @@ -1642,6 +1698,8 @@ namespace CSMWorld content.mWander.mDistance = static_cast(value.toInt()); else return; // return without saving + + break; // always save case 2: if (content.mType == ESM::AI_Wander || content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) @@ -1653,62 +1711,77 @@ namespace CSMWorld content.mWander.mTimeOfDay = static_cast(value.toInt()); else return; // return without saving + + break; // always save case 4: - if (content.mType == ESM::AI_Wander) - break; // FIXME: idle - else - return; // return without saving case 5: - if (content.mType == ESM::AI_Wander) - { - content.mWander.mShouldRepeat = static_cast(value.toInt()); - break; - } - case 6: // NAME32 - if (content.mType == ESM::AI_Activate) - { - content.mActivate.mName.assign(value.toString().toUtf8().constData()); - break; - } - else - return; // return without saving - case 7: // NAME32 - if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - { - content.mTarget.mId.assign(value.toString().toUtf8().constData()); - break; - } - else - return; // return without saving + case 6: + case 7: case 8: - if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - { - content.mCellName = std::string(value.toString().toUtf8().constData()); - break; - } - else - return; // return without saving case 9: - if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); - else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); - else - return; // return without saving case 10: - if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); - else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); - else - return; // return without saving case 11: + if (content.mType == ESM::AI_Wander) + content.mWander.mIdle[subColIndex-4] = static_cast(value.toInt()); + else + return; // return without saving + + break; // always save + case 12: + if (content.mType == ESM::AI_Wander) + content.mWander.mShouldRepeat = static_cast(value.toInt()); + else + return; // return without saving + + break; // always save + case 13: // NAME32 + if (content.mType == ESM::AI_Activate) + content.mActivate.mName.assign(value.toString().toUtf8().constData()); + else + return; // return without saving + + break; // always save + case 14: // NAME32 + if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mId.assign(value.toString().toUtf8().constData()); + else + return; // return without saving + + break; // always save + case 15: + if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mCellName = std::string(value.toString().toUtf8().constData()); + else + return; // return without saving + + break; // always save + case 16: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving + + break; // always save + case 17: + if (content.mType == ESM::AI_Travel) + content.mTravel.mZ = value.toFloat(); + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mZ = value.toFloat(); + else + return; // return without saving + + break; // always save + case 18: + if (content.mType == ESM::AI_Travel) + content.mTravel.mZ = value.toFloat(); + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mZ = value.toFloat(); + else + return; // return without saving + + break; // always save default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } @@ -1718,7 +1791,7 @@ namespace CSMWorld virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { - return 12; + return 19; } virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const @@ -1911,7 +1984,7 @@ namespace CSMWorld int index) const { if (column==mLevList.mLevList || column == mLevList.mNestedListLevList) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return BaseRefIdAdapter::getData (column, data, index); } diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index cda19c87b..a5e813338 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -10,6 +10,7 @@ #include "columns.hpp" #include "nestedtablewrapper.hpp" #include "nestedcoladapterimp.hpp" +#include "data.hpp" CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag, bool editable, bool userEditable) @@ -36,7 +37,7 @@ const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdapter (UniversalI return *iter->second; } -CSMWorld::RefIdCollection::RefIdCollection() +CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) { BaseColumns baseColumns; @@ -71,6 +72,21 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_CoinValue, ColumnBase::Display_Integer)); inventoryColumns.mValue = &mColumns.back(); + IngredientColumns ingredientColumns (inventoryColumns); + mColumns.push_back (RefIdColumn (Columns::ColumnId_EffectList, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + ingredientColumns.mEffects = &mColumns.back(); + std::map ingredientEffectsMap; + ingredientEffectsMap.insert(std::make_pair(UniversalId::Type_Ingredient, + new IngredEffectRefIdAdapter ())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), ingredientEffectsMap)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_IngredEffectId)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); + // nested table PotionColumns potionColumns (inventoryColumns); mColumns.push_back (RefIdColumn (Columns::ColumnId_EffectList, @@ -83,9 +99,9 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mColumns.back().addColumn( @@ -93,13 +109,13 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); EnchantableColumns enchantableColumns (inventoryColumns); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Enchantment, ColumnBase::Display_String)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_Enchantment, ColumnBase::Display_Enchantment)); enchantableColumns.mEnchantment = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_EnchantmentPoints, ColumnBase::Display_Integer)); enchantableColumns.mEnchantmentPoints = &mColumns.back(); @@ -113,8 +129,6 @@ CSMWorld::RefIdCollection::RefIdCollection() ActorColumns actorsColumns (nameColumns); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Ai, ColumnBase::Display_Boolean)); - actorsColumns.mHasAi = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_Integer)); actorsColumns.mHello = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_Integer)); @@ -135,7 +149,7 @@ CSMWorld::RefIdCollection::RefIdCollection() new NestedInventoryRefIdAdapter (UniversalId::Type_Creature))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), inventoryMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_ItemCount, CSMWorld::ColumnBase::Display_Integer)); @@ -145,12 +159,21 @@ CSMWorld::RefIdCollection::RefIdCollection() actorsColumns.mSpells = &mColumns.back(); std::map spellsMap; spellsMap.insert(std::make_pair(UniversalId::Type_Npc, - new NestedSpellRefIdAdapter (UniversalId::Type_Npc))); + new NestedSpellRefIdAdapter (UniversalId::Type_Npc, data))); spellsMap.insert(std::make_pair(UniversalId::Type_Creature, - new NestedSpellRefIdAdapter (UniversalId::Type_Creature))); + new NestedSpellRefIdAdapter (UniversalId::Type_Creature, data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), spellsMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_Spell)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellType, CSMWorld::ColumnBase::Display_SpellType, false/*editable*/, false/*user editable*/)); + // creatures do not have below columns + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellSrc, CSMWorld::ColumnBase::Display_Boolean, false, false)); // from race + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellCost, CSMWorld::ColumnBase::Display_Integer, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellChance, CSMWorld::ColumnBase::Display_Integer/*Percent*/, false, false)); // Nested table mColumns.push_back(RefIdColumn (Columns::ColumnId_NpcDestinations, @@ -163,7 +186,7 @@ CSMWorld::RefIdCollection::RefIdCollection() new NestedTravelRefIdAdapter (UniversalId::Type_Creature))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), destMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_DestinationCell, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_DestinationCell, CSMWorld::ColumnBase::Display_Cell)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PosX, CSMWorld::ColumnBase::Display_Float)); mColumns.back().addColumn( @@ -195,10 +218,26 @@ CSMWorld::RefIdCollection::RefIdCollection() new RefIdColumn (Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderIdle, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_YesNo)); + new RefIdColumn (Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle5, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String)); mColumns.back().addColumn( @@ -289,7 +328,7 @@ CSMWorld::RefIdCollection::RefIdCollection() new NestedInventoryRefIdAdapter (UniversalId::Type_Container))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), contMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_InventoryItemId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_ItemCount, CSMWorld::ColumnBase::Display_Integer)); @@ -297,21 +336,10 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_CreatureType, ColumnBase::Display_CreatureType)); creatureColumns.mType = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_SoulPoints, ColumnBase::Display_Integer)); - creatureColumns.mSoul = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float)); creatureColumns.mScale = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_OriginalCreature, ColumnBase::Display_String)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_OriginalCreature, ColumnBase::Display_Creature)); creatureColumns.mOriginal = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_CombatState, ColumnBase::Display_Integer)); - creatureColumns.mCombat = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_MagicState, ColumnBase::Display_Integer)); - creatureColumns.mMagic = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_StealthState, ColumnBase::Display_Integer)); - creatureColumns.mStealth = &mColumns.back(); static const struct { @@ -321,7 +349,6 @@ CSMWorld::RefIdCollection::RefIdCollection() { { Columns::ColumnId_Biped, ESM::Creature::Bipedal }, { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon }, - { Columns::ColumnId_NoMovement, ESM::Creature::None }, { Columns::ColumnId_Swims, ESM::Creature::Swims }, { Columns::ColumnId_Flies, ESM::Creature::Flies }, { Columns::ColumnId_Walks, ESM::Creature::Walks }, @@ -351,6 +378,59 @@ CSMWorld::RefIdCollection::RefIdCollection() creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn)); + // Nested table + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureAttributes, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + creatureColumns.mAttributes = &mColumns.back(); + std::map creaAttrMap; + creaAttrMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttributesRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), creaAttrMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_AttributeValue, CSMWorld::ColumnBase::Display_Integer)); + + // Nested table + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureAttack, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + creatureColumns.mAttacks = &mColumns.back(); + std::map attackMap; + attackMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttackRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attackMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_CreatureAttack, CSMWorld::ColumnBase::Display_Integer, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MinAttack, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MaxAttack, CSMWorld::ColumnBase::Display_Integer)); + + // Nested list + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureMisc, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); + creatureColumns.mMisc = &mColumns.back(); + std::map creaMiscMap; + creaMiscMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureMiscRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), creaMiscMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SoulPoints, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_CombatState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MagicState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_StealthState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_OpenSound, ColumnBase::Display_Sound)); const RefIdColumn *openSound = &mColumns.back(); @@ -381,7 +461,7 @@ CSMWorld::RefIdCollection::RefIdCollection() { Columns::ColumnId_Portable, ESM::Light::Carry }, { Columns::ColumnId_NegativeLight, ESM::Light::Negative }, { Columns::ColumnId_Flickering, ESM::Light::Flicker }, - { Columns::ColumnId_SlowFlickering, ESM::Light::Flicker }, + { Columns::ColumnId_SlowFlickering, ESM::Light::FlickerSlow }, { Columns::ColumnId_Pulsing, ESM::Light::Pulse }, { Columns::ColumnId_SlowPulsing, ESM::Light::PulseSlow }, { Columns::ColumnId_Fire, ESM::Light::Fire }, @@ -409,10 +489,10 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_Faction, ColumnBase::Display_Faction)); npcColumns.mFaction = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::Columnid_Hair, ColumnBase::Display_String)); + mColumns.push_back (RefIdColumn (Columns::Columnid_Hair, ColumnBase::Display_BodyPart)); npcColumns.mHair = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Head, ColumnBase::Display_String)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_Head, ColumnBase::Display_BodyPart)); npcColumns.mHead = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Female, ColumnBase::Display_Boolean)); @@ -437,10 +517,10 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mAttributes = &mColumns.back(); std::map attrMap; - attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter())); + attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); + new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); @@ -449,10 +529,10 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mSkills = &mColumns.back(); std::map skillsMap; - skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter())); + skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); + new RefIdColumn (Columns::ColumnId_Skill, CSMWorld::ColumnBase::Display_SkillId, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); @@ -461,18 +541,19 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); npcColumns.mMisc = &mColumns.back(); std::map miscMap; - miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter())); + miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcHealth, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcMana, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcFatigue, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcDisposition, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( @@ -480,7 +561,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcRank, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcGold, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcPersistence, CSMWorld::ColumnBase::Display_Boolean)); @@ -539,9 +620,9 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_PartRefType, CSMWorld::ColumnBase::Display_PartRefType)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PartRefMale, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_PartRefMale, CSMWorld::ColumnBase::Display_BodyPart)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_PartRefFemale, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_PartRefFemale, CSMWorld::ColumnBase::Display_BodyPart)); LevListColumns levListColumns (baseColumns); @@ -556,7 +637,7 @@ CSMWorld::RefIdCollection::RefIdCollection() new NestedLevListRefIdAdapter (UniversalId::Type_ItemLevelledList))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), levListMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_LevelledItemId, CSMWorld::ColumnBase::Display_String)); + new RefIdColumn (Columns::ColumnId_LevelledItemId, CSMWorld::ColumnBase::Display_Referenceable)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_LevelledItemLevel, CSMWorld::ColumnBase::Display_Integer)); @@ -596,7 +677,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mAdapters.insert (std::make_pair (UniversalId::Type_Door, new DoorRefIdAdapter (nameColumns, openSound, closeSound))); mAdapters.insert (std::make_pair (UniversalId::Type_Ingredient, - new InventoryRefIdAdapter (UniversalId::Type_Ingredient, inventoryColumns))); + new IngredientRefIdAdapter (ingredientColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_CreatureLevelledList, new LevelledListRefIdAdapter ( UniversalId::Type_CreatureLevelledList, levListColumns))); @@ -609,7 +690,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, new MiscRefIdAdapter (inventoryColumns, key))); mAdapters.insert (std::make_pair (UniversalId::Type_Npc, - new NpcRefIdAdapter (npcColumns))); + new NpcRefIdAdapter (npcColumns, data))); mAdapters.insert (std::make_pair (UniversalId::Type_Probe, new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Repair, @@ -914,3 +995,8 @@ const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdap } throw std::runtime_error("No such column in the nestedadapters"); } + +void CSMWorld::RefIdCollection::copyTo (int index, RefIdCollection& target) const +{ + mData.copyTo (index, target.mData); +} diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 4d511d12d..4db39348e 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -20,6 +20,7 @@ namespace CSMWorld class RefIdAdapter; struct NestedTableWrapperBase; class NestedRefIdAdapterBase; + class Data; class RefIdColumn : public NestableColumn { @@ -56,7 +57,8 @@ namespace CSMWorld public: - RefIdCollection(); + // race, classes and skills required for NPC autocalc + RefIdCollection(const Data& data); virtual ~RefIdCollection(); @@ -138,6 +140,7 @@ namespace CSMWorld void save (int index, ESM::ESMWriter& writer) const; const RefIdData& getDataSet() const; //I can't figure out a better name for this one :( + void copyTo (int index, RefIdCollection& target) const; }; } diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 7f5c25f36..7696c3763 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -1,7 +1,7 @@ - #include "refiddata.hpp" #include +#include #include @@ -345,7 +345,7 @@ const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStati return mStatics; } -void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) +void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) { std::map::iterator iter = mRecordContainers.find (type); @@ -358,3 +358,16 @@ void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::U mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), LocalIndex (iter->second->getSize()-1, type))); } + +void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const +{ + LocalIndex localIndex = globalToLocalIndex (index); + + RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second; + + std::string id = source->getId (localIndex.first); + + std::auto_ptr newRecord (source->getRecord (localIndex.first).modifiedCopy()); + + target.insertRecord (*newRecord, localIndex.second, id); +} diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 85d16a6eb..8909ae4fb 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -210,7 +210,8 @@ namespace CSMWorld void erase (int index, int count); - void insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id); + void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, + const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; @@ -252,11 +253,9 @@ namespace CSMWorld const RefIdDataContainer& getProbes() const; const RefIdDataContainer& getRepairs() const; const RefIdDataContainer& getStatics() const; + + void copyTo (int index, RefIdData& target) const; }; } #endif - - - - diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index f63426c04..10c67c909 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,4 +1,3 @@ - #include "regionmap.hpp" #include @@ -334,9 +333,9 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion)); if (iter!=mColours.end()) - return QBrush ( - QColor (iter->second>>24, (iter->second>>16) & 255, (iter->second>>8) & 255, - iter->second & 255)); + return QBrush (QColor (iter->second & 0xff, + (iter->second >> 8) & 0xff, + (iter->second >> 16) & 0xff)); if (cell->second.mRegion.empty()) return QBrush (Qt::Dense6Pattern); // no region diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 8c9439890..8bfd83248 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -1,4 +1,3 @@ - #include "resources.hpp" #include diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index deddd83b5..1b4770197 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -1,4 +1,3 @@ - #include "resourcesmanager.hpp" #include diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 2cd44781a..5227ec3e6 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -1,4 +1,3 @@ - #include "resourcetable.hpp" #include diff --git a/apps/opencs/model/world/scope.cpp b/apps/opencs/model/world/scope.cpp index 6e4ce4c02..b026c34c3 100644 --- a/apps/opencs/model/world/scope.cpp +++ b/apps/opencs/model/world/scope.cpp @@ -1,4 +1,3 @@ - #include "scope.hpp" #include diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index a8c2c9452..f644ad37a 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -1,4 +1,3 @@ - #include "scriptcontext.hpp" #include @@ -40,8 +39,6 @@ char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const std::pair CSMWorld::ScriptContext::getMemberType (const std::string& name, const std::string& id) const { - /// \todo invalidate locals cache on change to scripts - std::string id2 = Misc::StringUtils::lowerCase (id); int index = mData.getScripts().searchId (id2); @@ -121,3 +118,18 @@ void CSMWorld::ScriptContext::clear() mIdsUpdated = false; mLocals.clear(); } + +bool CSMWorld::ScriptContext::clearLocals (const std::string& script) +{ + std::map::iterator iter = + mLocals.find (Misc::StringUtils::lowerCase (script)); + + if (iter!=mLocals.end()) + { + mLocals.erase (iter); + mIdsUpdated = false; + return true; + } + + return false; +} diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp index 29ee42645..2cd59f070 100644 --- a/apps/opencs/model/world/scriptcontext.hpp +++ b/apps/opencs/model/world/scriptcontext.hpp @@ -46,6 +46,9 @@ namespace CSMWorld void clear(); ///< Remove all cached data. + + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); }; } diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index d40e0c217..101bbf9ba 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -264,6 +264,8 @@ namespace { CSMWorld::UniversalId::Type_Texture, CSMWorld::ColumnBase::Display_Texture }, { CSMWorld::UniversalId::Type_Video, CSMWorld::ColumnBase::Display_Video }, { CSMWorld::UniversalId::Type_Global, CSMWorld::ColumnBase::Display_GlobalVariable }, + { CSMWorld::UniversalId::Type_BodyPart, CSMWorld::ColumnBase::Display_BodyPart }, + { CSMWorld::UniversalId::Type_Enchantment, CSMWorld::ColumnBase::Display_Enchantment }, { CSMWorld::UniversalId::Type_None, CSMWorld::ColumnBase::Display_None } // end marker }; diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 06d252435..a42e97561 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -1,4 +1,3 @@ - #ifndef TABLEMIMEDATA_H #define TABLEMIMEDATA_H diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index e496fe79b..4c6601e52 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -1,4 +1,3 @@ - #include "universalid.hpp" #include @@ -56,6 +55,9 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Meta Data Table", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Texture Table", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Land Table", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; @@ -120,6 +122,9 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; @@ -363,8 +368,8 @@ std::vector CSMWorld::UniversalId::listTypes (int c for (int i=0; sIndexArg[i].mName; ++i) if (sIndexArg[i].mClass & classes) list.push_back (sIndexArg[i].mType); - - return list; + + return list; } CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 0a9fa3847..a05843597 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -131,6 +131,12 @@ namespace CSMWorld Type_StartScripts, Type_StartScript, Type_Search, + Type_MetaDatas, + Type_MetaData, + Type_LandTextures, + Type_LandTexture, + Type_Lands, + Type_Land, Type_RunLog }; diff --git a/apps/opencs/model/world/usertype.hpp b/apps/opencs/model/world/usertype.hpp new file mode 100644 index 000000000..23b5c90a4 --- /dev/null +++ b/apps/opencs/model/world/usertype.hpp @@ -0,0 +1,44 @@ +#ifndef CSM_WORLD_USERTYPE_H +#define CSM_WORLD_USERTYPE_H + +#include +#include + +namespace CSMWorld +{ + // Idea from ksimons @stackoverflow + class UserInt + { + public: + + UserInt() : mValue(0) { } + UserInt(int value) : mValue(value) { } + UserInt(const UserInt &other) { mValue = other.mValue; } + ~UserInt() { } + int value() const { return mValue; } + + private: + + int mValue; + }; + + class UserFloat + { + public: + + UserFloat() : mValue(0) { } + UserFloat(float value) : mValue(value) { } + UserFloat(const UserFloat &other) { mValue = other.mValue; } + ~UserFloat() { } + float value() const { return mValue; } + + private: + + float mValue; + }; +} + +Q_DECLARE_METATYPE(CSMWorld::UserInt) +Q_DECLARE_METATYPE(CSMWorld::UserFloat) + +#endif // CSM_WORLD_USERTYPE_H diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 6571ad7c8..620d853ac 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -1,4 +1,3 @@ - #include "adjusterwidget.hpp" #include @@ -64,6 +63,7 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) QString message; mValid = (!name.isEmpty()); + bool warning = false; if (!mValid) { @@ -106,13 +106,14 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { /// \todo add an user setting to make this an error. message += "

A file with the same name already exists. If you continue, it will be overwritten."; + warning = true; } } } mMessage->setText (message); mIcon->setPixmap (style()->standardIcon ( - mValid ? QStyle::SP_MessageBoxInformation : QStyle::SP_MessageBoxWarning). + mValid ? (warning ? QStyle::SP_MessageBoxWarning : QStyle::SP_MessageBoxInformation) : QStyle::SP_MessageBoxCritical). pixmap (QSize (16, 16))); emit stateChanged (mValid); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index eeec81109..b6f4aaec3 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -33,6 +33,11 @@ void CSVDoc::FileDialog::addFiles(const QString &path) mSelector->addFiles(path); } +void CSVDoc::FileDialog::clearFiles() +{ + mSelector->clearFiles(); +} + QStringList CSVDoc::FileDialog::selectedFilePaths() { QStringList filePaths; @@ -105,7 +110,6 @@ void CSVDoc::FileDialog::buildNewFileView() connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), this, SLOT (slotUpdateAcceptButton(const QString &, bool))); - } ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); @@ -139,7 +143,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(int) { QString name = ""; - if (mAction == ContentAction_New) + if (mFileWidget && mAction == ContentAction_New) name = mFileWidget->getName(); slotUpdateAcceptButton (name, true); diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 3c23a5cb5..648836565 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -45,6 +45,7 @@ namespace CSVDoc void showDialog (ContentAction action); void addFiles (const QString &path); + void clearFiles (); QString filename() const; QStringList selectedFilePaths(); diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index f18fe695a..110d561c1 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -1,4 +1,3 @@ - #include "filewidget.hpp" #include @@ -56,3 +55,11 @@ void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) { mType->setVisible(visible); } + +void CSVDoc::FileWidget::setName (const std::string& text) +{ + QString text2 = QString::fromUtf8 (text.c_str()); + + mInput->setText (text2); + textChanged (text2); +} diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp index ff09d71a3..ab06f37f1 100644 --- a/apps/opencs/view/doc/filewidget.hpp +++ b/apps/opencs/view/doc/filewidget.hpp @@ -3,6 +3,8 @@ #include +#include + class QLabel; class QString; class QLineEdit; @@ -29,6 +31,8 @@ namespace CSVDoc void extensionLabelIsVisible(bool visible); + void setName (const std::string& text); + private slots: void textChanged (const QString& text); diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.cpp b/apps/opencs/view/doc/globaldebugprofilemenu.cpp index b88381385..f0d9655dd 100644 --- a/apps/opencs/view/doc/globaldebugprofilemenu.cpp +++ b/apps/opencs/view/doc/globaldebugprofilemenu.cpp @@ -1,4 +1,3 @@ - #include "globaldebugprofilemenu.hpp" #include diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 30235d0f5..713295d70 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -1,4 +1,3 @@ - #include "loader.hpp" #include diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp index 98681c499..b3e2a4f63 100644 --- a/apps/opencs/view/doc/newgame.cpp +++ b/apps/opencs/view/doc/newgame.cpp @@ -1,4 +1,3 @@ - #include "newgame.hpp" #include @@ -66,3 +65,9 @@ void CSVDoc::NewGameDialogue::create() { emit createRequest (mAdjusterWidget->getPath()); } + +void CSVDoc::NewGameDialogue::reject() +{ + emit cancelCreateGame (); + QDialog::reject(); +} diff --git a/apps/opencs/view/doc/newgame.hpp b/apps/opencs/view/doc/newgame.hpp index 9ad7ea169..70e9d684b 100644 --- a/apps/opencs/view/doc/newgame.hpp +++ b/apps/opencs/view/doc/newgame.hpp @@ -36,11 +36,15 @@ namespace CSVDoc void createRequest (const boost::filesystem::path& file); + void cancelCreateGame (); + private slots: void stateChanged (bool valid); void create(); + + void reject(); }; } diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp index 95cbf012d..e5c1e7b89 100644 --- a/apps/opencs/view/doc/operation.cpp +++ b/apps/opencs/view/doc/operation.cpp @@ -19,6 +19,7 @@ void CSVDoc::Operation::updateLabel (int threads) case CSMDoc::State_Saving: name = "saving"; break; case CSMDoc::State_Verifying: name = "verifying"; break; case CSMDoc::State_Searching: name = "searching"; break; + case CSMDoc::State_Merging: name = "merging"; break; } std::ostringstream stream; @@ -122,7 +123,7 @@ void CSVDoc::Operation::setBarColor (int type) bottomColor = "#9ECB2D"; //green gloss break; - case CSMDoc::State_Compiling: + case CSMDoc::State_Merging: topColor = "#F3E2C7"; midTopColor = "#C19E67"; diff --git a/apps/opencs/view/doc/operations.cpp b/apps/opencs/view/doc/operations.cpp index 7ee4b8726..934c94a96 100644 --- a/apps/opencs/view/doc/operations.cpp +++ b/apps/opencs/view/doc/operations.cpp @@ -19,6 +19,7 @@ CSVDoc::Operations::Operations() setVisible (false); setFixedHeight (widgetContainer->height()); setTitleBarWidget (new QWidget (this)); + setObjectName (QString("operations")); // needed to suppress warning while saving window state } void CSVDoc::Operations::setProgress (int current, int max, int type, int threads) diff --git a/apps/opencs/view/doc/runlogsubview.cpp b/apps/opencs/view/doc/runlogsubview.cpp index 129396999..2b7182228 100644 --- a/apps/opencs/view/doc/runlogsubview.cpp +++ b/apps/opencs/view/doc/runlogsubview.cpp @@ -1,4 +1,3 @@ - #include "runlogsubview.hpp" #include diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 58a46c603..a9d697f1c 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -1,4 +1,3 @@ - #include "startup.hpp" #include diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index f4f0c6afe..84b2c6257 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -26,6 +26,8 @@ CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) /// \todo add a button to the title bar that clones this sub view setWindowTitle (QString::fromUtf8 (mUniversalId.toString().c_str())); + // set object name to suppress warning while saving window state + setObjectName (QString::fromUtf8 (mUniversalId.toString().c_str())); setAttribute(Qt::WA_DeleteOnClose); } @@ -45,6 +47,7 @@ void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) { mUniversalId = id; setWindowTitle (QString::fromUtf8(mUniversalId.toString().c_str())); + emit universalIdChanged (mUniversalId); } void CSVDoc::SubView::closeEvent (QCloseEvent *event) diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index b323f9ed9..189bb40eb 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -68,6 +68,8 @@ namespace CSVDoc void updateSubViewIndicies (SubView *view = 0); + void universalIdChanged (const CSMWorld::UniversalId& universalId); + protected slots: void closeRequest(); diff --git a/apps/opencs/view/doc/subviewfactory.cpp b/apps/opencs/view/doc/subviewfactory.cpp index 3137f7e32..82a8aeb05 100644 --- a/apps/opencs/view/doc/subviewfactory.cpp +++ b/apps/opencs/view/doc/subviewfactory.cpp @@ -1,4 +1,3 @@ - #include "subviewfactory.hpp" #include diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 5e3df2739..1f9762b31 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -37,6 +37,23 @@ void CSVDoc::View::closeEvent (QCloseEvent *event) event->ignore(); else { + if (mSaveWindowState) + { + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + if (isMaximized() && mXWorkaround) + { + userSettings.setDefinitions("window/maximized", (QStringList() << "true")); + userSettings.saveDefinitions(); // store previously saved geometry & state + } + else + { + userSettings.value("window/geometry", saveGeometry()); + userSettings.value("window/state", saveState()); + userSettings.setDefinitions("window/maximized", (QStringList() << "false")); + userSettings.saveDefinitions(); + } + } + // closeRequest() returns true if last document mViewManager.removeDocAndView(mDocument); } @@ -66,10 +83,18 @@ void CSVDoc::View::setupFileMenu() connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); file->addAction (mVerify); + mMerge = new QAction (tr ("Merge"), this); + connect (mMerge, SIGNAL (triggered()), this, SLOT (merge())); + file->addAction (mMerge); + QAction *loadErrors = new QAction (tr ("Load Error Log"), this); connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); file->addAction (loadErrors); + QAction *meta = new QAction (tr ("Meta Data"), this); + connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); + file->addAction (meta); + QAction *close = new QAction (tr ("&Close"), this); connect (close, SIGNAL (triggered()), this, SLOT (close())); file->addAction(close); @@ -148,6 +173,10 @@ void CSVDoc::View::setupWorldMenu() connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); world->addAction (grid); + QAction *land = new QAction (tr ("Lands"), this); + connect (land, SIGNAL (triggered()), this, SLOT (addLandSubView())); + world->addAction (land); + world->addSeparator(); // items that don't represent single record lists follow here QAction *regionMap = new QAction (tr ("Region Map"), this); @@ -267,6 +296,10 @@ void CSVDoc::View::setupAssetsMenu() connect (textures, SIGNAL (triggered()), this, SLOT (addTexturesSubView())); assets->addAction (textures); + QAction *land = new QAction (tr ("Land Textures"), this); + connect (land, SIGNAL (triggered()), this, SLOT (addLandTextureSubView())); + assets->addAction (land); + QAction *videos = new QAction (tr ("Videos"), this); connect (videos, SIGNAL (triggered()), this, SLOT (addVideosSubView())); assets->addAction (videos); @@ -389,26 +422,39 @@ void CSVDoc::View::updateActions() mGlobalDebugProfileMenu->updateActions (running); mStopDebug->setEnabled (running); + + mMerge->setEnabled (mDocument->getContentFiles().size()>1 && + !(mDocument->getState() & CSMDoc::State_Merging)); } CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), - mViewTotal (totalViews), mScroll(0), mScrollbarOnly(false) + mViewTotal (totalViews), mScroll(0), mScrollbarOnly(false), + mSaveWindowState(false), mXWorkaround(false) { - int width = CSMSettings::UserSettings::instance().settingValue - ("window/default-width").toInt(); + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + mXWorkaround = userSettings.settingValue ("window/x-save-state-workaround").toStdString() == "true"; + mSaveWindowState = userSettings.setting ("window/save-state", "true").toStdString() == "true"; - int height = CSMSettings::UserSettings::instance().settingValue - ("window/default-height").toInt(); + // check if saved state should be used and whether it is the first time + if (mSaveWindowState && userSettings.hasSettingDefinitions ("window/maximized")) + { + restoreGeometry(userSettings.value("window/geometry").toByteArray()); + restoreState(userSettings.value("window/state").toByteArray()); - width = std::max(width, 300); - height = std::max(height, 300); + if (mXWorkaround && userSettings.settingValue ("window/maximized").toStdString() == "true") + setWindowState(windowState() | Qt::WindowMaximized); + } + else + { + int width = userSettings.settingValue ("window/default-width").toInt(); + int height = userSettings.settingValue ("window/default-height").toInt(); - // trick to get the window decorations and their sizes - show(); - hide(); - resize (width - (frameGeometry().width() - geometry().width()), - height - (frameGeometry().height() - geometry().height())); + width = std::max(width, 300); + height = std::max(height, 300); + + resize (width, height); + } mSubViewWindow.setDockOptions (QMainWindow::AllowNestedDocks); @@ -428,6 +474,8 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mOperations = new Operations; addDockWidget (Qt::BottomDockWidgetArea, mOperations); + setContextMenuPolicy(Qt::NoContextMenu); + updateTitle(); setupUi(); @@ -471,6 +519,7 @@ void CSVDoc::View::updateDocumentState() static const int operations[] = { CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching, + CSMDoc::State_Merging, -1 // end marker }; @@ -519,6 +568,10 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin } } + if (mScroll) + QObject::connect(mScroll->horizontalScrollBar(), + SIGNAL(rangeChanged(int,int)), this, SLOT(moveScrollBarToEnd(int,int))); + // User setting for limiting the number of sub views per top level view. // Automatically open a new top level view if this number is exceeded // @@ -590,12 +643,6 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin mSubViewWindow.setMinimumWidth(mSubViewWindow.width()+minWidth); move(0, y()); } - - // Make the new subview visible, setFocus() or raise() don't seem to work - // On Ubuntu the scrollbar does not go right to the end, even if using - // mScroll->horizontalScrollBar()->setValue(mScroll->horizontalScrollBar()->maximum()); - if (mSubViewWindow.width() > rect.width()) - mScroll->horizontalScrollBar()->setValue(mSubViewWindow.width()); } mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); @@ -618,6 +665,17 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin view->useHint (hint); } +void CSVDoc::View::moveScrollBarToEnd(int min, int max) +{ + if (mScroll) + { + mScroll->horizontalScrollBar()->setValue(max); + + QObject::disconnect(mScroll->horizontalScrollBar(), + SIGNAL(rangeChanged(int,int)), this, SLOT(moveScrollBarToEnd(int,int))); + } +} + void CSVDoc::View::newView() { mViewManager.addView (mDocument); @@ -798,6 +856,16 @@ void CSVDoc::View::addPathgridSubView() addSubView (CSMWorld::UniversalId::Type_Pathgrids); } +void CSVDoc::View::addLandTextureSubView() +{ + addSubView (CSMWorld::UniversalId::Type_LandTextures); +} + +void CSVDoc::View::addLandSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Lands); +} + void CSVDoc::View::addStartScriptsSubView() { addSubView (CSMWorld::UniversalId::Type_StartScripts); @@ -808,6 +876,11 @@ void CSVDoc::View::addSearchSubView() addSubView (mDocument->newSearch()); } +void CSVDoc::View::addMetaDataSubView() +{ + addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_MetaData, "sys::meta")); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); @@ -841,6 +914,12 @@ void CSVDoc::View::updateUserSetting (const QString &name, const QStringList &li if (name=="window/hide-subview") updateSubViewIndicies (0); + if (name == "window/save-state") + mSaveWindowState = list.at(0) == "true"; + + if (name == "window/x-save-state-workaround") + mXWorkaround = list.at(0) == "true"; + foreach (SubView *subView, mSubViews) { subView->updateUserSetting (name, list); @@ -932,6 +1011,36 @@ void CSVDoc::View::closeRequest (SubView *subView) mViewManager.removeDocAndView (mDocument); } +// for more reliable detetion of isMaximized(), see https://bugreports.qt.io/browse/QTBUG-30085 +void CSVDoc::View::saveWindowState() +{ + if (!isMaximized()) + { + // update but don't save to config file yet + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + userSettings.value("window/geometry", saveGeometry()); + userSettings.value("window/state", saveState()); + } +} + +// For X11 where Qt does not remember pre-maximised state +void CSVDoc::View::moveEvent (QMoveEvent *event) +{ + if (mXWorkaround && mSaveWindowState) + QMetaObject::invokeMethod(this, "saveWindowState", Qt::QueuedConnection); + + QMainWindow::moveEvent(event); +} + +// For X11 where Qt does not remember pre-maximised state +void CSVDoc::View::resizeEvent (QResizeEvent *event) +{ + if (mXWorkaround && mSaveWindowState) + QMetaObject::invokeMethod(this, "saveWindowState", Qt::QueuedConnection); + + QMainWindow::resizeEvent(event); +} + void CSVDoc::View::updateScrollbar() { QRect rect; @@ -954,3 +1063,8 @@ void CSVDoc::View::updateScrollbar() else mSubViewWindow.setMinimumWidth(0); } + +void CSVDoc::View::merge() +{ + emit mergeDocument (mDocument); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 1d44cb7f5..f83e49627 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -43,6 +43,7 @@ namespace CSVDoc QAction *mVerify; QAction *mShowStatusBar; QAction *mStopDebug; + QAction *mMerge; std::vector mEditingActions; Operations *mOperations; SubViewFactoryManager mSubViewFactory; @@ -51,6 +52,9 @@ namespace CSVDoc QScrollArea *mScroll; bool mScrollbarOnly; + bool mSaveWindowState; + bool mXWorkaround; + // not implemented View (const View&); @@ -114,8 +118,10 @@ namespace CSVDoc Operations *getOperations() const; - /// Function called by view manager when user preferences are updated - void updateEditorSetting (const QString &, const QString &); + protected: + + virtual void moveEvent(QMoveEvent * event); + virtual void resizeEvent(QResizeEvent * event); signals: @@ -129,6 +135,8 @@ namespace CSVDoc void editSettingsRequest(); + void mergeDocument (CSMDoc::Document *document); + public slots: void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = ""); @@ -220,10 +228,16 @@ namespace CSVDoc void addPathgridSubView(); + void addLandTextureSubView(); + + void addLandSubView(); + void addStartScriptsSubView(); void addSearchSubView(); + void addMetaDataSubView(); + void toggleShowStatusBar (bool show); void loadErrorLog(); @@ -233,6 +247,12 @@ namespace CSVDoc void stop(); void closeRequest (SubView *subView); + + void saveWindowState(); + + void moveScrollBarToEnd(int min, int max); + + void merge(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 97b7aac19..766051bca 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -1,30 +1,31 @@ - #include "viewmanager.hpp" +#include #include #include #include +#include +#include #include "../../model/doc/documentmanager.hpp" #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/universalid.hpp" +#include "../../model/world/idcompletionmanager.hpp" #include "../world/util.hpp" #include "../world/enumdelegate.hpp" #include "../world/vartypedelegate.hpp" #include "../world/recordstatusdelegate.hpp" #include "../world/idtypedelegate.hpp" +#include "../world/idcompletiondelegate.hpp" +#include "../world/colordelegate.hpp" #include "../../model/settings/usersettings.hpp" #include "view.hpp" -#include -#include -#include - void CSVDoc::ViewManager::updateIndices() { std::map > documents; @@ -60,6 +61,17 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) mDelegateFactories->add (CSMWorld::ColumnBase::Display_RefRecordType, new CSVWorld::IdTypeDelegateFactory()); + mDelegateFactories->add (CSMWorld::ColumnBase::Display_Colour, + new CSVWorld::ColorDelegateFactory()); + + std::vector idCompletionColumns = CSMWorld::IdCompletionManager::getDisplayTypes(); + for (std::vector::const_iterator current = idCompletionColumns.begin(); + current != idCompletionColumns.end(); + ++current) + { + mDelegateFactories->add(*current, new CSVWorld::IdCompletionDelegateFactory()); + } + struct Mapping { CSMWorld::ColumnBase::Display mDisplay; @@ -84,16 +96,17 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_MeshType, CSMWorld::Columns::ColumnId_MeshType, false }, { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true }, { CSMWorld::ColumnBase::Display_SoundGeneratorType, CSMWorld::Columns::ColumnId_SoundGeneratorType, false }, - { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, true }, - { CSMWorld::ColumnBase::Display_SkillImpact, CSMWorld::Columns::ColumnId_SkillImpact, true }, + { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, false }, + { CSMWorld::ColumnBase::Display_SkillId, CSMWorld::Columns::ColumnId_Skill, true }, { CSMWorld::ColumnBase::Display_EffectRange, CSMWorld::Columns::ColumnId_EffectRange, false }, { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, - { CSMWorld::ColumnBase::Display_YesNo, CSMWorld::Columns::ColumnId_AiWanderRepeat, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, - { CSMWorld::ColumnBase::Display_RaceSkill, CSMWorld::Columns::ColumnId_RaceSkill, true }, + { CSMWorld::ColumnBase::Display_IngredEffectId, CSMWorld::Columns::ColumnId_EffectId, true }, + { CSMWorld::ColumnBase::Display_EffectSkill, CSMWorld::Columns::ColumnId_Skill, false }, + { CSMWorld::ColumnBase::Display_EffectAttribute, CSMWorld::Columns::ColumnId_Attribute, false }, }; for (std::size_t i=0; iisMaximized()) + { + userSettings.setDefinitions("window/maximized", (QStringList() << "true")); + userSettings.saveDefinitions(); // store previously saved geometry & state + } + else + { + userSettings.value("window/geometry", mViews.back()->saveGeometry()); + userSettings.value("window/state", mViews.back()->saveState()); + userSettings.setDefinitions("window/maximized", (QStringList() << "false")); + userSettings.saveDefinitions(); + } + } + mDocumentManager.removeDocument(document); mViews = remainingViews; } diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index cdd5ac768..70431107f 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -77,6 +77,8 @@ namespace CSVDoc void editSettingsRequest(); + void mergeDocument (CSMDoc::Document *document); + public slots: void exitApplication (CSVDoc::View *view); diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index bc7f9b5a1..600fa4f3b 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -1,4 +1,3 @@ - #include "editwidget.hpp" #include @@ -6,14 +5,17 @@ #include #include "../../model/world/data.hpp" +#include "../../model/world/idtablebase.hpp" +#include "../../model/world/columns.hpp" CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) -: QLineEdit (parent), mParser (data) +: QLineEdit (parent), mParser (data), mIsEmpty(true) { mPalette = palette(); connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); - QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); + const CSMWorld::IdTableBase *model = + static_cast (data.getTableModel (CSMWorld::UniversalId::Type_Filters)); connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), @@ -24,10 +26,23 @@ CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), Qt::QueuedConnection); + + mStateColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + mDescColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Description); } void CSVFilter::EditWidget::textChanged (const QString& text) { + //no need to parse and apply filter if it was empty and now is empty too. + //e.g. - we modifiing content of filter with already opened some other (big) tables. + if (text.length() == 0){ + if (mIsEmpty) + return; + else + mIsEmpty = true; + }else + mIsEmpty = false; + if (mParser.parse (text.toUtf8().constData())) { setPalette (mPalette); @@ -46,7 +61,9 @@ void CSVFilter::EditWidget::textChanged (const QString& text) void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - textChanged (text()); + for (int i = topLeft.column(); i <= bottomRight.column(); ++i) + if (i != mStateColumnIndex && i != mDescColumnIndex) + textChanged (text()); } void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index a0f9f8919..f672877d9 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -25,6 +25,9 @@ namespace CSVFilter CSMFilter::Parser mParser; QPalette mPalette; + bool mIsEmpty; + int mStateColumnIndex; + int mDescColumnIndex; public: diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index 7a42ef0a5..c6c6cc6cc 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -1,4 +1,3 @@ - #include "filterbox.hpp" #include diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index 97490d508..2bf589215 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -1,4 +1,3 @@ - #include "recordfilterbox.hpp" #include diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 99658e1c8..c6da1bef8 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,4 +1,3 @@ - #include "cell.hpp" #include @@ -34,7 +33,7 @@ bool CSVRender::Cell::addObjects (int start, int end) bool modified = false; const CSMWorld::RefCollection& collection = mData.getReferences(); - + for (int i=start; i<=end; ++i) { std::string cell = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mCell); @@ -71,20 +70,22 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, int landIndex = land.searchId(mId); if (landIndex != -1) { - const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); - if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT) + const ESM::Land& esmLand = land.getRecord(mId).get(); + + if (esmLand.getLandData (ESM::Land::DATA_VHGT)) { mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true, Terrain::Align_XY)); - mTerrain->loadCell(esmLand->mX, - esmLand->mY); + mTerrain->loadCell(esmLand.mX, + esmLand.mY); float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; - mX = esmLand->mX; - mY = esmLand->mY; + mX = esmLand.mX; + mY = esmLand.mY; + mPhysics->addHeightField(sceneManager, - esmLand->mLandData->mHeights, mX, mY, 0, worldsize / (verts-1), verts); + esmLand.getLandData(ESM::Land::DATA_VHGT)->mHeights, mX, mY, 0, worldsize / (verts-1), verts); } } } diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index 9361030a3..8a99ba049 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -1,4 +1,3 @@ - #include "editmode.hpp" #include "worldspacewidget.hpp" diff --git a/apps/opencs/view/render/lightingbright.cpp b/apps/opencs/view/render/lightingbright.cpp index a342ab093..41d7c5c65 100644 --- a/apps/opencs/view/render/lightingbright.cpp +++ b/apps/opencs/view/render/lightingbright.cpp @@ -1,4 +1,3 @@ - #include "lightingbright.hpp" #include diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 206820194..8aecc4a41 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include diff --git a/apps/opencs/view/render/overlaymask.cpp b/apps/opencs/view/render/overlaymask.cpp index 09f020354..b43abb3cc 100644 --- a/apps/opencs/view/render/overlaymask.cpp +++ b/apps/opencs/view/render/overlaymask.cpp @@ -1,5 +1,6 @@ #include "overlaymask.hpp" +#include #include #include diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index cf9edb548..bcaadbcea 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -1,4 +1,3 @@ - #include "pagedworldspacewidget.hpp" #include @@ -12,6 +11,8 @@ #include #include #include +#include +#include #include #include "textoverlay.hpp" diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index da18e7c89..c00c56ed0 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -1,4 +1,3 @@ - #include "previewwidget.hpp" #include diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index a14eea5dd..41fe70c01 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -8,7 +8,7 @@ namespace CSVRender { } - ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) { std::ostringstream stream; stream << "#" << cellX << " " << cellY; @@ -19,19 +19,26 @@ namespace CSVRender if (index == -1) return NULL; - ESM::Land* land = mData.getLand().getRecord(index).get().mLand.get(); + const ESM::Land& land = mData.getLand().getRecord(index).get(); int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(mask)) - land->loadData(mask); - return land; + land.loadData (mask); + return &land; } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { - std::ostringstream stream; - stream << index << "_" << plugin; + int numRecords = mData.getLandTextures().getSize(); - return &mData.getLandTextures().getRecord(stream.str()).get(); + for (int i=0; imIndex == index && ltex->mPluginIndex == plugin) + return ltex; + } + + std::stringstream error; + error << "Can't find LandTexture " << index << " from plugin " << plugin; + throw std::runtime_error(error.str()); } void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 97782ad17..16b0f3ec7 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -18,7 +18,7 @@ namespace CSVRender private: const CSMWorld::Data& mData; - virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/apps/opencs/view/render/textoverlay.cpp b/apps/opencs/view/render/textoverlay.cpp index c41d5f318..14b60be93 100644 --- a/apps/opencs/view/render/textoverlay.cpp +++ b/apps/opencs/view/render/textoverlay.cpp @@ -5,9 +5,11 @@ #include #include +#include #include #include #include +#include #include #include #include diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 462b62b7a..4f9dbb96c 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -1,4 +1,3 @@ - #include "unpagedworldspacewidget.hpp" #include @@ -6,7 +5,7 @@ #include #include -#include +#include #include "../../model/doc/document.hpp" diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index e88814818..823a38c80 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,4 +1,3 @@ - #include "worldspacewidget.hpp" #include @@ -7,7 +6,12 @@ #include #include -#include +#include +#include +#include +#include +#include +#include #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" diff --git a/apps/opencs/view/settings/dialog.cpp b/apps/opencs/view/settings/dialog.cpp index e8832e2bc..38eb7bbc7 100644 --- a/apps/opencs/view/settings/dialog.cpp +++ b/apps/opencs/view/settings/dialog.cpp @@ -8,19 +8,12 @@ #include #include #include +#include #include "../../model/settings/usersettings.hpp" #include "page.hpp" -#include - -#include -#include -#include - -#include -#include CSVSettings::Dialog::Dialog(QMainWindow *parent) : SettingWindow (parent), mStackedWidget (0), mDebugMode (false) diff --git a/apps/opencs/view/settings/dialog.hpp b/apps/opencs/view/settings/dialog.hpp index cb85bddb9..e3a3f575a 100644 --- a/apps/opencs/view/settings/dialog.hpp +++ b/apps/opencs/view/settings/dialog.hpp @@ -3,7 +3,6 @@ #include "settingwindow.hpp" #include "resizeablestackedwidget.hpp" -#include class QStackedWidget; class QListWidget; @@ -26,10 +25,6 @@ namespace CSVSettings { explicit Dialog (QMainWindow *parent = 0); - ///Enables setting debug mode. When the dialog opens, a page is created - ///which displays the SettingModel's contents in a Tree view. - void enableDebugMode (bool state, QStandardItemModel *model = 0); - protected: /// Settings are written on close diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp new file mode 100644 index 000000000..566a5ee06 --- /dev/null +++ b/apps/opencs/view/tools/merge.cpp @@ -0,0 +1,142 @@ + +#include "merge.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" +#include "../../model/doc/documentmanager.hpp" + +#include "../doc/filewidget.hpp" +#include "../doc/adjusterwidget.hpp" + +void CSVTools::Merge::keyPressEvent (QKeyEvent *event) +{ + if (event->key()==Qt::Key_Escape) + { + event->accept(); + cancel(); + } + else + QWidget::keyPressEvent (event); +} + +CSVTools::Merge::Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent) +: QWidget (parent), mDocument (0), mDocumentManager (documentManager) +{ + setWindowTitle ("Merge Content Files into a new Game File"); + + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout (mainLayout); + + QSplitter *splitter = new QSplitter (Qt::Horizontal, this); + + mainLayout->addWidget (splitter, 1); + + // left panel (files to be merged) + QWidget *left = new QWidget (this); + left->setContentsMargins (0, 0, 0, 0); + splitter->addWidget (left); + + QVBoxLayout *leftLayout = new QVBoxLayout; + left->setLayout (leftLayout); + + leftLayout->addWidget (new QLabel ("Files to be merged", this)); + + mFiles = new QListWidget (this); + leftLayout->addWidget (mFiles, 1); + + // right panel (new game file) + QWidget *right = new QWidget (this); + right->setContentsMargins (0, 0, 0, 0); + splitter->addWidget (right); + + QVBoxLayout *rightLayout = new QVBoxLayout; + rightLayout->setAlignment (Qt::AlignTop); + right->setLayout (rightLayout); + + rightLayout->addWidget (new QLabel ("New game file", this)); + + mNewFile = new CSVDoc::FileWidget (this); + mNewFile->setType (false); + mNewFile->extensionLabelIsVisible (true); + rightLayout->addWidget (mNewFile); + + mAdjuster = new CSVDoc::AdjusterWidget (this); + + rightLayout->addWidget (mAdjuster); + + connect (mNewFile, SIGNAL (nameChanged (const QString&, bool)), + mAdjuster, SLOT (setName (const QString&, bool))); + connect (mAdjuster, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); + + // buttons + QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); + + connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (cancel())); + + mOkay = new QPushButton ("Merge", this); + connect (mOkay, SIGNAL (clicked()), this, SLOT (accept())); + mOkay->setDefault (true); + buttons->addButton (mOkay, QDialogButtonBox::AcceptRole); + + mainLayout->addWidget (buttons); +} + +void CSVTools::Merge::configure (CSMDoc::Document *document) +{ + mDocument = document; + + mNewFile->setName (""); + + // content files + while (mFiles->count()) + delete mFiles->takeItem (0); + + std::vector files = document->getContentFiles(); + + for (std::vector::const_iterator iter (files.begin()); + iter!=files.end(); ++iter) + mFiles->addItem (QString::fromUtf8 (iter->filename().string().c_str())); +} + +void CSVTools::Merge::setLocalData (const boost::filesystem::path& localData) +{ + mAdjuster->setLocalData (localData); +} + +CSMDoc::Document *CSVTools::Merge::getDocument() const +{ + return mDocument; +} + +void CSVTools::Merge::cancel() +{ + mDocument = 0; + hide(); +} + +void CSVTools::Merge::accept() +{ + if ((mDocument->getState() & CSMDoc::State_Merging)==0) + { + std::vector< boost::filesystem::path > files (1, mAdjuster->getPath()); + + std::auto_ptr target ( + mDocumentManager.makeDocument (files, files[0], true)); + + mDocument->runMerge (target); + + hide(); + } +} + +void CSVTools::Merge::stateChanged (bool valid) +{ + mOkay->setEnabled (valid); +} diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp new file mode 100644 index 000000000..d4ed7e34b --- /dev/null +++ b/apps/opencs/view/tools/merge.hpp @@ -0,0 +1,61 @@ +#ifndef CSV_TOOLS_REPORTTABLE_H +#define CSV_TOOLS_REPORTTABLE_H + +#include + +#include + +class QPushButton; +class QListWidget; + +namespace CSMDoc +{ + class Document; + class DocumentManager; +} + +namespace CSVDoc +{ + class FileWidget; + class AdjusterWidget; +} + +namespace CSVTools +{ + class Merge : public QWidget + { + Q_OBJECT + + CSMDoc::Document *mDocument; + QPushButton *mOkay; + QListWidget *mFiles; + CSVDoc::FileWidget *mNewFile; + CSVDoc::AdjusterWidget *mAdjuster; + CSMDoc::DocumentManager& mDocumentManager; + + void keyPressEvent (QKeyEvent *event); + + public: + + Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = 0); + + /// Configure dialogue for a new merge + void configure (CSMDoc::Document *document); + + void setLocalData (const boost::filesystem::path& localData); + + CSMDoc::Document *getDocument() const; + + public slots: + + void cancel(); + + private slots: + + void accept(); + + void stateChanged (bool valid); + }; +} + +#endif diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index 492874c01..a7316359e 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -1,15 +1,25 @@ - #include "reportsubview.hpp" #include "reporttable.hpp" CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: CSVDoc::SubView (id) +: CSVDoc::SubView (id), mDocument (document), mRefreshState (0) { - setWidget (mTable = new ReportTable (document, id, false, this)); + if (id.getType()==CSMWorld::UniversalId::Type_VerificationResults) + mRefreshState = CSMDoc::State_Verifying; + + setWidget (mTable = new ReportTable (document, id, false, mRefreshState, this)); connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); + + if (mRefreshState==CSMDoc::State_Verifying) + { + connect (mTable, SIGNAL (refreshRequest()), this, SLOT (refreshRequest())); + + connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), + mTable, SLOT (stateChanged (int, CSMDoc::Document *))); + } } void CSVTools::ReportSubView::setEditLock (bool locked) @@ -21,3 +31,15 @@ void CSVTools::ReportSubView::updateUserSetting (const QString &name, const QStr { mTable->updateUserSetting (name, list); } + +void CSVTools::ReportSubView::refreshRequest() +{ + if (!(mDocument.getState() & mRefreshState)) + { + if (mRefreshState==CSMDoc::State_Verifying) + { + mTable->clear(); + mDocument.verify (getUniversalId()); + } + } +} diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp index 7e8a08e3c..b8eb2690a 100644 --- a/apps/opencs/view/tools/reportsubview.hpp +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -20,6 +20,8 @@ namespace CSVTools Q_OBJECT ReportTable *mTable; + CSMDoc::Document& mDocument; + int mRefreshState; public: @@ -28,6 +30,10 @@ namespace CSVTools virtual void setEditLock (bool locked); virtual void updateUserSetting (const QString &, const QStringList &); + + private slots: + + void refreshRequest(); }; } diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index 7cfe8e4f0..d4cecc971 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -1,4 +1,3 @@ - #include "reporttable.hpp" #include @@ -9,6 +8,9 @@ #include #include #include +#include +#include +#include #include "../../model/tools/reportmodel.hpp" @@ -21,7 +23,7 @@ namespace CSVTools public: RichTextDelegate (QObject *parent = 0); - + virtual void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; }; @@ -61,7 +63,7 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + QString hint = mProxyModel->data (mProxyModel->index (iter->row(), 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') { @@ -72,9 +74,11 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) if (found) menu.addAction (mReplaceAction); - } - + + if (mRefreshAction) + menu.addAction (mRefreshAction); + menu.exec (event->globalPos()); } @@ -94,21 +98,35 @@ void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) selectionModel()->select (index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows); - switch (modifiers) + std::map::iterator iter = + mDoubleClickActions.find (modifiers); + + if (iter==mDoubleClickActions.end()) { - case 0: + event->accept(); + return; + } + + switch (iter->second) + { + case Action_None: + + event->accept(); + break; + + case Action_Edit: event->accept(); showSelection(); break; - case Qt::ShiftModifier: + case Action_Remove: event->accept(); removeSelection(); break; - case Qt::ControlModifier: + case Action_EditAndRemove: event->accept(); showSelection(); @@ -118,17 +136,26 @@ void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) } CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, - const CSMWorld::UniversalId& id, bool richTextDescription, QWidget *parent) -: CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)) + const CSMWorld::UniversalId& id, bool richTextDescription, int refreshState, + QWidget *parent) +: CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)), + mRefreshAction (0), mRefreshState (refreshState) { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); +#else horizontalHeader()->setResizeMode (QHeaderView::Interactive); +#endif horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); setSortingEnabled (true); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - setModel (mModel); + mProxyModel = new QSortFilterProxyModel (this); + mProxyModel->setSourceModel (mModel); + + setModel (mProxyModel); setColumnHidden (2, true); mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate (0, @@ -138,7 +165,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, if (richTextDescription) setItemDelegateForColumn (mModel->columnCount()-1, new RichTextDelegate (this)); - + mShowAction = new QAction (tr ("Show"), this); connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); addAction (mShowAction); @@ -149,7 +176,19 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, mReplaceAction = new QAction (tr ("Replace"), this); connect (mReplaceAction, SIGNAL (triggered()), this, SIGNAL (replaceRequest())); - addAction (mReplaceAction); + addAction (mReplaceAction); + + if (mRefreshState) + { + mRefreshAction = new QAction (tr ("Refresh"), this); + mRefreshAction->setEnabled (!(mDocument.getState() & mRefreshState)); + connect (mRefreshAction, SIGNAL (triggered()), this, SIGNAL (refreshRequest())); + addAction (mRefreshAction); + } + + mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_Edit)); + mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_Remove)); + mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); } std::vector CSVTools::ReportTable::getDraggedRecords() const @@ -161,7 +200,7 @@ std::vector CSVTools::ReportTable::getDraggedRecords() co for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - ids.push_back (mModel->getUniversalId (iter->row())); + ids.push_back (mModel->getUniversalId (mProxyModel->mapToSource (*iter).row())); } return ids; @@ -170,6 +209,35 @@ std::vector CSVTools::ReportTable::getDraggedRecords() co void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStringList& list) { mIdTypeDelegate->updateUserSetting (name, list); + + QString base ("report-input/double"); + if (name.startsWith (base)) + { + QString modifierString = name.mid (base.size()); + Qt::KeyboardModifiers modifiers = 0; + + if (modifierString=="-s") + modifiers = Qt::ShiftModifier; + else if (modifierString=="-c") + modifiers = Qt::ControlModifier; + else if (modifierString=="-sc") + modifiers = Qt::ShiftModifier | Qt::ControlModifier; + + DoubleClickAction action = Action_None; + + QString value = list.at (0); + + if (value=="Edit") + action = Action_Edit; + else if (value=="Remove") + action = Action_Remove; + else if (value=="Edit And Remove") + action = Action_EditAndRemove; + + mDoubleClickActions[modifiers] = action; + + return; + } } std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const @@ -180,13 +248,22 @@ std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const { QModelIndexList selectedRows = selectionModel()->selectedRows(); + std::vector rows; + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + rows.push_back (mProxyModel->mapToSource (*iter).row()); + } + + std::sort (rows.begin(), rows.end()); + + for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + { + QString hint = mModel->data (mModel->index (*iter, 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') - indices.push_back (iter->row()); + indices.push_back (*iter); } } else @@ -207,25 +284,35 @@ void CSVTools::ReportTable::flagAsReplaced (int index) { mModel->flagAsReplaced (index); } - + void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - emit editRequest (mModel->getUniversalId (iter->row()), mModel->getHint (iter->row())); + { + int row = mProxyModel->mapToSource (*iter).row(); + emit editRequest (mModel->getUniversalId (row), mModel->getHint (row)); + } } void CSVTools::ReportTable::removeSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); - std::reverse (selectedRows.begin(), selectedRows.end()); + std::vector rows; - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + for (QModelIndexList::iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - mModel->removeRows (iter->row(), 1); + { + rows.push_back (mProxyModel->mapToSource (*iter).row()); + } + + std::sort (rows.begin(), rows.end()); + + for (std::vector::const_reverse_iterator iter (rows.rbegin()); iter!=rows.rend(); ++iter) + mProxyModel->removeRows (*iter, 1); selectionModel()->clear(); } @@ -234,3 +321,9 @@ void CSVTools::ReportTable::clear() { mModel->clear(); } + +void CSVTools::ReportTable::stateChanged (int state, CSMDoc::Document *document) +{ + if (mRefreshAction) + mRefreshAction->setEnabled (!(state & mRefreshState)); +} diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index c4d5b414e..c847b2d47 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -1,9 +1,12 @@ #ifndef CSV_TOOLS_REPORTTABLE_H #define CSV_TOOLS_REPORTTABLE_H +#include + #include "../world/dragrecordtable.hpp" class QAction; +class QSortFilterProxyModel; namespace CSMTools { @@ -21,11 +24,23 @@ namespace CSVTools { Q_OBJECT + enum DoubleClickAction + { + Action_None, + Action_Edit, + Action_Remove, + Action_EditAndRemove + }; + + QSortFilterProxyModel *mProxyModel; CSMTools::ReportModel *mModel; CSVWorld::CommandDelegate *mIdTypeDelegate; QAction *mShowAction; QAction *mRemoveAction; QAction *mReplaceAction; + QAction *mRefreshAction; + std::map mDoubleClickActions; + int mRefreshState; private: @@ -38,8 +53,11 @@ namespace CSVTools public: /// \param richTextDescription Use rich text in the description column. + /// \param refreshState Document state to check for refresh function. If value is + /// 0 no refresh function exists. If the document current has the specified state + /// the refresh function is disabled. ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, - bool richTextDescription, QWidget *parent = 0); + bool richTextDescription, int refreshState = 0, QWidget *parent = 0); virtual std::vector getDraggedRecords() const; @@ -47,11 +65,14 @@ namespace CSVTools void clear(); - // Return indices of rows that are suitable for replacement. - // - // \param selection Only list selected rows. + /// Return indices of rows that are suitable for replacement. + /// + /// \param selection Only list selected rows. + /// + /// \return rows in the original model std::vector getReplaceIndices (bool selection) const; + /// \param index row in the original model void flagAsReplaced (int index); private slots: @@ -60,11 +81,17 @@ namespace CSVTools void removeSelection(); + public slots: + + void stateChanged (int state, CSMDoc::Document *document); + signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void replaceRequest(); + + void refreshRequest(); }; } diff --git a/apps/opencs/view/tools/searchbox.cpp b/apps/opencs/view/tools/searchbox.cpp index 1307c1aab..d98044760 100644 --- a/apps/opencs/view/tools/searchbox.cpp +++ b/apps/opencs/view/tools/searchbox.cpp @@ -1,4 +1,3 @@ - #include "searchbox.hpp" #include diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index dc670af40..d3fdbbf5d 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -1,4 +1,3 @@ - #include "searchsubview.hpp" #include @@ -16,7 +15,7 @@ void CSVTools::SearchSubView::replace (bool selection) { if (mLocked) return; - + std::vector indices = mTable->getReplaceIndices (selection); std::string replace = mSearchBox.getReplaceText(); @@ -29,7 +28,7 @@ void CSVTools::SearchSubView::replace (bool selection) CSMTools::Search search (mSearch); CSMWorld::IdTableBase *currentTable = 0; - + // We are running through the indices in reverse order to avoid messing up multiple results // in a single string. for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) @@ -46,7 +45,7 @@ void CSVTools::SearchSubView::replace (bool selection) search.configure (table); currentTable = table; } - + std::string hint = model.getHint (*iter); if (search.verify (mDocument, table, id, hint)) @@ -63,7 +62,7 @@ void CSVTools::SearchSubView::replace (bool selection) void CSVTools::SearchSubView::showEvent (QShowEvent *event) { CSVDoc::SubView::showEvent (event); - mSearchBox.focus(); + mSearchBox.focus(); } CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) @@ -71,25 +70,23 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (&mSearchBox); - + layout->addWidget (mTable = new ReportTable (document, id, true), 2); QWidget *widget = new QWidget; - + widget->setLayout (layout); setWidget (widget); stateChanged (document.getState(), &document); - + connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest())); - + connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (stateChanged (int, CSMDoc::Document *))); @@ -124,7 +121,7 @@ void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) mSearch = search; mSearch.setPadding (paddingBefore, paddingAfter); - + mTable->clear(); mDocument.runSearch (getUniversalId(), mSearch); } diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp index 8a343ebe8..8c3d6d50e 100644 --- a/apps/opencs/view/tools/subviews.cpp +++ b/apps/opencs/view/tools/subviews.cpp @@ -1,4 +1,3 @@ - #include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" diff --git a/apps/opencs/view/widget/coloreditor.cpp b/apps/opencs/view/widget/coloreditor.cpp new file mode 100644 index 000000000..7ef1ec7b1 --- /dev/null +++ b/apps/opencs/view/widget/coloreditor.cpp @@ -0,0 +1,113 @@ +#include "coloreditor.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "colorpickerpopup.hpp" + +CSVWidget::ColorEditor::ColorEditor(const QColor &color, QWidget *parent, bool popupOnStart) + : QPushButton(parent), + mColor(color), + mColorPicker(new ColorPickerPopup(this)), + mPopupOnStart(popupOnStart) +{ + setCheckable(true); + connect(this, SIGNAL(clicked()), this, SLOT(showPicker())); + connect(mColorPicker, SIGNAL(hid()), this, SLOT(pickerHid())); + connect(mColorPicker, SIGNAL(colorChanged(const QColor &)), this, SLOT(pickerColorChanged(const QColor &))); +} + +void CSVWidget::ColorEditor::paintEvent(QPaintEvent *event) +{ + QPushButton::paintEvent(event); + + QRect buttonRect = rect(); + QRect coloredRect(buttonRect.x() + qRound(buttonRect.width() / 4.0), + buttonRect.y() + qRound(buttonRect.height() / 4.0), + buttonRect.width() / 2, + buttonRect.height() / 2); + QPainter painter(this); + painter.fillRect(coloredRect, mColor); + painter.setPen(Qt::black); + painter.drawRect(coloredRect); +} + +void CSVWidget::ColorEditor::showEvent(QShowEvent *event) +{ + QPushButton::showEvent(event); + if (isVisible() && mPopupOnStart) + { + setChecked(true); + showPicker(); + mPopupOnStart = false; + } +} + +QColor CSVWidget::ColorEditor::color() const +{ + return mColor; +} + +void CSVWidget::ColorEditor::setColor(const QColor &color) +{ + mColor = color; + update(); +} + +void CSVWidget::ColorEditor::showPicker() +{ + if (isChecked()) + { + mColorPicker->showPicker(calculatePopupPosition(), mColor); + } + else + { + mColorPicker->hide(); + } +} + +void CSVWidget::ColorEditor::pickerHid() +{ + setChecked(false); + emit pickingFinished(); +} + +void CSVWidget::ColorEditor::pickerColorChanged(const QColor &color) +{ + mColor = color; + update(); +} + +QPoint CSVWidget::ColorEditor::calculatePopupPosition() +{ + QRect editorGeometry = geometry(); + QRect popupGeometry = mColorPicker->geometry(); + QRect screenGeometry = QApplication::desktop()->screenGeometry(); + + // Center the popup horizontally relative to the editor + int localPopupX = (editorGeometry.width() - popupGeometry.width()) / 2; + // Popup position need to be specified in global coords + QPoint popupPosition = mapToGlobal(QPoint(localPopupX, editorGeometry.height())); + + // Make sure that the popup isn't out of the screen + if (popupPosition.x() < screenGeometry.left()) + { + popupPosition.setX(screenGeometry.left() + 1); + } + else if (popupPosition.x() + popupGeometry.width() > screenGeometry.right()) + { + popupPosition.setX(screenGeometry.right() - popupGeometry.width() - 1); + } + if (popupPosition.y() + popupGeometry.height() > screenGeometry.bottom()) + { + // Place the popup above the editor + popupPosition.setY(popupPosition.y() - popupGeometry.height() - editorGeometry.height() - 1); + } + + return popupPosition; +} diff --git a/apps/opencs/view/widget/coloreditor.hpp b/apps/opencs/view/widget/coloreditor.hpp new file mode 100644 index 000000000..61232cb13 --- /dev/null +++ b/apps/opencs/view/widget/coloreditor.hpp @@ -0,0 +1,44 @@ +#ifndef CSV_WIDGET_COLOREDITOR_HPP +#define CSV_WIDGET_COLOREDITOR_HPP + +#include + +class QColor; +class QPoint; +class QSize; + +namespace CSVWidget +{ + class ColorPickerPopup; + + class ColorEditor : public QPushButton + { + Q_OBJECT + + QColor mColor; + ColorPickerPopup *mColorPicker; + bool mPopupOnStart; + + QPoint calculatePopupPosition(); + + public: + ColorEditor(const QColor &color, QWidget *parent = 0, bool popupOnStart = false); + + QColor color() const; + void setColor(const QColor &color); + + protected: + virtual void paintEvent(QPaintEvent *event); + virtual void showEvent(QShowEvent *event); + + private slots: + void showPicker(); + void pickerHid(); + void pickerColorChanged(const QColor &color); + + signals: + void pickingFinished(); + }; +} + +#endif diff --git a/apps/opencs/view/widget/colorpickerpopup.cpp b/apps/opencs/view/widget/colorpickerpopup.cpp new file mode 100644 index 000000000..8e71ce39e --- /dev/null +++ b/apps/opencs/view/widget/colorpickerpopup.cpp @@ -0,0 +1,86 @@ +#include "colorpickerpopup.hpp" + +#include +#include +#include +#include +#include +#include +#include + +CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent) + : QFrame(parent) +{ + setWindowFlags(Qt::Popup); + setFrameStyle(QFrame::Box | QFrame::Plain); + hide(); + + mColorPicker = new QColorDialog(this); + mColorPicker->setWindowFlags(Qt::Widget); + mColorPicker->setOptions(QColorDialog::NoButtons | QColorDialog::DontUseNativeDialog); + mColorPicker->installEventFilter(this); + mColorPicker->open(); + connect(mColorPicker, + SIGNAL(currentColorChanged(const QColor &)), + this, + SIGNAL(colorChanged(const QColor &))); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(mColorPicker); + layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + setFixedSize(mColorPicker->size()); +} + +void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColor &initialColor) +{ + QRect geometry = this->geometry(); + geometry.moveTo(position); + setGeometry(geometry); + + mColorPicker->setCurrentColor(initialColor); + show(); +} + +void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) +{ + QPushButton *button = qobject_cast(parentWidget()); + if (button != NULL) + { + QStyleOptionButton option; + option.init(button); + QRect buttonRect = option.rect; + buttonRect.moveTo(button->mapToGlobal(buttonRect.topLeft())); + + // If the mouse is pressed above the pop-up parent, + // the pop-up will be hidden and the pressed signal won't be repeated for the parent + if (buttonRect.contains(event->globalPos()) || buttonRect.contains(event->pos())) + { + setAttribute(Qt::WA_NoMouseReplay); + } + } + QFrame::mousePressEvent(event); +} + +void CSVWidget::ColorPickerPopup::hideEvent(QHideEvent *event) +{ + QFrame::hideEvent(event); + emit hid(); +} + +bool CSVWidget::ColorPickerPopup::eventFilter(QObject *object, QEvent *event) +{ + if (object == mColorPicker && event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + // Prevent QColorDialog from closing when Escape is pressed. + // Instead, hide the popup. + if (keyEvent->key() == Qt::Key_Escape) + { + hide(); + return true; + } + } + return QFrame::eventFilter(object, event); +} diff --git a/apps/opencs/view/widget/colorpickerpopup.hpp b/apps/opencs/view/widget/colorpickerpopup.hpp new file mode 100644 index 000000000..602bbdb6d --- /dev/null +++ b/apps/opencs/view/widget/colorpickerpopup.hpp @@ -0,0 +1,32 @@ +#ifndef CSVWIDGET_COLORPICKERPOPUP_HPP +#define CSVWIDGET_COLORPICKERPOPUP_HPP + +#include + +class QColorDialog; + +namespace CSVWidget +{ + class ColorPickerPopup : public QFrame + { + Q_OBJECT + + QColorDialog *mColorPicker; + + public: + explicit ColorPickerPopup(QWidget *parent); + + void showPicker(const QPoint &position, const QColor &initialColor); + + protected: + virtual void mousePressEvent(QMouseEvent *event); + virtual void hideEvent(QHideEvent *event); + virtual bool eventFilter(QObject *object, QEvent *event); + + signals: + void hid(); + void colorChanged(const QColor &color); + }; +} + +#endif diff --git a/apps/opencs/view/widget/completerpopup.cpp b/apps/opencs/view/widget/completerpopup.cpp new file mode 100644 index 000000000..5777325c8 --- /dev/null +++ b/apps/opencs/view/widget/completerpopup.cpp @@ -0,0 +1,28 @@ +#include "completerpopup.hpp" + +CSVWidget::CompleterPopup::CompleterPopup(QWidget *parent) + : QListView(parent) +{ + setEditTriggers(QAbstractItemView::NoEditTriggers); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); +} + +int CSVWidget::CompleterPopup::sizeHintForRow(int row) const +{ + if (model() == NULL) + { + return -1; + } + if (row < 0 || row >= model()->rowCount()) + { + return -1; + } + + ensurePolished(); + QModelIndex index = model()->index(row, modelColumn()); + QStyleOptionViewItem option = viewOptions(); + QAbstractItemDelegate *delegate = itemDelegate(index); + return delegate->sizeHint(option, index).height(); +} diff --git a/apps/opencs/view/widget/completerpopup.hpp b/apps/opencs/view/widget/completerpopup.hpp new file mode 100644 index 000000000..6857064b8 --- /dev/null +++ b/apps/opencs/view/widget/completerpopup.hpp @@ -0,0 +1,17 @@ +#ifndef CSV_WIDGET_COMPLETERPOPUP_HPP +#define CSV_WIDGET_COMPLETERPOPUP_HPP + +#include + +namespace CSVWidget +{ + class CompleterPopup : public QListView + { + public: + CompleterPopup(QWidget *parent = 0); + + virtual int sizeHintForRow(int row) const; + }; +} + +#endif diff --git a/apps/opencs/view/widget/droplineedit.cpp b/apps/opencs/view/widget/droplineedit.cpp new file mode 100644 index 000000000..2ca306461 --- /dev/null +++ b/apps/opencs/view/widget/droplineedit.cpp @@ -0,0 +1,41 @@ +#include "droplineedit.hpp" + +#include + +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/universalid.hpp" + +#include "../world/dragdroputils.hpp" + +CSVWidget::DropLineEdit::DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent) + : QLineEdit(parent), + mDropType(type) +{ + setAcceptDrops(true); +} + +void CSVWidget::DropLineEdit::dragEnterEvent(QDragEnterEvent *event) +{ + if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) + { + event->acceptProposedAction(); + } +} + +void CSVWidget::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) +{ + if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) + { + event->accept(); + } +} + +void CSVWidget::DropLineEdit::dropEvent(QDropEvent *event) +{ + if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) + { + CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, mDropType); + setText(QString::fromUtf8(id.getId().c_str())); + emit tableMimeDataDropped(id, CSVWorld::DragDropUtils::getTableMimeData(*event)->getDocumentPtr()); + } +} diff --git a/apps/opencs/view/widget/droplineedit.hpp b/apps/opencs/view/widget/droplineedit.hpp new file mode 100644 index 000000000..60832e71b --- /dev/null +++ b/apps/opencs/view/widget/droplineedit.hpp @@ -0,0 +1,41 @@ +#ifndef CSV_WIDGET_DROPLINEEDIT_HPP +#define CSV_WIDGET_DROPLINEEDIT_HPP + +#include + +#include "../../model/world/columnbase.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class TableMimeData; + class UniversalId; +} + +namespace CSVWidget +{ + class DropLineEdit : public QLineEdit + { + Q_OBJECT + + CSMWorld::ColumnBase::Display mDropType; + ///< The accepted Display type for this LineEdit. + + public: + DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent = 0); + + protected: + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + + signals: + void tableMimeDataDropped(const CSMWorld::UniversalId &id, const CSMDoc::Document *document); + }; +} + +#endif diff --git a/apps/opencs/view/widget/modebutton.cpp b/apps/opencs/view/widget/modebutton.cpp index 56896b422..7c62f6bb1 100644 --- a/apps/opencs/view/widget/modebutton.cpp +++ b/apps/opencs/view/widget/modebutton.cpp @@ -1,4 +1,3 @@ - #include "modebutton.hpp" CSVWidget::ModeButton::ModeButton (const QIcon& icon, const QString& tooltip, QWidget *parent) diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp index 1baeb7ca2..424aaf68a 100644 --- a/apps/opencs/view/widget/pushbutton.cpp +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -1,4 +1,3 @@ - #include "pushbutton.hpp" #include diff --git a/apps/opencs/view/widget/scenetool.cpp b/apps/opencs/view/widget/scenetool.cpp index b8e9f895f..796b98567 100644 --- a/apps/opencs/view/widget/scenetool.cpp +++ b/apps/opencs/view/widget/scenetool.cpp @@ -1,4 +1,3 @@ - #include "scenetool.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolbar.cpp b/apps/opencs/view/widget/scenetoolbar.cpp index f7023b31f..b2e988dc9 100644 --- a/apps/opencs/view/widget/scenetoolbar.cpp +++ b/apps/opencs/view/widget/scenetoolbar.cpp @@ -1,4 +1,3 @@ - #include "scenetoolbar.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 39e051c48..9f963873c 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -1,4 +1,3 @@ - #include "scenetoolmode.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp index 8de334efe..1e2d44e7a 100644 --- a/apps/opencs/view/widget/scenetoolrun.cpp +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -1,4 +1,3 @@ - #include "scenetoolrun.hpp" #include @@ -65,8 +64,13 @@ CSVWidget::SceneToolRun::SceneToolRun (SceneToolbar *parent, const QString& tool mTable->setShowGrid (false); mTable->verticalHeader()->hide(); mTable->horizontalHeader()->hide(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); + mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); +#else mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch); mTable->horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); +#endif mTable->setSelectionMode (QAbstractItemView::NoSelection); layout->addWidget (mTable); diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index 07c448e45..d7251882a 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -1,4 +1,3 @@ - #include "scenetooltoggle.hpp" #include diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 313e519cb..e0431476e 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -1,4 +1,3 @@ - #include "scenetooltoggle2.hpp" #include diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index cdeee5655..2a710a940 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -1,4 +1,3 @@ - #include "cellcreator.hpp" #include @@ -8,6 +7,9 @@ #include #include +#include "../../model/world/commands.hpp" +#include "../../model/world/idtree.hpp" + std::string CSVWorld::CellCreator::getId() const { if (mType->currentIndex()==0) @@ -20,6 +22,15 @@ std::string CSVWorld::CellCreator::getId() const return stream.str(); } +void CSVWorld::CellCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const +{ + CSMWorld::IdTree *model = dynamic_cast(getData().getTableModel(getCollectionId())); + Q_ASSERT(model != NULL); + int parentIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Cell); + int index = model->findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); + command.addNestedValue(parentIndex, index, mType->currentIndex() == 0); +} + CSVWorld::CellCreator::CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator (data, undoStack, id) @@ -95,9 +106,16 @@ void CSVWorld::CellCreator::cloneMode(const std::string& originId, } } - -void CSVWorld::CellCreator::toggleWidgets(bool active) +std::string CSVWorld::CellCreator::getErrors() const { - CSVWorld::GenericCreator::toggleWidgets(active); - mType->setEnabled(active); + std::string errors; + if (mType->currentIndex() == 0) + { + errors = GenericCreator::getErrors(); + } + else if (getData().hasId(getId())) + { + errors = "The Exterior Cell is already exist"; + } + return errors; } diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp index db9fbf8a3..6c682c6cd 100644 --- a/apps/opencs/view/world/cellcreator.hpp +++ b/apps/opencs/view/world/cellcreator.hpp @@ -23,17 +23,22 @@ namespace CSVWorld virtual std::string getId() const; + /// Allow subclasses to add additional data to \a command. + virtual void configureCreateCommand(CSMWorld::CreateCommand& command) const; + public: CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); virtual void reset(); - virtual void toggleWidgets(bool active = true); - virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type); + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + private slots: void setType (int index); diff --git a/apps/opencs/view/world/colordelegate.cpp b/apps/opencs/view/world/colordelegate.cpp new file mode 100644 index 000000000..1a89fc675 --- /dev/null +++ b/apps/opencs/view/world/colordelegate.cpp @@ -0,0 +1,36 @@ +#include "colordelegate.hpp" + +#include +#include + +#include "../widget/coloreditor.hpp" + +CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document& document, + QObject *parent) + : CommandDelegate(dispatcher, document, parent) +{} + +void CSVWorld::ColorDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QRect coloredRect(option.rect.x() + qRound(option.rect.width() / 4.0), + option.rect.y() + qRound(option.rect.height() / 4.0), + option.rect.width() / 2, + option.rect.height() / 2); + painter->save(); + painter->fillRect(coloredRect, index.data().value()); + painter->setPen(Qt::black); + painter->drawRect(coloredRect); + painter->restore(); +} + +CSVWorld::CommandDelegate *CSVWorld::ColorDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document &document, + QObject *parent) const +{ + return new ColorDelegate(dispatcher, document, parent); +} + + diff --git a/apps/opencs/view/world/colordelegate.hpp b/apps/opencs/view/world/colordelegate.hpp new file mode 100644 index 000000000..87051e86d --- /dev/null +++ b/apps/opencs/view/world/colordelegate.hpp @@ -0,0 +1,37 @@ +#ifndef CSV_WORLD_COLORDELEGATE_HPP +#define CSV_WORLD_COLORDELEGATE_HPP + +#include "util.hpp" + +class QRect; + +namespace CSVWidget +{ + class ColorEditButton; +} + +namespace CSVWorld +{ + class ColorDelegate : public CommandDelegate + { + public: + ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document& document, + QObject *parent); + + virtual void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + }; + + class ColorDelegateFactory : public CommandDelegateFactory + { + public: + virtual CommandDelegate *makeDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document &document, + QObject *parent) const; + ///< The ownership of the returned CommandDelegate is transferred to the caller. + }; +} + +#endif diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp index 2e7c7fe22..7a93339c5 100644 --- a/apps/opencs/view/world/creator.cpp +++ b/apps/opencs/view/world/creator.cpp @@ -1,4 +1,3 @@ - #include "creator.hpp" #include @@ -15,8 +14,8 @@ void CSVWorld::Creator::setScope (unsigned int scope) CSVWorld::CreatorFactoryBase::~CreatorFactoryBase() {} -CSVWorld::Creator *CSVWorld::NullCreatorFactory::makeCreator (CSMWorld::Data& data, - QUndoStack& undoStack, const CSMWorld::UniversalId& id) const +CSVWorld::Creator *CSVWorld::NullCreatorFactory::makeCreator (CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const { return 0; } diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp index 506bdab2c..b76348199 100644 --- a/apps/opencs/view/world/creator.hpp +++ b/apps/opencs/view/world/creator.hpp @@ -5,16 +5,14 @@ #include -#include "../../model/world/universalid.hpp" +#include "../../model/doc/document.hpp" #include "../../model/world/scope.hpp" +#include "../../model/world/universalid.hpp" -class QUndoStack; - -namespace CSMWorld +namespace CSMDoc { - class Data; - class UniversalId; + class Document; } namespace CSVWorld @@ -59,8 +57,7 @@ namespace CSVWorld virtual ~CreatorFactoryBase(); - virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) const = 0; + virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const = 0; ///< The ownership of the returned Creator is transferred to the caller. /// /// \note The function can return a 0-pointer, which means no UI for creating/deleting @@ -72,8 +69,7 @@ namespace CSVWorld { public: - virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) const; + virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const; ///< The ownership of the returned Creator is transferred to the caller. /// /// \note The function always returns 0. @@ -84,8 +80,7 @@ namespace CSVWorld { public: - virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) const; + virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const; ///< The ownership of the returned Creator is transferred to the caller. /// /// \note The function can return a 0-pointer, which means no UI for creating/deleting @@ -93,10 +88,10 @@ namespace CSVWorld }; template - Creator *CreatorFactory::makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) const + Creator *CreatorFactory::makeCreator (CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const { - std::auto_ptr creator (new CreatorT (data, undoStack, id)); + std::auto_ptr creator (new CreatorT (document.getData(), document.getUndoStack(), id)); creator->setScope (scope); diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index b9df52bf7..72f45a18c 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -12,11 +12,10 @@ CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, const QString &settingName, QObject *parent) : EnumDelegate (values, dispatcher, document, parent), mDisplayMode (Mode_TextOnly), - mIcons (icons), mIconSize (QSize(16, 16)), mIconLeftOffset(3), + mIcons (icons), mIconSize (QSize(16, 16)), + mHorizontalMargin(QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1), mTextLeftOffset(8), mSettingKey (pageName + '/' + settingName) { - mTextAlignment.setAlignment (Qt::AlignLeft | Qt::AlignVCenter ); - buildPixmaps(); QString value = @@ -45,16 +44,35 @@ void CSVWorld::DataDisplayDelegate::setIconSize(const QSize& size) buildPixmaps(); } -void CSVWorld::DataDisplayDelegate::setIconLeftOffset(int offset) -{ - mIconLeftOffset = offset; -} - void CSVWorld::DataDisplayDelegate::setTextLeftOffset(int offset) { mTextLeftOffset = offset; } +QSize CSVWorld::DataDisplayDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QSize size = EnumDelegate::sizeHint(option, index); + + int valueIndex = getValueIndex(index); + if (valueIndex != -1) + { + if (mDisplayMode == Mode_IconOnly) + { + size.setWidth(mIconSize.width() + 2 * mHorizontalMargin); + } + else if (mDisplayMode == Mode_IconAndText) + { + size.setWidth(size.width() + mIconSize.width() + mTextLeftOffset); + } + + if (mDisplayMode != Mode_TextOnly) + { + size.setHeight(qMax(size.height(), mIconSize.height())); + } + } + return size; +} + void CSVWorld::DataDisplayDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); @@ -64,16 +82,11 @@ void CSVWorld::DataDisplayDelegate::paint (QPainter *painter, const QStyleOption EnumDelegate::paint(painter, option, index); else { - unsigned int i = 0; - - for (; i < mValues.size(); ++i) + int valueIndex = getValueIndex(index); + if (valueIndex != -1) { - if (mValues.at(i).first == index.data().toInt()) - break; + paintIcon(painter, option, valueIndex); } - - if (i < mValues.size() ) - paintIcon (painter, option, i); } painter->restore(); @@ -81,24 +94,28 @@ void CSVWorld::DataDisplayDelegate::paint (QPainter *painter, const QStyleOption void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int index) const { - //function-level statics QRect iconRect = option.rect; QRect textRect = iconRect; - const QString &text = mValues.at(index).second; - - iconRect.setSize (mIconSize); - iconRect.translate(mIconLeftOffset, (option.rect.height() - iconRect.height())/2); - - if (mDisplayMode == Mode_IconAndText ) + iconRect.setLeft(iconRect.left() + mHorizontalMargin); + iconRect.setRight(option.rect.right() - mHorizontalMargin); + if (mDisplayMode == Mode_IconAndText) { - textRect.translate (iconRect.width() + mTextLeftOffset, 0 ); - painter->drawText (textRect, text, mTextAlignment); - } - else - iconRect.translate( (option.rect.width() - iconRect.width()) / 2, 0); + iconRect.setWidth(mIconSize.width()); + textRect.setLeft(iconRect.right() + mTextLeftOffset); + textRect.setRight(option.rect.right() - mHorizontalMargin); - painter->drawPixmap (iconRect, mPixmaps.at(index).second); + QString text = option.fontMetrics.elidedText(mValues.at(index).second, + option.textElideMode, + textRect.width()); + QApplication::style()->drawItemText(painter, + textRect, + Qt::AlignLeft | Qt::AlignVCenter, + option.palette, + true, + text); + } + QApplication::style()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, mPixmaps.at(index).second); } void CSVWorld::DataDisplayDelegate::updateUserSetting (const QString &name, diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index f6e4c2688..e565a3469 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -30,9 +30,8 @@ namespace CSVWorld private: std::vector > mPixmaps; - QTextOption mTextAlignment; QSize mIconSize; - int mIconLeftOffset; + int mHorizontalMargin; int mTextLeftOffset; QString mSettingKey; @@ -46,12 +45,11 @@ namespace CSVWorld virtual void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + /// pass a QSize defining height / width of icon. Default is QSize (16,16). void setIconSize (const QSize& icon); - /// offset the horizontal position of the icon from the left edge of the cell. Default is 3 pixels. - void setIconLeftOffset (int offset); - /// offset the horizontal position of the text from the right edge of the icon. Default is 8 pixels. void setTextLeftOffset (int offset); diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp index 956cd26df..7c6fb2e81 100644 --- a/apps/opencs/view/world/dialoguecreator.cpp +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -1,8 +1,9 @@ - #include "dialoguecreator.hpp" #include +#include "../../model/doc/document.hpp" + #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" @@ -22,14 +23,14 @@ CSVWorld::DialogueCreator::DialogueCreator (CSMWorld::Data& data, QUndoStack& un : GenericCreator (data, undoStack, id, true), mType (type) {} -CSVWorld::Creator *CSVWorld::TopicCreatorFactory::makeCreator (CSMWorld::Data& data, - QUndoStack& undoStack, const CSMWorld::UniversalId& id) const +CSVWorld::Creator *CSVWorld::TopicCreatorFactory::makeCreator (CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const { - return new DialogueCreator (data, undoStack, id, ESM::Dialogue::Topic); + return new DialogueCreator (document.getData(), document.getUndoStack(), id, ESM::Dialogue::Topic); } -CSVWorld::Creator *CSVWorld::JournalCreatorFactory::makeCreator (CSMWorld::Data& data, - QUndoStack& undoStack, const CSMWorld::UniversalId& id) const +CSVWorld::Creator *CSVWorld::JournalCreatorFactory::makeCreator (CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const { - return new DialogueCreator (data, undoStack, id, ESM::Dialogue::Journal); + return new DialogueCreator (document.getData(), document.getUndoStack(), id, ESM::Dialogue::Journal); } diff --git a/apps/opencs/view/world/dialoguecreator.hpp b/apps/opencs/view/world/dialoguecreator.hpp index 26f866909..20430fdb6 100644 --- a/apps/opencs/view/world/dialoguecreator.hpp +++ b/apps/opencs/view/world/dialoguecreator.hpp @@ -23,8 +23,7 @@ namespace CSVWorld { public: - virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) const; + virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const; ///< The ownership of the returned Creator is transferred to the caller. }; @@ -32,8 +31,7 @@ namespace CSVWorld { public: - virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) const; + virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const; ///< The ownership of the returned Creator is transferred to the caller. }; } diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 66e8fcb7a..14b7fcac3 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -17,10 +17,9 @@ #include #include #include -#include -#include #include #include +#include #include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/columnbase.hpp" @@ -32,11 +31,16 @@ #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" #include "../../model/doc/document.hpp" +#include "../../model/settings/usersettings.hpp" + +#include "../widget/coloreditor.hpp" +#include "../widget/droplineedit.hpp" #include "recordstatusdelegate.hpp" #include "util.hpp" #include "tablebottombox.hpp" #include "nestedtable.hpp" +#include "recordbuttonbar.hpp" /* ==============================NotEditableSubDelegate========================================== */ @@ -61,16 +65,24 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo } } + CSMWorld::Columns::ColumnId columnId = static_cast ( + mTable->getColumnId (index.column())); + if (QVariant::String == v.type()) { label->setText(v.toString()); } - else //else we are facing enums + else if (CSMWorld::Columns::hasEnums (columnId)) { int data = v.toInt(); - std::vector enumNames (CSMWorld::Columns::getEnums (static_cast (mTable->getColumnId (index.column())))); + std::vector enumNames (CSMWorld::Columns::getEnums (columnId)); + label->setText(QString::fromUtf8(enumNames.at(data).c_str())); } + else + { + label->setText (v.toString()); + } } void CSVWorld::NotEditableSubDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const @@ -92,7 +104,9 @@ QWidget* CSVWorld::NotEditableSubDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { - return new QLabel(parent); + QLabel *label = new QLabel(parent); + label->setTextInteractionFlags (Qt::TextSelectableByMouse); + return label; } /* @@ -127,52 +141,6 @@ QWidget* CSVWorld::DialogueDelegateDispatcherProxy::getEditor() const return mEditor; } -void CSVWorld::DialogueDelegateDispatcherProxy::tableMimeDataDropped(const std::vector& data, const CSMDoc::Document* document) -{ - QLineEdit* lineEdit = qobject_cast(mEditor); - { - if (!lineEdit || !mIndexWrapper.get()) - { - return; - } - } - for (unsigned i = 0; i < data.size(); ++i) - { - CSMWorld::UniversalId::Type type = data[i].getType(); - if (mDisplay == CSMWorld::ColumnBase::Display_Referenceable) - { - if (type == CSMWorld::UniversalId::Type_Activator - || type == CSMWorld::UniversalId::Type_Potion - || type == CSMWorld::UniversalId::Type_Apparatus - || type == CSMWorld::UniversalId::Type_Armor - || type == CSMWorld::UniversalId::Type_Book - || type == CSMWorld::UniversalId::Type_Clothing - || type == CSMWorld::UniversalId::Type_Container - || type == CSMWorld::UniversalId::Type_Creature - || type == CSMWorld::UniversalId::Type_Door - || type == CSMWorld::UniversalId::Type_Ingredient - || type == CSMWorld::UniversalId::Type_CreatureLevelledList - || type == CSMWorld::UniversalId::Type_ItemLevelledList - || type == CSMWorld::UniversalId::Type_Light - || type == CSMWorld::UniversalId::Type_Lockpick - || type == CSMWorld::UniversalId::Type_Miscellaneous - || type == CSMWorld::UniversalId::Type_Npc - || type == CSMWorld::UniversalId::Type_Probe - || type == CSMWorld::UniversalId::Type_Repair - || type == CSMWorld::UniversalId::Type_Static - || type == CSMWorld::UniversalId::Type_Weapon) - { - type = CSMWorld::UniversalId::Type_Referenceable; - } - } - if (mDisplay == CSMWorld::TableMimeData::convertEnums(type)) - { - emit tableMimeDataDropped(mEditor, mIndexWrapper->mIndex, data[i], document); - emit editorDataCommited(mEditor, mIndexWrapper->mIndex, mDisplay); - break; - } - } -} /* ==============================DialogueDelegateDispatcher========================================== */ @@ -304,16 +272,12 @@ QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase:: // NOTE: For each entry in CSVWorld::CommandDelegate::createEditor() a corresponding entry // is required here - if (qobject_cast(editor)) + if (qobject_cast(editor)) { connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); - connect(editor, SIGNAL(tableMimeDataDropped(const std::vector&, const CSMDoc::Document*)), - proxy, SLOT(tableMimeDataDropped(const std::vector&, const CSMDoc::Document*))); - - connect(proxy, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), - this, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); - + connect(editor, SIGNAL(tableMimeDataDropped(const CSMWorld::UniversalId&, const CSMDoc::Document*)), + proxy, SLOT(editorDataCommited())); } else if (qobject_cast(editor)) { @@ -331,6 +295,10 @@ QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase:: { connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); } + else if (qobject_cast(editor)) + { + connect(editor, SIGNAL(pickingFinished()), proxy, SLOT(editorDataCommited())); + } else // throw an exception because this is a coding error throw std::logic_error ("Dialogue editor type missing"); @@ -350,10 +318,141 @@ CSVWorld::DialogueDelegateDispatcher::~DialogueDelegateDispatcher() } } + +CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display) + : QObject(widget), + mWidget(widget), + mIdType(CSMWorld::TableMimeData::convertEnums(display)) +{ + Q_ASSERT(mWidget != NULL); + Q_ASSERT(CSMWorld::ColumnBase::isId(display)); + Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); + + mWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(mWidget, + SIGNAL(customContextMenuRequested(const QPoint &)), + this, + SLOT(showContextMenu(const QPoint &))); + + mEditIdAction = new QAction(this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editIdRequest())); + + QLineEdit *lineEdit = qobject_cast(mWidget); + if (lineEdit != NULL) + { + mContextMenu = lineEdit->createStandardContextMenu(); + } + else + { + mContextMenu = new QMenu(mWidget); + } +} + +void CSVWorld::IdContextMenu::excludeId(const std::string &id) +{ + mExcludedIds.insert(id); +} + +QString CSVWorld::IdContextMenu::getWidgetValue() const +{ + QLineEdit *lineEdit = qobject_cast(mWidget); + QLabel *label = qobject_cast(mWidget); + + QString value = ""; + if (lineEdit != NULL) + { + value = lineEdit->text(); + } + else if (label != NULL) + { + value = label->text(); + } + return value; +} + +void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) +{ + mEditIdAction->setText(text); + if (mContextMenu->actions().isEmpty()) + { + mContextMenu->addAction(mEditIdAction); + } + else if (mContextMenu->actions().first() != mEditIdAction) + { + QAction *action = mContextMenu->actions().first(); + mContextMenu->insertAction(action, mEditIdAction); + mContextMenu->insertSeparator(action); + } +} + +void CSVWorld::IdContextMenu::removeEditIdActionFromMenu() +{ + if (mContextMenu->actions().isEmpty()) + { + return; + } + + if (mContextMenu->actions().first() == mEditIdAction) + { + mContextMenu->removeAction(mEditIdAction); + if (!mContextMenu->actions().isEmpty() && mContextMenu->actions().first()->isSeparator()) + { + mContextMenu->removeAction(mContextMenu->actions().first()); + } + } +} + +void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) +{ + QString value = getWidgetValue(); + bool isExcludedId = mExcludedIds.find(value.toUtf8().constData()) != mExcludedIds.end(); + if (!value.isEmpty() && !isExcludedId) + { + addEditIdActionToMenu("Edit '" + value + "'"); + } + else + { + removeEditIdActionFromMenu(); + } + + if (!mContextMenu->actions().isEmpty()) + { + mContextMenu->exec(mWidget->mapToGlobal(pos)); + } +} + +void CSVWorld::IdContextMenu::editIdRequest() +{ + CSMWorld::UniversalId editId(mIdType, getWidgetValue().toUtf8().constData()); + emit editIdRequest(editId, ""); +} + /* =============================================================EditWidget===================================================== */ +void CSVWorld::EditWidget::createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, + int currentRow) const +{ + Q_ASSERT(editor != NULL); + + if (CSMWorld::ColumnBase::isId(display) && + CSMWorld::TableMimeData::convertEnums(display) != CSMWorld::UniversalId::Type_None) + { + int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QString id = mTable->data(mTable->index(currentRow, idColumn)).toString(); + + IdContextMenu *menu = new IdContextMenu(editor, display); + // Current ID is already opened, so no need to create Edit 'ID' action for it + menu->excludeId(id.toUtf8().constData()); + connect(menu, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + } +} + CSVWorld::EditWidget::~EditWidget() { for (unsigned i = 0; i < mNestedModels.size(); ++i) @@ -380,9 +479,6 @@ mCommandDispatcher (commandDispatcher), mDocument (document) { remake (row); - - connect(mDispatcher, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), - this, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); } void CSVWorld::EditWidget::remake(int row) @@ -468,17 +564,33 @@ void CSVWorld::EditWidget::remake(int row) static_cast (mTable->data (mTable->index (row, typeColumn)).toInt()), mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); - NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this); - // FIXME: does not work well when enum delegates are used - //table->resizeColumnsToContents(); + bool editable = true; + bool fixedRows = false; + QVariant v = mTable->index(row, i).data(); + if (v.canConvert()) + { + assert (QString(v.typeName()) == "CSMWorld::ColumnBase::TableEditModes"); - if(mTable->index(row, i).data().type() == QVariant::UserType) + if (v.value() == CSMWorld::ColumnBase::TableEdit_None) + editable = false; + else if (v.value() == CSMWorld::ColumnBase::TableEdit_FixedRows) + fixedRows = true; + } + + NestedTable* table = + new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); + table->resizeColumnsToContents(); + if (!editable) { table->setEditTriggers(QAbstractItemView::NoEditTriggers); - table->setEnabled(false); + table->setSelectionMode(QAbstractItemView::NoSelection); + table->setStyleSheet("QTableView { color: gray; }"); + table->horizontalHeader()->setStyleSheet("QHeaderView { color: gray; }"); } - else - table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); + // Uncomment below two lines to activate editing of nested table cells by a single click + //else + //table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); + table->resizeColumnsToContents(); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); @@ -490,11 +602,16 @@ void CSVWorld::EditWidget::remake(int row) new QLabel (mTable->headerData (i, Qt::Horizontal, Qt::DisplayRole).toString(), mMainWidget); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - if(mTable->index(row, i).data().type() == QVariant::UserType) + if(!editable) label->setEnabled(false); tablesLayout->addWidget(label); tablesLayout->addWidget(table); + + connect(table, + SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { @@ -528,6 +645,8 @@ void CSVWorld::EditWidget::remake(int row) editor->setEnabled(false); label->setEnabled(false); } + + createEditorContextMenu(editor, display, row); } } else @@ -579,6 +698,8 @@ void CSVWorld::EditWidget::remake(int row) editor->setEnabled(false); label->setEnabled(false); } + + createEditorContextMenu(editor, display, row); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); @@ -595,17 +716,37 @@ void CSVWorld::EditWidget::remake(int row) this->setWidgetResizable(true); } -/* -==============================DialogueSubView========================================== -*/ -CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting) : +QVBoxLayout& CSVWorld::SimpleDialogueSubView::getMainLayout() +{ + return *mMainLayout; +} + +CSMWorld::IdTable& CSVWorld::SimpleDialogueSubView::getTable() +{ + return *mTable; +} + +CSMWorld::CommandDispatcher& CSVWorld::SimpleDialogueSubView::getCommandDispatcher() +{ + return mCommandDispatcher; +} + +CSVWorld::EditWidget& CSVWorld::SimpleDialogueSubView::getEditWidget() +{ + return *mEditWidget; +} + +bool CSVWorld::SimpleDialogueSubView::isLocked() const +{ + return mLocked; +} + +CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mEditWidget(0), mMainLayout(NULL), mTable(dynamic_cast(document.getData().getTableModel(id))), - mUndoStack(document.getUndoStack()), mLocked(false), mDocument(document), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) @@ -613,172 +754,45 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM connect(mTable, SIGNAL(dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT(dataChanged(const QModelIndex&))); connect(mTable, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int))); - changeCurrentId(id.getId()); + updateCurrentId(); QWidget *mainWidget = new QWidget(this); - QHBoxLayout *buttonsLayout = new QHBoxLayout; - QToolButton* prevButton = new QToolButton(mainWidget); - prevButton->setIcon(QIcon(":/go-previous.png")); - prevButton->setToolTip ("Switch to previous record"); - QToolButton* nextButton = new QToolButton(mainWidget); - nextButton->setIcon(QIcon(":/go-next.png")); - nextButton->setToolTip ("Switch to next record"); - buttonsLayout->addWidget(prevButton, 0); - buttonsLayout->addWidget(nextButton, 1); - buttonsLayout->addStretch(2); - - QToolButton* cloneButton = new QToolButton(mainWidget); - cloneButton->setIcon(QIcon(":/edit-clone.png")); - cloneButton->setToolTip ("Clone record"); - QToolButton* addButton = new QToolButton(mainWidget); - addButton->setIcon(QIcon(":/add.png")); - addButton->setToolTip ("Add new record"); - QToolButton* deleteButton = new QToolButton(mainWidget); - deleteButton->setIcon(QIcon(":/edit-delete.png")); - deleteButton->setToolTip ("Delete record"); - QToolButton* revertButton = new QToolButton(mainWidget); - revertButton->setIcon(QIcon(":/edit-undo.png")); - revertButton->setToolTip ("Revert record"); - - if (mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview) - { - QToolButton* previewButton = new QToolButton(mainWidget); - previewButton->setIcon(QIcon(":/edit-preview.png")); - previewButton->setToolTip ("Open a preview of this record"); - buttonsLayout->addWidget(previewButton); - connect(previewButton, SIGNAL(clicked()), this, SLOT(showPreview())); - } - - if (mTable->getFeatures() & CSMWorld::IdTable::Feature_View) - { - QToolButton* viewButton = new QToolButton(mainWidget); - viewButton->setIcon(QIcon(":/cell.png")); - viewButton->setToolTip ("Open a scene view of the cell this record is located in"); - buttonsLayout->addWidget(viewButton); - connect(viewButton, SIGNAL(clicked()), this, SLOT(viewRecord())); - } - - buttonsLayout->addWidget(cloneButton); - buttonsLayout->addWidget(addButton); - buttonsLayout->addWidget(deleteButton); - buttonsLayout->addWidget(revertButton); - - connect(nextButton, SIGNAL(clicked()), this, SLOT(nextId())); - connect(prevButton, SIGNAL(clicked()), this, SLOT(prevId())); - connect(cloneButton, SIGNAL(clicked()), this, SLOT(cloneRequest())); - connect(revertButton, SIGNAL(clicked()), &mCommandDispatcher, SLOT(executeRevert())); - connect(deleteButton, SIGNAL(clicked()), &mCommandDispatcher, SLOT(executeDelete())); - mMainLayout = new QVBoxLayout(mainWidget); + setWidget (mainWidget); + + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); mEditWidget = new EditWidget(mainWidget, - mTable->getModelIndex(mCurrentId, 0).row(), mTable, mCommandDispatcher, document, false); - connect(mEditWidget, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), - this, SLOT(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); + mTable->getModelIndex(getUniversalId().getId(), idColumn).row(), mTable, mCommandDispatcher, document, false); + + if (id.getType() == CSMWorld::UniversalId::Type_Referenceable) + { + CSMWorld::IdTree *objectTable = static_cast(mTable); + + connect (objectTable, SIGNAL (refreshNpcDialogue (int, const std::string&)), + this, SLOT (refreshNpcDialogue (int, const std::string&))); + } mMainLayout->addWidget(mEditWidget); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - mMainLayout->addWidget (mBottom = - new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this)); + dataChanged(mTable->getModelIndex (getUniversalId().getId(), idColumn)); - mBottom->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - - connect(mBottom, SIGNAL(requestFocus(const std::string&)), this, SLOT(requestFocus(const std::string&))); - - connect(addButton, SIGNAL(clicked()), mBottom, SLOT(createRequest())); - - if(!mBottom->canCreateAndDelete()) - { - cloneButton->setDisabled (true); - addButton->setDisabled (true); - deleteButton->setDisabled (true); - } - - dataChanged(mTable->getModelIndex (mCurrentId, 0)); - mMainLayout->addLayout (buttonsLayout); - setWidget (mainWidget); + connect(mEditWidget, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(focusId(const CSMWorld::UniversalId &, const std::string &))); } -void CSVWorld::DialogueSubView::prevId () +void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) { - int newRow = mTable->getModelIndex(mCurrentId, 0).row() - 1; - - if (newRow < 0) - { - return; - } - while (newRow >= 0) - { - QModelIndex newIndex(mTable->index(newRow, 0)); - - if (!newIndex.isValid()) - { - return; - } - - CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (newRow, 1)).toInt()); - if (!(state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased)) - { - mEditWidget->remake(newRow); - - setUniversalId(CSMWorld::UniversalId (static_cast (mTable->data (mTable->index (newRow, 2)).toInt()), - mTable->data (mTable->index (newRow, 0)).toString().toUtf8().constData())); - - changeCurrentId(std::string(mTable->data (mTable->index (newRow, 0)).toString().toUtf8().constData())); - - mEditWidget->setDisabled(mLocked); - - return; - } - --newRow; - } -} - -void CSVWorld::DialogueSubView::nextId () -{ - int newRow = mTable->getModelIndex(mCurrentId, 0).row() + 1; - - if (newRow >= mTable->rowCount()) - { - return; - } - - while (newRow < mTable->rowCount()) - { - QModelIndex newIndex(mTable->index(newRow, 0)); - - if (!newIndex.isValid()) - { - return; - } - - CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (newRow, 1)).toInt()); - if (!(state == CSMWorld::RecordBase::State_Deleted)) - { - mEditWidget->remake(newRow); - - setUniversalId(CSMWorld::UniversalId (static_cast (mTable->data (mTable->index (newRow, 2)).toInt()), - mTable->data (mTable->index (newRow, 0)).toString().toUtf8().constData())); - - changeCurrentId(std::string(mTable->data (mTable->index (newRow, 0)).toString().toUtf8().constData())); - - mEditWidget->setDisabled(mLocked); - - return; - } - ++newRow; - } -} - -void CSVWorld::DialogueSubView::setEditLock (bool locked) -{ - if (!mEditWidget) // hack to indicate that mCurrentId is no longer valid + if (!mEditWidget) // hack to indicate that getUniversalId().getId() is no longer valid return; + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); mLocked = locked; - QModelIndex currentIndex(mTable->getModelIndex(mCurrentId, 0)); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid()) { @@ -791,9 +805,10 @@ void CSVWorld::DialogueSubView::setEditLock (bool locked) } -void CSVWorld::DialogueSubView::dataChanged (const QModelIndex & index) +void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) { - QModelIndex currentIndex(mTable->getModelIndex(mCurrentId, 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && (index.parent().isValid() ? index.parent().row() : index.row()) == currentIndex.row()) @@ -824,11 +839,17 @@ void CSVWorld::DialogueSubView::dataChanged (const QModelIndex & index) } } -void CSVWorld::DialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - QModelIndex currentIndex(mTable->getModelIndex(mCurrentId, 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); - if (currentIndex.isValid() && currentIndex.row() >= start && currentIndex.row() <= end) + if (!currentIndex.isValid()) + { + return; + } + + if (currentIndex.parent() == parent && currentIndex.row() >= start && currentIndex.row() <= end) { if(mEditWidget) { @@ -839,60 +860,161 @@ void CSVWorld::DialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, } } -void CSVWorld::DialogueSubView::tableMimeDataDropped (QWidget* editor, - const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document) +void CSVWorld::SimpleDialogueSubView::updateCurrentId() { - if (document == &mDocument) + std::vector selection; + selection.push_back (getUniversalId().getId()); + mCommandDispatcher.setSelection(selection); +} + +void CSVWorld::SimpleDialogueSubView::refreshNpcDialogue (int type, const std::string& id) +{ + int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + if (CSMWorld::UniversalId::Type_Npc + != mTable->data(mTable->getModelIndex(getUniversalId().getId(), typeColumn), Qt::DisplayRole).toInt()) { - qobject_cast(editor)->setText(id.getId().c_str()); + return; + } + + int raceColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Race); + int classColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Class); + + if ((type == 0/*FIXME*/ && id == "") // skill or gmst changed + || (id == mTable->data(mTable->getModelIndex(getUniversalId().getId(), raceColumn), + Qt::DisplayRole).toString().toUtf8().constData()) // race + || (id == mTable->data(mTable->getModelIndex(getUniversalId().getId(), classColumn), + Qt::DisplayRole).toString().toUtf8().constData())) // class + { + int y = mEditWidget->verticalScrollBar()->value(); + mEditWidget->remake (mTable->getModelIndex(getUniversalId().getId(), 0).row()); + mEditWidget->verticalScrollBar()->setValue(y); } } -void CSVWorld::DialogueSubView::requestFocus (const std::string& id) +void CSVWorld::DialogueSubView::addButtonBar() { - changeCurrentId(id); + if (mButtons) + return; - mEditWidget->remake(mTable->getModelIndex (id, 0).row()); + mButtons = new RecordButtonBar (getUniversalId(), getTable(), mBottom, + &getCommandDispatcher(), this); + + getMainLayout().insertWidget (1, mButtons); + + // connections + connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview())); + connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord())); + connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), + mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); } -void CSVWorld::DialogueSubView::cloneRequest () +CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) +: SimpleDialogueSubView (id, document), mButtons (0) { - mBottom->cloneRequest(mCurrentId, static_cast(mTable->data(mTable->getModelIndex(mCurrentId, 2)).toInt())); + // bottom box + mBottom = new TableBottomBox (creatorFactory, document, id, this); + + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + this, SLOT (requestFocus (const std::string&))); + + // button bar + if (CSMSettings::UserSettings::instance().setting ("dialogues/toolbar", QString("true")) == "true") + addButtonBar(); + + // layout + getMainLayout().addWidget (mBottom); +} + +void CSVWorld::DialogueSubView::setEditLock (bool locked) +{ + SimpleDialogueSubView::setEditLock (locked); + + if (mButtons) + mButtons->setEditLock (locked); +} + +void CSVWorld::DialogueSubView::updateUserSetting (const QString& name, const QStringList& value) +{ + SimpleDialogueSubView::updateUserSetting (name, value); + + if (name=="dialogues/toolbar") + { + if (value.at(0)==QString ("true")) + { + addButtonBar(); + } + else + { + if (mButtons) + { + getMainLayout().removeWidget (mButtons); + delete mButtons; + mButtons = 0; + } + } + } + + if (mButtons) + mButtons->updateUserSetting (name, value); } void CSVWorld::DialogueSubView::showPreview () { - QModelIndex currentIndex(mTable->getModelIndex(mCurrentId, 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && - mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview && - currentIndex.row() < mTable->rowCount()) + getTable().getFeatures() & CSMWorld::IdTable::Feature_Preview && + currentIndex.row() < getTable().rowCount()) { - emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, mCurrentId), ""); + emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, getUniversalId().getId()), ""); } } void CSVWorld::DialogueSubView::viewRecord () { - QModelIndex currentIndex(mTable->getModelIndex (mCurrentId, 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && - currentIndex.row() < mTable->rowCount()) + currentIndex.row() < getTable().rowCount()) { - std::pair params = mTable->view (currentIndex.row()); + std::pair params = getTable().view (currentIndex.row()); if (params.first.getType()!=CSMWorld::UniversalId::Type_None) emit focusId (params.first, params.second); } } -void CSVWorld::DialogueSubView::changeCurrentId (const std::string& newId) +void CSVWorld::DialogueSubView::switchToRow (int row) { - std::vector selection; - mCurrentId = std::string(newId); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + std::string id = getTable().data (getTable().index (row, idColumn)).toString().toUtf8().constData(); - selection.push_back(mCurrentId); - mCommandDispatcher.setSelection(selection); + int typeColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + CSMWorld::UniversalId::Type type = static_cast ( + getTable().data (getTable().index (row, typeColumn)).toInt()); + + setUniversalId (CSMWorld::UniversalId (type, id)); + updateCurrentId(); + + getEditWidget().remake (row); + + int stateColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + CSMWorld::RecordBase::State state = static_cast ( + getTable().data (getTable().index (row, stateColumn)).toInt()); + + getEditWidget().setDisabled (isLocked() || state==CSMWorld::RecordBase::State_Deleted); +} + +void CSVWorld::DialogueSubView::requestFocus (const std::string& id) +{ + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex index = getTable().getModelIndex (id, idColumn); + + if (index.isValid()) + switchToRow (index.row()); } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 6cbd8ad77..b5c377c50 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -1,6 +1,7 @@ #ifndef CSV_WORLD_DIALOGUESUBVIEW_H #define CSV_WORLD_DIALOGUESUBVIEW_H +#include #include #include @@ -11,12 +12,14 @@ #include "../../model/world/columnbase.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/universalid.hpp" class QDataWidgetMapper; class QSize; class QEvent; class QLabel; class QVBoxLayout; +class QMenu; namespace CSMWorld { @@ -86,18 +89,12 @@ namespace CSVWorld public slots: void editorDataCommited(); void setIndex(const QModelIndex& index); - void tableMimeDataDropped(const std::vector& data, - const CSMDoc::Document* document); signals: void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); - void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document); - }; class DialogueDelegateDispatcher : public QAbstractItemDelegate @@ -153,11 +150,36 @@ namespace CSVWorld private slots: void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); + }; - signals: - void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document); + /// A context menu with "Edit 'ID'" action for editors in the dialogue subview + class IdContextMenu : public QObject + { + Q_OBJECT + + QWidget *mWidget; + CSMWorld::UniversalId::Type mIdType; + std::set mExcludedIds; + ///< A list of IDs that should not have the Edit 'ID' action. + + QMenu *mContextMenu; + QAction *mEditIdAction; + + QString getWidgetValue() const; + void addEditIdActionToMenu(const QString &text); + void removeEditIdActionFromMenu(); + + public: + IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); + + void excludeId(const std::string &id); + + private slots: + void showContextMenu(const QPoint &pos); + void editIdRequest(); + + signals: + void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; class EditWidget : public QScrollArea @@ -173,6 +195,9 @@ namespace CSVWorld CSMDoc::Document& mDocument; std::vector mNestedModels; //Plain, raw C pointers, deleted in the dtor + void createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, + int currentRow) const; public: EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, @@ -184,59 +209,81 @@ namespace CSVWorld void remake(int row); signals: - void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document); + void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; - class DialogueSubView : public CSVDoc::SubView + class SimpleDialogueSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - EditWidget* mEditWidget; - QVBoxLayout* mMainLayout; - CSMWorld::IdTable* mTable; - QUndoStack& mUndoStack; - std::string mCurrentId; - bool mLocked; - const CSMDoc::Document& mDocument; - TableBottomBox* mBottom; - CSMWorld::CommandDispatcher mCommandDispatcher; + EditWidget* mEditWidget; + QVBoxLayout* mMainLayout; + CSMWorld::IdTable* mTable; + bool mLocked; + const CSMDoc::Document& mDocument; + CSMWorld::CommandDispatcher mCommandDispatcher; + + protected: + + QVBoxLayout& getMainLayout(); + + CSMWorld::IdTable& getTable(); + + CSMWorld::CommandDispatcher& getCommandDispatcher(); + + EditWidget& getEditWidget(); + + void updateCurrentId(); + + bool isLocked() const; public: - DialogueSubView (const CSMWorld::UniversalId& id, - CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, - bool sorting = false); + SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); virtual void setEditLock (bool locked); - private: - void changeCurrentId(const std::string& newCurrent); - private slots: - void nextId(); + void dataChanged(const QModelIndex & index); + ///\brief we need to care for deleting currently edited record - void prevId(); + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + + void refreshNpcDialogue (int type, const std::string& id); + }; + + class RecordButtonBar; + + class DialogueSubView : public SimpleDialogueSubView + { + Q_OBJECT + + TableBottomBox* mBottom; + RecordButtonBar *mButtons; + + private: + + void addButtonBar(); + + public: + + DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting = false); + + virtual void setEditLock (bool locked); + + virtual void updateUserSetting (const QString& name, const QStringList& value); + + private slots: void showPreview(); void viewRecord(); - void cloneRequest(); - - void dataChanged(const QModelIndex & index); - ///\brief we need to care for deleting currently edited record - - void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document); + void switchToRow (int row); void requestFocus (const std::string& id); - - void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); }; } diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp new file mode 100644 index 000000000..7f3974e53 --- /dev/null +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -0,0 +1,26 @@ +#include "dragdroputils.hpp" + +#include + +#include "../../model/world/tablemimedata.hpp" + +const CSMWorld::TableMimeData *CSVWorld::DragDropUtils::getTableMimeData(const QDropEvent &event) +{ + return dynamic_cast(event.mimeData()); +} + +bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) +{ + const CSMWorld::TableMimeData *data = getTableMimeData(event); + return data != NULL && data->holdsType(type); +} + +CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, + CSMWorld::ColumnBase::Display type) +{ + if (canAcceptData(event, type)) + { + return getTableMimeData(event)->returnMatching(type); + } + return CSMWorld::UniversalId::Type_None; +} diff --git a/apps/opencs/view/world/dragdroputils.hpp b/apps/opencs/view/world/dragdroputils.hpp new file mode 100644 index 000000000..d1d780708 --- /dev/null +++ b/apps/opencs/view/world/dragdroputils.hpp @@ -0,0 +1,29 @@ +#ifndef CSV_WORLD_DRAGDROPUTILS_HPP +#define CSV_WORLD_DRAGDROPUTILS_HPP + +#include "../../model/world/columnbase.hpp" + +class QDropEvent; + +namespace CSMWorld +{ + class TableMimeData; + class UniversalId; +} + +namespace CSVWorld +{ + namespace DragDropUtils + { + const CSMWorld::TableMimeData *getTableMimeData(const QDropEvent &event); + + bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + ///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type + + CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + ///< Gets the accepted data from the \a event using the \a type + ///< \return Type_None if the \a event data doesn't holds the \a type + } +} + +#endif diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index 7032fee6d..a5f933283 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -1,11 +1,24 @@ +#include "dragrecordtable.hpp" + #include +#include + +#include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" -#include "dragrecordtable.hpp" +#include "../../model/world/commands.hpp" + +#include "dragdroputils.hpp" void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTable& table) { - CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (table.getDraggedRecords(), mDocument); + std::vector records = table.getDraggedRecords(); + if (records.empty()) + { + return; + } + + CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument); if (mime) { @@ -20,7 +33,9 @@ CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* QTableView(parent), mDocument(document), mEditLock(false) -{} +{ + setAcceptDrops(true); +} void CSVWorld::DragRecordTable::setEditLock (bool locked) { @@ -34,5 +49,49 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event) void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) { - event->accept(); + QModelIndex index = indexAt(event->pos()); + if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index))) + { + if (index.flags() & Qt::ItemIsEditable) + { + event->accept(); + return; + } + } + event->ignore(); +} + +void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + CSMWorld::ColumnBase::Display display = getIndexDisplayType(index); + if (CSVWorld::DragDropUtils::canAcceptData(*event, display)) + { + const CSMWorld::TableMimeData *data = CSVWorld::DragDropUtils::getTableMimeData(*event); + if (data->fromDocument(mDocument)) + { + CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, display); + QVariant newIndexData = QString::fromUtf8(id.getId().c_str()); + QVariant oldIndexData = index.data(Qt::EditRole); + if (newIndexData != oldIndexData) + { + mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*model(), index, newIndexData)); + } + } + } +} + +CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const +{ + Q_ASSERT(model() != NULL); + + if (index.isValid()) + { + QVariant display = model()->headerData(index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display); + if (display.isValid()) + { + return static_cast(display.toInt()); + } + } + return CSMWorld::ColumnBase::Display_None; } diff --git a/apps/opencs/view/world/dragrecordtable.hpp b/apps/opencs/view/world/dragrecordtable.hpp index 4996c03ac..560864ba5 100644 --- a/apps/opencs/view/world/dragrecordtable.hpp +++ b/apps/opencs/view/world/dragrecordtable.hpp @@ -2,7 +2,9 @@ #define CSV_WORLD_DRAGRECORDTABLE_H #include -#include +#include + +#include "../../model/world/columnbase.hpp" class QWidget; class QAction; @@ -38,6 +40,11 @@ namespace CSVWorld void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); + + void dropEvent(QDropEvent *event); + + private: + CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const; }; } diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 4b76bf9d6..e582e3356 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -1,4 +1,3 @@ - #include "enumdelegate.hpp" #include @@ -10,6 +9,24 @@ #include "../../model/world/commands.hpp" +int CSVWorld::EnumDelegate::getValueIndex(const QModelIndex &index, int role) const +{ + if (index.isValid() && index.data(role).isValid()) + { + int value = index.data(role).toInt(); + + int size = static_cast(mValues.size()); + for (int i = 0; i < size; ++i) + { + if (value == mValues.at(i).first) + { + return i; + } + } + } + return -1; +} + void CSVWorld::EnumDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const { @@ -67,54 +84,59 @@ QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptio void CSVWorld::EnumDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const { - if (QComboBox *comboBox = dynamic_cast (editor)) + if (QComboBox *comboBox = dynamic_cast(editor)) { - QVariant data = index.data (Qt::EditRole); - - if (tryDisplay && !data.isValid()) + int role = Qt::EditRole; + if (tryDisplay && !index.data(role).isValid()) { - data = index.data (Qt::DisplayRole); - if (!data.isValid()) + role = Qt::DisplayRole; + if (!index.data(role).isValid()) { return; } } - int value = data.toInt(); - - std::size_t size = mValues.size(); - - for (std::size_t i=0; isetCurrentIndex (i); - break; - } + int valueIndex = getValueIndex(index, role); + if (valueIndex != -1) + { + comboBox->setCurrentIndex(valueIndex); + } } } void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - if (index.data().isValid()) + int valueIndex = getValueIndex(index); + if (valueIndex != -1) { - QStyleOptionViewItemV4 option2 (option); - - int value = index.data().toInt(); - - for (std::vector >::const_iterator iter (mValues.begin()); - iter!=mValues.end(); ++iter) - if (iter->first==value) - { - option2.text = iter->second; - - QApplication::style()->drawControl (QStyle::CE_ItemViewItem, &option2, painter); - - break; - } + QStyleOptionViewItemV4 itemOption(option); + itemOption.text = mValues.at(valueIndex).second; + QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter); } } +QSize CSVWorld::EnumDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + int valueIndex = getValueIndex(index); + if (valueIndex != -1) + { + // Calculate the size hint as for a combobox. + // So, the whole text is visible (isn't elided) when the editor is created + QStyleOptionComboBox itemOption; + itemOption.fontMetrics = option.fontMetrics; + itemOption.palette = option.palette; + itemOption.rect = option.rect; + itemOption.state = option.state; + + const QString &valueText = mValues.at(valueIndex).second; + QSize valueSize = QSize(itemOption.fontMetrics.width(valueText), itemOption.fontMetrics.height()); + + itemOption.currentText = valueText; + return QApplication::style()->sizeFromContents(QStyle::CT_ComboBox, &itemOption, valueSize); + } + return option.rect.size(); +} CSVWorld::EnumDelegateFactory::EnumDelegateFactory() {} diff --git a/apps/opencs/view/world/enumdelegate.hpp b/apps/opencs/view/world/enumdelegate.hpp index 82890c791..a31945427 100644 --- a/apps/opencs/view/world/enumdelegate.hpp +++ b/apps/opencs/view/world/enumdelegate.hpp @@ -19,6 +19,8 @@ namespace CSVWorld std::vector > mValues; + int getValueIndex(const QModelIndex &index, int role = Qt::DisplayRole) const; + private: virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model, @@ -46,6 +48,8 @@ namespace CSVWorld virtual void paint (QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + }; class EnumDelegateFactory : public CommandDelegateFactory diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp new file mode 100644 index 000000000..2cf6222a6 --- /dev/null +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -0,0 +1,239 @@ +#include "extendedcommandconfigurator.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" + +#include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/data.hpp" + +CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, + const CSMWorld::UniversalId &id, + QWidget *parent) + : QWidget(parent), + mNumUsedCheckBoxes(0), + mNumChecked(0), + mMode(Mode_None), + mData(document.getData()), + mEditLock(false) +{ + mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); + + connect(&mData, SIGNAL(idListChanged()), this, SLOT(dataIdListChanged())); + + mPerformButton = new QPushButton(this); + mPerformButton->setDefault(true); + mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(mPerformButton, SIGNAL(clicked(bool)), this, SLOT(performExtendedCommand())); + + mCancelButton = new QPushButton("Cancel", this); + mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(mCancelButton, SIGNAL(clicked(bool)), this, SIGNAL(done())); + + mTypeGroup = new QGroupBox(this); + + QGridLayout *groupLayout = new QGridLayout(mTypeGroup); + groupLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + mTypeGroup->setLayout(groupLayout); + + QHBoxLayout *mainLayout = new QHBoxLayout(this); + mainLayout->setSizeConstraint(QLayout::SetNoConstraint); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->addWidget(mTypeGroup); + mainLayout->addWidget(mPerformButton); + mainLayout->addWidget(mCancelButton); +} + +void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds) +{ + mMode = mode; + if (mMode != Mode_None) + { + mPerformButton->setText((mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"); + mSelectedIds = selectedIds; + mCommandDispatcher->setSelection(mSelectedIds); + + setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); + setupGroupLayout(); + lockWidgets(mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::setEditLock(bool locked) +{ + if (mEditLock != locked) + { + mEditLock = locked; + lockWidgets(mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + setupGroupLayout(); +} + +void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() +{ + if (mMode == Mode_None) + { + return; + } + + int groupWidth = mTypeGroup->geometry().width(); + QGridLayout *layout = qobject_cast(mTypeGroup->layout()); + + // Find the optimal number of rows to place the checkboxes within the available space + int divider = 1; + do + { + while (layout->itemAt(0) != NULL) + { + layout->removeItem(layout->itemAt(0)); + } + + int counter = 0; + int itemsPerRow = mNumUsedCheckBoxes / divider; + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (counter < mNumUsedCheckBoxes) + { + int row = counter / itemsPerRow; + int column = counter - (counter / itemsPerRow) * itemsPerRow; + layout->addWidget(current->first, row, column); + } + ++counter; + } + divider *= 2; + } + while (groupWidth < mTypeGroup->sizeHint().width() && divider <= mNumUsedCheckBoxes); +} + +void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector &types) +{ + // Make sure that we have enough checkboxes + int numTypes = static_cast(types.size()); + int numCheckBoxes = static_cast(mTypeCheckBoxes.size()); + if (numTypes > numCheckBoxes) + { + for (int i = numTypes - numCheckBoxes; i > 0; --i) + { + QCheckBox *checkBox = new QCheckBox(mTypeGroup); + connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxStateChanged(int))); + mTypeCheckBoxes.insert(std::make_pair(checkBox, CSMWorld::UniversalId::Type_None)); + } + } + + // Set up the checkboxes + int counter = 0; + CheckBoxMap::iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (counter < numTypes) + { + CSMWorld::UniversalId type = types[counter]; + current->first->setText(QString::fromUtf8(type.getTypeName().c_str())); + current->first->setChecked(true); + current->second = type; + ++counter; + } + else + { + current->first->hide(); + } + } + mNumChecked = mNumUsedCheckBoxes = numTypes; +} + +void CSVWorld::ExtendedCommandConfigurator::lockWidgets(bool locked) +{ + mPerformButton->setEnabled(!mEditLock && mNumChecked > 0); + + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (int i = 0; current != end && i < mNumUsedCheckBoxes; ++current, ++i) + { + current->first->setEnabled(!mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() +{ + std::vector types; + + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (current->first->isChecked()) + { + types.push_back(current->second); + } + } + + mCommandDispatcher->setExtendedTypes(types); + if (mMode == Mode_Delete) + { + mCommandDispatcher->executeExtendedDelete(); + } + else + { + mCommandDispatcher->executeExtendedRevert(); + } + emit done(); +} + +void CSVWorld::ExtendedCommandConfigurator::checkBoxStateChanged(int state) +{ + switch (state) + { + case Qt::Unchecked: + --mNumChecked; + break; + case Qt::Checked: + ++mNumChecked; + break; + case Qt::PartiallyChecked: // Not used + break; + } + + mPerformButton->setEnabled(mNumChecked > 0); +} + +void CSVWorld::ExtendedCommandConfigurator::dataIdListChanged() +{ + bool idsRemoved = false; + for (int i = 0; i < static_cast(mSelectedIds.size()); ++i) + { + if (!mData.hasId(mSelectedIds[i])) + { + std::swap(mSelectedIds[i], mSelectedIds.back()); + mSelectedIds.pop_back(); + idsRemoved = true; + --i; + } + } + + // If all selected IDs were removed, cancel the configurator + if (mSelectedIds.empty()) + { + emit done(); + return; + } + + if (idsRemoved) + { + mCommandDispatcher->setSelection(mSelectedIds); + } +} diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp new file mode 100644 index 000000000..641b4a524 --- /dev/null +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -0,0 +1,78 @@ +#ifndef CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP +#define CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP + +#include + +#include + +#include "../../model/world/universalid.hpp" + +class QPushButton; +class QGroupBox; +class QCheckBox; +class QLabel; +class QHBoxLayout; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; + class Data; +} + +namespace CSVWorld +{ + class ExtendedCommandConfigurator : public QWidget + { + Q_OBJECT + + public: + enum Mode { Mode_None, Mode_Delete, Mode_Revert }; + + private: + typedef std::map CheckBoxMap; + + QPushButton *mPerformButton; + QPushButton *mCancelButton; + QGroupBox *mTypeGroup; + CheckBoxMap mTypeCheckBoxes; + int mNumUsedCheckBoxes; + int mNumChecked; + + Mode mMode; + CSMWorld::CommandDispatcher *mCommandDispatcher; + CSMWorld::Data &mData; + std::vector mSelectedIds; + + bool mEditLock; + + void setupGroupLayout(); + void setupCheckBoxes(const std::vector &types); + void lockWidgets(bool locked); + + public: + ExtendedCommandConfigurator(CSMDoc::Document &document, + const CSMWorld::UniversalId &id, + QWidget *parent = 0); + + void configure(Mode mode, const std::vector &selectedIds); + void setEditLock(bool locked); + + protected: + virtual void resizeEvent(QResizeEvent *event); + + private slots: + void performExtendedCommand(); + void checkBoxStateChanged(int state); + void dataIdListChanged(); + + signals: + void done(); + }; +} + +#endif diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index a123e127f..df7739941 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -1,4 +1,3 @@ - #include "genericcreator.hpp" #include @@ -48,6 +47,16 @@ std::string CSVWorld::GenericCreator::getId() const return mId->text().toUtf8().constData(); } +std::string CSVWorld::GenericCreator::getIdValidatorResult() const +{ + std::string errors; + + if (!mId->hasAcceptableInput()) + errors = mValidator->getError(); + + return errors; +} + void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {} void CSVWorld::GenericCreator::pushCommand (std::auto_ptr command, @@ -133,6 +142,15 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo mClonedType (CSMWorld::UniversalId::Type_None), mScopes (CSMWorld::Scope_Content), mScope (0), mScopeLabel (0), mCloneMode (false) { + // If the collection ID has a parent type, use it instead. + // It will change IDs with Record/SubRecord class (used for creators in Dialogue subviews) + // to IDs with general RecordList class (used for creators in Table subviews). + CSMWorld::UniversalId::Type listParentType = CSMWorld::UniversalId::getParentType(mListId.getType()); + if (listParentType != CSMWorld::UniversalId::Type_None) + { + mListId = listParentType; + } + mLayout = new QHBoxLayout; mLayout->setContentsMargins (0, 0, 0, 0); @@ -152,6 +170,8 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create())); connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + + connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); } void CSVWorld::GenericCreator::setEditLock (bool locked) @@ -282,3 +302,12 @@ void CSVWorld::GenericCreator::scopeChanged (int index) update(); updateNamespace(); } + +void CSVWorld::GenericCreator::dataIdListChanged() +{ + // If the original ID of cloned record was removed, cancel the creator + if (mCloneMode && !mData.hasId(mClonedId)) + { + emit done(); + } +} diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 1f854c69e..f63c45109 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -13,10 +13,12 @@ class QLineEdit; class QHBoxLayout; class QComboBox; class QLabel; +class QUndoStack; namespace CSMWorld { class CreateCommand; + class Data; } namespace CSVWorld @@ -58,6 +60,8 @@ namespace CSVWorld virtual std::string getId() const; + virtual std::string getIdValidatorResult() const; + /// Allow subclasses to add additional data to \a command. virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; @@ -111,6 +115,8 @@ namespace CSVWorld void create(); void scopeChanged (int index); + + void dataIdListChanged(); }; } diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp new file mode 100644 index 000000000..970490828 --- /dev/null +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -0,0 +1,41 @@ +#include "idcompletiondelegate.hpp" + +#include "../../model/world/idcompletionmanager.hpp" + +#include "../widget/droplineedit.hpp" + +CSVWorld::IdCompletionDelegate::IdCompletionDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document& document, + QObject *parent) + : CommandDelegate(dispatcher, document, parent) +{} + +QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + return createEditor(parent, option, index, getDisplayTypeFromIndex(index)); +} + +QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index, + CSMWorld::ColumnBase::Display display) const +{ + if (!index.data(Qt::EditRole).isValid() && !index.data(Qt::DisplayRole).isValid()) + { + return NULL; + } + + CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); + CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); + editor->setCompleter(completionManager.getCompleter(display).get()); + return editor; +} + +CSVWorld::CommandDelegate *CSVWorld::IdCompletionDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document& document, + QObject *parent) const +{ + return new IdCompletionDelegate(dispatcher, document, parent); +} diff --git a/apps/opencs/view/world/idcompletiondelegate.hpp b/apps/opencs/view/world/idcompletiondelegate.hpp new file mode 100644 index 000000000..d2ac6874f --- /dev/null +++ b/apps/opencs/view/world/idcompletiondelegate.hpp @@ -0,0 +1,36 @@ +#ifndef CSV_WORLD_IDCOMPLETIONDELEGATE_HPP +#define CSV_WORLD_IDCOMPLETIONDELEGATE_HPP + +#include "util.hpp" + +namespace CSVWorld +{ + /// \brief Enables the Id completion for a column + class IdCompletionDelegate : public CommandDelegate + { + public: + IdCompletionDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document& document, + QObject *parent); + + virtual QWidget *createEditor (QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + virtual QWidget *createEditor (QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index, + CSMWorld::ColumnBase::Display display) const; + }; + + class IdCompletionDelegateFactory : public CommandDelegateFactory + { + public: + virtual CommandDelegate *makeDelegate(CSMWorld::CommandDispatcher *dispatcher, + CSMDoc::Document& document, + QObject *parent) const; + ///< The ownership of the returned CommandDelegate is transferred to the caller. + }; +} + +#endif diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 13b05d2d1..1092d7217 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -1,4 +1,3 @@ - #include "idvalidator.hpp" #include diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index f88b9f0b9..1139afd69 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -1,18 +1,21 @@ - #include "infocreator.hpp" #include #include -#include #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/idcompletionmanager.hpp" + +#include "../widget/droplineedit.hpp" std::string CSVWorld::InfoCreator::getId() const { @@ -39,13 +42,19 @@ void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& com } CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) + const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager) : GenericCreator (data, undoStack, id) { QLabel *label = new QLabel ("Topic", this); insertBeforeButtons (label, false); - mTopic = new QLineEdit (this); + CSMWorld::ColumnBase::Display displayType = CSMWorld::ColumnBase::Display_Topic; + if (getCollectionId().getType() == CSMWorld::UniversalId::Type_JournalInfos) + { + displayType = CSMWorld::ColumnBase::Display_Journal; + } + mTopic = new CSVWidget::DropLineEdit(displayType, this); + mTopic->setCompleter(completionManager.getCompleter(displayType).get()); insertBeforeButtons (mTopic, true); setManualEditing (false); @@ -100,3 +109,12 @@ void CSVWorld::InfoCreator::topicChanged() { update(); } + +CSVWorld::Creator *CSVWorld::InfoCreatorFactory::makeCreator(CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const +{ + return new InfoCreator(document.getData(), + document.getUndoStack(), + id, + document.getIdCompletionManager()); +} diff --git a/apps/opencs/view/world/infocreator.hpp b/apps/opencs/view/world/infocreator.hpp index edc12975c..d131e3fac 100644 --- a/apps/opencs/view/world/infocreator.hpp +++ b/apps/opencs/view/world/infocreator.hpp @@ -3,11 +3,15 @@ #include "genericcreator.hpp" -class QLineEdit; - namespace CSMWorld { class InfoCollection; + class IdCompletionManager; +} + +namespace CSVWidget +{ + class DropLineEdit; } namespace CSVWorld @@ -16,7 +20,7 @@ namespace CSVWorld { Q_OBJECT - QLineEdit *mTopic; + CSVWidget::DropLineEdit *mTopic; virtual std::string getId() const; @@ -25,7 +29,7 @@ namespace CSVWorld public: InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id); + const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager& completionManager); virtual void cloneMode (const std::string& originId, const CSMWorld::UniversalId::Type type); @@ -43,6 +47,14 @@ namespace CSVWorld void topicChanged(); }; + + class InfoCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const; + ///< The ownership of the returned Creator is transferred to the caller. + }; } #endif diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 5c8762020..23d566439 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -1,21 +1,28 @@ #include "nestedtable.hpp" -#include "../../model/world/nestedtableproxymodel.hpp" -#include "../../model/world/universalid.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/commanddispatcher.hpp" -#include "util.hpp" #include #include #include #include +#include "../../model/world/nestedtableproxymodel.hpp" +#include "../../model/world/universalid.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commanddispatcher.hpp" + +#include "tableeditidaction.hpp" +#include "util.hpp" + CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent) - : QTableView(parent), - mUndoStack(document.getUndoStack()), + QWidget* parent, + bool editable, + bool fixedRows) + : DragRecordTable(document, parent), + mAddNewRowAction(NULL), + mRemoveRowAction(NULL), + mEditIdAction(NULL), mModel(model) { mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); @@ -23,7 +30,11 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); +#else horizontalHeader()->setResizeMode (QHeaderView::Interactive); +#endif verticalHeader()->hide(); int columns = model->columnCount(QModelIndex()); @@ -43,53 +54,78 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, setModel(model); - setAcceptDrops(true); + if (editable) + { + if (!fixedRows) + { + mAddNewRowAction = new QAction (tr ("Add new row"), this); - mAddNewRowAction = new QAction (tr ("Add new row"), this); + connect(mAddNewRowAction, SIGNAL(triggered()), + this, SLOT(addNewRowActionTriggered())); - connect(mAddNewRowAction, SIGNAL(triggered()), - this, SLOT(addNewRowActionTriggered())); + mRemoveRowAction = new QAction (tr ("Remove row"), this); - mRemoveRowAction = new QAction (tr ("Remove row"), this); + connect(mRemoveRowAction, SIGNAL(triggered()), + this, SLOT(removeRowActionTriggered())); + } - connect(mRemoveRowAction, SIGNAL(triggered()), - this, SLOT(removeRowActionTriggered())); + mEditIdAction = new TableEditIdAction(*this, this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); + } } -void CSVWorld::NestedTable::dragEnterEvent(QDragEnterEvent *event) -{ -} - -void CSVWorld::NestedTable::dragMoveEvent(QDragMoveEvent *event) +std::vector CSVWorld::NestedTable::getDraggedRecords() const { + // No drag support for nested tables + return std::vector(); } void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) { + if (!mEditIdAction) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu(this); - if (selectionModel()->selectedRows().size() == 1) - menu.addAction(mRemoveRowAction); + int currentRow = rowAt(event->y()); + int currentColumn = columnAt(event->x()); + if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) + { + mEditIdAction->setCell(currentRow, currentColumn); + menu.addAction(mEditIdAction); + menu.addSeparator(); + } - menu.addAction(mAddNewRowAction); + if (mAddNewRowAction && mRemoveRowAction) + { + if (selectionModel()->selectedRows().size() == 1) + menu.addAction(mRemoveRowAction); + + menu.addAction(mAddNewRowAction); + } menu.exec (event->globalPos()); } void CSVWorld::NestedTable::removeRowActionTriggered() { - mUndoStack.push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().begin()->row(), - mModel->getParentColumn())); + mDocument.getUndoStack().push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), + mModel->getParentId(), + selectionModel()->selectedRows().begin()->row(), + mModel->getParentColumn())); } void CSVWorld::NestedTable::addNewRowActionTriggered() { - mUndoStack.push(new CSMWorld::AddNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().size(), - mModel->getParentColumn())); + mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), + mModel->getParentId(), + selectionModel()->selectedRows().size(), + mModel->getParentColumn())); +} + +void CSVWorld::NestedTable::editCell() +{ + emit editRequest(mEditIdAction->getCurrentId(), ""); } diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index b8e91844c..765060ea5 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -1,10 +1,10 @@ #ifndef CSV_WORLD_NESTEDTABLE_H #define CSV_WORLD_NESTEDTABLE_H -#include -#include +#include + +#include "dragrecordtable.hpp" -class QUndoStack; class QAction; class QContextMenuEvent; @@ -22,13 +22,15 @@ namespace CSMDoc namespace CSVWorld { - class NestedTable : public QTableView + class TableEditIdAction; + + class NestedTable : public DragRecordTable { Q_OBJECT QAction *mAddNewRowAction; QAction *mRemoveRowAction; - QUndoStack& mUndoStack; + TableEditIdAction *mEditIdAction; CSMWorld::NestedTableProxyModel* mModel; CSMWorld::CommandDispatcher *mDispatcher; @@ -36,12 +38,11 @@ namespace CSVWorld NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent = NULL); + QWidget* parent = NULL, + bool editable = true, + bool fixedRows = false); - protected: - void dragEnterEvent(QDragEnterEvent *event); - - void dragMoveEvent(QDragMoveEvent *event); + virtual std::vector getDraggedRecords() const; private: void contextMenuEvent (QContextMenuEvent *event); @@ -50,6 +51,11 @@ namespace CSVWorld void removeRowActionTriggered(); void addNewRowActionTriggered(); + + void editCell(); + + signals: + void editRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; } diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index 2cbe17dcf..7dce611c4 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -179,7 +181,7 @@ namespace CSVWorld } void PhysicsSystem::addHeightField(Ogre::SceneManager *sceneManager, - float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) + const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) { std::string name = "HeightField_" + QString::number(x).toStdString() + "_" + QString::number(y).toStdString(); diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp index 0036bf769..17661caa8 100644 --- a/apps/opencs/view/world/physicssystem.hpp +++ b/apps/opencs/view/world/physicssystem.hpp @@ -63,7 +63,7 @@ namespace CSVWorld void moveSceneNodes(const std::string sceneNodeName, const Ogre::Vector3 &position); void addHeightField(Ogre::SceneManager *sceneManager, - float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); + const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); void removeHeightField(Ogre::SceneManager *sceneManager, int x, int y); diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index 1c2d6b95c..f3312bb20 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -1,4 +1,3 @@ - #include "previewsubview.hpp" #include @@ -13,8 +12,6 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo { QHBoxLayout *layout = new QHBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - if (document.getData().getReferenceables().searchId (id.getId())==-1) { std::string referenceableId = diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp new file mode 100644 index 000000000..1a838a3b3 --- /dev/null +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -0,0 +1,205 @@ +#include "recordbuttonbar.hpp" + +#include +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/commanddispatcher.hpp" + +#include "../../model/settings/usersettings.hpp" + +#include "../world/tablebottombox.hpp" + +void CSVWorld::RecordButtonBar::updateModificationButtons() +{ + bool createAndDeleteDisabled = !mBottom || !mBottom->canCreateAndDelete() || mLocked; + + mCloneButton->setDisabled (createAndDeleteDisabled); + mAddButton->setDisabled (createAndDeleteDisabled); + + bool commandDisabled = !mCommandDispatcher || mLocked; + + mRevertButton->setDisabled (commandDisabled); + mDeleteButton->setDisabled (commandDisabled || createAndDeleteDisabled); +} + +void CSVWorld::RecordButtonBar::updatePrevNextButtons() +{ + int rows = mTable.rowCount(); + + if (rows<=1) + { + mPrevButton->setDisabled (true); + mNextButton->setDisabled (true); + } + else if (CSMSettings::UserSettings::instance().settingValue ("general-input/cycle")=="true") + { + mPrevButton->setDisabled (false); + mNextButton->setDisabled (false); + } + else + { + int row = mTable.getModelIndex (mId.getId(), 0).row(); + + mPrevButton->setDisabled (row<=0); + mNextButton->setDisabled (row>=rows-1); + } +} + +CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, + CSMWorld::IdTable& table, TableBottomBox *bottomBox, + CSMWorld::CommandDispatcher *commandDispatcher, QWidget *parent) +: QWidget (parent), mId (id), mTable (table), mBottom (bottomBox), + mCommandDispatcher (commandDispatcher), mLocked (false) +{ + QHBoxLayout *buttonsLayout = new QHBoxLayout; + buttonsLayout->setContentsMargins (0, 0, 0, 0); + + // left section + mPrevButton = new QToolButton (this); + mPrevButton->setIcon(QIcon(":/go-previous.png")); + mPrevButton->setToolTip ("Switch to previous record"); + buttonsLayout->addWidget (mPrevButton, 0); + + mNextButton = new QToolButton (this); + mNextButton->setIcon(QIcon(":/go-next.png")); + mNextButton->setToolTip ("Switch to next record"); + buttonsLayout->addWidget (mNextButton, 1); + + buttonsLayout->addStretch(2); + + // optional buttons of the right section + if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview) + { + QToolButton* previewButton = new QToolButton (this); + previewButton->setIcon(QIcon(":/edit-preview.png")); + previewButton->setToolTip ("Open a preview of this record"); + buttonsLayout->addWidget(previewButton); + connect (previewButton, SIGNAL(clicked()), this, SIGNAL (showPreview())); + } + + if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) + { + QToolButton* viewButton = new QToolButton (this); + viewButton->setIcon(QIcon(":/cell.png")); + viewButton->setToolTip ("Open a scene view of the cell this record is located in"); + buttonsLayout->addWidget(viewButton); + connect (viewButton, SIGNAL(clicked()), this, SIGNAL (viewRecord())); + } + + // right section + mCloneButton = new QToolButton (this); + mCloneButton->setIcon(QIcon(":/edit-clone.png")); + mCloneButton->setToolTip ("Clone record"); + buttonsLayout->addWidget(mCloneButton); + + mAddButton = new QToolButton (this); + mAddButton->setIcon(QIcon(":/add.png")); + mAddButton->setToolTip ("Add new record"); + buttonsLayout->addWidget(mAddButton); + + mDeleteButton = new QToolButton (this); + mDeleteButton->setIcon(QIcon(":/edit-delete.png")); + mDeleteButton->setToolTip ("Delete record"); + buttonsLayout->addWidget(mDeleteButton); + + mRevertButton = new QToolButton (this); + mRevertButton->setIcon(QIcon(":/edit-undo.png")); + mRevertButton->setToolTip ("Revert record"); + buttonsLayout->addWidget(mRevertButton); + + setLayout (buttonsLayout); + + // connections + if(mBottom && mBottom->canCreateAndDelete()) + { + connect (mAddButton, SIGNAL (clicked()), mBottom, SLOT (createRequest())); + connect (mCloneButton, SIGNAL (clicked()), this, SLOT (cloneRequest())); + } + + connect (mNextButton, SIGNAL (clicked()), this, SLOT (nextId())); + connect (mPrevButton, SIGNAL (clicked()), this, SLOT (prevId())); + + if (mCommandDispatcher) + { + connect (mRevertButton, SIGNAL (clicked()), mCommandDispatcher, SLOT (executeRevert())); + connect (mDeleteButton, SIGNAL (clicked()), mCommandDispatcher, SLOT (executeDelete())); + } + + connect (&mTable, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); + connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), + this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); + + updateModificationButtons(); + updatePrevNextButtons(); +} + +void CSVWorld::RecordButtonBar::setEditLock (bool locked) +{ + mLocked = locked; + updateModificationButtons(); +} + +void CSVWorld::RecordButtonBar::updateUserSetting (const QString& name, const QStringList& value) +{ + if (name=="general-input/cycle") + updatePrevNextButtons(); +} + +void CSVWorld::RecordButtonBar::universalIdChanged (const CSMWorld::UniversalId& id) +{ + mId = id; + updatePrevNextButtons(); +} + +void CSVWorld::RecordButtonBar::cloneRequest() +{ + if (mBottom) + { + int typeColumn = mTable.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + + QModelIndex typeIndex = mTable.getModelIndex (mId.getId(), typeColumn); + CSMWorld::UniversalId::Type type = static_cast ( + mTable.data (typeIndex).toInt()); + + mBottom->cloneRequest (mId.getId(), type); + } +} + +void CSVWorld::RecordButtonBar::nextId() +{ + int newRow = mTable.getModelIndex (mId.getId(), 0).row() + 1; + + if (newRow >= mTable.rowCount()) + { + if (CSMSettings::UserSettings::instance().settingValue ("general-input/cycle") + =="true") + newRow = 0; + else + return; + } + + emit switchToRow (newRow); +} + +void CSVWorld::RecordButtonBar::prevId() +{ + int newRow = mTable.getModelIndex (mId.getId(), 0).row() - 1; + + if (newRow < 0) + { + if (CSMSettings::UserSettings::instance().settingValue ("general-input/cycle") + =="true") + newRow = mTable.rowCount()-1; + else + return; + } + + emit switchToRow (newRow); +} + +void CSVWorld::RecordButtonBar::rowNumberChanged (const QModelIndex& parent, int start, int end) +{ + updatePrevNextButtons(); +} diff --git a/apps/opencs/view/world/recordbuttonbar.hpp b/apps/opencs/view/world/recordbuttonbar.hpp new file mode 100644 index 000000000..93ca45518 --- /dev/null +++ b/apps/opencs/view/world/recordbuttonbar.hpp @@ -0,0 +1,87 @@ +#ifndef CSV_WORLD_RECORDBUTTONBAR_H +#define CSV_WORLD_RECORDBUTTONBAR_H + +#include + +#include "../../model/world/universalid.hpp" + +class QToolButton; +class QModelIndex; + +namespace CSMWorld +{ + class IdTable; + class CommandDispatcher; +} + +namespace CSVWorld +{ + class TableBottomBox; + + /// \brief Button bar for use in dialogue-type subviews + /// + /// Contains the following buttons: + /// - next/prev + /// - clone + /// - add + /// - delete + /// - revert + /// - preview (optional) + /// - view (optional) + class RecordButtonBar : public QWidget + { + Q_OBJECT + + CSMWorld::UniversalId mId; + CSMWorld::IdTable& mTable; + TableBottomBox *mBottom; + CSMWorld::CommandDispatcher *mCommandDispatcher; + QToolButton *mPrevButton; + QToolButton *mNextButton; + QToolButton *mCloneButton; + QToolButton *mAddButton; + QToolButton *mDeleteButton; + QToolButton *mRevertButton; + bool mLocked; + + private: + + void updateModificationButtons(); + + void updatePrevNextButtons(); + + public: + + RecordButtonBar (const CSMWorld::UniversalId& id, + CSMWorld::IdTable& table, TableBottomBox *bottomBox = 0, + CSMWorld::CommandDispatcher *commandDispatcher = 0, QWidget *parent = 0); + + void setEditLock (bool locked); + + void updateUserSetting (const QString& name, const QStringList& value); + + public slots: + + void universalIdChanged (const CSMWorld::UniversalId& id); + + private slots: + + void cloneRequest(); + + void nextId(); + + void prevId(); + + void rowNumberChanged (const QModelIndex& parent, int start, int end); + + signals: + + void showPreview(); + + void viewRecord(); + + void switchToRow (int row); + }; +} + +#endif diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index e8055ed31..1357ca46f 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -1,4 +1,3 @@ - #include "referenceablecreator.hpp" #include diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index e9bb04ba7..73ca62e02 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -1,13 +1,16 @@ - #include "referencecreator.hpp" #include -#include + +#include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/idcompletionmanager.hpp" + +#include "../widget/droplineedit.hpp" std::string CSVWorld::ReferenceCreator::getId() const { @@ -71,13 +74,14 @@ int CSVWorld::ReferenceCreator::getRefNumCount() const } CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) + const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager &completionManager) : GenericCreator (data, undoStack, id) { QLabel *label = new QLabel ("Cell", this); insertBeforeButtons (label, false); - mCell = new QLineEdit (this); + mCell = new CSVWidget::DropLineEdit(CSMWorld::ColumnBase::Display_Cell, this); + mCell->setCompleter(completionManager.getCompleter(CSMWorld::ColumnBase::Display_Cell).get()); insertBeforeButtons (mCell, true); setManualEditing (false); @@ -142,3 +146,12 @@ void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, CSVWorld::GenericCreator::cloneMode(originId, type); cellChanged(); //otherwise ok button will remain disabled } + +CSVWorld::Creator *CSVWorld::ReferenceCreatorFactory::makeCreator (CSMDoc::Document& document, + const CSMWorld::UniversalId& id) const +{ + return new ReferenceCreator(document.getData(), + document.getUndoStack(), + id, + document.getIdCompletionManager()); +} diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp index 877307c29..c230d0126 100644 --- a/apps/opencs/view/world/referencecreator.hpp +++ b/apps/opencs/view/world/referencecreator.hpp @@ -3,15 +3,24 @@ #include "genericcreator.hpp" -class QLineEdit; +namespace CSMWorld +{ + class IdCompletionManager; +} + +namespace CSVWidget +{ + class DropLineEdit; +} namespace CSVWorld { + class ReferenceCreator : public GenericCreator { Q_OBJECT - QLineEdit *mCell; + CSVWidget::DropLineEdit *mCell; std::string mId; private: @@ -28,7 +37,7 @@ namespace CSVWorld public: ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id); + const CSMWorld::UniversalId& id, CSMWorld::IdCompletionManager &completionManager); virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type); @@ -46,6 +55,14 @@ namespace CSVWorld void cellChanged(); }; + + class ReferenceCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const; + ///< The ownership of the returned Creator is transferred to the caller. + }; } #endif diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index bc96b0952..49764bd17 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -1,4 +1,3 @@ - #include "regionmap.hpp" #include diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index 411e24e75..996d1dc8b 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -1,4 +1,3 @@ - #include "regionmapsubview.hpp" #include "regionmap.hpp" diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index aa2161259..c2f3442f8 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -1,4 +1,3 @@ - #include "scenesubview.hpp" #include @@ -31,11 +30,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - - layout->addWidget (mBottom = - new TableBottomBox (NullCreatorFactory(), document.getData(), document.getUndoStack(), id, - this), 0); + layout->addWidget (mBottom = new TableBottomBox (NullCreatorFactory(), document, id, this), 0); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); @@ -137,11 +132,6 @@ void CSVWorld::SceneSubView::setEditLock (bool locked) } -void CSVWorld::SceneSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) -{ - -} - void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); @@ -252,8 +242,6 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mToolbar = toolbar; connect (mScene, SIGNAL (focusToolbarRequest()), mToolbar, SLOT (setFocus())); - connect (this, SIGNAL (updateSceneUserSetting(const QString &, const QStringList &)), - mScene, SLOT (updateUserSetting(const QString &, const QStringList &))); connect (mToolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); mLayout->addWidget (mToolbar, 0); @@ -262,8 +250,3 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mScene->selectDefaultNavigationMode(); setFocusProxy (mScene); } - -void CSVWorld::SceneSubView::updateUserSetting (const QString &key, const QStringList &list) -{ - emit updateSceneUserSetting(key, list); -} diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index fc45347d0..a34d71901 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -52,8 +52,6 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateEditorSetting (const QString& key, const QString& value); - virtual void setStatusBar (bool show); virtual void useHint (const std::string& hint); @@ -83,14 +81,6 @@ namespace CSVWorld void cellSelectionChanged (const CSMWorld::UniversalId& id); void handleDrop(const std::vector& data); - - public slots: - - void updateUserSetting (const QString &, const QStringList &); - - signals: - - void updateSceneUserSetting (const QString &, const QStringList &); }; } diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 2f0d82ae1..25f4fd077 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -31,11 +31,11 @@ bool CSVWorld::ScriptEdit::event (QEvent *event) if (event->type()==QEvent::ShortcutOverride) { QKeyEvent *keyEvent = static_cast (event); - + if (keyEvent->matches (QKeySequence::Undo) || keyEvent->matches (QKeySequence::Redo)) return true; } - + return QPlainTextEdit::event (event); } @@ -92,13 +92,16 @@ CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighli connect (&mUpdateTimer, SIGNAL (timeout()), this, SLOT (updateHighlighting())); + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + connect (&userSettings, SIGNAL (userSettingUpdated(const QString &, const QStringList &)), + this, SLOT (updateUserSetting (const QString &, const QStringList &))); + mUpdateTimer.setSingleShot (true); // TODO: provide a font selector dialogue mMonoFont.setStyleHint(QFont::TypeWriter); - std::string useMonoFont = - CSMSettings::UserSettings::instance().setting("script-editor/mono-font", "true").toStdString(); - if (useMonoFont == "true") + + if (userSettings.setting("script-editor/mono-font", "true") == "true") setFont(mMonoFont); mLineNumberArea = new LineNumberArea(this); @@ -107,10 +110,13 @@ CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighli connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); - std::string showStatusBar = - CSMSettings::UserSettings::instance().settingValue("script-editor/show-linenum").toStdString(); + showLineNum(userSettings.settingValue("script-editor/show-linenum") == "true"); +} - showLineNum(showStatusBar == "true"); +void CSVWorld::ScriptEdit::updateUserSetting (const QString &name, const QStringList &list) +{ + if (mHighlighter->updateUserSetting (name, list)) + updateHighlighting(); } void CSVWorld::ScriptEdit::showLineNum(bool show) @@ -270,11 +276,11 @@ void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event) if(textCursor().hasSelection()) { QString str = textCursor().selection().toPlainText(); - int selectedLines = str.count("\n")+1; + int offset = str.count("\n"); if(textCursor().position() < textCursor().anchor()) - endBlock += selectedLines; + endBlock += offset; else - startBlock -= selectedLines; + startBlock -= offset; } painter.setBackgroundMode(Qt::OpaqueMode); QFont font = painter.font(); diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index fb577e60e..d17abf24e 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -56,7 +56,7 @@ namespace CSVWorld protected: bool event (QEvent *event); - + public: ScriptEdit (const CSMDoc::Document& document, ScriptHighlighter::Mode mode, @@ -96,7 +96,12 @@ namespace CSVWorld void updateHighlighting(); void updateLineNumberAreaWidth(int newBlockCount); + void updateLineNumberArea(const QRect &, int); + + public slots: + + void updateUserSetting (const QString &name, const QStringList &list); }; class LineNumberArea : public QWidget diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp new file mode 100644 index 000000000..a9e315c73 --- /dev/null +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -0,0 +1,147 @@ +#include "scripterrortable.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" +#include "../../model/settings/usersettings.hpp" + +void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) +{ + std::ostringstream stream; + stream << message << " (" << loc.mLiteral << ")"; + + addMessage (stream.str(), type==Compiler::ErrorHandler::WarningMessage ? + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, + loc.mLine, loc.mColumn-loc.mLiteral.length()); +} + +void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type) +{ + addMessage (message, type==Compiler::ErrorHandler::WarningMessage ? + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error); +} + +void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, + CSMDoc::Message::Severity severity, int line, int column) +{ + int row = rowCount(); + + setRowCount (row+1); + + QTableWidgetItem *severityItem = new QTableWidgetItem ( + QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str())); + severityItem->setFlags (severityItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 0, severityItem); + + if (line!=-1) + { + QTableWidgetItem *lineItem = new QTableWidgetItem; + lineItem->setData (Qt::DisplayRole, line+1); + lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 1, lineItem); + + QTableWidgetItem *columnItem = new QTableWidgetItem; + columnItem->setData (Qt::DisplayRole, column); + columnItem->setFlags (columnItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 3, columnItem); + } + + QTableWidgetItem *messageItem = new QTableWidgetItem (QString::fromUtf8 (message.c_str())); + messageItem->setFlags (messageItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 2, messageItem); +} + +void CSVWorld::ScriptErrorTable::setWarningsMode (const QString& value) +{ + if (value=="Ignore") + Compiler::ErrorHandler::setWarningsMode (0); + else if (value=="Normal") + Compiler::ErrorHandler::setWarningsMode (1); + else if (value=="Strict") + Compiler::ErrorHandler::setWarningsMode (2); +} + +CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent) +: QTableWidget (parent), mContext (document.getData()) +{ + setColumnCount (4); + + QStringList headers; + headers << "Severity" << "Line" << "Description"; + setHorizontalHeaderLabels (headers); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + horizontalHeader()->setSectionResizeMode (0, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); +#else + horizontalHeader()->setResizeMode (0, QHeaderView::ResizeToContents); + horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); +#endif + horizontalHeader()->setStretchLastSection (true); + verticalHeader()->hide(); + setColumnHidden (3, true); + + setSelectionMode (QAbstractItemView::NoSelection); + + Compiler::registerExtensions (mExtensions); + mContext.setExtensions (&mExtensions); + + setWarningsMode (CSMSettings::UserSettings::instance().settingValue ("script-editor/warnings")); + + connect (this, SIGNAL (cellClicked (int, int)), this, SLOT (cellClicked (int, int))); +} + +void CSVWorld::ScriptErrorTable::updateUserSetting (const QString& name, const QStringList& value) +{ + if (name=="script-editor/warnings" && !value.isEmpty()) + setWarningsMode (value.at (0)); +} + +void CSVWorld::ScriptErrorTable::update (const std::string& source) +{ + clear(); + + try + { + std::istringstream input (source); + + Compiler::Scanner scanner (*this, input, mContext.getExtensions()); + + Compiler::FileParser parser (*this, mContext); + + scanner.scan (parser); + } + catch (const Compiler::SourceException&) + { + // error has already been reported via error handler + } + catch (const std::exception& error) + { + addMessage (error.what(), CSMDoc::Message::Severity_SeriousError); + } +} + +void CSVWorld::ScriptErrorTable::clear() +{ + setRowCount (0); +} + +bool CSVWorld::ScriptErrorTable::clearLocals (const std::string& script) +{ + return mContext.clearLocals (script); +} + +void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) +{ + if (item (row, 1)) + { + int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt(); + int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt(); + emit highlightError (scriptLine-1, scriptColumn); + } +} diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp new file mode 100644 index 000000000..33af7c864 --- /dev/null +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -0,0 +1,62 @@ +#ifndef CSV_WORLD_SCRIPTERRORTABLE_H +#define CSV_WORLD_SCRIPTERRORTABLE_H + +#include + +#include +#include + +#include "../../model/world/scriptcontext.hpp" +#include "../../model/doc/messages.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class ScriptErrorTable : public QTableWidget, private Compiler::ErrorHandler + { + Q_OBJECT + + Compiler::Extensions mExtensions; + CSMWorld::ScriptContext mContext; + + virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); + ///< Report error to the user. + + virtual void report (const std::string& message, Type type); + ///< Report a file related error + + void addMessage (const std::string& message, CSMDoc::Message::Severity severity, + int line = -1, int column = -1); + + void setWarningsMode (const QString& value); + + public: + + ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = 0); + + void updateUserSetting (const QString& name, const QStringList& value); + + void update (const std::string& source); + + void clear(); + + /// Clear local variable cache for \a script. + /// + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); + + private slots: + + void cellClicked (int row, int column); + + signals: + + void highlightError (int line, int column); + }; +} + +#endif diff --git a/apps/opencs/view/world/scripthighlighter.cpp b/apps/opencs/view/world/scripthighlighter.cpp index 6dda8d4fa..487b5b139 100644 --- a/apps/opencs/view/world/scripthighlighter.cpp +++ b/apps/opencs/view/world/scripthighlighter.cpp @@ -1,4 +1,3 @@ - #include "scripthighlighter.hpp" #include @@ -6,6 +5,8 @@ #include #include +#include "../../model/settings/usersettings.hpp" + bool CSVWorld::ScriptHighlighter::parseInt (int value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) { @@ -78,46 +79,77 @@ CSVWorld::ScriptHighlighter::ScriptHighlighter (const CSMWorld::Data& data, Mode : QSyntaxHighlighter (parent), Compiler::Parser (mErrorHandler, mContext), mContext (data), mMode (mode) { - /// \todo replace this with user settings + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + + QColor color = QColor(); + { + color.setNamedColor(userSettings.setting("script-editor/colour-int", "Dark magenta")); + if (!color.isValid()) + color = QColor(Qt::darkMagenta); + QTextCharFormat format; - format.setForeground (Qt::darkMagenta); + format.setForeground (color); mScheme.insert (std::make_pair (Type_Int, format)); } { + color.setNamedColor(userSettings.setting ("script-editor/colour-float", "Magenta")); + if (!color.isValid()) + color = QColor(Qt::magenta); + QTextCharFormat format; - format.setForeground (Qt::magenta); + format.setForeground (color); mScheme.insert (std::make_pair (Type_Float, format)); } { + color.setNamedColor(userSettings.setting ("script-editor/colour-name", "Gray")); + if (!color.isValid()) + color = QColor(Qt::gray); + QTextCharFormat format; - format.setForeground (Qt::gray); + format.setForeground (color); mScheme.insert (std::make_pair (Type_Name, format)); } { + color.setNamedColor(userSettings.setting ("script-editor/colour-keyword", "Red")); + if (!color.isValid()) + color = QColor(Qt::red); + QTextCharFormat format; - format.setForeground (Qt::red); + format.setForeground (color); mScheme.insert (std::make_pair (Type_Keyword, format)); } { + color.setNamedColor(userSettings.setting ("script-editor/colour-special", "Dark yellow")); + if (!color.isValid()) + color = QColor(Qt::darkYellow); + QTextCharFormat format; - format.setForeground (Qt::darkYellow); + format.setForeground (color); mScheme.insert (std::make_pair (Type_Special, format)); } { + color.setNamedColor(userSettings.setting ("script-editor/colour-comment", "Green")); + if (!color.isValid()) + color = QColor(Qt::green); + QTextCharFormat format; - format.setForeground (Qt::green); + format.setForeground (color); mScheme.insert (std::make_pair (Type_Comment, format)); } { + color.setNamedColor(userSettings.setting ("script-editor/colour-id", "Blue")); + if (!color.isValid()) + color = QColor(Qt::blue); + QTextCharFormat format; - format.setForeground (Qt::blue); + format.setForeground (color); mScheme.insert (std::make_pair (Type_Id, format)); } @@ -143,3 +175,86 @@ void CSVWorld::ScriptHighlighter::invalidateIds() { mContext.invalidateIds(); } + +bool CSVWorld::ScriptHighlighter::updateUserSetting (const QString &name, const QStringList &list) +{ + if (list.empty()) + return false; + + QColor color = QColor(); + + if (name == "script-editor/colour-int") + { + color.setNamedColor(list.at(0)); + if (!color.isValid()) + return false; + + QTextCharFormat format; + format.setForeground (color); + mScheme[Type_Int] = format; + } + else if (name == "script-editor/colour-float") + { + color.setNamedColor(list.at(0)); + if (!color.isValid()) + return false; + + QTextCharFormat format; + format.setForeground (color); + mScheme[Type_Float] = format; + } + else if (name == "script-editor/colour-name") + { + color.setNamedColor(list.at(0)); + if (!color.isValid()) + return false; + + QTextCharFormat format; + format.setForeground (color); + mScheme[Type_Name] = format; + } + else if (name == "script-editor/colour-keyword") + { + color.setNamedColor(list.at(0)); + if (!color.isValid()) + return false; + + QTextCharFormat format; + format.setForeground (color); + mScheme[Type_Keyword] = format; + } + else if (name == "script-editor/colour-special") + { + color.setNamedColor(list.at(0)); + if (!color.isValid()) + return false; + + QTextCharFormat format; + format.setForeground (color); + mScheme[Type_Special] = format; + } + else if (name == "script-editor/colour-comment") + { + color.setNamedColor(list.at(0)); + if (!color.isValid()) + return false; + + QTextCharFormat format; + format.setForeground (color); + mScheme[Type_Comment] = format; + } + else if (name == "script-editor/colour-id") + { + color.setNamedColor(list.at(0)); + if (!color.isValid()) + return false; + + QTextCharFormat format; + format.setForeground (color); + mScheme[Type_Id] = format; + } + else + return false; + + return true; +} diff --git a/apps/opencs/view/world/scripthighlighter.hpp b/apps/opencs/view/world/scripthighlighter.hpp index 953f2f953..6f1f58e82 100644 --- a/apps/opencs/view/world/scripthighlighter.hpp +++ b/apps/opencs/view/world/scripthighlighter.hpp @@ -87,6 +87,8 @@ namespace CSVWorld virtual void highlightBlock (const QString& text); void invalidateIds(); + + bool updateUserSetting (const QString &name, const QStringList &list); }; } diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 411eb3660..eb0c70656 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" @@ -15,45 +16,97 @@ #include "../../model/settings/usersettings.hpp" #include "scriptedit.hpp" +#include "recordbuttonbar.hpp" +#include "tablebottombox.hpp" +#include "genericcreator.hpp" +#include "scripterrortable.hpp" + +void CSVWorld::ScriptSubView::addButtonBar() +{ + if (mButtons) + return; + + mButtons = new RecordButtonBar (getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this); + + mLayout.insertWidget (1, mButtons); + + connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), + mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); +} + +void CSVWorld::ScriptSubView::recompile() +{ + if (!mCompileDelay->isActive() && !isDeleted()) + mCompileDelay->start ( + CSMSettings::UserSettings::instance().setting ("script-editor/compile-delay").toInt()); +} + +bool CSVWorld::ScriptSubView::isDeleted() const +{ + return mModel->data (mModel->getModelIndex (getUniversalId().getId(), mStateColumn)).toInt() + ==CSMWorld::RecordBase::State_Deleted; +} + +void CSVWorld::ScriptSubView::updateDeletedState() +{ + if (isDeleted()) + { + mErrors->clear(); + mEditor->setEnabled (false); + } + else + { + mEditor->setEnabled (true); + recompile(); + } +} CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0) +: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) { - QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); + std::vector selection (1, id.getId()); + mCommandDispatcher.setSelection (selection); - mBottom = new QWidget(this); - QStackedLayout *bottmLayout = new QStackedLayout(mBottom); - bottmLayout->setContentsMargins (0, 0, 0, 0); - QStatusBar *statusBar = new QStatusBar(mBottom); - mStatus = new QLabel(mBottom); - statusBar->addWidget (mStatus); - bottmLayout->addWidget (statusBar); - mBottom->setLayout (bottmLayout); + mMain = new QSplitter (this); + mMain->setOrientation (Qt::Vertical); + mLayout.addWidget (mMain, 2); - layout->addWidget (mBottom, 0); - layout->insertWidget (0, mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); + mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this); + mMain->addWidget (mEditor); + mMain->setCollapsible (0, false); - QWidget *widget = new QWidget; - widget->setLayout (layout); + mErrors = new ScriptErrorTable (document, this); + mMain->addWidget (mErrors); + + QWidget *widget = new QWidget (this);; + widget->setLayout (&mLayout); setWidget (widget); mModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); - for (int i=0; icolumnCount(); ++i) - if (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display)== - CSMWorld::ColumnBase::Display_ScriptFile) - { - mColumn = i; - break; - } + mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText); + mIdColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - if (mColumn==-1) - throw std::logic_error ("Can't find script column"); + QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); - mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString()); + mEditor->setPlainText (source); + // bottom box and buttons + mBottom = new TableBottomBox (CreatorFactory(), document, id, this); + if (CSMSettings::UserSettings::instance().setting ("script-editor/toolbar", QString("true")) == "true") + addButtonBar(); + + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + this, SLOT (switchToId (const std::string&))); + + mLayout.addWidget (mBottom); + + // signals connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), @@ -64,35 +117,80 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: updateStatusBar(); connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); + + mErrors->update (source.toUtf8().constData()); + + connect (mErrors, SIGNAL (highlightError (int, int)), + this, SLOT (highlightError (int, int))); + + mCompileDelay = new QTimer (this); + mCompileDelay->setSingleShot (true); + connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest())); + + updateDeletedState(); } void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) { if (name == "script-editor/show-linenum") { - std::string showLinenum = value.at(0).toStdString(); + std::string showLinenum = value.at(0).toUtf8().constData(); mEditor->showLineNum(showLinenum == "true"); mBottom->setVisible(showLinenum == "true"); } else if (name == "script-editor/mono-font") { - mEditor->setMonoFont(value.at(0).toStdString() == "true"); + mEditor->setMonoFont (value.at(0)==QString ("true")); } + else if (name=="script-editor/toolbar") + { + if (value.at(0)==QString ("true")) + { + addButtonBar(); + } + else + { + if (mButtons) + { + mLayout.removeWidget (mButtons); + delete mButtons; + mButtons = 0; + } + } + } + else if (name=="script-editor/compile-delay") + { + mCompileDelay->setInterval (value.at (0).toInt()); + } + + if (mButtons) + mButtons->updateUserSetting (name, value); + + mErrors->updateUserSetting (name, value); + + if (name=="script-editor/warnings") + recompile(); +} + +void CSVWorld::ScriptSubView::setStatusBar (bool show) +{ + mBottom->setStatusBar (show); } void CSVWorld::ScriptSubView::updateStatusBar () { - std::ostringstream stream; - - stream << "(" << mEditor->textCursor().blockNumber() + 1 << ", " - << mEditor->textCursor().columnNumber() + 1 << ")"; - - mStatus->setText (QString::fromUtf8 (stream.str().c_str())); + mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1, + mEditor->textCursor().columnNumber() + 1); } void CSVWorld::ScriptSubView::setEditLock (bool locked) { mEditor->setReadOnly (locked); + + if (mButtons) + mButtons->setEditLock (locked); + + mCommandDispatcher.setEditLock (locked); } void CSVWorld::ScriptSubView::useHint (const std::string& hint) @@ -100,26 +198,41 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) if (hint.empty()) return; - if (hint[0]=='l') - { - std::istringstream stream (hint.c_str()+1); - - char ignore; - int line; - int column; - - if (stream >> ignore >> line >> column) + unsigned line = 0, column = 0; + char c; + std::istringstream stream (hint.c_str()+1); + switch(hint[0]){ + case 'R': + case 'r': { - QTextCursor cursor = mEditor->textCursor(); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + QString source = mModel->data (index).toString(); + unsigned pos, dummy; + if (!(stream >> c >> dummy >> pos) ) + return; - cursor.movePosition (QTextCursor::Start); - if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) - cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); - - mEditor->setFocus(); - mEditor->setTextCursor (cursor); + for (unsigned i = 0; i <= pos; ++i){ + if (source[i] == '\n'){ + ++line; + column = i+1; + } + } + column = pos - column; + break; } + case 'l': + if (!(stream >> c >> line >> column)) + return; } + + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + + mEditor->setFocus(); + mEditor->setTextCursor (cursor); } void CSVWorld::ScriptSubView::textChanged() @@ -129,8 +242,12 @@ void CSVWorld::ScriptSubView::textChanged() ScriptEdit::ChangeLock lock (*mEditor); + QString source = mEditor->toPlainText(); + mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, - mModel->getModelIndex (getUniversalId().getId(), mColumn), mEditor->toPlainText())); + mModel->getModelIndex (getUniversalId().getId(), mColumn), source)); + + recompile(); } void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) @@ -140,22 +257,94 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo ScriptEdit::ChangeLock lock (*mEditor); + bool updateRequired = false; + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); - if (index.row()>=topLeft.row() && index.row()<=bottomRight.row() && - index.column()>=topLeft.column() && index.column()<=bottomRight.column()) + if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) { - QTextCursor cursor = mEditor->textCursor(); - mEditor->setPlainText (mModel->data (index).toString()); - mEditor->setTextCursor (cursor); + if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column()) + updateDeletedState(); + + if (mColumn>=topLeft.column() && mColumn<=bottomRight.column()) + { + QString source = mModel->data (index).toString(); + + QTextCursor cursor = mEditor->textCursor(); + mEditor->setPlainText (source); + mEditor->setTextCursor (cursor); + + updateRequired = true; + } } + + if (updateRequired) + recompile(); } void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { + bool updateRequired = false; + + for (int i=start; i<=end; ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + + if (updateRequired) + recompile(); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (!parent.isValid() && index.row()>=start && index.row()<=end) emit closeRequest(); } +void CSVWorld::ScriptSubView::switchToRow (int row) +{ + int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData(); + setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id)); + + mEditor->setPlainText (mModel->data (mModel->index (row, mColumn)).toString()); + + std::vector selection (1, id); + mCommandDispatcher.setSelection (selection); + + updateDeletedState(); +} + +void CSVWorld::ScriptSubView::switchToId (const std::string& id) +{ + switchToRow (mModel->getModelIndex (id, 0).row()); +} + +void CSVWorld::ScriptSubView::highlightError (int line, int column) +{ + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + + mEditor->setFocus(); + mEditor->setTextCursor (cursor); +} + +void CSVWorld::ScriptSubView::updateRequest() +{ + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + + QString source = mModel->data (index).toString(); + + mErrors->update (source.toUtf8().constData()); +} diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 1c6474e54..907dc7958 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -1,10 +1,17 @@ #ifndef CSV_WORLD_SCRIPTSUBVIEW_H #define CSV_WORLD_SCRIPTSUBVIEW_H +#include + +#include "../../model/world/commanddispatcher.hpp" + #include "../doc/subview.hpp" class QModelIndex; class QLabel; +class QVBoxLayout; +class QSplitter; +class QTime; namespace CSMDoc { @@ -19,6 +26,9 @@ namespace CSMWorld namespace CSVWorld { class ScriptEdit; + class RecordButtonBar; + class TableBottomBox; + class ScriptErrorTable; class ScriptSubView : public CSVDoc::SubView { @@ -28,8 +38,25 @@ namespace CSVWorld CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; - QWidget *mBottom; - QLabel *mStatus; + int mIdColumn; + int mStateColumn; + TableBottomBox *mBottom; + RecordButtonBar *mButtons; + CSMWorld::CommandDispatcher mCommandDispatcher; + QVBoxLayout mLayout; + QSplitter *mMain; + ScriptErrorTable *mErrors; + QTimer *mCompileDelay; + + private: + + void addButtonBar(); + + void recompile(); + + bool isDeleted() const; + + void updateDeletedState(); public: @@ -41,6 +68,8 @@ namespace CSVWorld virtual void updateUserSetting (const QString& name, const QStringList& value); + virtual void setStatusBar (bool show); + public slots: void textChanged(); @@ -52,6 +81,14 @@ namespace CSVWorld private slots: void updateStatusBar(); + + void switchToRow (int row); + + void switchToId (const std::string& id); + + void highlightError (int line, int column); + + void updateRequest(); }; } diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp new file mode 100644 index 000000000..69b1b3ff1 --- /dev/null +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -0,0 +1,20 @@ +#include "startscriptcreator.hpp" + +CSVWorld::StartScriptCreator::StartScriptCreator(CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, bool relaxedIdRules): + GenericCreator (data, undoStack, id, true) +{} + +std::string CSVWorld::StartScriptCreator::getErrors() const +{ + std::string errors; + + errors = getIdValidatorResult(); + if (errors.length() > 0) + return errors; + else if (getData().getScripts().searchId(getId()) == -1) + errors = "Script ID not found"; + else if (getData().getStartScripts().searchId(getId()) > -1 ) + errors = "Script with this ID already registered as Start Script"; + + return errors; +} diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp new file mode 100644 index 000000000..07fe8ff3d --- /dev/null +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -0,0 +1,25 @@ +#ifndef STARTSCRIPTCREATOR_HPP +#define STARTSCRIPTCREATOR_HPP + +#include "genericcreator.hpp" + +namespace CSVWorld { + + class StartScriptCreator : public GenericCreator + { + Q_OBJECT + + public: + StartScriptCreator(CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, bool relaxedIdRules = false); + + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + }; + +} + + + +#endif // STARTSCRIPTCREATOR_HPP diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index cd9b37a64..cffe283d4 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -1,4 +1,3 @@ - #include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" @@ -11,6 +10,7 @@ #include "cellcreator.hpp" #include "referenceablecreator.hpp" #include "referencecreator.hpp" +#include "startscriptcreator.hpp" #include "scenesubview.hpp" #include "dialoguecreator.hpp" #include "infocreator.hpp" @@ -43,6 +43,8 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_BodyParts, CSMWorld::UniversalId::Type_SoundGens, CSMWorld::UniversalId::Type_Pathgrids, + CSMWorld::UniversalId::Type_LandTextures, + CSMWorld::UniversalId::Type_Lands, CSMWorld::UniversalId::Type_StartScripts, CSMWorld::UniversalId::Type_None // end marker @@ -52,6 +54,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (sTableTypes[i], new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_StartScripts, + new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Cells, new CSVDoc::SubViewFactoryWithCreator >); @@ -59,7 +64,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator >); manager.add (CSMWorld::UniversalId::Type_References, - new CSVDoc::SubViewFactoryWithCreator >); + new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Topics, new CSVDoc::SubViewFactoryWithCreator); @@ -68,10 +73,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_TopicInfos, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_JournalInfos, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator); // Subviews for resources tables manager.add (CSMWorld::UniversalId::Type_Meshes, @@ -124,7 +129,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_BodyPart, CSMWorld::UniversalId::Type_SoundGen, CSMWorld::UniversalId::Type_Pathgrid, - CSMWorld::UniversalId::Type_StartScript, CSMWorld::UniversalId::Type_None // end marker }; @@ -134,6 +138,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_StartScript, + new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_Skill, new CSVDoc::SubViewFactoryWithCreator (false)); @@ -147,16 +155,16 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_Reference, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Cell, new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_JournalInfo, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_TopicInfo, - new CSVDoc::SubViewFactoryWithCreator >(false)); + new CSVDoc::SubViewFactoryWithCreator(false)); manager.add (CSMWorld::UniversalId::Type_Topic, new CSVDoc::SubViewFactoryWithCreator (false)); @@ -170,6 +178,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Filter, new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_MetaData, + new CSVDoc::SubViewFactory); + //preview manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 692f54b8e..6c4ede6e2 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -1,4 +1,3 @@ - #include "table.hpp" #include @@ -10,10 +9,13 @@ #include #include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" +#include "../../model/world/infotableproxymodel.hpp" #include "../../model/world/idtableproxymodel.hpp" #include "../../model/world/idtablebase.hpp" #include "../../model/world/idtable.hpp" @@ -22,67 +24,37 @@ #include "../../model/world/tablemimedata.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/filter/parser.hpp" +#include "../../model/filter/andnode.hpp" +#include "../../model/filter/ornode.hpp" #include "../../model/settings/usersettings.hpp" #include "recordstatusdelegate.hpp" +#include "tableeditidaction.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { // configure dispatcher - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - std::vector records; - - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); - - records.push_back (mModel->data ( - mModel->index (row, columnIndex)).toString().toUtf8().constData()); - } - - mDispatcher->setSelection (records); + mDispatcher->setSelection (getSelectedIds()); std::vector extendedTypes = mDispatcher->getExtendedTypes(); mDispatcher->setExtendedTypes (extendedTypes); // create context menu + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu (this); /// \todo add menu items for select all and clear selection + int currentRow = rowAt(event->y()); + int currentColumn = columnAt(event->x()); + if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) { - // Request UniversalId editing from table columns. - - int currRow = rowAt( event->y() ), - currCol = columnAt( event->x() ); - - currRow = mProxyModel->mapToSource(mProxyModel->index( currRow, 0 )).row(); - - CSMWorld::ColumnBase::Display colDisplay = - static_cast( - mModel->headerData( - currCol, - Qt::Horizontal, - CSMWorld::ColumnBase::Role_Display ).toInt()); - - QString cellData = mModel->data(mModel->index( currRow, currCol )).toString(); - CSMWorld::UniversalId::Type colType = CSMWorld::TableMimeData::convertEnums( colDisplay ); - - if ( !cellData.isEmpty() - && colType != CSMWorld::UniversalId::Type_None ) - { - mEditCellAction->setText(tr("Edit '").append(cellData).append("'")); - - menu.addAction( mEditCellAction ); - - mEditCellId = CSMWorld::UniversalId( colType, cellData.toUtf8().constData() ); - } + mEditIdAction->setCell(currentRow, currentColumn); + menu.addAction(mEditIdAction); + menu.addSeparator(); } if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) @@ -128,17 +100,24 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { int row = mProxyModel->mapToSource ( mProxyModel->index (selectedRows.begin()->row(), 0)).row(); + QString curData = mModel->data(mModel->index(row, column)).toString(); - if (row>0 && mModel->data (mModel->index (row, column))== - mModel->data (mModel->index (row-1, column))) + if (row > 0) { - menu.addAction (mMoveUpAction); + QString prevData = mModel->data(mModel->index(row - 1, column)).toString(); + if (Misc::StringUtils::ciEqual(curData.toStdString(), prevData.toStdString())) + { + menu.addAction(mMoveUpAction); + } } - if (rowrowCount()-1 && mModel->data (mModel->index (row, column))== - mModel->data (mModel->index (row+1, column))) + if (row < mModel->rowCount() - 1) { - menu.addAction (mMoveDownAction); + QString nextData = mModel->data(mModel->index(row + 1, column)).toString(); + if (Misc::StringUtils::ciEqual(curData.toStdString(), nextData.toStdString())) + { + menu.addAction(mMoveDownAction); + } } } } @@ -277,20 +256,37 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); - mProxyModel = new CSMWorld::IdTableProxyModel (this); + bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos || + id.getType() == CSMWorld::UniversalId::Type_JournalInfos; + if (isInfoTable) + { + mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this); + } + else + { + mProxyModel = new CSMWorld::IdTableProxyModel (this); + } mProxyModel->setSourceModel (mModel); mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); setModel (mProxyModel); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); +#else horizontalHeader()->setResizeMode (QHeaderView::Interactive); +#endif verticalHeader()->hide(); - setSortingEnabled (sorting); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - int columns = mModel->columnCount(); + setSortingEnabled (sorting); + if (sorting) + { + sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); + } + int columns = mModel->columnCount(); for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); @@ -341,10 +337,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); addAction (mMoveDownAction); - mEditCellAction = new QAction( tr("Edit Cell"), this ); - connect( mEditCellAction, SIGNAL(triggered()), this, SLOT(editCell()) ); - addAction( mEditCellAction ); - mViewAction = new QAction (tr ("View"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); addAction (mViewAction); @@ -353,23 +345,28 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); - /// \todo add a user option, that redirects the extended action to an input panel (in - /// the bottom bar) that lets the user select which record collections should be - /// modified. - mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); - connect (mExtendedDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedDelete())); + connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete())); addAction (mExtendedDeleteAction); mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); - connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); + connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert())); addAction (mExtendedRevertAction); + mEditIdAction = new TableEditIdAction (*this, this); + connect (mEditIdAction, SIGNAL (triggered()), this, SLOT (editCell())); + addAction (mEditIdAction); + connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); - connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); + // parent, start and end depend on the model sending the signal, in this case mProxyModel + // If, for example, mModel was used instead, then scrolTo() should use the index + // mProxyModel->mapFromSource(mModel->index(end, 0)) + //connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + // this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); + connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), + this, SLOT (rowAdded (const std::string &))); /// \note This signal could instead be connected to a slot that filters out changes not affecting /// the records status column (for permanence reasons) @@ -385,6 +382,24 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_EditRecord)); mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_View)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose)); + + CSMFilter::Parser parser(mDocument.getData()); + + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + if(userSettings.settingValue ("filter/project-added") == "true") + { + parser.parse("!string(\"Modified\", \"Added\")"); + mAdded = parser.getFilter(); + } + + if(userSettings.settingValue ("filter/project-modified") == "true") + { + parser.parse("!string(\"Modified\", \"Modified\")"); + mModified = parser.getFilter(); + } + + if(mAdded || mModified) + recordFilterChanged(boost::shared_ptr()); } void CSVWorld::Table::setEditLock (bool locked) @@ -408,6 +423,22 @@ CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData()); } +std::vector CSVWorld::Table::getSelectedIds() const +{ + std::vector ids; + QModelIndexList selectedRows = selectionModel()->selectedRows(); + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); + iter != selectedRows.end(); + ++iter) + { + int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); + ids.push_back (mModel->data (mModel->index (row, columnIndex)).toString().toUtf8().constData()); + } + return ids; +} + void CSVWorld::Table::editRecord() { if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) @@ -425,7 +456,7 @@ void CSVWorld::Table::cloneRecord() { QModelIndexList selectedRows = selectionModel()->selectedRows(); const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); - if (selectedRows.size()==1 && !mModel->isDeleted (toClone.getId())) + if (selectedRows.size() == 1) { emit cloneRequest (toClone); } @@ -500,7 +531,7 @@ void CSVWorld::Table::moveDownRecord() void CSVWorld::Table::editCell() { - emit editRequest( mEditCellId, std::string() ); + emit editRequest(mEditIdAction->getCurrentId(), ""); } void CSVWorld::Table::viewRecord() @@ -540,6 +571,34 @@ void CSVWorld::Table::previewRecord() } } +void CSVWorld::Table::executeExtendedDelete() +{ + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + QString configSetting = settings.settingValue ("table-input/extended-config"); + if (configSetting == "true") + { + emit extendedDeleteConfigRequest(getSelectedIds()); + } + else + { + QMetaObject::invokeMethod(mDispatcher, "executeExtendedDelete", Qt::QueuedConnection); + } +} + +void CSVWorld::Table::executeExtendedRevert() +{ + CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); + QString configSetting = settings.settingValue ("table-input/extended-config"); + if (configSetting == "true") + { + emit extendedRevertConfigRequest(getSelectedIds()); + } + else + { + QMetaObject::invokeMethod(mDispatcher, "executeExtendedRevert", Qt::QueuedConnection); + } +} + void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList &list) { if (name=="table-input/jump-to-added") @@ -650,10 +709,6 @@ void CSVWorld::Table::tableSizeUpdate() } emit tableSizeChanged (size, deleted, modified); - - // not really related to tableSizeUpdate() but placed here for convenience rather than - // creating a bunch of extra connections & slot - mProxyModel->refreshFilter(); } void CSVWorld::Table::selectionSizeUpdate() @@ -671,7 +726,37 @@ void CSVWorld::Table::requestFocus (const std::string& id) void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) { - mProxyModel->setFilter (filter); + mFilter = filter; + + boost::shared_ptr global; + if(mAdded && mModified) + { + std::vector > nodes; + nodes.push_back(mAdded); + nodes.push_back(mModified); + global = boost::shared_ptr(new CSMFilter::OrNode (nodes)); + } + else if(mAdded) + { + global = mAdded; + } + else if(mModified) + { + global = mModified; + } + + if(filter && global) + { + std::vector > nodes; + nodes.push_back(filter); + nodes.push_back(global); + boost::shared_ptr combined(new CSMFilter::AndNode (nodes)); + mProxyModel->setFilter (combined); + } + else if(global) + mProxyModel->setFilter (global); + else + mProxyModel->setFilter (filter); tableSizeUpdate(); selectionSizeUpdate(); } @@ -684,36 +769,6 @@ void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) } } -void CSVWorld::Table::dropEvent(QDropEvent *event) -{ - QModelIndex index = indexAt (event->pos()); - - if (!index.isValid()) - { - return; - } - - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); - if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped - return; - - if (mime->fromDocument (mDocument)) - { - CSMWorld::ColumnBase::Display display = static_cast - (mModel->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - if (mime->holdsType (display)) - { - CSMWorld::UniversalId record (mime->returnMatching (display)); - - std::auto_ptr command (new CSMWorld::ModifyCommand - (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str())))); - - mDocument.getUndoStack().push (command.release()); - } - } //TODO handle drops from different document -} - std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const { const int count = mModel->columnCount(); @@ -745,16 +800,42 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const return idToDrag; } -// parent, start and end depend on the model sending the signal, in this case mProxyModel -// -// If, for example, mModel was used instead, then scrolTo() should use the index -// mProxyModel->mapFromSource(mModel->index(end, 0)) -void CSVWorld::Table::rowsInsertedEvent(const QModelIndex& parent, int start, int end) +void CSVWorld::Table::globalFilterAddedChanged(int state) +{ + if(state == Qt::Checked && !mAdded) + { + CSMFilter::Parser parser(mDocument.getData()); + parser.parse("!string(\"Modified\", \"Added\")"); + mAdded = parser.getFilter(); + } + else if(state == Qt::Unchecked) + mAdded.reset(); + + recordFilterChanged(mFilter); +} + +void CSVWorld::Table::globalFilterModifiedChanged(int state) +{ + if(state == Qt::Checked && !mModified) + { + CSMFilter::Parser parser(mDocument.getData()); + parser.parse("!string(\"Modified\", \"Modified\")"); + mModified = parser.getFilter(); + } + else if(state == Qt::Unchecked) + mModified.reset(); + + recordFilterChanged(mFilter); +} + +void CSVWorld::Table::rowAdded(const std::string &id) { tableSizeUpdate(); if(mJumpToAddedRecord) { + int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + int end = mProxyModel->getModelIndex(id, idColumn).row(); selectRow(end); // without this delay the scroll works but goes to top for add/clone @@ -777,8 +858,8 @@ void CSVWorld::Table::dataChangedEvent(const QModelIndex &topLeft, const QModelI if (mAutoJump) { selectRow(bottomRight.row()); - scrollTo(mProxyModel->index(bottomRight.row(), 0), QAbstractItemView::PositionAtCenter); - //scrollTo(bottomRight, QAbstractItemView::PositionAtCenter); // alternative + //scrollTo(mProxyModel->index(bottomRight.row(), 0), QAbstractItemView::PositionAtCenter); + scrollTo(bottomRight, QAbstractItemView::PositionAtCenter); // alternative } } diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index a3d726a7f..cc62d6e4f 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../../model/filter/node.hpp" #include "../../model/world/columnbase.hpp" @@ -30,6 +30,7 @@ namespace CSMWorld namespace CSVWorld { class CommandDelegate; + class TableEditIdAction; ///< Table widget class Table : public DragRecordTable @@ -57,28 +58,29 @@ namespace CSVWorld QAction *mMoveUpAction; QAction *mMoveDownAction; QAction *mViewAction; - QAction *mEditCellAction; QAction *mPreviewAction; QAction *mExtendedDeleteAction; QAction *mExtendedRevertAction; + TableEditIdAction *mEditIdAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTableBase *mModel; int mRecordStatusDisplay; CSMWorld::CommandDispatcher *mDispatcher; - CSMWorld::UniversalId mEditCellId; std::map mDoubleClickActions; bool mJumpToAddedRecord; bool mUnselectAfterJump; bool mAutoJump; + boost::shared_ptr mFilter; + boost::shared_ptr mAdded; + boost::shared_ptr mModified; + private: void contextMenuEvent (QContextMenuEvent *event); void mouseMoveEvent(QMouseEvent *event); - void dropEvent(QDropEvent *event); - protected: virtual void mouseDoubleClickEvent (QMouseEvent *event); @@ -96,6 +98,8 @@ namespace CSVWorld std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; + std::vector getSelectedIds() const; + virtual std::vector getDraggedRecords() const; signals: @@ -115,6 +119,10 @@ namespace CSVWorld void closeRequest(); + void extendedDeleteConfigRequest(const std::vector &selectedIds); + + void extendedRevertConfigRequest(const std::vector &selectedIds); + private slots: void editCell(); @@ -131,6 +139,10 @@ namespace CSVWorld void previewRecord(); + void executeExtendedDelete(); + + void executeExtendedRevert(); + public slots: void tableSizeUpdate(); @@ -143,13 +155,17 @@ namespace CSVWorld void updateUserSetting (const QString &name, const QStringList &list); - void rowsInsertedEvent(const QModelIndex &parent, int start, int end); - void dataChangedEvent(const QModelIndex &topLeft, const QModelIndex &bottomRight); void jumpAfterModChanged(int state); void queuedScrollTo(int state); + + void globalFilterAddedChanged (int state); + + void globalFilterModifiedChanged (int state); + + void rowAdded(const std::string &id); }; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index e9d644f61..00d563607 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -1,4 +1,3 @@ - #include "tablebottombox.hpp" #include @@ -6,9 +5,25 @@ #include #include #include +#include +#include #include "creator.hpp" +void CSVWorld::TableBottomBox::updateSize() +{ + // Make sure that the size of the bottom box is determined by the currently visible widget + for (int i = 0; i < mLayout->count(); ++i) + { + QSizePolicy::Policy verPolicy = QSizePolicy::Ignored; + if (mLayout->widget(i) == mLayout->currentWidget()) + { + verPolicy = QSizePolicy::Expanding; + } + mLayout->widget(i)->setSizePolicy(QSizePolicy::Expanding, verPolicy); + } +} + void CSVWorld::TableBottomBox::updateStatus() { if (mShowStatusBar) @@ -35,13 +50,33 @@ void CSVWorld::TableBottomBox::updateStatus() } } + if (mHasPosition) + { + if (!first) + stream << " -- "; + + stream << "(" << mRow << ", " << mColumn << ")"; + } + mStatus->setText (QString::fromUtf8 (stream.str().c_str())); } } +void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds) +{ + mExtendedConfigurator->configure (mode, selectedIds); + mLayout->setCurrentWidget (mExtendedConfigurator); + mEditMode = EditMode_ExtendedConfig; + setVisible (true); + mExtendedConfigurator->setFocus(); +} + CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, - CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, QWidget *parent) -: QWidget (parent), mShowStatusBar (false), mCreating (false) + CSMDoc::Document& document, + const CSMWorld::UniversalId& id, + QWidget *parent) +: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition (false) { for (int i=0; i<4; ++i) mStatusCount[i] = 0; @@ -50,6 +85,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto mLayout = new QStackedLayout; mLayout->setContentsMargins (0, 0, 0, 0); + connect (mLayout, SIGNAL (currentChanged (int)), this, SLOT (currentWidgetChanged (int))); mStatus = new QLabel; @@ -61,23 +97,32 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto setLayout (mLayout); - mCreator = creatorFactory.makeCreator (data, undoStack, id); + mCreator = creatorFactory.makeCreator (document, id); if (mCreator) { + mCreator->installEventFilter(this); mLayout->addWidget (mCreator); - connect (mCreator, SIGNAL (done()), this, SLOT (createRequestDone())); + connect (mCreator, SIGNAL (done()), this, SLOT (requestDone())); connect (mCreator, SIGNAL (requestFocus (const std::string&)), this, SIGNAL (requestFocus (const std::string&))); } + + mExtendedConfigurator = new ExtendedCommandConfigurator (document, id, this); + mExtendedConfigurator->installEventFilter(this); + mLayout->addWidget (mExtendedConfigurator); + connect (mExtendedConfigurator, SIGNAL (done()), this, SLOT (requestDone())); + + updateSize(); } void CSVWorld::TableBottomBox::setEditLock (bool locked) { if (mCreator) mCreator->setEditLock (locked); + mExtendedConfigurator->setEditLock (locked); } CSVWorld::TableBottomBox::~TableBottomBox() @@ -85,11 +130,25 @@ CSVWorld::TableBottomBox::~TableBottomBox() delete mCreator; } +bool CSVWorld::TableBottomBox::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) + { + requestDone(); + return true; + } + } + return QWidget::eventFilter(object, event); +} + void CSVWorld::TableBottomBox::setStatusBar (bool show) { if (show!=mShowStatusBar) { - setVisible (show || mCreating); + setVisible (show || (mEditMode != EditMode_None)); mShowStatusBar = show; @@ -103,7 +162,7 @@ bool CSVWorld::TableBottomBox::canCreateAndDelete() const return mCreator; } -void CSVWorld::TableBottomBox::createRequestDone() +void CSVWorld::TableBottomBox::requestDone() { if (!mShowStatusBar) setVisible (false); @@ -111,8 +170,12 @@ void CSVWorld::TableBottomBox::createRequestDone() updateStatus(); mLayout->setCurrentWidget (mStatusBar); + mEditMode = EditMode_None; +} - mCreating = false; +void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/) +{ + updateSize(); } void CSVWorld::TableBottomBox::selectionSizeChanged (int size) @@ -150,24 +213,48 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi updateStatus(); } +void CSVWorld::TableBottomBox::positionChanged (int row, int column) +{ + mRow = row; + mColumn = column; + mHasPosition = true; + updateStatus(); +} + +void CSVWorld::TableBottomBox::noMorePosition() +{ + mHasPosition = false; + updateStatus(); +} + void CSVWorld::TableBottomBox::createRequest() { mCreator->reset(); mCreator->toggleWidgets(true); mLayout->setCurrentWidget (mCreator); setVisible (true); - mCreating = true; + mEditMode = EditMode_Creation; mCreator->focus(); } -void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, - const CSMWorld::UniversalId::Type type) +void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, + const CSMWorld::UniversalId::Type type) { mCreator->reset(); mCreator->cloneMode(id, type); mLayout->setCurrentWidget(mCreator); mCreator->toggleWidgets(false); setVisible (true); - mCreating = true; + mEditMode = EditMode_Creation; mCreator->focus(); } + +void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector &selectedIds) +{ + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete, selectedIds); +} + +void CSVWorld::TableBottomBox::extendedRevertConfigRequest(const std::vector &selectedIds) +{ + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Revert, selectedIds); +} diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 8f0d85163..781cccc9e 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -4,15 +4,15 @@ #include #include +#include "extendedcommandconfigurator.hpp" + class QLabel; class QStackedLayout; class QStatusBar; -class QUndoStack; -namespace CSMWorld +namespace CSMDoc { - class Data; - class UniversalId; + class Document; } namespace CSVWorld @@ -24,13 +24,21 @@ namespace CSVWorld { Q_OBJECT + enum EditMode { EditMode_None, EditMode_Creation, EditMode_ExtendedConfig }; + bool mShowStatusBar; QLabel *mStatus; QStatusBar *mStatusBar; int mStatusCount[4]; + + EditMode mEditMode; Creator *mCreator; - bool mCreating; + ExtendedCommandConfigurator *mExtendedConfigurator; + QStackedLayout *mLayout; + bool mHasPosition; + int mRow; + int mColumn; private: @@ -38,15 +46,24 @@ namespace CSVWorld TableBottomBox (const TableBottomBox&); TableBottomBox& operator= (const TableBottomBox&); + void updateSize(); + void updateStatus(); + void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds); + public: - TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMWorld::Data& data, - QUndoStack& undoStack, const CSMWorld::UniversalId& id, QWidget *parent = 0); + TableBottomBox (const CreatorFactoryBase& creatorFactory, + CSMDoc::Document& document, + const CSMWorld::UniversalId& id, + QWidget *parent = 0); virtual ~TableBottomBox(); + virtual bool eventFilter(QObject *object, QEvent *event); + void setEditLock (bool locked); void setStatusBar (bool show); @@ -64,9 +81,11 @@ namespace CSVWorld private slots: - void createRequestDone(); + void requestDone(); ///< \note This slot being called does not imply success. + void currentWidgetChanged(int index); + public slots: void selectionSizeChanged (int size); @@ -76,9 +95,16 @@ namespace CSVWorld /// \param deleted Number of deleted records /// \param modified Number of added and modified records + void positionChanged (int row, int column); + + void noMorePosition(); + void createRequest(); void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); + + void extendedDeleteConfigRequest(const std::vector &selectedIds); + void extendedRevertConfigRequest(const std::vector &selectedIds); }; } diff --git a/apps/opencs/view/world/tableeditidaction.cpp b/apps/opencs/view/world/tableeditidaction.cpp new file mode 100644 index 000000000..4dfc537cc --- /dev/null +++ b/apps/opencs/view/world/tableeditidaction.cpp @@ -0,0 +1,49 @@ +#include "tableeditidaction.hpp" + +#include + +#include "../../model/world/tablemimedata.hpp" + +CSVWorld::TableEditIdAction::CellData CSVWorld::TableEditIdAction::getCellData(int row, int column) const +{ + QModelIndex index = mTable.model()->index(row, column); + if (index.isValid()) + { + QVariant display = mTable.model()->data(index, CSMWorld::ColumnBase::Role_Display); + QString value = mTable.model()->data(index).toString(); + return std::make_pair(static_cast(display.toInt()), value); + } + return std::make_pair(CSMWorld::ColumnBase::Display_None, ""); +} + +CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView &table, QWidget *parent) + : QAction(parent), + mTable(table), + mCurrentId(CSMWorld::UniversalId::Type_None) +{} + +void CSVWorld::TableEditIdAction::setCell(int row, int column) +{ + CellData data = getCellData(row, column); + CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); + + if (idType != CSMWorld::UniversalId::Type_None) + { + mCurrentId = CSMWorld::UniversalId(idType, data.second.toUtf8().constData()); + setText("Edit '" + data.second + "'"); + } +} + +CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const +{ + return mCurrentId; +} + +bool CSVWorld::TableEditIdAction::isValidIdCell(int row, int column) const +{ + CellData data = getCellData(row, column); + CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); + return CSMWorld::ColumnBase::isId(data.first) && + idType != CSMWorld::UniversalId::Type_None && + !data.second.isEmpty(); +} diff --git a/apps/opencs/view/world/tableeditidaction.hpp b/apps/opencs/view/world/tableeditidaction.hpp new file mode 100644 index 000000000..f2cf0b7bd --- /dev/null +++ b/apps/opencs/view/world/tableeditidaction.hpp @@ -0,0 +1,31 @@ +#ifndef CSVWORLD_TABLEEDITIDACTION_HPP +#define CSVWORLD_TABLEEDITIDACTION_HPP + +#include + +#include "../../model/world/columnbase.hpp" +#include "../../model/world/universalid.hpp" + +class QTableView; + +namespace CSVWorld +{ + class TableEditIdAction : public QAction + { + const QTableView &mTable; + CSMWorld::UniversalId mCurrentId; + + typedef std::pair CellData; + CellData getCellData(int row, int column) const; + + public: + TableEditIdAction(const QTableView &table, QWidget *parent = 0); + + void setCell(int row, int column); + + CSMWorld::UniversalId getCurrentId() const; + bool isValidIdCell(int row, int column) const; + }; +} + +#endif diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 46393faf8..7a2680434 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -8,9 +8,11 @@ #include #include #include +#include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../../model/settings/usersettings.hpp" #include "../doc/sizehint.hpp" #include "../filter/filterbox.hpp" @@ -24,10 +26,8 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (mBottom = - new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); + new TableBottomBox (creatorFactory, document, id, this), 0); layout->insertWidget (0, mTable = new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2); @@ -37,6 +37,16 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D QHBoxLayout *hLayout = new QHBoxLayout; hLayout->insertWidget(0,mFilterBox); + QCheckBox *added = new QCheckBox("A"); + QCheckBox *modified = new QCheckBox("M"); + added->setToolTip("Apply filter project::added. Changes to\nthis filter setting is not saved in preferences."); + modified->setToolTip("Apply filter project::modified. Changes to\nthis filter setting is not saved in preferences."); + CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + added->setCheckState( + userSettings.settingValue ("filter/project-added") == "true" ? Qt::Checked : Qt::Unchecked); + modified->setCheckState( + userSettings.settingValue ("filter/project-modified") == "true" ? Qt::Checked : Qt::Unchecked); + mOptions = new QWidget; QHBoxLayout *optHLayout = new QHBoxLayout; @@ -47,6 +57,8 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D autoJump->setCheckState(Qt::Unchecked); connect(autoJump, SIGNAL (stateChanged(int)), mTable, SLOT (jumpAfterModChanged(int))); optHLayout->insertWidget(0, autoJump); + optHLayout->insertWidget(1, added); + optHLayout->insertWidget(2, modified); optHLayout->setContentsMargins (QMargins (0, 3, 0, 0)); mOptions->setLayout(optHLayout); mOptions->resize(mOptions->width(), mFilterBox->height()); @@ -87,6 +99,9 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), mBottom, SLOT (tableSizeChanged (int, int, int))); + connect(added, SIGNAL (stateChanged(int)), mTable, SLOT (globalFilterAddedChanged(int))); + connect(modified, SIGNAL (stateChanged(int)), mTable, SLOT (globalFilterModifiedChanged(int))); + mTable->tableSizeUpdate(); mTable->selectionSizeUpdate(); mTable->viewport()->installEventFilter(this); @@ -102,6 +117,11 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + + connect (mTable, SIGNAL(extendedDeleteConfigRequest(const std::vector &)), + mBottom, SLOT(extendedDeleteConfigRequest(const std::vector &))); + connect (mTable, SIGNAL(extendedRevertConfigRequest(const std::vector &)), + mBottom, SLOT(extendedRevertConfigRequest(const std::vector &))); } connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 5452214ef..1da9878ea 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -1,4 +1,3 @@ - #include "util.hpp" #include @@ -13,10 +12,16 @@ #include #include #include +#include #include "../../model/world/commands.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" + +#include "../widget/coloreditor.hpp" +#include "../../model/world/usertype.hpp" +#include "../widget/droplineedit.hpp" + #include "dialoguespinbox.hpp" #include "scriptedit.hpp" @@ -111,16 +116,31 @@ CSMDoc::Document& CSVWorld::CommandDelegate::getDocument() const return mDocument; } +CSMWorld::ColumnBase::Display CSVWorld::CommandDelegate::getDisplayTypeFromIndex(const QModelIndex &index) const +{ + int rawDisplay = index.data(CSMWorld::ColumnBase::Role_Display).toInt(); + return static_cast(rawDisplay); +} + void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const { if (!mCommandDispatcher) return; + QVariant new_; + // Color columns use a custom editor, so we need explicitly extract a data from it + CSVWidget::ColorEditor *colorEditor = qobject_cast(editor); + if (colorEditor != NULL) + { + new_ = colorEditor->color(); + } + else + { NastyTableModelHack hack (*model); QStyledItemDelegate::setModelData (editor, &hack, index); - - QVariant new_ = hack.getData(); + new_ = hack.getData(); + } if ((model->data (index)!=new_) && (model->flags(index) & Qt::ItemIsEditable)) mCommandDispatcher->executeModify (model, index, new_); @@ -146,7 +166,23 @@ void CSVWorld::CommandDelegate::setModelData (QWidget *editor, QAbstractItemMode QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { - return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_None); + CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index); + + // This createEditor() method is called implicitly from tables. + // For boolean values in tables use the default editor (combobox). + // Checkboxes is looking ugly in the table view. + // TODO: Find a better solution? + if (display == CSMWorld::ColumnBase::Display_Boolean) + { + return QItemEditorFactory::defaultFactory()->createEditor(QVariant::Bool, parent); + } + // For tables the pop-up of the color editor should appear immediately after the editor creation + // (the third parameter of ColorEditor's constructor) + else if (display == CSMWorld::ColumnBase::Display_Colour) + { + return new CSVWidget::ColorEditor(index.data().value(), parent, true); + } + return createEditor (parent, option, index, display); } QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, @@ -168,7 +204,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO { case CSMWorld::ColumnBase::Display_Colour: - return new QLineEdit(parent); + return new CSVWidget::ColorEditor(index.data().value(), parent); case CSMWorld::ColumnBase::Display_Integer: { @@ -197,33 +233,35 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return edit; } + case CSMWorld::ColumnBase::Display_LongString256: + { + /// \todo implement size limit. QPlainTextEdit does not support a size limit. + QPlainTextEdit *edit = new QPlainTextEdit(parent); + edit->setUndoRedoEnabled (false); + return edit; + } + case CSMWorld::ColumnBase::Display_Boolean: return new QCheckBox(parent); - case CSMWorld::ColumnBase::Display_String: - case CSMWorld::ColumnBase::Display_Skill: - case CSMWorld::ColumnBase::Display_Script: - case CSMWorld::ColumnBase::Display_Race: - case CSMWorld::ColumnBase::Display_Region: - case CSMWorld::ColumnBase::Display_Class: - case CSMWorld::ColumnBase::Display_Faction: - case CSMWorld::ColumnBase::Display_Miscellaneous: - case CSMWorld::ColumnBase::Display_Sound: - case CSMWorld::ColumnBase::Display_Mesh: - case CSMWorld::ColumnBase::Display_Icon: - case CSMWorld::ColumnBase::Display_Music: - case CSMWorld::ColumnBase::Display_SoundRes: - case CSMWorld::ColumnBase::Display_Texture: - case CSMWorld::ColumnBase::Display_Video: - case CSMWorld::ColumnBase::Display_GlobalVariable: - - return new DropLineEdit(parent); - case CSMWorld::ColumnBase::Display_ScriptLines: return new ScriptEdit (mDocument, ScriptHighlighter::Mode_Console, parent); + case CSMWorld::ColumnBase::Display_String: + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + + return new CSVWidget::DropLineEdit(display, parent); + + case CSMWorld::ColumnBase::Display_String32: + { + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + CSVWidget::DropLineEdit *widget = new CSVWidget::DropLineEdit(display, parent); + widget->setMaxLength (32); + return widget; + } + default: return QStyledItemDelegate::createEditor (parent, option, index); @@ -268,6 +306,14 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde } } + // Color columns use a custom editor, so we need explicitly set a data for it + CSVWidget::ColorEditor *colorEditor = qobject_cast(editor); + if (colorEditor != NULL) + { + colorEditor->setColor(index.data().value()); + return; + } + QByteArray n = editor->metaObject()->userProperty().name(); if (n == "dateTime") { @@ -279,34 +325,15 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde if (!n.isEmpty()) { if (!v.isValid()) - v = QVariant(editor->property(n).userType(), (const void *)0); - editor->setProperty(n, v); + editor->setProperty(n, QVariant(editor->property(n).userType(), (const void *)0)); + else if (v.type() == QVariant::UserType + && QString(v.typeName()) == "CSMWorld::UserFloat" && v.canConvert()) + editor->setProperty(n, QVariant(v.value().value())); + else if (v.type() == QVariant::UserType + && QString(v.typeName()) == "CSMWorld::UserInt" && v.canConvert()) + editor->setProperty(n, QVariant(v.value().value())); + else + editor->setProperty(n, v); } } - -CSVWorld::DropLineEdit::DropLineEdit(QWidget* parent) : -QLineEdit(parent) -{ - setAcceptDrops(true); -} - -void CSVWorld::DropLineEdit::dragEnterEvent(QDragEnterEvent *event) -{ - event->acceptProposedAction(); -} - -void CSVWorld::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) -{ - event->accept(); -} - -void CSVWorld::DropLineEdit::dropEvent(QDropEvent *event) -{ - const CSMWorld::TableMimeData* data(dynamic_cast(event->mimeData())); - if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped - return; - - emit tableMimeDataDropped(data->getData(), data->getDocumentPtr()); - //WIP -} diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index a12e6ae36..d695be0d7 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -5,7 +5,6 @@ #include #include -#include #include "../../model/world/columnbase.hpp" #include "../../model/doc/document.hpp" @@ -91,24 +90,6 @@ namespace CSVWorld }; - class DropLineEdit : public QLineEdit - { - Q_OBJECT - - public: - DropLineEdit(QWidget *parent); - - private: - void dragEnterEvent(QDragEnterEvent *event); - - void dragMoveEvent(QDragMoveEvent *event); - - void dropEvent(QDropEvent *event); - - signals: - void tableMimeDataDropped(const std::vector& data, const CSMDoc::Document* document); - }; - ///< \brief Use commands instead of manipulating the model directly class CommandDelegate : public QStyledItemDelegate { @@ -124,6 +105,8 @@ namespace CSVWorld CSMDoc::Document& getDocument() const; + CSMWorld::ColumnBase::Display getDisplayTypeFromIndex(const QModelIndex &index) const; + virtual void setModelDataImp (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const; diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index 90a686a67..a63e6028c 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -1,4 +1,3 @@ - #include "vartypedelegate.hpp" #include diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a183d172d..d54152100 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -66,7 +66,7 @@ add_openmw_dir (mwworld cells localscripts customdata weather inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader actiontrap cellreflist projectilemanager cellref + contentloader esmloader actiontrap cellreflist projectilemanager cellref mwstore ) add_openmw_dir (mwclass @@ -78,7 +78,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning + disease pickpocket levelledlist combat steering obstacle difficultyscaling aicombataction actor summoning ) add_openmw_dir (mwstate @@ -91,17 +91,6 @@ add_openmw_dir (mwbase ) # Main executable -if (ANDROID) - set(BOOST_COMPONENTS system filesystem program_options thread wave atomic) -else () - set(BOOST_COMPONENTS system filesystem program_options thread wave) -endif () - -if(WIN32) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) -endif(WIN32) - -find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) if (NOT ANDROID) add_executable(openmw @@ -126,10 +115,14 @@ target_link_libraries(openmw ${OGRE_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} - ${Boost_LIBRARIES} ${OPENAL_LIBRARY} ${SOUND_INPUT_LIBRARY} ${BULLET_LIBRARIES} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_WAVE_LIBRARY} ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} ${MYGUI_PLATFORM_LIBRARIES} diff --git a/apps/openmw/android_commandLine.cpp b/apps/openmw/android_commandLine.cpp index ebfff28ca..7e7f368e0 100644 --- a/apps/openmw/android_commandLine.cpp +++ b/apps/openmw/android_commandLine.cpp @@ -7,21 +7,21 @@ int argcData; extern "C" void releaseArgv(); void releaseArgv() { - delete[] argvData; + delete[] argvData; } JNIEXPORT void JNICALL Java_ui_activity_GameActivity_commandLine(JNIEnv *env, - jobject obj, jint argc, jobjectArray stringArray) { - jboolean iscopy; - argcData = (int) argc; - argvData = new const char *[argcData + 1]; - argvData[0] = "openmw"; - for (int i = 1; i < argcData + 1; i++) { - jstring string = (jstring) (env)->GetObjectArrayElement(stringArray, - i - 1); - argvData[i] = (env)->GetStringUTFChars(string, &iscopy); - (env)->DeleteLocalRef(string); - } - (env)->DeleteLocalRef(stringArray); + jobject obj, jint argc, jobjectArray stringArray) { + jboolean iscopy; + argcData = (int) argc; + argvData = new const char *[argcData + 1]; + argvData[0] = "openmw"; + for (int i = 1; i < argcData + 1; i++) { + jstring string = (jstring) (env)->GetObjectArrayElement(stringArray, + i - 1); + argvData[i] = (env)->GetStringUTFChars(string, &iscopy); + (env)->DeleteLocalRef(string); + } + (env)->DeleteLocalRef(stringArray); } diff --git a/apps/openmw/android_commandLine.h b/apps/openmw/android_commandLine.h index 21d1064c6..5ca79c2d0 100644 --- a/apps/openmw/android_commandLine.h +++ b/apps/openmw/android_commandLine.h @@ -1,5 +1,4 @@ - /* DO NOT EDIT THIS FILE - it is machine generated */ #include #ifndef _Included_ui_activity_GameActivity_commandLine diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 1b2839519..47b77a8b3 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -16,22 +16,22 @@ extern const char **argvData; void releaseArgv(); int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, - jobject obj) { + jobject obj) { - SDL_Android_Init(env, cls); + SDL_Android_Init(env, cls); - SDL_SetMainReady(); + SDL_SetMainReady(); - /* Run the application code! */ + /* Run the application code! */ - int status; + int status; - status = main(argcData+1, argvData); - releaseArgv(); - /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ - /* exit(status); */ + status = main(argcData+1, argvData); + releaseArgv(); + /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ + /* exit(status); */ - return status; + return status; } #endif /* __ANDROID__ */ diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 8f25d041c..373d78746 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -37,8 +37,8 @@ static const char pipe_err[] = "!!! Failed to create pipe\n"; static const char fork_err[] = "!!! Failed to fork debug process\n"; static const char exec_err[] = "!!! Failed to exec debug process\n"; -#ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ -# define PATH_MAX 256 +#ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ +# define PATH_MAX 256 #endif static char argv0[PATH_MAX]; diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4496490d4..9d40ebb1b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 070136dfd..2d2c9af0c 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -15,7 +15,7 @@ #if defined(_WIN32) // For OutputDebugString #define WIN32_LEAN_AND_MEAN -#include +#include // makes __argc and __argv available on windows #include #endif diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index a90eec5bf..4efa7c273 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -1,4 +1,3 @@ - #include "environment.hpp" #include diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 457b0cec1..d8a0c8091 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,4 +1,3 @@ - #include "activator.hpp" #include diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 2abd071bd..3add529a9 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -1,4 +1,3 @@ - #include "apparatus.hpp" #include diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 686f5af61..7a3c395e4 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -1,4 +1,3 @@ - #include "armor.hpp" #include diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index e9538a6cb..c303b23af 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -1,4 +1,3 @@ - #include "classes.hpp" #include "activator.hpp" diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index b387a3e9f..488142e72 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -1,4 +1,3 @@ - #include "clothing.hpp" #include diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 5f49a74b6..9d2e34ea3 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -1,4 +1,3 @@ - #include "container.hpp" #include diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 192bdf2ce..13fd22a0c 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,4 +1,3 @@ - #include "creature.hpp" #include @@ -808,27 +807,25 @@ namespace MWClass const ESM::CreatureState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - - // If we do the following instead we get a sizable speedup, but this causes compatibility issues - // with 0.30 savegames, where some state in CreatureStats was not saved yet, - // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. - /* - if (!ptr.getRefData().getCustomData()) + if (state.mVersion > 0) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::auto_ptr data (new CreatureCustomData); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new CreatureCustomData); - MWWorld::LiveCellRef *ref = ptr.get(); + MWWorld::LiveCellRef *ref = ptr.get(); - if (ref->mBase->mFlags & ESM::Creature::Weapon) - data->mContainerStore = new MWWorld::InventoryStore(); - else - data->mContainerStore = new MWWorld::ContainerStore(); + if (ref->mBase->mFlags & ESM::Creature::Weapon) + data->mContainerStore = new MWWorld::InventoryStore(); + else + data->mContainerStore = new MWWorld::ContainerStore(); - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData (data.release()); + } } - */ + else + ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index dbc4b6af7..433e5fcea 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -1,4 +1,3 @@ - #include "creaturelevlist.hpp" #include diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 2d39881b1..ab9cfa289 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -1,4 +1,3 @@ - #include "door.hpp" #include @@ -147,7 +146,7 @@ namespace MWClass if (ptr.getCellRef().getTeleport()) { - boost::shared_ptr action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest())); + boost::shared_ptr action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true)); action->setSound(openSound); diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index de43e818e..6388c9f9c 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -1,4 +1,3 @@ - #include "ingredient.hpp" #include diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index d31080bb2..a70f31115 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -1,4 +1,3 @@ - #include "itemlevlist.hpp" #include diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 90c708f97..9a80c7036 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -1,4 +1,3 @@ - #include "light.hpp" #include diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 478c50301..2b6a217e6 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -1,4 +1,3 @@ - #include "lockpick.hpp" #include diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index f5daafeec..7f1f2c5a8 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -1,4 +1,3 @@ - #include "misc.hpp" #include diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3ca57aca8..bc806078a 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,4 +1,3 @@ - #include "npc.hpp" #include @@ -11,6 +10,10 @@ #include #include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -24,7 +27,6 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/combat.hpp" -#include "../mwmechanics/autocalcspell.hpp" #include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/character.hpp" @@ -36,6 +38,7 @@ #include "../mwworld/customdata.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/mwstore.hpp" #include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" @@ -58,116 +61,44 @@ namespace return new NpcCustomData (*this); } - int is_even(double d) { - double int_part; - modf(d / 2.0, &int_part); - return 2.0 * int_part == d; - } - - int round_ieee_754(double d) { - double i = floor(d); - d -= i; - if(d < 0.5) - return static_cast(i); - if(d > 0.5) - return static_cast(i) + 1; - if(is_even(i)) - return static_cast(i); - return static_cast(i) + 1; - } - - void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) + class Stats : public AutoCalc::StatsBase + { + MWMechanics::NpcStats& mNpcStats; + + public: + + Stats(MWMechanics::NpcStats& npcStats) : mNpcStats(npcStats) {} + + virtual unsigned char getBaseAttribute(int index) const { return mNpcStats.getAttribute(index).getBase(); } + + virtual void setAttribute(int index, unsigned char value) { mNpcStats.setAttribute(index, value); } + + virtual void addSpell(const std::string& id) { mNpcStats.getSpells().add(id); } + + virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); } + + virtual void setBaseSkill(int index, unsigned char value) { mNpcStats.getSkill(index).setBase(value); } + }; + + void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::NpcStats& npcStats) { - // race bonus const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); - bool male = (npc->mFlags & ESM::NPC::Female) == 0; - - int level = creatureStats.getLevel(); - for (int i=0; imData.mAttributeValues[i]; - creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); - } - - // class bonus const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); - for (int i=0; i<2; ++i) - { - int attribute = class_->mData.mAttribute[i]; - if (attribute>=0 && attribute<8) - { - creatureStats.setAttribute(attribute, - creatureStats.getAttribute(attribute).getBase() + 10); - } - } + int level = npcStats.getLevel(); - // skill bonus - for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute) - { - float modifierSum = 0; + Stats stats(npcStats); - for (int j=0; jgetStore().get().find(j); + MWWorld::MWStore store; - if (skill->mData.mAttribute != attribute) - continue; + AutoCalc::autoCalcAttributesImpl (npc, race, class_, level, stats, &store); - // is this a minor or major skill? - float add=0.2f; - for (int k=0; k<5; ++k) - { - if (class_->mData.mSkills[k][0] == j) - add=0.5; - } - for (int k=0; k<5; ++k) - { - if (class_->mData.mSkills[k][1] == j) - add=1.0; - } - modifierSum += add; - } - creatureStats.setAttribute(attribute, std::min( - round_ieee_754(creatureStats.getAttribute(attribute).getBase() - + (level-1) * modifierSum), 100) ); - } - - // initial health - int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); - int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); - - int multiplier = 3; - - if (class_->mData.mSpecialization == ESM::Class::Combat) - multiplier += 2; - else if (class_->mData.mSpecialization == ESM::Class::Stealth) - multiplier += 1; - - if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance - || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) - multiplier += 1; - - creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); + npcStats.setHealth(AutoCalc::autoCalculateHealth(level, class_, stats)); } - /** - * @brief autoCalculateSkills - * - * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): - * - * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) - * - * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. - * - * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, - * zero for other Skills. - * - * and by adding class, race, specialization bonus. - */ void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) { const ESM::Class *class_ = @@ -177,77 +108,13 @@ namespace const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); + Stats stats(npcStats); - for (int i = 0; i < 2; ++i) - { - int bonus = (i==0) ? 10 : 25; + MWWorld::MWStore store; - for (int i2 = 0; i2 < 5; ++i2) - { - int index = class_->mData.mSkills[i2][i]; - if (index >= 0 && index < ESM::Skill::Length) - { - npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); - } - } - } + AutoCalc::autoCalcSkillsImpl(npc, race, class_, level, stats, &store); - for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) - { - float majorMultiplier = 0.1f; - float specMultiplier = 0.0f; - - int raceBonus = 0; - int specBonus = 0; - - for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) - { - if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) - { - raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; - break; - } - } - - for (int k = 0; k < 5; ++k) - { - // is this a minor or major skill? - if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) - { - majorMultiplier = 1.0f; - break; - } - } - - // is this skill in the same Specialization as the class? - const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillIndex); - if (skill->mData.mSpecialization == class_->mData.mSpecialization) - { - specMultiplier = 0.5f; - specBonus = 5; - } - - npcStats.getSkill(skillIndex).setBase( - std::min( - round_ieee_754( - npcStats.getSkill(skillIndex).getBase() - + 5 - + raceBonus - + specBonus - +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 - } - - int skills[ESM::Skill::Length]; - for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); - for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) - npcStats.getSpells().add(*it); + AutoCalc::autoCalculateSpells(race, stats, &store); } } @@ -392,7 +259,7 @@ namespace MWClass // store ptr.getRefData().setCustomData (data.release()); - getInventoryStore(ptr).autoEquip(ptr); + getInventoryStore(ptr).autoEquip(ptr); } } @@ -1236,18 +1103,17 @@ namespace MWClass const ESM::NpcState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - // If we do the following instead we get a sizable speedup, but this causes compatibility issues - // with 0.30 savegames, where some state in CreatureStats was not saved yet, - // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. - /* - if (!ptr.getRefData().getCustomData()) + if (state.mVersion > 0) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::auto_ptr data (new NpcCustomData); - ptr.getRefData().setCustomData (data.release()); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new NpcCustomData); + ptr.getRefData().setCustomData (data.release()); + } } - */ + else + ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index ee299ab4f..5e1849a72 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -1,4 +1,3 @@ - #include "potion.hpp" #include diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index da22e9be6..00d79653d 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -1,4 +1,3 @@ - #include "probe.hpp" #include diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index c02146f12..403eb611b 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -1,4 +1,3 @@ - #include "repair.hpp" #include diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index dbbe7e43a..7bfd4c76d 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -1,4 +1,3 @@ - #include "static.hpp" #include diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index a484ad668..3e39c305a 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -1,4 +1,3 @@ - #include "weapon.hpp" #include diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 1785575fc..982f32956 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -1,4 +1,3 @@ - #include "dialoguemanagerimp.hpp" #include diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index adb7d3892..35ccc287c 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -1,4 +1,3 @@ - #include "filter.hpp" #include diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 9f07f7b6f..2f5f02b01 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -1,4 +1,3 @@ - #include "journalentry.hpp" #include diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 99dab0cf8..e6ffe22ab 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -1,4 +1,3 @@ - #include "journalimp.hpp" #include diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index a9e39b379..846597886 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,4 +1,3 @@ - #include "quest.hpp" #include diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index fa0fbfe13..a4eba30ae 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -1,4 +1,3 @@ - #include "selectwrapper.hpp" #include diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index c1a45f841..eb7fbdc1d 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -1,4 +1,3 @@ - #include "topic.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index a54744370..768ad82e4 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -130,6 +130,7 @@ namespace MWGui mSortModel = new SortFilterItemModel(model); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); mItemView->setModel (mSortModel); + mItemView->resetScrollBars(); mNameEdit->setCaption(""); diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 4433f9ef8..69b6aa9f9 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -114,6 +114,7 @@ void CompanionWindow::open(const MWWorld::Ptr& npc) mModel = new CompanionItemModel(npc); mSortModel = new SortFilterItemModel(mModel); mItemView->setModel(mSortModel); + mItemView->resetScrollBars(); setTitle(npc.getClass().getName(npc)); } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 4eb9a271c..083dd32b0 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -295,9 +295,12 @@ namespace MWGui mCurrent = mCommandHistory.end(); mEditString.clear(); - execute (cm); - + // Reset the command line before the command execution. + // It prevents the re-triggering of the acceptCommand() event for the same command + // during the actual command execution mCommandLine->setCaption(""); + + execute (cm); } std::string Console::complete( std::string input, std::vector &matches ) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 1317e1e25..76e9cbb5b 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -151,6 +151,7 @@ namespace MWGui mSortModel = new SortFilterItemModel(mModel); mItemView->setModel (mSortModel); + mItemView->resetScrollBars(); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index a6dab66c1..37ea3470d 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -18,9 +18,9 @@ namespace float accumulated_time=0,parent_time = pit->Is_Root() ? CProfileManager::Get_Time_Since_Reset() : pit->Get_Current_Parent_Total_Time(); int i,j; int frames_since_reset = CProfileManager::Get_Frame_Count_Since_Reset(); - for (i=0;iGet_Current_Parent_Name())+" (total running time: "+MyGUI::utility::toString(parent_time,3)+" ms) ---\n"; os << s; @@ -35,7 +35,7 @@ namespace accumulated_time += current_total_time; float fraction = parent_time > SIMD_EPSILON ? (current_total_time / parent_time) * 100 : 0.f; - for (j=0;jGet_Current_Name()+" ("+MyGUI::utility::toString(fraction,2)+" %) :: "+MyGUI::utility::toString(ms,3)+" ms / frame ("+MyGUI::utility::toString(pit->Get_Current_Total_Calls())+" calls)\n"; os << s; @@ -47,7 +47,7 @@ namespace { os << "what's wrong\n"; } - for (i=0;i SIMD_EPSILON ? ((parent_time - accumulated_time) / parent_time) * 100 : 0.f; s = "Unaccounted: ("+MyGUI::utility::toString(unaccounted,3)+" %) :: "+MyGUI::utility::toString(parent_time - accumulated_time,3)+" ms\n"; os << s; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 692cea952..0cb0475c9 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -291,7 +291,10 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); } else + { MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + mTopicsList->scrollToTop(); + } } void DialogueWindow::onWindowResize(MyGUI::Window* _sender) diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp index 916f13360..095f392b7 100644 --- a/apps/openmw/mwgui/itemselection.cpp +++ b/apps/openmw/mwgui/itemselection.cpp @@ -39,6 +39,7 @@ namespace MWGui mModel = new InventoryItemModel(container); mSortModel = new SortFilterItemModel(mModel); mItemView->setModel(mSortModel); + mItemView->resetScrollBars(); } void ItemSelectionDialog::setCategory(int category) diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index aade232d2..c5320a2be 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -128,6 +128,11 @@ void ItemView::update() layoutWidgets(); } +void ItemView::resetScrollBars() +{ + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); +} + void ItemView::onSelectedItem(MyGUI::Widget *sender) { ItemModel::ModelIndex index = (*sender->getUserData >()).first; diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp index 9aeba6752..f87a48aa6 100644 --- a/apps/openmw/mwgui/itemview.hpp +++ b/apps/openmw/mwgui/itemview.hpp @@ -30,6 +30,8 @@ namespace MWGui void update(); + void resetScrollBars(); + private: virtual void initialiseOverride(); diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 4407bf927..862b719d4 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -114,6 +114,8 @@ void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel) void MerchantRepair::open() { center(); + // Reset scrollbars + mList->setViewOffset(MyGUI::IntPoint(0, 0)); } void MerchantRepair::exit() diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 834c156f9..685c7d45c 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -548,6 +548,7 @@ namespace MWGui WindowModal::open(); mMagicList->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mMagicList->resetScrollbars(); } void MagicSelectionDialog::onModelIndexSelected(SpellModel::ModelIndex index) diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index a0e5991b4..b7280565b 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -44,6 +44,8 @@ Recharge::Recharge() void Recharge::open() { center(); + // Reset scrollbars + mView->setViewOffset(MyGUI::IntPoint(0, 0)); } void Recharge::exit() diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 9f26923d4..534226aeb 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -36,6 +36,8 @@ Repair::Repair() void Repair::open() { center(); + // Reset scrollbars + mRepairView->setViewOffset(MyGUI::IntPoint(0, 0)); } void Repair::exit() diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index d895a28ea..6991bb294 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -170,6 +171,7 @@ namespace MWGui setTitle("#{sOptions}"); + getWidget(mSettingsTab, "SettingsTab"); getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); getWidget(mFullscreenButton, "FullscreenButton"); @@ -208,6 +210,7 @@ namespace MWGui mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SettingsWindow::onWindowResize); + mSettingsTab->eventTabChangeSelect += MyGUI::newDelegate(this, &SettingsWindow::onTabChanged); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mShaderModeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShaderModeToggled); mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); @@ -275,6 +278,11 @@ namespace MWGui mControllerSwitch->setStateSelected(false); } + void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/) + { + resetScrollbars(); + } + void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) { exit(); @@ -480,6 +488,7 @@ namespace MWGui mKeyboardSwitch->setStateSelected(true); mControllerSwitch->setStateSelected(false); updateControlsBox(); + resetScrollbars(); } void SettingsWindow::onControllerSwitchClicked(MyGUI::Widget* _sender) @@ -490,6 +499,7 @@ namespace MWGui mKeyboardSwitch->setStateSelected(false); mControllerSwitch->setStateSelected(true); updateControlsBox(); + resetScrollbars(); } void SettingsWindow::updateControlsBox() @@ -584,6 +594,7 @@ namespace MWGui void SettingsWindow::open() { updateControlsBox (); + resetScrollbars(); } void SettingsWindow::exit() @@ -595,4 +606,10 @@ namespace MWGui { updateControlsBox(); } + + void SettingsWindow::resetScrollbars() + { + mResolutionList->setScrollPosition(0); + mControlsBox->setViewOffset(MyGUI::IntPoint(0, 0)); + } } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 1b970b8de..8910960b2 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -22,6 +22,7 @@ namespace MWGui void updateControlsBox(); protected: + MyGUI::TabControl* mSettingsTab; MyGUI::Button* mOkButton; // graphics @@ -50,6 +51,7 @@ namespace MWGui MyGUI::Button* mControllerSwitch; bool mKeyboardMode; //if true, setting up the keyboard. Otherwise, it's controller + void onTabChanged(MyGUI::TabControl* _sender, size_t index); void onOkButtonClicked(MyGUI::Widget* _sender); void onFpsToggled(MyGUI::Widget* _sender); void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); @@ -74,6 +76,9 @@ namespace MWGui void apply(); void configureWidgets(MyGUI::Widget* widget); + + private: + void resetScrollbars(); }; } diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 1c670838f..a492ea7aa 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -551,6 +551,7 @@ namespace MWGui ++i; } mAvailableEffectsList->adjustSize (); + mAvailableEffectsList->scrollToTop(); for (std::vector::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it) { diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index 6d86b4a23..af6ff00a5 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -311,4 +311,8 @@ namespace MWGui mScrollView->setViewOffset(MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + _rel*0.3f))); } + void SpellView::resetScrollbars() + { + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); + } } diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp index 7af1bda7a..0f0b8a7d2 100644 --- a/apps/openmw/mwgui/spellview.hpp +++ b/apps/openmw/mwgui/spellview.hpp @@ -51,6 +51,8 @@ namespace MWGui virtual void setSize(const MyGUI::IntSize& _value); virtual void setCoord(const MyGUI::IntCoord& _value); + void resetScrollbars(); + private: MyGUI::ScrollView* mScrollView; diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index c50d47ef5..84bd15e12 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -1,4 +1,3 @@ - #ifndef MWGUI_TOOLTIPS_H #define MWGUI_TOOLTIPS_H @@ -107,7 +106,7 @@ namespace MWGui static std::string sSchoolNames[6]; - int mHorizontalScrollIndex; + int mHorizontalScrollIndex; float mDelay; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index aecfce98d..bdcf28bf2 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -136,6 +136,7 @@ namespace MWGui mTradeModel = new TradeItemModel(new ContainerItemModel(itemSources, worldItems), mPtr); mSortModel = new SortFilterItemModel(mTradeModel); mItemView->setModel (mSortModel); + mItemView->resetScrollBars(); updateLabels(); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 4da1ab33a..ba6fc2a78 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -183,7 +183,7 @@ namespace MWGui MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); // Teleports any followers, too. - MWWorld::ActionTeleport action(interior ? cellname : "", pos); + MWWorld::ActionTeleport action(interior ? cellname : "", pos, true); action.execute(player); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 718624a16..f373ce2c4 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -607,7 +607,7 @@ namespace MWGui controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); + MyGUI::ControllerManager::getInstance().addItem(this, controller); } void MWScrollBar::onDecreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) @@ -623,7 +623,7 @@ namespace MWGui controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); + MyGUI::ControllerManager::getInstance().addItem(this, controller); } void MWScrollBar::onIncreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 2e82faa6d..795d81ff9 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -159,9 +159,9 @@ namespace MWInput mControlSwitch["playerviewswitch"] = true; mControlSwitch["vanitymode"] = true; - /* Joystick Init */ + /* Joystick Init */ - //Load controller mappings + // Load controller mappings #if SDL_VERSION_ATLEAST(2,0,2) if(controllerBindingsFile!="") { @@ -169,10 +169,10 @@ namespace MWInput } #endif - //Open all presently connected sticks - int numSticks = SDL_NumJoysticks(); - for(int i = 0; i < numSticks; i++) - { + // Open all presently connected sticks + int numSticks = SDL_NumJoysticks(); + for(int i = 0; i < numSticks; i++) + { if(SDL_IsGameController(i)) { SDL_ControllerDeviceEvent evt; @@ -183,7 +183,7 @@ namespace MWInput { //ICS_LOG(std::string("Unusable controller plugged in: ")+SDL_JoystickNameForIndex(i)); } - } + } } void InputManager::clear() diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 649f259d9..5f0eff4e9 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -65,7 +65,7 @@ namespace Ogre::Vector3 dir = to - from; dir.z = 0; dir.normalise(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor + float verticalOffset = 200; // instead of '200' here we want the height of the actor Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; // cast up-down ray and find height in world space of hit @@ -677,44 +677,32 @@ namespace MWMechanics return false; } + bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) + { + if (!mPathFinder.getPath().empty()) + { + Ogre::Vector3 currPathTarget(PathFinder::MakeOgreVector3(mPathFinder.getPath().back())); + Ogre::Vector3 newPathTarget = PathFinder::MakeOgreVector3(dest); + float dist = (newPathTarget - currPathTarget).length(); + float targetPosThreshold = (cell->isExterior()) ? 300.0f : 100.0f; + return dist > targetPosThreshold; + } + else + { + // necessarily construct a new path + return true; + } + } + void AiCombat::buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { - Ogre::Vector3 newPathTarget = Ogre::Vector3(target.getRefData().getPosition().pos); - - float dist; - - if(!mPathFinder.getPath().empty()) - { - ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - Ogre::Vector3 currPathTarget(PathFinder::MakeOgreVector3(lastPt)); - dist = (newPathTarget - currPathTarget).length(); - } - else dist = 1e+38F; // necessarily construct a new path - - float targetPosThreshold = (actor.getCell()->getCell()->isExterior())? 300.0f : 100.0f; + ESM::Pathgrid::Point newPathTarget = PathFinder::MakePathgridPoint(target.getRefData().getPosition()); //construct new path only if target has moved away more than on [targetPosThreshold] - if(dist > targetPosThreshold) + if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) { - ESM::Position pos = actor.getRefData().getPosition(); - - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(newPathTarget)); - - if(!mPathFinder.isPathConstructed()) - mPathFinder.buildPath(start, dest, actor.getCell(), false); - else - { - PathFinder newPathFinder; - newPathFinder.buildPath(start, dest, actor.getCell(), false); - - if(!mPathFinder.getPath().empty()) - { - newPathFinder.syncStart(mPathFinder.getPath()); - mPathFinder = newPathFinder; - } - } + ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); + mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell(), false); } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 307df3872..8248975b5 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -53,6 +53,9 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + protected: + virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + private: int mTargetActorId; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 52a975320..216bf7b09 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -30,9 +30,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po ESM::Position pos = actor.getRefData().getPosition(); //position of the actor /// Stops the actor when it gets too close to a unloaded cell + const ESM::Cell *cell = actor.getCell()->getCell(); { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const ESM::Cell *cell = actor.getCell()->getCell(); Movement &movement = actor.getClass().getMovementSettings(actor); //Ensure pursuer doesn't leave loaded cells @@ -67,8 +67,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //*********************** if(mTimer > 0.25) { - if(distance(mPrevDest, dest) > 10) { //Only rebuild path if it's moved - mPathFinder.buildPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved + if (doesPathNeedRecalc(dest, cell)) { //Only rebuild path if it's moved + mPathFinder.buildSyncedPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved mPrevDest = dest; } @@ -123,3 +123,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po return false; } + +bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) +{ + return distance(mPrevDest, dest) > 10; +} \ No newline at end of file diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 179ae440b..78a2bfd9f 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -14,6 +14,7 @@ namespace MWWorld namespace ESM { + struct Cell; namespace AiSequence { struct AiSequence; @@ -71,6 +72,8 @@ namespace MWMechanics /** \return If the actor has arrived at his destination **/ bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); + virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 2824e2c6c..4f4d4c79f 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -51,64 +51,31 @@ namespace MWMechanics bool AiTravel::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { - MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); - Movement &movement = actor.getClass().getMovementSettings(actor); - const ESM::Cell *cell = actor.getCell()->getCell(); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); - MWWorld::Ptr player = world->getPlayerPtr(); - if(cell->mData.mX != player.getCell()->getCell()->mData.mX) - { - int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > - sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - if(cell->mData.mY != player.getCell()->getCell()->mData.mY) - { - int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > - sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - if (!isWithinMaxRange(Ogre::Vector3(mX, mY, mZ), Ogre::Vector3(pos.pos))) return false; + if (pathTo(actor, ESM::Pathgrid::Point(static_cast(mX), static_cast(mY), static_cast(mZ)), duration)) + { + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + return true; + } + return false; + } + + bool AiTravel::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) + { bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; - if(!mPathFinder.isPathConstructed() || cellChange) + if (!mPathFinder.isPathConstructed() || cellChange) { mCellX = cell->mData.mX; mCellY = cell->mData.mY; - - ESM::Pathgrid::Point dest(static_cast(mX), static_cast(mY), static_cast(mZ)); - - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - mPathFinder.buildPath(start, dest, actor.getCell(), true); - } - - if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) - { - movement.mPosition[1] = 0; return true; } - - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); - movement.mPosition[1] = 1; - return false; } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index c2732e3aa..a5a4577e6 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -34,6 +34,9 @@ namespace MWMechanics virtual int getTypeId() const; + protected: + virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + private: float mX; float mY; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 076636974..d89a29e1d 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -669,6 +669,9 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); + + // may have changed cell + mStoredAvailableNodes = false; } void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) @@ -694,7 +697,8 @@ namespace MWMechanics // actor can wander from the spawn position. AiWander assumes that // pathgrid points are available, and uses them to randomly select wander // destinations within the allowed set of pathgrid points (nodes). - if(mDistance) + // ... pathgrids don't usually include water, so swimmers ignore them + if (mDistance && !actor.getClass().isPureWaterCreature(actor)) { float cellXOffset = 0; float cellYOffset = 0; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 58c42ddb8..42dd3048f 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -1,4 +1,3 @@ - #include "alchemy.hpp" #include @@ -44,6 +43,8 @@ std::set MWMechanics::Alchemy::listEffects() const { const MWWorld::LiveCellRef *ingredient = iter->get(); + std::set seenEffects; + for (int i=0; i<4; ++i) if (ingredient->mBase->mData.mEffectID[i]!=-1) { @@ -51,7 +52,8 @@ std::set MWMechanics::Alchemy::listEffects() const ingredient->mBase->mData.mEffectID[i], ingredient->mBase->mData.mSkills[i]!=-1 ? ingredient->mBase->mData.mSkills[i] : ingredient->mBase->mData.mAttributes[i]); - ++effects[key]; + if (seenEffects.insert(key).second) + ++effects[key]; } } } @@ -460,7 +462,10 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na return Result_NoName; if (listEffects().empty()) + { + removeIngredients(); return Result_NoEffects; + } if (beginEffects() == endEffects()) { diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 308e72027..e5cb561bf 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -20,7 +20,7 @@ namespace MWMechanics mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mAttackStrength(0.f), mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), - mDeathAnimation(0), mIsWerewolf(false), mLevel (0) + mDeathAnimation(0), mLevel (0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; @@ -55,7 +55,7 @@ namespace MWMechanics if (index < 0 || index > 7) { throw std::runtime_error("attribute index is out of range"); } - return (!mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]); + return mAttributes[index]; } const DynamicStat &CreatureStats::getHealth() const @@ -139,14 +139,11 @@ namespace MWMechanics throw std::runtime_error("attribute index is out of range"); } - const AttributeValue& currentValue = !mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]; + const AttributeValue& currentValue = mAttributes[index]; if (value != currentValue) { - if(!mIsWerewolf) - mAttributes[index] = value; - else - mWerewolfAttributes[index] = value; + mAttributes[index] = value; if (index == ESM::Attribute::Intelligence) mRecalcMagicka = true; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 145eb8a5b..146d9fb1e 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -77,10 +77,6 @@ namespace MWMechanics std::vector mSummonGraveyard; protected: - // These two are only set by NpcStats, but they are declared in CreatureStats to prevent using virtual methods. - bool mIsWerewolf; - AttributeValue mWerewolfAttributes[8]; - int mLevel; public: diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 0b19df0a8..021691d6a 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -1,4 +1,3 @@ - #include "magiceffects.hpp" #include diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index c829154e2..dc388555e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -6,8 +6,11 @@ #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/mwstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -23,7 +26,6 @@ #include #include "spellcasting.hpp" -#include "autocalcspell.hpp" #include @@ -252,10 +254,12 @@ namespace MWMechanics continue; static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->getFloat(); - if (calcAutoCastChance(spell, skills, attributes, -1) < fAutoPCSpellChance) + MWWorld::MWStore store; + + if (AutoCalc::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance) continue; - if (!attrSkillCheck(spell, skills, attributes)) + if (!AutoCalc::attrSkillCheck(spell, skills, attributes, &store)) continue; selectedSpells.push_back(spell->mId); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 94819e626..10d603ff1 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -1,4 +1,3 @@ - #include "npcstats.hpp" #include @@ -32,6 +31,7 @@ MWMechanics::NpcStats::NpcStats() , mWerewolfKills (0) , mLevelProgress(0) , mTimeToStartDrowning(20.0) + , mIsWerewolf(false) { mSkillIncreases.resize (ESM::Attribute::Length, 0); } @@ -51,7 +51,7 @@ const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) const if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); - return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); + return mSkill[index]; } MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) @@ -59,7 +59,15 @@ MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); - return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); + return mSkill[index]; +} + +void MWMechanics::NpcStats::setSkill(int index, const MWMechanics::SkillValue &value) +{ + if (index<0 || index>=ESM::Skill::Length) + throw std::runtime_error ("skill index out of range"); + + mSkill[index] = value; } const std::map& MWMechanics::NpcStats::getFactionRanks() const @@ -188,10 +196,6 @@ float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType, float extraFactor) { - // Don't increase skills as a werewolf - if(mIsWerewolf) - return; - const ESM::Skill *skill = MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); float skillGain = 1; @@ -403,34 +407,12 @@ bool MWMechanics::NpcStats::isWerewolf() const void MWMechanics::NpcStats::setWerewolf (bool set) { + if (mIsWerewolf == set) + return; + if(set != false) { - const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - mWerewolfKills = 0; - - for(size_t i = 0;i < ESM::Attribute::Length;i++) - { - mWerewolfAttributes[i] = getAttribute(i); - // Oh, Bethesda. It's "Intelligence". - std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") : - ESM::Attribute::sAttributeNames[i]); - mWerewolfAttributes[i].setBase(int(gmst.find(name)->getFloat())); - } - - for(size_t i = 0;i < ESM::Skill::Length;i++) - { - mWerewolfSkill[i] = getSkill(i); - - // Acrobatics is set separately for some reason. - if(i == ESM::Skill::Acrobatics) - continue; - - // "Mercantile"! >_< - std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : - ESM::Skill::sSkillNames[i]); - mWerewolfSkill[i].setBase(int(gmst.find(name)->getFloat())); - } } mIsWerewolf = set; } @@ -464,14 +446,8 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mDisposition = mDisposition; for (int i=0; i& getFactionRanks() const; /// Increase the rank in this faction by 1, if such a rank exists. diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 5795f818a..fea993e23 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -114,8 +114,7 @@ namespace MWMechanics } PathFinder::PathFinder() - : mIsPathConstructed(false), - mPathgrid(NULL), + : mPathgrid(NULL), mCell(NULL) { } @@ -124,7 +123,6 @@ namespace MWMechanics { if(!mPath.empty()) mPath.clear(); - mIsPathConstructed = false; } /* @@ -180,7 +178,6 @@ namespace MWMechanics static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ))) { mPath.push_back(endPoint); - mIsPathConstructed = true; return; } } @@ -197,7 +194,6 @@ namespace MWMechanics if(!mPathgrid || mPathgrid->mPoints.empty()) { mPath.push_back(endPoint); - mIsPathConstructed = true; return; } @@ -235,7 +231,6 @@ namespace MWMechanics if(startNode == endNode.first) { mPath.push_back(endPoint); - mIsPathConstructed = true; return; } @@ -243,7 +238,6 @@ namespace MWMechanics if(!mPath.empty()) { - mIsPathConstructed = true; // Add the destination (which may be different to the closest // pathgrid point). However only add if endNode was the closest // point to endPoint. @@ -256,14 +250,8 @@ namespace MWMechanics if(endNode.second) mPath.push_back(endPoint); } - else - mIsPathConstructed = false; } - else - mIsPathConstructed = false; } - else - mIsPathConstructed = false; return; } @@ -271,7 +259,7 @@ namespace MWMechanics float PathFinder::getZAngleToNext(float x, float y) const { // This should never happen (programmers should have an if statement checking - // mIsPathConstructed that prevents this call if otherwise). + // isPathConstructed that prevents this call if otherwise). if(mPath.empty()) return 0.; @@ -293,7 +281,6 @@ namespace MWMechanics mPath.pop_front(); if(mPath.empty()) { - mIsPathConstructed = false; return true; } } @@ -301,23 +288,35 @@ namespace MWMechanics return false; } - // used by AiCombat, see header for the rationale - bool PathFinder::syncStart(const std::list &path) + // see header for the rationale + void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, + const ESM::Pathgrid::Point &endPoint, + const MWWorld::CellStore* cell, + bool allowShortcuts) { if (mPath.size() < 2) - return false; //nothing to pop - - std::list::const_iterator oldStart = path.begin(); - std::list::iterator iter = ++mPath.begin(); - - if( (*iter).mX == oldStart->mX - && (*iter).mY == oldStart->mY - && (*iter).mZ == oldStart->mZ) { - mPath.pop_front(); - return true; + // if path has one point, then it's the destination. + // don't need to worry about bad path for this case + buildPath(startPoint, endPoint, cell, allowShortcuts); + } + else + { + const ESM::Pathgrid::Point oldStart(*getPath().begin()); + buildPath(startPoint, endPoint, cell, allowShortcuts); + if (mPath.size() >= 2) + { + // if 2nd waypoint of new path == 1st waypoint of old, + // delete 1st waypoint of new path. + std::list::iterator iter = ++mPath.begin(); + if (iter->mX == oldStart.mX + && iter->mY == oldStart.mY + && iter->mZ == oldStart.mZ) + { + mPath.pop_front(); + } + } } - return false; } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index f48de6624..644d79236 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -48,7 +48,7 @@ namespace MWMechanics bool isPathConstructed() const { - return mIsPathConstructed; + return !mPath.empty(); } int getPathSize() const @@ -63,13 +63,13 @@ namespace MWMechanics /** Synchronize new path with old one to avoid visiting 1 waypoint 2 times @note - If the first point is chosen as the nearest one - the situation can occur when the 1st point of the new path is undesirable - (i.e. the 2nd point of new path == the 1st point of old path). - @param path - old path - @return true if such point was found and deleted + BuildPath() takes closest PathGrid point to NPC as first point of path. + This is undesireable if NPC has just passed a Pathgrid point, as this + makes the 2nd point of the new path == the 1st point of old path. + Which results in NPC "running in a circle" back to the just passed waypoint. */ - bool syncStart(const std::list &path); + void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + const MWWorld::CellStore* cell, bool allowShortcuts = true); void addPointToPath(ESM::Pathgrid::Point &point) { @@ -96,8 +96,6 @@ namespace MWMechanics private: - bool mIsPathConstructed; - std::list mPath; const ESM::Pathgrid *mPathgrid; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 8a43cc932..1f3a88827 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -679,7 +679,7 @@ namespace MWMechanics if (markedCell) { MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, - markedPosition); + markedPosition, false); action.execute(target); } } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index b1829964b..4636ecfae 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,4 +1,3 @@ - #include "spells.hpp" #include diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 90cf27049..2a1556c7c 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -83,6 +83,9 @@ namespace MWRender land->loadData(mask); } + const ESM::Land::LandData *landData = + land ? land->getLandData (ESM::Land::DATA_WNAM) : 0; + for (int cellY=0; cellY(float(cellX)/float(mCellSize) * 9); int vertexY = static_cast(float(cellY) / float(mCellSize) * 9); - int texelX = (x-mMinX) * mCellSize + cellX; int texelY = (mHeight-1) - ((y-mMinY) * mCellSize + cellY); unsigned char r,g,b; float y = 0; - if (land && land->mDataTypes & ESM::Land::DATA_WNAM) - y = (land->mLandData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; + if (landData) + y = (landData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; else y = (SCHAR_MIN << 4) / 2048.f; if (y < 0) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 6a6d52e26..f4943ba55 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -196,7 +196,11 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { - if (!mListenerDisabled) + if (!mListenerDisabled + // No need to getInventoryStore() to reset, if none exists + // This is to avoid triggering the listener via ensureCustomData()->autoEquip()->fireEquipmentChanged() + // all from within this destructor. ouch! + && mPtr.getRefData().getCustomData()) mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); } diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 8ad2ea321..2808187a1 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -50,7 +50,7 @@ namespace MWRender maxY += 1; } - ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index e6f4a04ad..4aa5a188e 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -10,7 +10,7 @@ namespace MWRender class TerrainStorage : public ESMTerrain::Storage { private: - virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index f0cb8a967..c4228884c 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -1,4 +1,3 @@ - #include "aiextensions.hpp" #include diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index c43cdf565..07a8a300a 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -1,4 +1,3 @@ - #include "animationextensions.hpp" #include diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 3ff75eeae..083f463bc 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -1,4 +1,3 @@ - #include "compilercontext.hpp" #include "../mwworld/esmstore.hpp" diff --git a/apps/openmw/mwscript/consoleextensions.cpp b/apps/openmw/mwscript/consoleextensions.cpp index 30956d429..d2e07d3e1 100644 --- a/apps/openmw/mwscript/consoleextensions.cpp +++ b/apps/openmw/mwscript/consoleextensions.cpp @@ -1,4 +1,3 @@ - #include "consoleextensions.hpp" #include diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 70475d8e2..c456032a4 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -1,4 +1,3 @@ - #include "containerextensions.hpp" #include diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 904e0ee85..626fafb5a 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -1,4 +1,3 @@ - #include "controlextensions.hpp" #include diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 8b6805264..c305fb81f 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -1,4 +1,3 @@ - #include "dialogueextensions.hpp" #include diff --git a/apps/openmw/mwscript/extensions.cpp b/apps/openmw/mwscript/extensions.cpp index 2170ba4fb..12bf3413a 100644 --- a/apps/openmw/mwscript/extensions.cpp +++ b/apps/openmw/mwscript/extensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index a6ad2cc11..e0c78fc6d 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -1,4 +1,3 @@ - #include "globalscripts.hpp" #include diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 40c555f50..f48360c04 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -1,4 +1,3 @@ - #include "guiextensions.hpp" #include diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index df675aebb..b0d4d3f2d 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -1,4 +1,3 @@ - #include "interpretercontext.hpp" #include diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 38106340a..5941cba0d 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1,4 +1,3 @@ - #include "miscextensions.hpp" #include diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 5f755e542..084beb812 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -1,4 +1,3 @@ - #include "scriptmanagerimp.hpp" #include diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index d28d01b63..e0fccd16d 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -1,4 +1,3 @@ - #include "skyextensions.hpp" #include diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 606de7aa0..a9896d203 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/apps/openmw/mwscript/userextensions.cpp b/apps/openmw/mwscript/userextensions.cpp index 538133c65..165a93062 100644 --- a/apps/openmw/mwscript/userextensions.cpp +++ b/apps/openmw/mwscript/userextensions.cpp @@ -1,4 +1,3 @@ - #include "userextensions.hpp" #include diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index f190565da..733ac1171 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -1,4 +1,3 @@ - #include "character.hpp" #include @@ -29,9 +28,6 @@ void MWState::Character::addSlot (const boost::filesystem::path& path, const std ESM::ESMReader reader; reader.open (slot.mPath.string()); - if (reader.getFormat()>ESM::Header::CurrentFormat) - return; // format is too new -> ignore - if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 70e9f0925..22192c355 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -1,4 +1,3 @@ - #include "charactermanager.hpp" #include diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 0883bc63b..3e8bc4d23 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -1,4 +1,3 @@ - #include "statemanagerimp.hpp" #include @@ -202,7 +201,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot ++iter) writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 - writer.setFormat (ESM::Header::CurrentFormat); + writer.setFormat (ESM::SavedGame::sCurrentFormat); // all unused writer.setVersion(0); @@ -311,8 +310,6 @@ void MWState::StateManager::loadGame(const std::string& filepath) // have to peek into the save file to get the player name ESM::ESMReader reader; reader.open (filepath); - if (reader.getFormat()>ESM::Header::CurrentFormat) - return; // format is too new -> ignore if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore reader.getRecHeader(); @@ -334,6 +331,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str ESM::ESMReader reader; reader.open (filepath); + if (reader.getFormat() > ESM::SavedGame::sCurrentFormat) + throw std::runtime_error("This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file."); + std::map contentFileMap = buildContentFileIndexMap (reader); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp index 5e1fb41a6..fb2059de9 100644 --- a/apps/openmw/mwworld/action.cpp +++ b/apps/openmw/mwworld/action.cpp @@ -1,4 +1,3 @@ - #include "action.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index bfd64c85d..00c9628ce 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -1,4 +1,3 @@ - #include "actionapply.hpp" #include "class.hpp" diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 660915523..7ca7dcbfd 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -1,4 +1,3 @@ - #include "actioneat.hpp" #include diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index 8578995ae..454cc09f1 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -1,4 +1,3 @@ - #ifndef GAME_MWWORLD_ACTIONOPEN_H #define GAME_MWWORLD_ACTIONOPEN_H diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 269d941dc..4e6135764 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -1,4 +1,3 @@ - #include "actiontake.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 905497f85..051380ff5 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -1,4 +1,3 @@ - #include "actiontalk.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 8bbb08008..fccd176a8 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -25,23 +25,26 @@ namespace namespace MWWorld { ActionTeleport::ActionTeleport (const std::string& cellName, - const ESM::Position& position) - : Action (true), mCellName (cellName), mPosition (position) + const ESM::Position& position, bool teleportFollowers) + : Action (true), mCellName (cellName), mPosition (position), mTeleportFollowers(teleportFollowers) { } void ActionTeleport::executeImp (const Ptr& actor) { - //find any NPC that is following the actor and teleport him too - std::set followers; - getFollowers(actor, followers); - for(std::set::iterator it = followers.begin();it != followers.end();++it) + if (mTeleportFollowers) { - MWWorld::Ptr follower = *it; - if (Ogre::Vector3(follower.getRefData().getPosition().pos).squaredDistance( - Ogre::Vector3( actor.getRefData().getPosition().pos)) - <= 800*800) - teleport(*it); + //find any NPC that is following the actor and teleport him too + std::set followers; + getFollowers(actor, followers); + for(std::set::iterator it = followers.begin();it != followers.end();++it) + { + MWWorld::Ptr follower = *it; + if (Ogre::Vector3(follower.getRefData().getPosition().pos).squaredDistance( + Ogre::Vector3( actor.getRefData().getPosition().pos)) + <= 800*800) + teleport(*it); + } } teleport(actor); diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp index 9ca664de8..6191ee9f6 100644 --- a/apps/openmw/mwworld/actionteleport.hpp +++ b/apps/openmw/mwworld/actionteleport.hpp @@ -13,6 +13,7 @@ namespace MWWorld { std::string mCellName; ESM::Position mPosition; + bool mTeleportFollowers; /// Teleports this actor and also teleports anyone following that actor. virtual void executeImp (const Ptr& actor); @@ -22,8 +23,9 @@ namespace MWWorld public: - ActionTeleport (const std::string& cellName, const ESM::Position& position); + ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers); ///< If cellName is empty, an exterior cell is assumed. + /// @param teleportFollowers Whether to teleport any following actors of the target actor as well. }; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6fa9ba9b6..98d30ad24 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -1,4 +1,3 @@ - #include "class.hpp" #include diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d4aadc6c7..bf3732a3a 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -1,4 +1,3 @@ - #include "containerstore.hpp" #include diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index e7cb04590..dcd7924a2 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -1,4 +1,3 @@ - #include "globals.hpp" #include diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index a2e445d58..5b0c2311a 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -1,4 +1,3 @@ - #include "inventorystore.hpp" #include diff --git a/apps/openmw/mwworld/mwstore.cpp b/apps/openmw/mwworld/mwstore.cpp new file mode 100644 index 000000000..bdc61033e --- /dev/null +++ b/apps/openmw/mwworld/mwstore.cpp @@ -0,0 +1,37 @@ +#include "mwstore.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "esmstore.hpp" + +namespace MWWorld +{ + MWStore::MWStore() + : mGmst(MWBase::Environment::get().getWorld()->getStore().get()), + mSpells(MWBase::Environment::get().getWorld()->getStore().get()) + { } + + MWStore::~MWStore() + { } + + int MWStore::findGmstInt(const std::string& name) const { return mGmst.find(name)->getInt(); } + + float MWStore::findGmstFloat(const std::string& name) const { return mGmst.find(name)->getFloat(); } + + const ESM::Skill *MWStore::findSkill(int index) const + { + return MWBase::Environment::get().getWorld()->getStore().get().find(index); + } + + const ESM::MagicEffect* MWStore::findMagicEffect(int id) const + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id); + } + + void MWStore::getSpells(std::vector& spells) + { + for (Store::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) + spells.push_back(const_cast(&*iter)); + } +} diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp new file mode 100644 index 000000000..b833f1503 --- /dev/null +++ b/apps/openmw/mwworld/mwstore.hpp @@ -0,0 +1,34 @@ +#ifndef GAME_MWWORLD_MWSTORE_H +#define GAME_MWWORLD_MWSTORE_H + +#include + +#include + +#include "store.hpp" + +namespace MWWorld +{ + class MWStore : public AutoCalc::StoreCommon + { + const MWWorld::Store& mGmst; + const MWWorld::Store &mSpells; + + public: + + MWStore(); + ~MWStore(); + + virtual int findGmstInt(const std::string& name) const; + + virtual float findGmstFloat(const std::string& name) const; + + virtual const ESM::Skill *findSkill(int index) const; + + virtual const ESM::MagicEffect* findMagicEffect(int id) const; + + virtual void getSpells(std::vector& spells); + }; +} + +#endif // GAME_MWWORLD_MWSTORE_H diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index bec4c6db3..50212afea 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -660,7 +660,7 @@ namespace MWWorld return MovementSolver::traceDown(ptr, mEngine, maxHeight); } - void PhysicsSystem::addHeightField (float* heights, + void PhysicsSystem::addHeightField (const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) { diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index c1046aacb..21d712109 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -42,7 +42,7 @@ namespace MWWorld void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - void addHeightField (float* heights, + void addHeightField (const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 0b81532e1..795266a33 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -49,6 +49,55 @@ namespace MWWorld mPlayer.mData.setPosition(playerPos); } + void Player::saveSkillsAttributes() + { + MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); + for (int i=0; i& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); + for(size_t i = 0;i < ESM::Attribute::Length;++i) + { + // Oh, Bethesda. It's "Intelligence". + std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") : + ESM::Attribute::sAttributeNames[i]); + + MWMechanics::AttributeValue value = stats.getAttribute(i); + value.setBase(int(gmst.find(name)->getFloat())); + stats.setAttribute(i, value); + } + + for(size_t i = 0;i < ESM::Skill::Length;i++) + { + // Acrobatics is set separately for some reason. + if(i == ESM::Skill::Acrobatics) + continue; + + // "Mercantile"! >_< + std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : + ESM::Skill::sSkillNames[i]); + + MWMechanics::SkillValue value = stats.getSkill(i); + value.setBase(int(gmst.find(name)->getFloat())); + stats.setSkill(i, value); + } + } + void Player::set(const ESM::NPC *player) { mPlayer.mBase = player; @@ -222,6 +271,11 @@ namespace MWWorld player.mAutoMove = mAutoMove ? 1 : 0; + for (int i=0; i +#include + #include namespace ESM @@ -50,10 +55,18 @@ namespace MWWorld int mCurrentCrimeId; // the id assigned witnesses int mPaidCrimeId; // the last id paid off (0 bounty) + // Saved skills and attributes prior to becoming a werewolf + MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; + MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; + public: Player(const ESM::NPC *player, const MWBase::World& world); + void saveSkillsAttributes(); + void restoreSkillsAttributes(); + void setWerewolfSkillsAttributes(); + // For mark/recall magic effects void markPosition (CellStore* markedCell, ESM::Position markedPosition); void getMarkedPosition (CellStore*& markedCell, ESM::Position& markedPosition) const; diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 1cf22744a..4d74c3c58 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -1,4 +1,3 @@ - #include "ptr.hpp" #include diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index ae985f857..15ac52811 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -1,4 +1,3 @@ - #include "refdata.hpp" #include diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f5a9b8960..c3daafb62 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -236,16 +236,10 @@ namespace MWWorld // Actually only VHGT is needed here, but we'll need the rest for rendering anyway. // Load everything now to reduce IO overhead. const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(flags)) - land->loadData(flags); - mPhysics->addHeightField ( - land->mLandData->mHeights, - cell->getCell()->getGridX(), - cell->getCell()->getGridY(), - 0, - worldsize / (verts-1), - verts) - ; + + const ESM::Land::LandData *data = land->getLandData (flags); + mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), + 0, worldsize / (verts-1), verts); } } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index d6aeeb51e..ab09782b1 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -188,7 +188,7 @@ namespace MWWorld const T *ptr = search(id); if (ptr == 0) { std::ostringstream msg; - msg << "Object '" << id << "' not found (const)"; + msg << T::getRecordType() << " '" << id << "' not found"; throw std::runtime_error(msg.str()); } return ptr; @@ -202,7 +202,7 @@ namespace MWWorld if(ptr == 0) { std::ostringstream msg; - msg << "Object starting with '"<second = scpt; } + template <> + inline void Store::load(ESM::ESMReader &esm, const std::string &id) + { + ESM::StartScript s; + s.load(esm); + s.mId = Misc::StringUtils::toLower(s.mId); + std::pair inserted = mStatic.insert(std::make_pair(s.mId, s)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = s; + } + template <> class Store : public StoreBase { @@ -976,7 +989,7 @@ namespace MWWorld const T *ptr = search(index); if (ptr == 0) { std::ostringstream msg; - msg << "Object with index " << index << " not found"; + msg << T::getRecordType() << " with index " << index << " not found"; throw std::runtime_error(msg.str()); } return ptr; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b1bac2f4b..b6d5c8e93 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,11 +1,15 @@ #include "worldimp.hpp" -#ifdef _WIN32 -#include +#if defined(_WIN32) && !defined(__MINGW32__) +# if (_MSC_VER < 1900) +# include +# else +# include +# endif #elif defined HAVE_UNORDERED_MAP -#include +# include #else -#include +# include #endif #include "../mwbase/scriptmanager.hpp" #include "../mwscript/globalscripts.hpp" @@ -283,7 +287,7 @@ namespace MWWorld if (mPlayer) { - mPlayer->clear(); + mPlayer->clear(); mPlayer->setCell(0); mPlayer->getPlayer().getRefData() = RefData(); mPlayer->set(mStore.get().find ("player")); @@ -2485,6 +2489,17 @@ namespace MWWorld if (npcStats.isWerewolf() == werewolf) return; + if (actor == getPlayerPtr()) + { + if (werewolf) + { + mPlayer->saveSkillsAttributes(); + mPlayer->setWerewolfSkillsAttributes(); + } + else + mPlayer->restoreSkillsAttributes(); + } + npcStats.setWerewolf(werewolf); // This is a bit dangerous. Equipped items other than WerewolfRobe may reference @@ -2919,7 +2934,7 @@ namespace MWWorld if ( !closestMarker.mCell->isExterior() ) cellName = closestMarker.mCell->getCell()->mName; - MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition()); + MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false); action.execute(ptr); } diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index b8cc3fda4..89438640c 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -81,7 +81,6 @@ endif (OPENMW_USE_UNSHIELD) source_group(wizard FILES ${WIZARD} ${WIZARD_HEADER}) -find_package(Qt4 REQUIRED) set(QT_USE_QTGUI 1) # Set some platform specific settings @@ -90,12 +89,17 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) -QT4_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) -QT4_WRAP_UI(UI_HDRS ${WIZARD_UI}) +if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) + QT4_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) + QT4_WRAP_UI(UI_HDRS ${WIZARD_UI}) +else() + QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) + QT5_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) + QT5_WRAP_UI(UI_HDRS ${WIZARD_UI}) +endif() - -include(${QT_USE_FILE}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if (OPENMW_USE_UNSHIELD) @@ -112,11 +116,24 @@ add_executable(openmw-wizard ) target_link_libraries(openmw-wizard - ${Boost_LIBRARIES} - ${QT_LIBRARIES} components ) +if (DESIRED_QT_VERSION MATCHES 4) + target_link_libraries(openmw-wizard + ${QT_QTGUI_LIBRARY} + ${QT_QTCORE_LIBRARY}) + + if (WIN32) + target_link_libraries(openmw-wizard ${QT_QTMAIN_LIBRARY}) + endif() +else() + qt5_use_modules(openmw-wizard Widgets Core) + if (WIN32) + target_link_libraries(Qt5::WinMain) + endif() +endif() + if (OPENMW_USE_UNSHIELD) target_link_libraries(openmw-wizard ${LIBUNSHIELD_LIBRARY}) endif() diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index e6a94118a..c861a4ac8 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -38,9 +38,6 @@ int main(int argc, char *argv[]) QDir::setCurrent(dir.absolutePath()); - // Support non-latin characters - QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); - Wizard::MainWizard wizard; wizard.show(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 11e090ed1..9daea2b71 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 5ea7b04ae..c1d3cfff1 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -24,7 +24,6 @@ namespace Wizard class UnshieldWorker : public QObject { Q_OBJECT - Q_ENUMS(Wizard::Component) public: UnshieldWorker(QObject *parent = 0); diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 40fa2373f..694cf3505 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -19,7 +19,46 @@ include(PreprocessorUtils) # ENDIF (MYGUI_LIBRARIES AND MYGUI_INCLUDE_DIRS) IF (WIN32) #Windows + MESSAGE(STATUS "Looking for MyGUI") + + IF(MINGW) + + FIND_PATH ( MYGUI_INCLUDE_DIRS MyGUI.h PATH_SUFFIXES MYGUI) + FIND_PATH ( MYGUI_PLATFORM_INCLUDE_DIRS MyGUI_OgrePlatform.h PATH_SUFFIXES MYGUI) + FIND_LIBRARY ( MYGUI_LIBRARIES_REL NAMES + libMyGUIEngine${CMAKE_SHARED_LIBRARY_SUFFIX} + libMyGUI.OgrePlatform${CMAKE_STATIC_LIBRARY_SUFFIX} + HINTS + ${MYGUI_LIB_DIR} + PATH_SUFFIXES "" release relwithdebinfo minsizerel ) + + FIND_LIBRARY ( MYGUI_LIBRARIES_DBG NAMES + libMyGUIEngine_d${CMAKE_SHARED_LIBRARY_SUFFIX} + libMyGUI.OgrePlatform_d${CMAKE_STATIC_LIBRARY_SUFFIX} + HINTS + ${MYGUI_LIB_DIR} + PATH_SUFFIXES "" debug ) + + FIND_LIBRARY ( MYGUI_PLATFORM_LIBRARIES_REL NAMES + libMyGUI.OgrePlatform${CMAKE_STATIC_LIBRARY_SUFFIX} + HINTS + ${MYGUI_LIB_DIR} + PATH_SUFFIXES "" release relwithdebinfo minsizerel ) + + FIND_LIBRARY ( MYGUI_PLATFORM_LIBRARIES_DBG NAMES + MyGUI.OgrePlatform_d${CMAKE_STATIC_LIBRARY_SUFFIX} + HINTS + ${MYGUI_LIB_DIR} + PATH_SUFFIXES "" debug ) + + make_library_set ( MYGUI_LIBRARIES ) + make_library_set ( MYGUI_PLATFORM_LIBRARIES ) + + MESSAGE ("${MYGUI_LIBRARIES}") + MESSAGE ("${MYGUI_PLATFORM_LIBRARIES}") + ENDIF(MINGW) + SET(MYGUISDK $ENV{MYGUI_HOME}) IF (MYGUISDK) findpkg_begin ( "MYGUI" ) @@ -143,11 +182,12 @@ IF (MYGUI_FOUND) IF (NOT MYGUI_FIND_QUIETLY) MESSAGE(STATUS "MyGUI version: ${MYGUI_VERSION}") ENDIF (NOT MYGUI_FIND_QUIETLY) - -ELSE (MYGUI_FOUND) - IF (MYGUI_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find MYGUI") - ENDIF (MYGUI_FIND_REQUIRED) ENDIF (MYGUI_FOUND) +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(MyGUI DEFAULT_MSG + MYGUI_INCLUDE_DIRS + FREETYPE_LIBRARIES + MYGUI_LIBRARIES) + CMAKE_POLICY(POP) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 38fcd88e3..b2dbd3ff6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -119,6 +119,10 @@ add_component_dir (fontloader fontloader ) +add_component_dir (autocalc + autocalc autocalcspell + ) + add_component_dir (version version ) @@ -126,29 +130,30 @@ add_component_dir (version set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) -find_package(Qt4 COMPONENTS QtCore QtGui) - -if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) - add_component_qt_dir (contentselector - model/modelitem model/esmfile - model/naturalsort model/contentmodel - model/loadordererror - view/combobox view/contentselector - ) - add_component_qt_dir (config - gamesettings - launchersettings - settingsbase - ) - - add_component_qt_dir (process - processinvoker +add_component_qt_dir (contentselector + model/modelitem model/esmfile + model/naturalsort model/contentmodel + model/loadordererror + view/combobox view/contentselector + ) +add_component_qt_dir (config + gamesettings + launchersettings + settingsbase ) - include(${QT_USE_FILE}) +add_component_qt_dir (process + processinvoker +) + +if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) -endif(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) + QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) +else() + QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) + QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) +endif() if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" AND NOT APPLE) @@ -161,15 +166,40 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components - ${Boost_LIBRARIES} + ${Boost_SYSTEM_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_WAVE_LIBRARY} ${OGRE_LIBRARIES} ${OENGINE_LIBRARY} + ${BULLET_LIBRARIES} ) +if (WIN32) + target_link_libraries(components + ${Boost_LOCALE_LIBRARY}) +endif() + +if (DESIRED_QT_VERSION MATCHES 4) + target_link_libraries(components + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY}) +else() + qt5_use_modules(components Widgets Core) +endif() + if (GIT_CHECKOUT) add_dependencies (components git-version) endif (GIT_CHECKOUT) +if (WIN32) + target_link_libraries(components shlwapi) + if(MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOGDI") + endif(MINGW) +endif() + # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(components ${CMAKE_THREAD_LIBS_INIT}) diff --git a/components/autocalc/autocalc.cpp b/components/autocalc/autocalc.cpp new file mode 100644 index 000000000..122d19763 --- /dev/null +++ b/components/autocalc/autocalc.cpp @@ -0,0 +1,208 @@ +#include "autocalc.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "autocalcspell.hpp" + +namespace +{ + int is_even(double d) + { + double int_part; + + modf(d / 2.0, &int_part); + return 2.0 * int_part == d; + } + + int round_ieee_754(double d) + { + double i = floor(d); + d -= i; + + if(d < 0.5) + return static_cast(i); + if(d > 0.5) + return static_cast(i) + 1; + if(is_even(i)) + return static_cast(i); + return static_cast(i) + 1; + } +} + +namespace AutoCalc +{ + void autoCalcAttributesImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store) + { + // race bonus + bool male = (npc->mFlags & ESM::NPC::Female) == 0; + + for (int i=0; imData.mAttributeValues[i]; + stats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); + } + + // class bonus + for (int i=0; i<2; ++i) + { + int attribute = class_->mData.mAttribute[i]; + if (attribute>=0 && attributefindSkill(j); + + if (skill->mData.mAttribute != attribute) + continue; + + // is this a minor or major skill? + float add=0.2f; + for (int k=0; k<5; ++k) + { + if (class_->mData.mSkills[k][0] == j) + add=0.5; + } + for (int k=0; k<5; ++k) + { + if (class_->mData.mSkills[k][1] == j) + add=1.0; + } + modifierSum += add; + } + stats.setAttribute(attribute, + std::min(round_ieee_754(stats.getBaseAttribute(attribute) + (level-1) * modifierSum), 100) ); + } + } + + /** + * @brief autoCalculateSkills + * + * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): + * + * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) + * + * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. + * + * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, + * zero for other Skills. + * + * and by adding class, race, specialization bonus. + */ + void autoCalcSkillsImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store) + { + for (int i = 0; i < 2; ++i) + { + int bonus = (i==0) ? 10 : 25; + + for (int i2 = 0; i2 < 5; ++i2) + { + int index = class_->mData.mSkills[i2][i]; + if (index >= 0 && index < ESM::Skill::Length) + { + stats.setBaseSkill (index, bonus); + } + } + } + + for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + { + float majorMultiplier = 0.1f; + float specMultiplier = 0.0f; + + int raceBonus = 0; + int specBonus = 0; + + for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) + { + if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) + { + raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; + break; + } + } + + for (int k = 0; k < 5; ++k) + { + // is this a minor or major skill? + if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) + { + majorMultiplier = 1.0f; + break; + } + } + + // is this skill in the same Specialization as the class? + const ESM::Skill* skill = store->findSkill(skillIndex); + if (skill->mData.mSpecialization == class_->mData.mSpecialization) + { + specMultiplier = 0.5f; + specBonus = 5; + } + + stats.setBaseSkill(skillIndex, + std::min( + round_ieee_754( + stats.getBaseSkill(skillIndex) + + 5 + + raceBonus + + specBonus + +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 + } + } + + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats) + { + // initial health + int strength = stats.getBaseAttribute(ESM::Attribute::Strength); + int endurance = stats.getBaseAttribute(ESM::Attribute::Endurance); + + int multiplier = 3; + + if (class_->mData.mSpecialization == ESM::Class::Combat) + multiplier += 2; + else if (class_->mData.mSpecialization == ESM::Class::Stealth) + multiplier += 1; + + if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance + || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) + multiplier += 1; + + return static_cast(floor(0.5f * (strength + endurance)) + multiplier * (level-1)); + } + + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store) + { + int skills[ESM::Skill::Length]; + for (int i=0; i spells = autoCalcNpcSpells(skills, attributes, race, store); + for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) + stats.addSpell(*it); + } + + StatsBase::StatsBase() {} + + StatsBase::~StatsBase() {} +} diff --git a/components/autocalc/autocalc.hpp b/components/autocalc/autocalc.hpp new file mode 100644 index 000000000..5cfe06b42 --- /dev/null +++ b/components/autocalc/autocalc.hpp @@ -0,0 +1,47 @@ +#ifndef COMPONENTS_AUTOCALC_AUTOCALC_H +#define COMPONENTS_AUTOCALC_AUTOCALC_H + +#include + +#include "store.hpp" + +namespace ESM +{ + struct NPC; + struct Race; + struct Class; +} + +namespace AutoCalc +{ + // wrapper class for sharing the autocalc code between OpenMW and OpenCS + class StatsBase + { + + public: + + StatsBase(); + virtual ~StatsBase(); + + virtual unsigned char getBaseAttribute(int index) const = 0; + + virtual void setAttribute(int index, unsigned char value) = 0; + + virtual void addSpell(const std::string& id) = 0; + + virtual unsigned char getBaseSkill(int index) const = 0; + + virtual void setBaseSkill(int index, unsigned char value) = 0; + }; + + void autoCalcAttributesImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store); + + void autoCalcSkillsImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store); + + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats); + + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store); +} +#endif // COMPONENTS_AUTOCALC_AUTOCALC_H diff --git a/components/autocalc/autocalcspell.cpp b/components/autocalc/autocalcspell.cpp new file mode 100644 index 000000000..78499a092 --- /dev/null +++ b/components/autocalc/autocalcspell.cpp @@ -0,0 +1,249 @@ +#include "autocalcspell.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "autocalc.hpp" + +namespace AutoCalc +{ + + struct SchoolCaps + { + int mCount; + int mLimit; + bool mReachedLimit; + int mMinCost; + std::string mWeakestSpell; + }; + + std::vector autoCalcNpcSpells(const int *actorSkills, + const int *actorAttributes, const ESM::Race* race, StoreCommon *store) + { + static const float fNPCbaseMagickaMult = store->findGmstFloat("fNPCbaseMagickaMult"); + float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + static int iAutoSpellSchoolMax[6]; + static bool init = false; + if (!init) + { + for (int i=0; i<6; ++i) + { + const std::string& gmstName = "iAutoSpell" + schools[i] + "Max"; + iAutoSpellSchoolMax[i] = store->findGmstInt(gmstName); + } + init = true; + } + + std::map schoolCaps; + for (int i=0; i<6; ++i) + { + SchoolCaps caps; + caps.mCount = 0; + caps.mLimit = iAutoSpellSchoolMax[i]; + caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0; + caps.mMinCost = INT_MAX; + caps.mWeakestSpell.clear(); + schoolCaps[i] = caps; + } + + std::vector selectedSpells; + std::vector spells; + store->getSpells(spells); + + // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the + // Store must preserve the record ordering as it was in the content files. + for (std::vector::const_iterator iter = spells.begin(); iter != spells.end(); ++iter) + { + ESM::Spell* spell = *iter; + + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc)) + continue; + static const int iAutoSpellTimesCanCast = store->findGmstInt("iAutoSpellTimesCanCast"); + if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost) + continue; + + if (race && race->mPowers.exists(spell->mId)) + continue; + + if (!attrSkillCheck(spell, actorSkills, actorAttributes, store)) + continue; + + int school; + float skillTerm; + calcWeakestSchool(spell, actorSkills, school, skillTerm, store); + assert(school >= 0 && school < 6); + SchoolCaps& cap = schoolCaps[school]; + + if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost) + continue; + + static const float fAutoSpellChance = store->findGmstFloat("fAutoSpellChance"); + if (calcAutoCastChance(spell, actorSkills, actorAttributes, school, store) < fAutoSpellChance) + continue; + + selectedSpells.push_back(spell->mId); + + if (cap.mReachedLimit) + { + std::vector::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); + if (found != selectedSpells.end()) + selectedSpells.erase(found); + + cap.mMinCost = INT_MAX; + for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + { + std::vector::const_iterator it = spells.begin(); + for (; it != spells.end(); ++it) + { + if ((*it)->mId == *weakIt) + break; + } + + if (it == spells.end()) + continue; + + const ESM::Spell* testSpell = *it; + + //int testSchool; + //float dummySkillTerm; + //calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm); + + // Note: if there are multiple spells with the same cost, we pick the first one we found. + // So the algorithm depends on the iteration order of the outer loop. + if ( + // There is a huge bug here. It is not checked that weakestSpell is of the correct school. + // As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school + // already erased it, and so the number of spells would often exceed the sum of limits. + // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested. + //testSchool == school && + testSpell->mData.mCost < cap.mMinCost) + { + cap.mMinCost = testSpell->mData.mCost; + cap.mWeakestSpell = testSpell->mId; + } + } + } + else + { + cap.mCount += 1; + if (cap.mCount == cap.mLimit) + cap.mReachedLimit = true; + + if (spell->mData.mCost < cap.mMinCost) + { + cap.mWeakestSpell = spell->mId; + cap.mMinCost = spell->mData.mCost; + } + } + } + + return selectedSpells; + } + + bool attrSkillCheck (const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, StoreCommon *store) + { + const std::vector& effects = spell->mEffects.mList; + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = store->findMagicEffect(effectIt->mEffectID); + static const int iAutoSpellAttSkillMin = store->findGmstInt("iAutoSpellAttSkillMin"); + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) + { + assert (effectIt->mSkill >= 0 && effectIt->mSkill < ESM::Skill::Length); + if (actorSkills[effectIt->mSkill] < iAutoSpellAttSkillMin) + return false; + } + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) + { + assert (effectIt->mAttribute >= 0 && effectIt->mAttribute < ESM::Attribute::Length); + if (actorAttributes[effectIt->mAttribute] < iAutoSpellAttSkillMin) + return false; + } + } + + return true; + } + + ESM::Skill::SkillEnum mapSchoolToSkill(int school) + { + std::map schoolSkillMap; // maps spell school to skill id + schoolSkillMap[0] = ESM::Skill::Alteration; + schoolSkillMap[1] = ESM::Skill::Conjuration; + schoolSkillMap[3] = ESM::Skill::Illusion; + schoolSkillMap[2] = ESM::Skill::Destruction; + schoolSkillMap[4] = ESM::Skill::Mysticism; + schoolSkillMap[5] = ESM::Skill::Restoration; + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); + return schoolSkillMap[school]; + } + + void calcWeakestSchool (const ESM::Spell* spell, + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store) + { + float minChance = FLT_MAX; + + const ESM::EffectList& effects = spell->mEffects; + for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + { + const ESM::ENAMstruct& effect = *it; + float x = static_cast(effect.mDuration); + + const ESM::MagicEffect* magicEffect = store->findMagicEffect(effect.mEffectID); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) + x = std::max(1.f, x); + + x *= 0.1f * magicEffect->mData.mBaseCost; + x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); + x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mRange == ESM::RT_Target) + x *= 1.5f; + + static const float fEffectCostMult = store->findGmstFloat("fEffectCostMult"); + x *= fEffectCostMult; + + float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; + if (s - x < minChance) + { + minChance = s - x; + effectiveSchool = magicEffect->mData.mSchool; + skillTerm = s; + } + } + } + + float calcAutoCastChance(const ESM::Spell *spell, + const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreCommon *store) + { + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100.f; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100.f; + + float skillTerm = 0; + if (effectiveSchool != -1) + skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)]; + else + calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm, store); // Note effectiveSchool is unused after this + + float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; + return castChance; + } +} diff --git a/components/autocalc/autocalcspell.hpp b/components/autocalc/autocalcspell.hpp new file mode 100644 index 000000000..769e60248 --- /dev/null +++ b/components/autocalc/autocalcspell.hpp @@ -0,0 +1,39 @@ +#ifndef COMPONENTS_AUTOCALC_AUTOCALCSPELL_H +#define COMPONENTS_AUTOCALC_AUTOCALCSPELL_H + +#include + +#include + +namespace ESM +{ + struct Spell; + struct Race; +} + +namespace AutoCalc +{ + +class StoreCommon; + +/// Contains algorithm for calculating an NPC's spells based on stats + +std::vector autoCalcNpcSpells(const int* actorSkills, + const int* actorAttributes, const ESM::Race* race, StoreCommon *store); + +// Helpers + +bool attrSkillCheck (const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, StoreCommon *store); + +ESM::Skill::SkillEnum mapSchoolToSkill(int school); + +void calcWeakestSchool(const ESM::Spell* spell, + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store); + +float calcAutoCastChance(const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreCommon *store); + +} + +#endif // COMPONENTS_AUTOCALC_AUTOCALCSPELL_H diff --git a/components/autocalc/store.hpp b/components/autocalc/store.hpp new file mode 100644 index 000000000..9a798a5ce --- /dev/null +++ b/components/autocalc/store.hpp @@ -0,0 +1,35 @@ +#ifndef COMPONENTS_AUTOCALC_STORE_H +#define COMPONENTS_AUTOCALC_STORE_H + +#include +#include + +namespace ESM +{ + struct Spell; + struct Skill; + struct MagicEffect; +} + +namespace AutoCalc +{ + // interface class for sharing the autocalc component between OpenMW and OpenCS + class StoreCommon + { + + public: + StoreCommon() {} + virtual ~StoreCommon() {} + + virtual int findGmstInt(const std::string& gmst) const = 0; + + virtual float findGmstFloat(const std::string& gmst) const = 0; + + virtual const ESM::Skill *findSkill(int index) const = 0; + + virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; + + virtual void getSpells(std::vector& spells) = 0; + }; +} +#endif // COMPONENTS_AUTOCALC_STORE_H diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 4f656f9c4..35fbf62a8 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -30,6 +30,17 @@ #include #include #include +/* + * This test for ogre version is not realy correct, because the change happened since + * commit d5e05e9d97f47bce40aa41a2bf31c2b6c3fde5f3 (2014-02-24) on the default branch + * rather than during an ogre version change event. However it should be good enough. + */ +#if OGRE_VERSION < 0x010a00 +#define OGRE_CONST const +#else +#define OGRE_CONST +#endif + #include "bsa_file.hpp" #include "../files/constrainedfiledatastream.hpp" @@ -104,7 +115,7 @@ public: void load() {} void unload() {} - DataStreamPtr open(const String& filename, bool readonly = true) const + virtual DataStreamPtr open(const String& filename, bool readonly = true) OGRE_CONST { index::const_iterator i = lookup_filename (filename); @@ -149,8 +160,8 @@ public: time_t getModifiedTime(const String&) { return 0; } - FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) const + virtual FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) OGRE_CONST { std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); @@ -216,7 +227,7 @@ public: void load() {} void unload() {} - DataStreamPtr open(const String& filename, bool readonly = true) const + virtual DataStreamPtr open(const String& filename, bool readonly = true) OGRE_CONST { // Get a non-const reference to arc. This is a hack and it's all // OGRE's fault. You should NOT expect an open() command not to @@ -262,8 +273,8 @@ public: return ptr; } - FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) const + virtual FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) OGRE_CONST { std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp index aefe6d16d..b202467db 100644 --- a/components/compiler/controlparser.cpp +++ b/components/compiler/controlparser.cpp @@ -1,4 +1,3 @@ - #include "controlparser.hpp" #include diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp index 7961b8f41..ffac252d5 100644 --- a/components/compiler/declarationparser.cpp +++ b/components/compiler/declarationparser.cpp @@ -1,4 +1,3 @@ - #include "declarationparser.hpp" #include diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp index 6028968bb..da114fb3d 100644 --- a/components/compiler/discardparser.cpp +++ b/components/compiler/discardparser.cpp @@ -1,4 +1,3 @@ - #include "discardparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index bcd30ef2d..a987a86da 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -1,4 +1,3 @@ - #include "errorhandler.hpp" namespace Compiler diff --git a/components/compiler/errorhandler.hpp b/components/compiler/errorhandler.hpp index c92e7bb8d..ea904e385 100644 --- a/components/compiler/errorhandler.hpp +++ b/components/compiler/errorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_ERRORHANDLER_H_INCLUDED #define COMPILER_ERRORHANDLER_H_INCLUDED diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index dc36b58d8..c0375b436 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -1,4 +1,3 @@ - #include "exprparser.hpp" #include @@ -353,7 +352,10 @@ namespace Compiler if (extensions->isInstruction (keyword, argumentType, hasExplicit)) { // pretend this is not a keyword - return parseName (loc.mLiteral, loc, scanner); + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); } } @@ -650,6 +652,13 @@ namespace Compiler return true; } + if (code ==Scanner::S_plus && mNextOperand) + { + // Also unary, but +, just ignore it + mTokenLoc = loc; + return true; + } + if (code==Scanner::S_open) { if (mNextOperand) diff --git a/components/compiler/extensions.cpp b/components/compiler/extensions.cpp index c2b11c615..dbb953e20 100644 --- a/components/compiler/extensions.cpp +++ b/components/compiler/extensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index c56ee2ffb..a16e653c3 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -531,7 +531,7 @@ namespace Compiler extensions.registerInstruction("placeitemcell","ccffff",opcodePlaceItemCell); extensions.registerInstruction("placeitem","cffff",opcodePlaceItem); extensions.registerInstruction("placeatpc","clfl",opcodePlaceAtPc); - extensions.registerInstruction("placeatme","clfl",opcodePlaceAtMe,opcodePlaceAtMeExplicit); + extensions.registerInstruction("placeatme","clflX",opcodePlaceAtMe,opcodePlaceAtMeExplicit); extensions.registerInstruction("modscale","f",opcodeModScale,opcodeModScaleExplicit); extensions.registerInstruction("rotate","cf",opcodeRotate,opcodeRotateExplicit); extensions.registerInstruction("rotateworld","cf",opcodeRotateWorld,opcodeRotateWorldExplicit); diff --git a/components/compiler/extensions0.hpp b/components/compiler/extensions0.hpp index 83f3a44fa..e9ce6a6d6 100644 --- a/components/compiler/extensions0.hpp +++ b/components/compiler/extensions0.hpp @@ -44,7 +44,7 @@ namespace Compiler namespace Gui { - void registerExtensions (Extensions& extensions); + void registerExtensions (Extensions& extensions); } namespace Misc diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 423841ac3..e90e9a8f6 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -65,7 +65,6 @@ namespace Compiler if (mState==BeginState && keyword==Scanner::K_begin) { mState = NameState; - scanner.allowNameStartingwithDigit(); return true; } @@ -112,7 +111,6 @@ namespace Compiler scanner.scan (mScriptParser); mState = EndNameState; - scanner.allowNameStartingwithDigit(); return true; } diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index ead0c7290..7e6437e20 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -1,4 +1,3 @@ - #include "generator.hpp" #include diff --git a/components/compiler/junkparser.cpp b/components/compiler/junkparser.cpp index cfa94044e..7608e9bab 100644 --- a/components/compiler/junkparser.cpp +++ b/components/compiler/junkparser.cpp @@ -1,4 +1,3 @@ - #include "junkparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index a71672916..c1622c3e0 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -1,4 +1,3 @@ - #include "lineparser.hpp" #include @@ -222,6 +221,23 @@ namespace Compiler bool LineParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { + if (mState==MessageState || mState==MessageCommaState) + { + if (const Extensions *extensions = getContext().getExtensions()) + { + std::string argumentType; // ignored + bool hasExplicit = false; // ignored + if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + { + // pretend this is not a keyword + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); + } + } + } + if (mState==SetMemberVarState) { mMemberName = loc.mLiteral; @@ -539,7 +555,7 @@ namespace Compiler } if (mAllowExpression && mState==BeginState && - (code==Scanner::S_open || code==Scanner::S_minus)) + (code==Scanner::S_open || code==Scanner::S_minus || code==Scanner::S_plus)) { scanner.putbackSpecial (code, loc); parseExpression (scanner, loc); diff --git a/components/compiler/literals.cpp b/components/compiler/literals.cpp index 626b03afb..ee2c4d345 100644 --- a/components/compiler/literals.cpp +++ b/components/compiler/literals.cpp @@ -1,4 +1,3 @@ - #include "literals.hpp" #include diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index 60a5704bf..768fc077c 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -1,4 +1,3 @@ - #include "locals.hpp" #include diff --git a/components/compiler/nullerrorhandler.cpp b/components/compiler/nullerrorhandler.cpp index ee2884705..a0db53a00 100644 --- a/components/compiler/nullerrorhandler.cpp +++ b/components/compiler/nullerrorhandler.cpp @@ -1,4 +1,3 @@ - #include "nullerrorhandler.hpp" void Compiler::NullErrorHandler::report (const std::string& message, const TokenLoc& loc, Type type) {} diff --git a/components/compiler/nullerrorhandler.hpp b/components/compiler/nullerrorhandler.hpp index bb4db99a2..3dcff9250 100644 --- a/components/compiler/nullerrorhandler.hpp +++ b/components/compiler/nullerrorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_NULLERRORHANDLER_H_INCLUDED #define COMPILER_NULLERRORHANDLER_H_INCLUDED diff --git a/components/compiler/output.cpp b/components/compiler/output.cpp index 46e04b8dc..785b2ce84 100644 --- a/components/compiler/output.cpp +++ b/components/compiler/output.cpp @@ -1,4 +1,3 @@ - #include "output.hpp" #include diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index 0f442c350..fe019718a 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -1,4 +1,3 @@ - #include "parser.hpp" #include diff --git a/components/compiler/quickfileparser.cpp b/components/compiler/quickfileparser.cpp index 4e9f76e13..53aaf96e5 100644 --- a/components/compiler/quickfileparser.cpp +++ b/components/compiler/quickfileparser.cpp @@ -1,4 +1,3 @@ - #include "quickfileparser.hpp" #include "skipparser.hpp" diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 83d435962..3c5bb7747 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -1,4 +1,3 @@ - #include "scanner.hpp" #include @@ -48,9 +47,6 @@ namespace Compiler bool Scanner::scanToken (Parser& parser) { - bool allowDigit = mNameStartingWithDigit; - mNameStartingWithDigit = false; - switch (mPutback) { case Putback_Special: @@ -115,7 +111,6 @@ namespace Compiler else if (isWhitespace (c)) { mLoc.mLiteral.clear(); - mNameStartingWithDigit = allowDigit; return true; } else if (c==':') @@ -124,7 +119,7 @@ namespace Compiler mLoc.mLiteral.clear(); return true; } - else if (std::isalpha (c) || c=='_' || c=='"' || (allowDigit && std::isdigit (c))) + else if (std::isalpha (c) || c=='_' || c=='"') { bool cont = false; @@ -180,10 +175,18 @@ namespace Compiler { value += c; } - else if (std::isalpha (c) || c=='_') - error = true; - else if (c=='.' && !error) + else if (c!='-' && isStringCharacter (c)) { + error = true; + value += c; + } + else if (c=='.') + { + if (error) + { + putback (c); + break; + } return scanFloat (value, parser, cont); } else @@ -194,7 +197,15 @@ namespace Compiler } if (error) - return false; + { + /// workaround that allows names to begin with digits + /// \todo disable + TokenLoc loc (mLoc); + mLoc.mLiteral.clear(); + cont = parser.parseName (value, loc, *this); + return true; +// return false; + } TokenLoc loc (mLoc); mLoc.mLiteral.clear(); @@ -281,8 +292,10 @@ namespace Compiler if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') { name = name.substr (1, name.size()-2); - cont = parser.parseName (name, loc, *this); - return true; +// allow keywords enclosed in "" +/// \todo optionally disable +// cont = parser.parseName (name, loc, *this); +// return true; } int i = 0; @@ -554,8 +567,7 @@ namespace Compiler Scanner::Scanner (ErrorHandler& errorHandler, std::istream& inputStream, const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), - mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), - mNameStartingWithDigit (false) + mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0) { } @@ -607,9 +619,4 @@ namespace Compiler if (mExtensions) mExtensions->listKeywords (keywords); } - - void Scanner::allowNameStartingwithDigit() - { - mNameStartingWithDigit = true; - } } diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index ed01bbe44..847895978 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -124,9 +124,6 @@ namespace Compiler void listKeywords (std::vector& keywords); ///< Append all known keywords to \a kaywords. - - /// For the next token allow names to start with a digit. - void allowNameStartingwithDigit(); }; } diff --git a/components/compiler/scriptparser.cpp b/components/compiler/scriptparser.cpp index ea11be5f0..a3bf23288 100644 --- a/components/compiler/scriptparser.cpp +++ b/components/compiler/scriptparser.cpp @@ -1,4 +1,3 @@ - #include "scriptparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/skipparser.cpp b/components/compiler/skipparser.cpp index c7cb31f58..3e704253d 100644 --- a/components/compiler/skipparser.cpp +++ b/components/compiler/skipparser.cpp @@ -1,4 +1,3 @@ - #include "skipparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/streamerrorhandler.cpp b/components/compiler/streamerrorhandler.cpp index fc1a05943..9ca8aa74b 100644 --- a/components/compiler/streamerrorhandler.cpp +++ b/components/compiler/streamerrorhandler.cpp @@ -1,4 +1,3 @@ - #include "streamerrorhandler.hpp" #include "tokenloc.hpp" diff --git a/components/compiler/streamerrorhandler.hpp b/components/compiler/streamerrorhandler.hpp index 96e02b588..85de1833a 100644 --- a/components/compiler/streamerrorhandler.hpp +++ b/components/compiler/streamerrorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_STREAMERRORHANDLER_H_INCLUDED #define COMPILER_STREAMERRORHANDLER_H_INCLUDED diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index a86c15794..f8798eccd 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -1,12 +1,14 @@ - #include "stringparser.hpp" #include #include +#include + #include "scanner.hpp" #include "generator.hpp" -#include +#include "context.hpp" +#include "extensions.hpp" namespace Compiler { @@ -33,6 +35,25 @@ namespace Compiler return Parser::parseName (name, loc, scanner); } + bool StringParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) + { + if (const Extensions *extensions = getContext().getExtensions()) + { + std::string argumentType; // ignored + bool hasExplicit = false; // ignored + if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + { + // pretend this is not a keyword + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); + } + } + + return Parser::parseKeyword (keyword, loc, scanner); + } + bool StringParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_comma && mState==StartState) diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp index 3859a2496..52469128f 100644 --- a/components/compiler/stringparser.hpp +++ b/components/compiler/stringparser.hpp @@ -32,6 +32,10 @@ namespace Compiler ///< Handle a name token. /// \return fetch another token? + virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner); + ///< Handle a keyword token. + /// \return fetch another token? + virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); ///< Handle a special character token. /// \return fetch another token? diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 0481235c7..10b0234d1 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -1,6 +1,7 @@ #include "gamesettings.hpp" #include "launchersettings.hpp" +#include #include #include #include @@ -173,6 +174,258 @@ bool Config::GameSettings::writeFile(QTextStream &stream) return true; } +bool Config::GameSettings::isOrderedLine(const QString& line) const +{ + return line.contains(QRegExp("^\\s*fallback-archive\\s*=")) + || line.contains(QRegExp("^\\s*fallback\\s*=")) + || line.contains(QRegExp("^\\s*data\\s*=")) + || line.contains(QRegExp("^\\s*data-local\\s*=")) + || line.contains(QRegExp("^\\s*resources\\s*=")) + || line.contains(QRegExp("^\\s*content\\s*=")); +} + +// Policy: +// +// - Always ignore a line beginning with '#' or empty lines; added above a config +// entry. +// +// - If a line in file exists with matching key and first part of value (before ',', +// '\n', etc) also matches, then replace the line with that of mUserSettings. +// - else remove line +// +// - If there is no corresponding line in file, add at the end +// +// - Removed content items are saved as comments if the item had any comments. +// Content items prepended with '##' are considered previously removed. +// +bool Config::GameSettings::writeFileWithComments(QFile &file) +{ + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + // slurp + std::vector fileCopy; + QString line = stream.readLine(); + while (!line.isNull()) + { + fileCopy.push_back(line); + line = stream.readLine(); + } + stream.seek(0); + + // empty file, no comments to keep + if (fileCopy.empty()) + return writeFile(stream); + + // start + // | + // | +----------------------------------------------------------+ + // | | | + // v v | + // skip non-"ordered" lines (remove "ordered" lines) | + // | ^ | + // | | | + // | non-"ordered" line, write saved comments | + // | ^ | + // v | | + // blank or comment line, save in temp buffer <--------+ | + // | | | | + // | +------- comment line ------+ | + // v (special processing '##') | + // "ordered" line | + // | | + // v | + // save in a separate map of comments keyed by "ordered" line | + // | | + // +----------------------------------------------------------+ + // + // + QRegExp settingRegex("^([^=]+)\\s*=\\s*([^,]+)(.*)$"); + std::vector comments; + std::vector::iterator commentStart = fileCopy.end(); + std::map > commentsMap; + for (std::vector::iterator iter = fileCopy.begin(); iter != fileCopy.end(); ++iter) + { + if (isOrderedLine(*iter)) + { + // save in a separate map of comments keyed by "ordered" line + if (!comments.empty()) + { + if (settingRegex.indexIn(*iter) != -1) + { + commentsMap[settingRegex.cap(1)+"="+settingRegex.cap(2)] = comments; + comments.clear(); + commentStart = fileCopy.end(); + } + // else do nothing, malformed line + } + + *iter = QString(); // "ordered" lines to be removed later + } + else if ((*iter).isEmpty() || (*iter).contains(QRegExp("^\\s*#"))) + { + // comment line, save in temp buffer + if (comments.empty()) + commentStart = iter; + + // special removed content processing + if ((*iter).contains(QRegExp("^##content\\s*="))) + { + if (!comments.empty()) + { + commentsMap[*iter] = comments; + comments.clear(); + commentStart = fileCopy.end(); + } + } + else + comments.push_back(*iter); + + *iter = QString(); // assume to be deleted later + } + else + { + int index = settingRegex.indexIn(*iter); + + // blank or non-"ordered" line, write saved comments + if (!comments.empty() && index != -1 && settingRegex.captureCount() >= 2 && + mUserSettings.find(settingRegex.cap(1)) != mUserSettings.end()) + { + for (std::vector::const_iterator it = comments.begin(); + it != comments.end() && commentStart != fileCopy.end(); ++it) + { + *commentStart = *it; + ++commentStart; + } + comments.clear(); + commentStart = fileCopy.end(); + } + + // keep blank lines and non-"ordered" lines other than comments + + // look for a key in the line + if (index == -1 || settingRegex.captureCount() < 2) + { + // no key or first part of value found in line, replace with a null string which + // will be remved later + *iter = QString(); + comments.clear(); + commentStart = fileCopy.end(); + continue; + } + + // look for a matching key in user settings + *iter = QString(); // assume no match + QString key = settingRegex.cap(1); + QString keyVal = settingRegex.cap(1)+"="+settingRegex.cap(2); + QMap::const_iterator i = mUserSettings.find(key); + while (i != mUserSettings.end() && i.key() == key) + { + QString settingLine = i.key() + "=" + i.value(); + if (settingRegex.indexIn(settingLine) != -1) + { + if ((settingRegex.cap(1)+"="+settingRegex.cap(2)) == keyVal) + { + *iter = settingLine; + break; + } + } + ++i; + } + } + } + + // comments at top of file + for (std::vector::iterator iter = fileCopy.begin(); iter != fileCopy.end(); ++iter) + { + if ((*iter).isNull()) + continue; + + // Below is based on readFile() code, if that changes corresponding change may be + // required (for example duplicates may be inserted if the rules don't match) + if (/*(*iter).isEmpty() ||*/ (*iter).contains(QRegExp("^\\s*#"))) + { + stream << *iter << "\n"; + continue; + } + } + + // Iterate in reverse order to preserve insertion order + QString settingLine; + QMapIterator it(mUserSettings); + it.toBack(); + + while (it.hasPrevious()) + { + it.previous(); + + // Quote paths with spaces + if ((it.key() == QLatin1String("data") + || it.key() == QLatin1String("data-local") + || it.key() == QLatin1String("resources")) && it.value().contains(QChar(' '))) + { + QString stripped = it.value(); + stripped.remove(QChar('\"')); // Remove quotes + + settingLine = it.key() + "=\"" + stripped + "\""; + } + else + settingLine = it.key() + "=" + it.value(); + + if (settingRegex.indexIn(settingLine) != -1) + { + std::map >::iterator i = + commentsMap.find(settingRegex.cap(1)+"="+settingRegex.cap(2)); + + // check if previous removed content item with comments + if (i == commentsMap.end()) + i = commentsMap.find("##"+settingRegex.cap(1)+"="+settingRegex.cap(2)); + + if (i != commentsMap.end()) + { + std::vector cLines = i->second; + for (std::vector::const_iterator ci = cLines.begin(); ci != cLines.end(); ++ci) + stream << *ci << "\n"; + + commentsMap.erase(i); + } + } + + stream << settingLine << "\n"; + } + + // flush any removed settings + if (!commentsMap.empty()) + { + std::map >::const_iterator i = commentsMap.begin(); + for (; i != commentsMap.end(); ++i) + { + if (i->first.contains(QRegExp("^\\s*content\\s*="))) + { + std::vector cLines = i->second; + for (std::vector::const_iterator ci = cLines.begin(); ci != cLines.end(); ++ci) + stream << *ci << "\n"; + + // mark the content line entry for future preocessing + stream << "##" << i->first << "\n"; + + //commentsMap.erase(i); + } + } + } + + // flush any end comments + if (!comments.empty()) + { + for (std::vector::const_iterator ci = comments.begin(); ci != comments.end(); ++ci) + stream << *ci << "\n"; + } + + file.resize(file.pos()); + + return true; +} + bool Config::GameSettings::hasMaster() { bool result = false; diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index cc5033f35..992a3e565 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -66,6 +67,7 @@ namespace Config bool readUserFile(QTextStream &stream); bool writeFile(QTextStream &stream); + bool writeFileWithComments(QFile &file); void setContentList(const QStringList& fileNames); QStringList getContentList() const; @@ -81,6 +83,8 @@ namespace Config QString mDataLocal; static const char sContentKey[]; + + bool isOrderedLine(const QString& line) const; }; } #endif // GAMESETTINGS_HPP diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 62f6d9014..769afee37 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -30,17 +30,6 @@ ContentSelectorModel::ContentModel::~ContentModel() void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) { mEncoding = encoding; - if (encoding == QLatin1String("win1252")) - mCodec = QTextCodec::codecForName("windows-1252"); - - else if (encoding == QLatin1String("win1251")) - mCodec = QTextCodec::codecForName("windows-1251"); - - else if (encoding == QLatin1String("win1250")) - mCodec = QTextCodec::codecForName("windows-1250"); - - else - return; // This should never happen; } int ContentSelectorModel::ContentModel::columnCount(const QModelIndex &parent) const @@ -484,6 +473,13 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) sortFiles(); } +void ContentSelectorModel::ContentModel::clearFiles() +{ + beginRemoveRows(QModelIndex(), 0, mFiles.count()-1); + mFiles.clear(); + endRemoveRows(); +} + QStringList ContentSelectorModel::ContentModel::gameFiles() const { QStringList gameFiles; diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 658555852..bc785a276 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -44,6 +44,7 @@ namespace ContentSelectorModel bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); void addFiles(const QString &path); + void clearFiles(); QModelIndex indexFromItem(const EsmFile *item) const; const EsmFile *item(const QString &name) const; @@ -81,7 +82,6 @@ namespace ContentSelectorModel ContentFileList mFiles; QHash mCheckStates; QSet mPluginsWithLoadOrderError; - QTextCodec *mCodec; QString mEncoding; QIcon mWarningIcon; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 2fae8e74b..78aa20cd2 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -150,6 +150,11 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) mContentModel->uncheckAll(); } +void ContentSelectorView::ContentSelector::clearFiles() +{ + mContentModel->clearFiles(); +} + QString ContentSelectorView::ContentSelector::currentFile() const { QModelIndex currentIdx = ui.addonView->currentIndex(); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index e455807c9..4e9fcfb3c 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -29,6 +29,7 @@ namespace ContentSelectorView QString currentFile() const; void addFiles(const QString &path); + void clearFiles(); void setProfileContent (const QStringList &fileList); void clearCheckStates(); diff --git a/components/esm/cellid.cpp b/components/esm/cellid.cpp index 3c6e23ffd..5ac8c4cab 100644 --- a/components/esm/cellid.cpp +++ b/components/esm/cellid.cpp @@ -1,4 +1,3 @@ - #include "cellid.hpp" #include "esmreader.hpp" diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index c3b889df5..33ac4a91e 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -1,4 +1,3 @@ - #include "cellref.hpp" #include "esmreader.hpp" diff --git a/components/esm/cellstate.cpp b/components/esm/cellstate.cpp index 4df04d0e5..83b130dcd 100644 --- a/components/esm/cellstate.cpp +++ b/components/esm/cellstate.cpp @@ -1,4 +1,3 @@ - #include "cellstate.hpp" #include "esmreader.hpp" diff --git a/components/esm/containerstate.cpp b/components/esm/containerstate.cpp index 80ad5cbdc..301549d59 100644 --- a/components/esm/containerstate.cpp +++ b/components/esm/containerstate.cpp @@ -1,4 +1,3 @@ - #include "containerstate.hpp" void ESM::ContainerState::load (ESMReader &esm) diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp index c15becd98..bffa4e5e4 100644 --- a/components/esm/creaturestate.cpp +++ b/components/esm/creaturestate.cpp @@ -1,4 +1,3 @@ - #include "creaturestate.hpp" void ESM::CreatureState::load (ESMReader &esm) diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 6c05fac2a..9d605a6af 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -1,4 +1,3 @@ - #include "debugprofile.hpp" #include "esmreader.hpp" diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp index f302e36dc..2b1887e4e 100644 --- a/components/esm/dialoguestate.cpp +++ b/components/esm/dialoguestate.cpp @@ -1,4 +1,3 @@ - #include "dialoguestate.hpp" #include "esmreader.hpp" diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 2804f89d4..77ac0ae32 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -174,6 +174,17 @@ bool ESMReader::isNextSub(const char* name) return !mCtx.subCached; } +bool ESMReader::peekNextSub(const char *name) +{ + if (!mCtx.leftRec) + return false; + + getSubName(); + + mCtx.subCached = true; + return mCtx.subName == name; +} + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void ESMReader::getSubName() diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 2df2a86b3..4e92b7e5f 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -185,6 +185,8 @@ public: */ bool isNextSub(const char* name); + bool peekNextSub(const char* name); + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void getSubName(); diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index c64678e70..4d9999143 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -174,6 +174,15 @@ namespace ESM endRecord(name); } + void ESMWriter::writeFixedSizeString(const std::string &data, int size) + { + std::string string; + if (!data.empty()) + string = mEncoder ? mEncoder->getLegacyEnc(data) : data; + string.resize(size); + write(string.c_str(), string.size()); + } + void ESMWriter::writeHString(const std::string& data) { if (data.size() == 0) diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 30cec58b4..d11b3c940 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -120,6 +120,7 @@ public: void startSubRecord(const std::string& name); void endRecord(const std::string& name); void endRecord(uint32_t name); + void writeFixedSizeString(const std::string& data, int size); void writeHString(const std::string& data); void writeHCString(const std::string& data); void writeName(const std::string& data); diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index a80427bbe..5bc768f72 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -1,4 +1,3 @@ - #include "filter.hpp" #include "esmreader.hpp" diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp index 0129f8eb7..a42cdc230 100644 --- a/components/esm/globalscript.cpp +++ b/components/esm/globalscript.cpp @@ -1,4 +1,3 @@ - #include "globalscript.hpp" #include "esmreader.hpp" diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index 4eaaa9f9f..e7257ae53 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -1,4 +1,3 @@ - #include "inventorystate.hpp" #include "esmreader.hpp" diff --git a/components/esm/journalentry.cpp b/components/esm/journalentry.cpp index 445213de4..93011e581 100644 --- a/components/esm/journalentry.cpp +++ b/components/esm/journalentry.cpp @@ -1,4 +1,3 @@ - #include "journalentry.hpp" #include "esmreader.hpp" diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index 88f27de27..d9a55023b 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -12,6 +12,8 @@ class ESMWriter; struct Activator { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Activator"; } std::string mId, mName, mScript, mModel; diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index 141765aa8..b90a7c448 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -19,6 +19,9 @@ struct Potion { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Potion"; } + struct ALDTstruct { float mWeight; diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index adc8e071f..f18b04648 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct Apparatus { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Apparatus"; } enum AppaType { diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 356dfc1c5..54416fd31 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -66,6 +66,8 @@ struct PartReferenceList struct Armor { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Armor"; } enum Type { diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index 5e9869d24..c48c31305 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -12,6 +12,8 @@ class ESMWriter; struct BodyPart { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "BodyPart"; } enum MeshPart { diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index f96fbd709..6211b3e45 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct Book { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Book"; } struct BKDTstruct { diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 9f9435c8f..f91f91c97 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -14,6 +14,8 @@ class ESMWriter; struct BirthSign { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "BirthSign"; } std::string mId, mName, mDescription, mTexture; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 1aef97d9f..12fb8c068 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -56,6 +56,8 @@ typedef std::list CellRefTracker; struct Cell { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Cell"; } enum Flags { diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 3e489bb58..972b48e88 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct Class { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Class"; } enum AutoCalc { diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 50896622a..6945f224a 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct Clothing { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Clothing"; } enum Type { diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index 76c522d74..ab587f935 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -36,6 +36,8 @@ struct InventoryList struct Container { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Container"; } enum Flags { diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 50c47349c..fb235e6b3 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -96,7 +96,12 @@ namespace ESM { mInventory.save(esm); mSpells.save(esm); - if (mHasAI) { + if (mAiData.mHello != 0 + || mAiData.mFight != 0 + || mAiData.mFlee != 0 + || mAiData.mAlarm != 0 + || mAiData.mServices != 0) + { esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } mTransport.save(esm); @@ -115,7 +120,7 @@ namespace ESM { for (int i=0; i<6; ++i) mData.mAttack[i] = 0; mData.mGold = 0; mFlags = 0; - mScale = 0; + mScale = 1.f; mModel.clear(); mName.clear(); mScript.clear(); diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 1b02aa0ab..47e5954a5 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -22,6 +22,8 @@ class ESMWriter; struct Creature { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Creature"; } // Default is 0x48? enum Flags diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index d29948c63..58598d353 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -21,6 +21,8 @@ class ESMWriter; struct Dialogue { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Dialogue"; } enum Type { diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index ee2b7f7ac..3073f4e9d 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -12,6 +12,8 @@ class ESMWriter; struct Door { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Door"; } std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index 3b7746812..cfcdd4edc 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct Enchantment { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Enchantment"; } enum Type { diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index d31670fe2..8645e23fd 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -30,6 +30,8 @@ struct RankData struct Faction { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Faction"; } std::string mId, mName; diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 51b2e2dc9..cc5dbbdcf 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct Global { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Global"; } std::string mId; Variant mValue; diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index 398b8047f..d9d9048b6 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -19,6 +19,8 @@ class ESMWriter; struct GameSetting { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "GameSetting"; } std::string mId; diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 59b1af31a..54003b0d9 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -21,6 +21,8 @@ class ESMWriter; struct DialInfo { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "DialInfo"; } enum Gender { diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 85f2d5e7d..5846a9780 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct Ingredient { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Ingredient"; } struct IRDTstruct { diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index b0897ec67..784cfd407 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -1,5 +1,7 @@ #include "loadland.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -8,7 +10,7 @@ namespace ESM { unsigned int Land::sRecordId = REC_LAND; -void Land::LandData::save(ESMWriter &esm) +void Land::LandData::save(ESMWriter &esm) const { if (mDataTypes & Land::DATA_VNML) { esm.writeHNT("VNML", mNormals, sizeof(mNormals)); @@ -53,7 +55,7 @@ void Land::LandData::save(ESMWriter &esm) } } -void Land::LandData::transposeTextureData(uint16_t *in, uint16_t *out) +void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) { int readPos = 0; //bit ugly, but it works for ( int y1 = 0; y1 < 4; y1++ ) @@ -137,7 +139,7 @@ void Land::save(ESMWriter &esm) const esm.writeHNT("DATA", mFlags); } -void Land::loadData(int flags) +void Land::loadData(int flags) const { // Try to load only available data flags = flags & mDataTypes; @@ -199,7 +201,7 @@ void Land::unloadData() } } -bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) +bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const { if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { mEsm->getHExact(ptr, size); @@ -215,4 +217,69 @@ bool Land::isDataLoaded(int flags) const return (mDataLoaded & flags) == (flags & mDataTypes); } + Land::Land (const Land& land) + : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), + mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes), + mDataLoaded (land.mDataLoaded), + mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) + {} + + Land& Land::operator= (Land land) + { + swap (land); + return *this; + } + + void Land::swap (Land& land) + { + std::swap (mFlags, land.mFlags); + std::swap (mX, land.mX); + std::swap (mY, land.mY); + std::swap (mPlugin, land.mPlugin); + std::swap (mEsm, land.mEsm); + std::swap (mContext, land.mContext); + std::swap (mDataTypes, land.mDataTypes); + std::swap (mDataLoaded, land.mDataLoaded); + std::swap (mLandData, land.mLandData); + } + + const Land::LandData *Land::getLandData (int flags) const + { + if (!(flags & mDataTypes)) + return 0; + + loadData (flags); + return mLandData; + } + + const Land::LandData *Land::getLandData() const + { + return mLandData; + } + + Land::LandData *Land::getLandData() + { + return mLandData; + } + + void Land::add (int flags) + { + if (!mLandData) + mLandData = new LandData; + + mDataTypes |= flags; + mDataLoaded |= flags; + } + + void Land::remove (int flags) + { + mDataTypes &= ~flags; + mDataLoaded &= ~flags; + + if (!mDataLoaded) + { + delete mLandData; + mLandData = 0; + } + } } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index e510616af..8ec4f74ea 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct Land { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Land"; } Land(); ~Land(); @@ -33,7 +35,6 @@ struct Land ESM_Context mContext; int mDataTypes; - int mDataLoaded; enum { @@ -75,26 +76,36 @@ struct Land struct LandData { + // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset; + // Height in world space for each vertex float mHeights[LAND_NUM_VERTS]; + + // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. VNML mNormals[LAND_NUM_VERTS * 3]; + + // 2D array of texture indices. An index can be used to look up an ESM::LandTexture, + // but to do so you must subtract 1 from the index first! + // An index of 0 indicates the default texture. uint16_t mTextures[LAND_NUM_TEXTURES]; - char mColours[3 * LAND_NUM_VERTS]; + // 24-bit RGB color for each vertex + unsigned char mColours[3 * LAND_NUM_VERTS]; + + // DataTypes available in this LandData, accessing data that is not available is an undefined operation int mDataTypes; // low-LOD heightmap (used for rendering the global map) signed char mWnam[81]; + // ??? short mUnk1; uint8_t mUnk2; - void save(ESMWriter &esm); - static void transposeTextureData(uint16_t *in, uint16_t *out); + void save(ESMWriter &esm) const; + static void transposeTextureData(const uint16_t *in, uint16_t *out); }; - LandData *mLandData; - void load(ESMReader &esm); void save(ESMWriter &esm) const; @@ -103,7 +114,7 @@ struct Land /** * Actually loads data */ - void loadData(int flags); + void loadData(int flags) const; /** * Frees memory allocated for land data @@ -114,14 +125,41 @@ struct Land /// @note We only check data types that *can* be loaded (present in mDataTypes) bool isDataLoaded(int flags) const; + Land (const Land& land); + + Land& operator= (Land land); + + void swap (Land& land); + + /// Return land data with at least the data types specified in \a flags loaded (if they + /// are available). Will return a 0-pointer if there is no data for any of the + /// specified types. + const LandData *getLandData (int flags) const; + + /// Return land data without loading first anything. Can return a 0-pointer. + const LandData *getLandData() const; + + /// Return land data without loading first anything. Can return a 0-pointer. + LandData *getLandData(); + + /// \attention Must not be called on objects that aren't fully loaded. + /// + /// \note Added data fields will be uninitialised + void add (int flags); + + /// \attention Must not be called on objects that aren't fully loaded. + void remove (int flags); + private: - Land(const Land& land); - Land& operator=(const Land& land); /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded - bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size); + bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const; + + mutable int mDataLoaded; + + mutable LandData *mLandData; }; } diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index bcea2b234..dc6fcda5e 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -46,6 +46,8 @@ struct LevelledListBase struct CreatureLevList: LevelledListBase { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "CreatureLevList"; } enum Flags { @@ -64,6 +66,8 @@ struct CreatureLevList: LevelledListBase struct ItemLevList: LevelledListBase { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "ItemLevList"; } enum Flags { diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 2c83248f8..ed8c36665 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Light { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Light"; } enum Flags { diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index c44e2b006..0d678cd64 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -12,6 +12,8 @@ class ESMWriter; struct Lockpick { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Lockpick"; } struct Data { diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 8b45f8211..50a788105 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -28,6 +28,8 @@ class ESMWriter; struct LandTexture { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "LandTexture"; } std::string mId, mTexture; int mIndex; diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index e66322832..eeb4268c2 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -13,6 +13,8 @@ class ESMWriter; struct MagicEffect { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "MagicEffect"; } std::string mId; @@ -29,9 +31,9 @@ struct MagicEffect CastTouch = 0x80, // Allows range - cast on touch. CastTarget = 0x100, // Allows range - cast on target. UncappedDamage = 0x1000, // Negates multiple cap behaviours. Allows an effect to reduce an attribute below zero; removes the normal minimum effect duration of 1 second. - NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. + NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. Unreflectable = 0x10000, // Cannot be reflected, the effect always lands normally. - CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. + CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. // Originally modifiable flags AllowSpellmaking = 0x200, // Can be used for spellmaking diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 576bd18c0..6e0b4e01b 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Miscellaneous { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Miscellaneous"; } struct MCDTstruct { diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 44d298785..67a437176 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -121,7 +121,12 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - if (mHasAI) { + if (mAiData.mHello != 0 + || mAiData.mFight != 0 + || mAiData.mFlee != 0 + || mAiData.mAlarm != 0 + || mAiData.mServices != 0) + { esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index b535b91b0..9bda9560e 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -23,6 +23,8 @@ class ESMWriter; struct NPC { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "NPC"; } // Services enum Services @@ -52,12 +54,12 @@ struct NPC enum Flags { - Female = 0x0001, - Essential = 0x0002, - Respawn = 0x0004, - Autocalc = 0x0008, - Skeleton = 0x0400, // Skeleton blood effect (white) - Metal = 0x0800 // Metal blood effect (golden?) + Female = 0x0001, + Essential = 0x0002, + Respawn = 0x0004, + Autocalc = 0x0010, + Skeleton = 0x0400, // Skeleton blood effect (white) + Metal = 0x0800 // Metal blood effect (golden?) }; enum NpcType diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index 256b86cda..f33ccbedf 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct Pathgrid { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Pathgrid"; } struct DATAstruct { diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index b89b2ddeb..c737757aa 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -12,6 +12,8 @@ class ESMWriter; struct Probe { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Probe"; } struct Data { diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index 7d5736d9b..553d2e68b 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct Race { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Race"; } struct SkillBonus { diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index c231b6aa0..1e241fffb 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -19,6 +19,8 @@ class ESMWriter; struct Region { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Region"; } #pragma pack(push) #pragma pack(1) diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index 5b404b0e4..e765bc93a 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -12,6 +12,8 @@ class ESMWriter; struct Repair { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Repair"; } struct Data { diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 0c2bdd42f..60b4a3304 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -77,8 +77,13 @@ namespace ESM break; case ESM::FourCC<'S','C','D','T'>::value: // compiled script - mScriptData.resize(mData.mScriptDataSize); - esm.getHExact(&mScriptData[0], mScriptData.size()); + if (mData.mScriptDataSize) + { + mScriptData.resize(mData.mScriptDataSize); + esm.getHExact(&mScriptData[0], mScriptData.size()); + } + else + esm.skipHSub(); break; case ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index deb71de6a..56390f384 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -20,6 +20,8 @@ class Script { public: static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Script"; } struct SCHDstruct { diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index 1b9db5bcf..e00184297 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -20,6 +20,8 @@ class ESMWriter; struct Skill { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Skill"; } std::string mId; diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index f89a11208..056958f0a 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct SoundGenerator { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "SoundGenerator"; } enum Type { diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index 04a0984fd..ff2202ca7 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -17,6 +17,8 @@ struct SOUNstruct struct Sound { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Sound"; } SOUNstruct mData; std::string mId, mSound; diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 4bd2210ec..491da1d17 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -14,6 +14,8 @@ class ESMWriter; struct Spell { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Spell"; } enum SpellType { diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index 1420d16c4..dc7ad6a42 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -20,6 +20,8 @@ class ESMWriter; struct StartScript { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "StartScript"; } std::string mData; std::string mId; diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index 45b05136a..21a9e66e8 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -23,6 +23,8 @@ class ESMWriter; struct Static { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Static"; } std::string mId, mModel; diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index 9c0c55b8f..df35a2579 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -1,4 +1,3 @@ - #include "loadtes3.hpp" #include "esmcommon.hpp" @@ -71,7 +70,13 @@ void ESM::Header::save (ESMWriter &esm) if (mFormat>0) esm.writeHNT ("FORM", mFormat); - esm.writeHNT ("HEDR", mData, 300); + esm.startSubRecord("HEDR"); + esm.writeT(mData.version); + esm.writeT(mData.type); + esm.writeFixedSizeString(mData.author.toString(), 32); + esm.writeFixedSizeString(mData.desc.toString(), 256); + esm.writeT(mData.records); + esm.endRecord("HEDR"); for (std::vector::iterator iter = mMaster.begin(); iter != mMaster.end(); ++iter) diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index 14ddb4708..f66e9f3a6 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct Weapon { static unsigned int sRecordId; + /// Return a string descriptor for this record type. Currently used for debugging / error logs only. + static std::string getRecordType() { return "Weapon"; } enum Type { diff --git a/components/esm/locals.cpp b/components/esm/locals.cpp index f0cfd49f0..bd51be08f 100644 --- a/components/esm/locals.cpp +++ b/components/esm/locals.cpp @@ -1,4 +1,3 @@ - #include "locals.hpp" #include "esmreader.hpp" diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp index 724d67326..6c9988d50 100644 --- a/components/esm/npcstate.cpp +++ b/components/esm/npcstate.cpp @@ -1,4 +1,3 @@ - #include "npcstate.hpp" void ESM::NpcState::load (ESMReader &esm) diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index cc1d6b3dd..e854410b1 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -1,4 +1,3 @@ - #include "npcstats.hpp" #include "esmreader.hpp" @@ -31,18 +30,44 @@ void ESM::NpcStats::load (ESMReader &esm) esm.getHNOT (mDisposition, "DISP"); for (int i=0; i<27; ++i) + mSkills[i].load (esm); + + mWerewolfDeprecatedData = false; + if (esm.peekNextSub("STBA")) { - mSkills[i].mRegular.load (esm); - mSkills[i].mWerewolf.load (esm); + // we have deprecated werewolf skills, stored interleaved + // Load into one big vector, then remove every 2nd value + mWerewolfDeprecatedData = true; + std::vector > skills(mSkills, mSkills + sizeof(mSkills)/sizeof(mSkills[0])); + + for (int i=0; i<27; ++i) + { + ESM::StatState skill; + skill.load(esm); + skills.push_back(skill); + } + + int i=0; + for (std::vector >::iterator it = skills.begin(); it != skills.end(); ++i) + { + if (i%2 == 1) + it = skills.erase(it); + else + ++it; + } + assert(skills.size() == 27); + std::copy(skills.begin(), skills.end(), mSkills); } + // No longer used bool hasWerewolfAttributes = false; esm.getHNOT (hasWerewolfAttributes, "HWAT"); - if (hasWerewolfAttributes) { + ESM::StatState dummy; for (int i=0; i<8; ++i) - mWerewolfAttributes[i].load (esm); + dummy.load(esm); + mWerewolfDeprecatedData = true; } mIsWerewolf = false; @@ -112,14 +137,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const esm.writeHNT ("DISP", mDisposition); for (int i=0; i<27; ++i) - { - mSkills[i].mRegular.save (esm); - mSkills[i].mWerewolf.save (esm); - } - - esm.writeHNT ("HWAT", true); - for (int i=0; i<8; ++i) - mWerewolfAttributes[i].save (esm); + mSkills[i].save (esm); if (mIsWerewolf) esm.writeHNT ("WOLF", mIsWerewolf); @@ -151,6 +169,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const void ESM::NpcStats::blank() { + mWerewolfDeprecatedData = false; mIsWerewolf = false; mDisposition = 0; mBounty = 0; diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index 0138ab209..9b27f587c 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -16,12 +16,6 @@ namespace ESM struct NpcStats { - struct Skill - { - StatState mRegular; - StatState mWerewolf; - }; - struct Faction { bool mExpelled; @@ -31,12 +25,13 @@ namespace ESM Faction(); }; - StatState mWerewolfAttributes[8]; bool mIsWerewolf; + bool mWerewolfDeprecatedData; + std::map mFactions; // lower case IDs int mDisposition; - Skill mSkills[27]; + StatState mSkills[27]; int mBounty; int mReputation; int mWerewolfKills; diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 9ef1ccf80..62aa0452a 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -1,4 +1,3 @@ - #include "objectstate.hpp" #include "esmreader.hpp" @@ -6,6 +5,8 @@ void ESM::ObjectState::load (ESMReader &esm) { + mVersion = esm.getFormat(); + mRef.loadData(esm); mHasLocals = 0; diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index d1077733a..674bcb8fc 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -29,7 +29,9 @@ namespace ESM // Is there any class-specific state following the ObjectState bool mHasCustomState; - ObjectState() : mHasCustomState(true) + unsigned int mVersion; + + ObjectState() : mHasCustomState(true), mVersion(0) {} /// @note Does not load the CellRef ID, it should already be loaded before calling this method diff --git a/components/esm/player.cpp b/components/esm/player.cpp index 70c4b79e2..9ec53240a 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -1,4 +1,3 @@ - #include "player.hpp" #include "esmreader.hpp" @@ -31,6 +30,14 @@ void ESM::Player::load (ESMReader &esm) esm.getHNOT (mCurrentCrimeId, "CURD"); mPaidCrimeId = -1; esm.getHNOT (mPaidCrimeId, "PAYD"); + + if (esm.hasMoreSubs()) + { + for (int i=0; i mSaveAttributes[ESM::Attribute::Length]; + StatState mSaveSkills[ESM::Skill::Length]; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/queststate.cpp b/components/esm/queststate.cpp index c8cff7adc..5408cd2ff 100644 --- a/components/esm/queststate.cpp +++ b/components/esm/queststate.cpp @@ -1,4 +1,3 @@ - #include "queststate.hpp" #include "esmreader.hpp" diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index b5e0810db..2e5509b7a 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -1,4 +1,3 @@ - #include "savedgame.hpp" #include "esmreader.hpp" @@ -6,6 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; +int ESM::SavedGame::sCurrentFormat = 1; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp index 3e7cae775..aa0429657 100644 --- a/components/esm/savedgame.hpp +++ b/components/esm/savedgame.hpp @@ -15,6 +15,8 @@ namespace ESM { static unsigned int sRecordId; + static int sCurrentFormat; + struct TimeStamp { float mGameHour; diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index eeb0bf04f..aeea5017e 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -1,4 +1,3 @@ - #include "variantimp.hpp" #include diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 763f0f4e0..83fa85890 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -16,6 +16,14 @@ namespace ESMTerrain { + const ESM::Land::LandData *Storage::getLandData (int cellX, int cellY, int flags) + { + if (const ESM::Land *land = getLand (cellX, cellY)) + return land->getLandData (flags); + + return 0; + } + bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) { assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); @@ -30,24 +38,25 @@ namespace ESMTerrain int cellX = static_cast(origin.x); int cellY = static_cast(origin.y); - const ESM::Land* land = getLand(cellX, cellY); - if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) - return false; - - min = std::numeric_limits::max(); - max = -std::numeric_limits::max(); - for (int row=0; row::max(); + max = -std::numeric_limits::max(); + for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - if (h > max) - max = h; - if (h < min) - min = h; + for (int col=0; colmHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } } + return true; } - return true; + + return false; } void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) @@ -72,12 +81,12 @@ namespace ESMTerrain --cellX; row += ESM::Land::LAND_SIZE-1; } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mDataTypes&ESM::Land::DATA_VNML) + + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VNML)) { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.x = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; normal.normalise(); } else @@ -107,12 +116,12 @@ namespace ESMTerrain ++cellX; row = 0; } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mDataTypes&ESM::Land::DATA_VCLR) + + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VCLR)) { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + color.r = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; } else { @@ -156,9 +165,9 @@ namespace ESMTerrain float vertX_ = 0; // of current cell corner for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) { - ESM::Land* land = getLand(cellX, cellY); - if (land && !(land->mDataTypes&ESM::Land::DATA_VHGT)) - land = NULL; + const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); + const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); + const ESM::Land::LandData *colourData = getLandData (cellX, cellY, ESM::Land::DATA_VCLR); int rowStart = 0; int colStart = 0; @@ -177,16 +186,17 @@ namespace ESMTerrain { positions[static_cast(vertX*numVerts * 3 + vertY * 3)] = ((vertX / float(numVerts - 1) - 0.5f) * size * 8192); positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 1)] = ((vertY / float(numVerts - 1) - 0.5f) * size * 8192); - if (land) - positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row]; - else - positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = -2048; - if (land && land->mDataTypes&ESM::Land::DATA_VNML) + float height = -2048; + if (heightData) + height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; + positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = height; + + if (normalData) { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.x = normalData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = normalData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = normalData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; normal.normalise(); } else @@ -206,11 +216,11 @@ namespace ESMTerrain normals[static_cast(vertX*numVerts * 3 + vertY * 3 + 1)] = normal.y; normals[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = normal.z; - if (land && land->mDataTypes&ESM::Land::DATA_VCLR) + if (colourData) { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + color.r = colourData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = colourData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = colourData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; } else { @@ -261,13 +271,12 @@ namespace ESMTerrain assert(xmDataTypes&ESM::Land::DATA_VTEX)) + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VTEX)) { - int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; if (tex == 0) return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin - return std::make_pair(tex, land->mPlugin); + return std::make_pair(tex, getLand (cellX, cellY)->mPlugin); } else return std::make_pair(0,0); @@ -382,7 +391,7 @@ namespace ESMTerrain int cellX = static_cast(std::floor(worldPos.x / 8192.f)); int cellY = static_cast(std::floor(worldPos.y / 8192.f)); - ESM::Land* land = getLand(cellX, cellY); + const ESM::Land* land = getLand(cellX, cellY); if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) return -2048; @@ -459,7 +468,7 @@ namespace ESMTerrain { assert(x < ESM::Land::LAND_SIZE); assert(y < ESM::Land::LAND_SIZE); - return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + return land->getLandData()->mHeights[y * ESM::Land::LAND_SIZE + x]; } Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index e184cbc4c..af1b0d5c3 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -16,11 +16,16 @@ namespace ESMTerrain private: // Not implemented in this class, because we need different Store implementations for game and editor - virtual ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::Land* getLand (int cellX, int cellY)= 0; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: + /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for + /// any of the data types specified via \a flags. Will also return a 0-pointer if there + /// is no land record for the coordinates \a cellX / \a cellY. + const ESM::Land::LandData *getLandData (int cellX, int cellY, int flags); + // Not implemented in this class, because we need different Store implementations for game and editor /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index e321b5814..ac461697a 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -52,16 +52,30 @@ void ConfigurationManager::setupTokensMapping() } void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, - boost::program_options::options_description& description) + boost::program_options::options_description& description, bool quiet) { + bool silent = mSilent; + mSilent = quiet; + + boost::filesystem::path pUser = boost::filesystem::canonical(mFixedPath.getUserConfigPath()); + boost::filesystem::path pLocal = boost::filesystem::canonical(mFixedPath.getLocalPath()); + boost::filesystem::path pGlobal = boost::filesystem::canonical(mFixedPath.getGlobalConfigPath()); + loadConfig(mFixedPath.getUserConfigPath(), variables, description); boost::program_options::notify(variables); - loadConfig(mFixedPath.getLocalPath(), variables, description); - boost::program_options::notify(variables); - loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); - boost::program_options::notify(variables); + if (pLocal != pUser) + { + loadConfig(mFixedPath.getLocalPath(), variables, description); + boost::program_options::notify(variables); + } + if (pGlobal != pUser && pGlobal != pLocal) + { + loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + boost::program_options::notify(variables); + } + mSilent = silent; } void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool create) diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index b0b7fea9a..c7c738b19 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -1,8 +1,12 @@ #ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__MINGW32__) +#if (_MSC_VER >= 1900) +#include +#else #include +#endif #elif defined HAVE_UNORDERED_MAP #include #else @@ -29,7 +33,7 @@ struct ConfigurationManager virtual ~ConfigurationManager(); void readConfiguration(boost::program_options::variables_map& variables, - boost::program_options::options_description& description); + boost::program_options::options_description& description, bool quiet=false); void processPaths(Files::PathContainer& dataDirs, bool create = false); ///< \param create Try creating the directory, if it does not exist. @@ -52,11 +56,11 @@ struct ConfigurationManager typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; - #if defined HAVE_UNORDERED_MAP - typedef std::unordered_map TokensMappingContainer; - #else - typedef std::tr1::unordered_map TokensMappingContainer; - #endif + #if defined HAVE_UNORDERED_MAP + typedef std::unordered_map TokensMappingContainer; + #else + typedef std::tr1::unordered_map TokensMappingContainer; + #endif void loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index 1abbae3ae..7b3b0c440 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -1,4 +1,3 @@ - #include "multidircollection.hpp" #include diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index 0df782702..ece4049a8 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -6,9 +6,7 @@ #include #include -#include - -#pragma comment(lib, "Shlwapi.lib") +#include #include namespace bconv = boost::locale::conv; diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index d705a109c..31e911f8b 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -1,4 +1,3 @@ - #include "installopcodes.hpp" #include diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index ea1e9fc91..e6e06370f 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -1,4 +1,3 @@ - #include "interpreter.hpp" #include @@ -134,7 +133,34 @@ namespace Interpreter throw std::runtime_error (error.str()); } - Interpreter::Interpreter() + void Interpreter::begin() + { + if (mRunning) + { + mCallstack.push (mRuntime); + mRuntime.clear(); + } + else + { + mRunning = true; + } + } + + void Interpreter::end() + { + if (mCallstack.empty()) + { + mRuntime.clear(); + mRunning = false; + } + else + { + mRuntime = mCallstack.top(); + mCallstack.pop(); + } + } + + Interpreter::Interpreter() : mRunning (false) {} Interpreter::~Interpreter() @@ -204,19 +230,29 @@ namespace Interpreter { assert (codeSize>=4); - mRuntime.configure (code, codeSize, context); + begin(); - int opcodes = static_cast (code[0]); - - const Type_Code *codeBlock = code + 4; - - while (mRuntime.getPC()>=0 && mRuntime.getPC() (code[0]); + + const Type_Code *codeBlock = code + 4; + + while (mRuntime.getPC()>=0 && mRuntime.getPC() +#include #include "runtime.hpp" #include "types.hpp" @@ -14,6 +15,8 @@ namespace Interpreter class Interpreter { + std::stack mCallstack; + bool mRunning; Runtime mRuntime; std::map mSegment0; std::map mSegment1; @@ -32,6 +35,10 @@ namespace Interpreter void abortUnknownSegment (Type_Code code); + void begin(); + + void end(); + public: Interpreter(); diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp index dc3da07a8..6599882f1 100644 --- a/components/interpreter/runtime.cpp +++ b/components/interpreter/runtime.cpp @@ -1,4 +1,3 @@ - #include "runtime.hpp" #include diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index a9a78d035..7827388b1 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -2,6 +2,7 @@ #include +#include // FIXME: workaround compilation error with OgreCommon.h included by OgreStringConverter.h #include #include diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index bb99ca23e..feea61993 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "chunk.hpp" diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index 5a79de3d1..df7e7d61d 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -48,7 +48,7 @@ namespace Gui const int _scrollBarWidth = 20; // fetch this from skin? const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0; const int spacing = 3; - size_t viewPosition = -mScrollView->getViewOffset().top; + int viewPosition = -mScrollView->getViewOffset().top; while (mScrollView->getChildCount()) { @@ -99,10 +99,10 @@ namespace Gui if (!scrollbarShown && mItemHeight > mClient->getSize().height) redraw(true); - size_t viewRange = mScrollView->getCanvasSize().height; + int viewRange = mScrollView->getCanvasSize().height; if(viewPosition > viewRange) viewPosition = viewRange; - mScrollView->setViewOffset(MyGUI::IntPoint(0, viewPosition * -1)); + mScrollView->setViewOffset(MyGUI::IntPoint(0, -viewPosition)); } void MWList::setPropertyOverride(const std::string &_key, const std::string &_value) @@ -157,4 +157,8 @@ namespace Gui return mScrollView->findWidget (getName() + "_item_" + name)->castType(); } + void MWList::scrollToTop() + { + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); + } } diff --git a/components/widgets/list.hpp b/components/widgets/list.hpp index 72c8a733c..3efe1ff75 100644 --- a/components/widgets/list.hpp +++ b/components/widgets/list.hpp @@ -46,6 +46,8 @@ namespace Gui MyGUI::Button* getItemWidget(const std::string& name); ///< get widget for an item name, useful to set up tooltip + void scrollToTop(); + virtual void setPropertyOverride(const std::string& _key, const std::string& _value); protected: diff --git a/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt b/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt index 299a57799..edd5575f4 100644 --- a/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt @@ -11,32 +11,8 @@ set(OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES audiofactory.hpp ) -# Find FFMPEG -set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE AVRESAMPLE) -unset(FFMPEG_LIBRARIES CACHE) -find_package(FFmpeg) -if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND ) - message(FATAL_ERROR "FFmpeg component required, but not found!") -endif() -set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) -if( SWRESAMPLE_FOUND ) - add_definitions(-DHAVE_LIBSWRESAMPLE) - set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES}) -else() - if( AVRESAMPLE_FOUND ) - set(VIDEO_FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES}) - else() - message(FATAL_ERROR "Install either libswresample (FFmpeg) or libavresample (Libav).") - endif() -endif() include_directories(${FFMPEG_INCLUDE_DIRS}) - -# Find Boost -set(BOOST_COMPONENTS thread) -find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -include_directories(${Boost_INCLUDE_DIRS}) - add_library(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} STATIC ${OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES}) -target_link_libraries(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} ${VIDEO_FFMPEG_LIBRARIES} ${Boost_LIBRARIES}) +target_link_libraries(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} ${FFMPEG_LIBRARIES} ${Boost_THREAD_LIBRARY}) link_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp b/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp index b05b16d42..fe36ec39f 100644 --- a/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp +++ b/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp @@ -21,8 +21,8 @@ extern "C" #endif } -#ifdef _WIN32 -#include +#if defined(_WIN32) && !defined(__MINGW32__) +#include typedef SSIZE_T ssize_t; #endif diff --git a/extern/oics/tinyxml.cpp b/extern/oics/tinyxml.cpp index 21b2d9c9a..5d8eb475a 100644 --- a/extern/oics/tinyxml.cpp +++ b/extern/oics/tinyxml.cpp @@ -32,7 +32,7 @@ distribution. #include "tinyxml.h" #ifdef _WIN32 -#include // import MultiByteToWideChar +#include // import MultiByteToWideChar #endif diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index a7023207c..4b57a1c95 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -27,8 +27,8 @@ namespace SFO void setControllerEventCallback(ControllerListener* listen) { mConListener = listen; } void capture(bool windowEventsOnly); - bool isModifierHeld(SDL_Keymod mod); - bool isKeyDown(SDL_Scancode key); + bool isModifierHeld(SDL_Keymod mod); + bool isKeyDown(SDL_Scancode key); void setMouseVisible (bool visible); void setMouseRelative(bool relative); diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp index 637fae0ef..6690e3cab 100644 --- a/extern/sdl4ogre/sdlwindowhelper.cpp +++ b/extern/sdl4ogre/sdlwindowhelper.cpp @@ -30,7 +30,7 @@ SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, switch (wmInfo.subsystem) { -#ifdef WIN32 +#ifdef _WIN32 case SDL_SYSWM_WINDOWS: // Windows code winHandle = Ogre::StringConverter::toString((uintptr_t)wmInfo.info.win.window); diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 13d1a9e1a..1833adcee 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -1,4 +1,3 @@ - # Copy resource files into the build directory set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml new file mode 100644 index 000000000..1c16cb9a4 --- /dev/null +++ b/files/openmw.appdata.xml @@ -0,0 +1,39 @@ + + + + openmw.desktop + CC0-1.0 + GPL-3.0 and MIT and zlib + OpenMW +

Unofficial open source engine re-implementation of the game Morrowind + +

+ OpenMW is a new engine for 2002's Game of the Year, The Elder Scrolls 3: Morrowind. +

+

+ It aims to be a fully playable (and improved!), open source implementation of the game's engine and functionality (including mods). +

+

+ You will still need the original game data to play OpenMW. +

+
+ + + + http://wiki.openmw.org/images/b/b2/Openmw_0.11.1_launcher_1.png + The OpenMW launcher + + + http://wiki.openmw.org/images/f/f1/Screenshot_mournhold_plaza_0.35.png + The Mournhold's plaza on OpenMW + + + http://wiki.openmw.org/images/5/5b/Screenshot_Vivec_seen_from_Ebonheart_0.35.png + Vivec seen from Ebonheart on OpenMW + + +nobrakal@gmail.com + https://openmw.org + https://bugs.openmw.org/ + https://openmw.org/faq/ + diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 15be7665a..44a928121 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -363,7 +363,7 @@ namespace Physic //delete BulletShapeManager::getSingletonPtr(); } - void PhysicEngine::addHeightField(float* heights, + void PhysicEngine::addHeightField(const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) { diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 7784e8941..2c7ac8f05 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -227,7 +227,7 @@ namespace Physic /** * Add a HeightField to the simulation */ - void addHeightField(float* heights, + void addHeightField(const float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); diff --git a/libs/platform/strings.h b/libs/platform/strings.h index c0fbb1a1b..305705044 100644 --- a/libs/platform/strings.h +++ b/libs/platform/strings.h @@ -9,7 +9,9 @@ #elif defined(MSVC) || defined(_MSC_VER) # pragma warning(disable: 4996) # define strcasecmp stricmp -# define snprintf _snprintf +# if (_MSC_VER < 1900) +# define snprintf _snprintf +# endif #else # warning "Unable to determine your compiler, you should probably take a look here." # include // Just take a guess