From d8f20d2e667ee4104fb6c4b9e5e23fce73f44b5a Mon Sep 17 00:00:00 2001 From: pvdk Date: Sat, 7 Dec 2013 16:17:07 +0100 Subject: [PATCH 0001/1983] Added the infrastructure to start working on the installation wizard --- CMakeLists.txt | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f6014dff6..f5e2d0a81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) option(BUILD_OPENCS "build OpenMW Construction Set" ON) +option(BUILD_WIZARD "build Installation Wizard" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock frameworks" OFF) @@ -384,6 +385,10 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_OPENCS) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENCS) + IF(BUILD_WIZARD) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/wizard" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_WIZARD) + # Install licenses INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) @@ -434,7 +439,7 @@ IF(NOT WIN32 AND NOT APPLE) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW opencs;OpenCS bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW opencs;OpenCS bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter openmw-wizard;OpenMW Wizard") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") @@ -476,6 +481,10 @@ if(WIN32) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION ".") ENDIF(BUILD_OPENCS) + IF(BUILD_WIZARD) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-wizard.exe" DESTINATION ".") + ENDIF(BUILD_WIZARD) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") @@ -493,6 +502,9 @@ if(WIN32) IF(BUILD_OPENCS) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};opencs;OpenMW Construction Set") ENDIF(BUILD_OPENCS) + IF(BUILD_WIZARD) + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-wizard;OpenMW Wizard") + ENDIF(BUILD_WIZARD) SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") SET(CPACK_NSIS_DELETE_ICONS_EXTRA " !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP @@ -573,6 +585,10 @@ if (BUILD_OPENCS) add_subdirectory (apps/opencs) endif() +if (BUILD_WIZARD) + add_subdirectory(apps/wizard) +endif() + # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) @@ -649,6 +665,9 @@ if (WIN32) if (BUILD_ESMTOOL) set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS}) endif (BUILD_ESMTOOL) + if (BUILD_WIZARD) + set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS ${WARNINGS}) + endif (BUILD_WIZARD) endif(MSVC) # Same for MinGW From f458f0c6c16d13bac0d45ccd7ed972d92cfed870 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sat, 7 Dec 2013 23:17:50 +0100 Subject: [PATCH 0002/1983] Added the Wizard .ui/resource files and some basic source to get started --- apps/wizard/CMakeLists.txt | 96 +++++++++++ apps/wizard/main.cpp | 51 ++++++ apps/wizard/wizard.cpp | 1 + apps/wizard/wizard.hpp | 1 + files/ui/wizard/componentselectionpage.ui | 60 +++++++ files/ui/wizard/conclusionpage.ui | 36 ++++ files/ui/wizard/configurationpage.ui | 58 +++++++ files/ui/wizard/existinginstallationpage.ui | 85 ++++++++++ files/ui/wizard/importpage.ui | 67 ++++++++ files/ui/wizard/installationpage.ui | 51 ++++++ files/ui/wizard/installationtargetpage.ui | 99 +++++++++++ files/ui/wizard/intropage.ui | 34 ++++ files/ui/wizard/methodselectionpage.ui | 156 ++++++++++++++++++ files/wizard/icons/tango/48x48/folder.png | Bin 0 -> 1610 bytes .../icons/tango/48x48/system-installer.png | Bin 0 -> 2663 bytes files/wizard/icons/tango/index.theme | 8 + files/wizard/wizard.qrc | 7 + 17 files changed, 810 insertions(+) create mode 100644 apps/wizard/CMakeLists.txt create mode 100644 apps/wizard/main.cpp create mode 100644 apps/wizard/wizard.cpp create mode 100644 apps/wizard/wizard.hpp create mode 100644 files/ui/wizard/componentselectionpage.ui create mode 100644 files/ui/wizard/conclusionpage.ui create mode 100644 files/ui/wizard/configurationpage.ui create mode 100644 files/ui/wizard/existinginstallationpage.ui create mode 100644 files/ui/wizard/importpage.ui create mode 100644 files/ui/wizard/installationpage.ui create mode 100644 files/ui/wizard/installationtargetpage.ui create mode 100644 files/ui/wizard/intropage.ui create mode 100644 files/ui/wizard/methodselectionpage.ui create mode 100644 files/wizard/icons/tango/48x48/folder.png create mode 100644 files/wizard/icons/tango/48x48/system-installer.png create mode 100644 files/wizard/icons/tango/index.theme create mode 100644 files/wizard/wizard.qrc diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt new file mode 100644 index 000000000..d6423daad --- /dev/null +++ b/apps/wizard/CMakeLists.txt @@ -0,0 +1,96 @@ +set(WIZARD + main.cpp + wizard.cpp + +) +# if(NOT WIN32) +# LIST(APPEND WIZARD unshieldthread.cpp) +# endif(NOT WIN32) + +set(WIZARD_HEADER + wizard.hpp +) +# if(NOT WIN32) +# LIST(APPEND WIZARD_HEADER unshieldthread.hpp) +# endif(NOT WIN32) + + +# Headers that must be pre-processed +set(WIZARD_HEADER_MOC + wizard.hpp +) + +# if(NOT WIN32) +# LIST(APPEND WIZARD_HEADER_MOC unshieldthread.hpp) +# endif(NOT WIN32) + + +set(WIZARD_UI + ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/configurationpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/existinginstallationpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/importpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationtargetpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/intropage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/methodselectionpage.ui +) + +source_group(wizard FILES ${WIZARD} ${WIZARD_HEADER}) + +find_package(Qt4 REQUIRED) +set(QT_USE_QTGUI 1) + +# Set some platform specific settings +if(WIN32) + set(GUI_TYPE 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}) + + +include(${QT_USE_FILE}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(NOT WIN32) + include_directories(${LIBUNSHIELD_INCLUDE}) +endif(NOT WIN32) + +add_executable(openmw-wizard + ${GUI_TYPE} + ${WIZARD} + ${WIZARD_HEADER} + ${RCC_SRCS} + ${MOC_SRCS} + ${UI_HDRS} +) + +target_link_libraries(openmw-wizard +# ${Boost_LIBRARIES} + ${QT_LIBRARIES} +) +if(NOT WIN32) + target_link_libraries(openmw-wizard + ${LIBUNSHIELD_LIBRARY} + ) +endif(NOT WIN32) + + + +if(DPKG_PROGRAM) + INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION games COMPONENT openmw-wizard) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(openmw-wizard gcov) +endif() + +# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream +if (UNIX AND NOT APPLE) +target_link_libraries(openmw-wizard dl Xt) +endif() + diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp new file mode 100644 index 000000000..ed3f3a6ea --- /dev/null +++ b/apps/wizard/main.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +#ifdef MAC_OS_X_VERSION_MIN_REQUIRED +#undef MAC_OS_X_VERSION_MIN_REQUIRED +// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 +#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + +int main(int argc, char *argv[]) +{ + + QApplication app(argc, argv); + + // Now we make sure the current dir is set to application path + QDir dir(QCoreApplication::applicationDirPath()); + + #ifdef Q_OS_MAC + if (dir.dirName() == "MacOS") { + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + } + + // force Qt to load only LOCAL plugins, don't touch system Qt installation + QDir pluginsPath(QCoreApplication::applicationDirPath()); + pluginsPath.cdUp(); + pluginsPath.cd("Plugins"); + + QStringList libraryPaths; + libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); + app.setLibraryPaths(libraryPaths); + #endif + + QDir::setCurrent(dir.absolutePath()); + + // Support non-latin characters + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); + +// Launcher::MainDialog mainWin; + +// if (mainWin.setup()) { +// mainWin.show(); +// } else { +// return 0; +// } + + return app.exec(); +} diff --git a/apps/wizard/wizard.cpp b/apps/wizard/wizard.cpp new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/apps/wizard/wizard.cpp @@ -0,0 +1 @@ + diff --git a/apps/wizard/wizard.hpp b/apps/wizard/wizard.hpp new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/apps/wizard/wizard.hpp @@ -0,0 +1 @@ + diff --git a/files/ui/wizard/componentselectionpage.ui b/files/ui/wizard/componentselectionpage.ui new file mode 100644 index 000000000..c3333752a --- /dev/null +++ b/files/ui/wizard/componentselectionpage.ui @@ -0,0 +1,60 @@ + + + ComponentSelectionPage + + + + 0 + 0 + 454 + 393 + + + + WizardPage + + + Select Components + + + Which components should be installed? + + + + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + + + true + + + + + + + Selected components: + + + + + + + + + + Qt::Vertical + + + + 20 + 81 + + + + + + + + + diff --git a/files/ui/wizard/conclusionpage.ui b/files/ui/wizard/conclusionpage.ui new file mode 100644 index 000000000..bedad3e2a --- /dev/null +++ b/files/ui/wizard/conclusionpage.ui @@ -0,0 +1,36 @@ + + + ConclusionPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Completing the OpenMW Wizard + + + + + + The OpenMW Wizard successfully installed Morrowind on your computer. + +Click Finish to close the Wizard. + + + true + + + + + + + + diff --git a/files/ui/wizard/configurationpage.ui b/files/ui/wizard/configurationpage.ui new file mode 100644 index 000000000..8340b2ebf --- /dev/null +++ b/files/ui/wizard/configurationpage.ui @@ -0,0 +1,58 @@ + + + WizardPage + + + + 0 + 0 + 440 + 300 + + + + WizardPage + + + + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly. Select the steps you want to be taken and click Next when you are ready to continue.</p><p><span style=" font-weight:600;">Note:</span> it is possible to import settings later, by re-running this Wizard.</p><p/></body></html> + + + true + + + + + + + Import settings from Morrowind.ini + + + + + + + Import selected add-ons (creates a new Cotent List in the launcher) + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/files/ui/wizard/existinginstallationpage.ui b/files/ui/wizard/existinginstallationpage.ui new file mode 100644 index 000000000..579ddbe67 --- /dev/null +++ b/files/ui/wizard/existinginstallationpage.ui @@ -0,0 +1,85 @@ + + + ExistingInstallationPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Select Existing Installation + + + Select an existing installation for OpenMW to use or modify. + + + + + + Detected Installations: + + + + + + + + No existing installations detected + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Browse... + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/files/ui/wizard/importpage.ui b/files/ui/wizard/importpage.ui new file mode 100644 index 000000000..21037f3e8 --- /dev/null +++ b/files/ui/wizard/importpage.ui @@ -0,0 +1,67 @@ + + + ImportPage + + + + 0 + 0 + 513 + 328 + + + + WizardPage + + + Import Settings + + + Import settings from the Morrowind installation. + + + + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + + + true + + + + + + + Import settings from Morrowind.ini + + + true + + + + + + + Import previously selected add-ons (creates a new Content List in the launcher) + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/files/ui/wizard/installationpage.ui b/files/ui/wizard/installationpage.ui new file mode 100644 index 000000000..3e94c1e30 --- /dev/null +++ b/files/ui/wizard/installationpage.ui @@ -0,0 +1,51 @@ + + + InstallationPage + + + + 0 + 0 + 518 + 423 + + + + WizardPage + + + Installing + + + Please wait while Morrowind is installed on your computer. + + + + + + Extracting: %1 + + + + + + + 24 + + + + + + + false + + + true + + + + + + + + diff --git a/files/ui/wizard/installationtargetpage.ui b/files/ui/wizard/installationtargetpage.ui new file mode 100644 index 000000000..6bdac5592 --- /dev/null +++ b/files/ui/wizard/installationtargetpage.ui @@ -0,0 +1,99 @@ + + + InstallationTargetPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Select Installation Destination + + + Where should Morrowind be installed? + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Morrowind will be installed to the following location. + + + true + + + + + + + + + Click Next to continue. If you would like to select a different location, click Browse. + + + true + + + + + + + + + + + + Browse... + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/files/ui/wizard/intropage.ui b/files/ui/wizard/intropage.ui new file mode 100644 index 000000000..bb597e3a6 --- /dev/null +++ b/files/ui/wizard/intropage.ui @@ -0,0 +1,34 @@ + + + IntroPage + + + + 0 + 0 + 474 + 370 + + + + WizardPage + + + Welcome to the OpenMW Wizard + + + + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + + + true + + + + + + + + diff --git a/files/ui/wizard/methodselectionpage.ui b/files/ui/wizard/methodselectionpage.ui new file mode 100644 index 000000000..e77349f5c --- /dev/null +++ b/files/ui/wizard/methodselectionpage.ui @@ -0,0 +1,156 @@ + + + MethodSelectionPage + + + + 0 + 0 + 398 + 298 + + + + WizardPage + + + Select Installation Method + + + Select how OpenMW should get the required Morrowind installation files. + + + + + + font-weight:bold; + + + Install to a new location + + + true + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + + + Qt::RichText + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + newLocationRadioButton + + + + + + + Install Morrowind to a new location for OpenMW to use. + + + true + + + + + + + + + font-weight:bold + + + Select an existing installation + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Select an existing Morrowind installation for OpenMW to use. + + + true + + + + + + + + + + + + diff --git a/files/wizard/icons/tango/48x48/folder.png b/files/wizard/icons/tango/48x48/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..e93d7cf8f8460c1576f40b6981d42181a1e1b38f GIT binary patch literal 1610 zcmV-Q2DSN#P)$Qp1=mSL zK~!ko?U~DqWJwjqe-ZbQkIIKN(@NvQh5aXpOL5^W8pPGW;L?rD?gl|-(F>P0xaw}) z%tG)F(4~zi!fZqvM22Bt#Dy>vgWXnERc7A2_r~+M=zDXrvZ^byYUoOoIuPW^xWwiA z;yWkeL|nMB8@sXpX;Z6z9zJ~d6Q$JqB9a0qr99voKPaVa<42{GSs7XZgb z=+U39i$J>iM?`*h_wL%jxO;fG4 ze)sy z2wSPJ9#Xe&-yZdPJ@)ta>G%6+tx-y$)Tu>}hSc&|2!UtMo_+7$y?eiY`t<4S(gZ>X z)>_N)@i9k7N5mK}rNU|`0UR70EJ3#*+orW%5^McD&jCVF6k}j^6$NVN9HY@_Is2;w zHmR^qpatIkt}4;`Ns=rHXsr>En5Jp(sv%T+@0rbJ93CDn0l!X#^(0;gze=JdvOepp zewJkb(HPUcDuK#5$8b2rT1yCl-~H;Go+S#O ze{<;b?2q4l@B_R49_PcSfBxkU-?=CUDr+r8Q81ZI2q7@7Z}HO~eTQK$yROYu17BV5 zENjotfA!(pTPZLY45+FK<07Ew zKBeE)2(mNq08xa>c(i76o?NR6l&u1z(TKD=V$|>8{qDf~ASk7%YKId2;z9zg0!2}9 ze0n3j%yzk|Ex5Ng54D9!S!DJ$zlc)2NwYin(66;NTo zpx432cRPCIjEK81NHQh{MLuh97RlNEjXI61woCSctsXCWKs? zrrH?8yw;>|=Tt79jd|G?sBG?)&E<4iJG?h9HxcL_92|7-+_{rvS%&KLQAvuife_@& z&FCdM*B_e@S(Mhfay|z}Kmo*c0y(fZola8`NsKX6Zop6!#%)esx%7kWLrk2oFvh!j zRsn-%==?kao%wvOVvI?aWq933rwL(qhq2{x($prH*^3F#0Su6}-!1f74v11J{JTgf zLr$;X-7R{7>B(G7ZNuk(|LsT3{fk=tFw-jFn`VtMAN=*jTR#q(?30iG$<8hMmoGp0 z!~50bVAfm~y9VnQ*Gi2IfIXlO^cuO6MkTe2^L%^K1!|ydTG0?WLja(=0zPdBBs(Gy zn$^ZPfK5XnoVnOLuZC(8MX&D_*BLww*wCmT+}MrX*#DpX7iXTUrIOxbGynhq07*qo IM6N<$f?B=}0ssI2 literal 0 HcmV?d00001 diff --git a/files/wizard/icons/tango/48x48/system-installer.png b/files/wizard/icons/tango/48x48/system-installer.png new file mode 100644 index 0000000000000000000000000000000000000000..dbd08f7e2f5d40f313206326b379ccc467c139c1 GIT binary patch literal 2663 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 + + icons/tango/index.theme + icons/tango/48x48/folder.png + icons/tango/48x48/system-installer.png + + From 8162f8370b21d4b666693a3260fdd5cbc1bb6b03 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sun, 8 Dec 2013 21:35:57 +0100 Subject: [PATCH 0003/1983] Added all the pages to the wizard --- apps/wizard/CMakeLists.txt | 32 +++++++++++-- apps/wizard/componentselectionpage.cpp | 7 +++ apps/wizard/componentselectionpage.hpp | 21 ++++++++ apps/wizard/conclusionpage.cpp | 7 +++ apps/wizard/conclusionpage.hpp | 21 ++++++++ apps/wizard/existinginstallationpage.cpp | 7 +++ apps/wizard/existinginstallationpage.hpp | 21 ++++++++ apps/wizard/importpage.cpp | 7 +++ apps/wizard/importpage.hpp | 21 ++++++++ apps/wizard/installationpage.cpp | 7 +++ apps/wizard/installationpage.hpp | 21 ++++++++ apps/wizard/installationtargetpage.cpp | 7 +++ apps/wizard/installationtargetpage.hpp | 21 ++++++++ apps/wizard/intropage.cpp | 7 +++ apps/wizard/intropage.hpp | 21 ++++++++ apps/wizard/main.cpp | 11 ++--- apps/wizard/mainwizard.cpp | 36 ++++++++++++++ apps/wizard/mainwizard.hpp | 33 +++++++++++++ apps/wizard/methodselectionpage.cpp | 7 +++ apps/wizard/methodselectionpage.hpp | 21 ++++++++ apps/wizard/wizard.cpp | 1 - apps/wizard/wizard.hpp | 1 - files/ui/wizard/configurationpage.ui | 58 ----------------------- files/ui/wizard/installationtargetpage.ui | 10 ---- 24 files changed, 324 insertions(+), 82 deletions(-) create mode 100644 apps/wizard/componentselectionpage.cpp create mode 100644 apps/wizard/componentselectionpage.hpp create mode 100644 apps/wizard/conclusionpage.cpp create mode 100644 apps/wizard/conclusionpage.hpp create mode 100644 apps/wizard/existinginstallationpage.cpp create mode 100644 apps/wizard/existinginstallationpage.hpp create mode 100644 apps/wizard/importpage.cpp create mode 100644 apps/wizard/importpage.hpp create mode 100644 apps/wizard/installationpage.cpp create mode 100644 apps/wizard/installationpage.hpp create mode 100644 apps/wizard/installationtargetpage.cpp create mode 100644 apps/wizard/installationtargetpage.hpp create mode 100644 apps/wizard/intropage.cpp create mode 100644 apps/wizard/intropage.hpp create mode 100644 apps/wizard/mainwizard.cpp create mode 100644 apps/wizard/mainwizard.hpp create mode 100644 apps/wizard/methodselectionpage.cpp create mode 100644 apps/wizard/methodselectionpage.hpp delete mode 100644 apps/wizard/wizard.cpp delete mode 100644 apps/wizard/wizard.hpp delete mode 100644 files/ui/wizard/configurationpage.ui diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index d6423daad..3ff41c206 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -1,14 +1,29 @@ set(WIZARD + componentselectionpage.cpp + conclusionpage.cpp + existinginstallationpage.cpp + importpage.cpp + installationpage.cpp + installationtargetpage.cpp + intropage.cpp main.cpp - wizard.cpp - + mainwizard.cpp + methodselectionpage.cpp ) # if(NOT WIN32) # LIST(APPEND WIZARD unshieldthread.cpp) # endif(NOT WIN32) set(WIZARD_HEADER - wizard.hpp + componentselectionpage.hpp + conclusionpage.hpp + existinginstallationpage.hpp + importpage.hpp + installationpage.hpp + installationtargetpage.hpp + intropage.hpp + mainwizard.hpp + methodselectionpage.hpp ) # if(NOT WIN32) # LIST(APPEND WIZARD_HEADER unshieldthread.hpp) @@ -17,7 +32,15 @@ set(WIZARD_HEADER # Headers that must be pre-processed set(WIZARD_HEADER_MOC - wizard.hpp + componentselectionpage.hpp + conclusionpage.hpp + existinginstallationpage.hpp + importpage.hpp + installationpage.hpp + installationtargetpage.hpp + intropage.hpp + mainwizard.hpp + methodselectionpage.hpp ) # if(NOT WIN32) @@ -28,7 +51,6 @@ set(WIZARD_HEADER_MOC set(WIZARD_UI ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/configurationpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/existinginstallationpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/importpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp new file mode 100644 index 000000000..dbfa9412e --- /dev/null +++ b/apps/wizard/componentselectionpage.cpp @@ -0,0 +1,7 @@ +#include "componentselectionpage.hpp" + +Wizard::ComponentSelectionPage::ComponentSelectionPage(QWidget *parent) : + QWizardPage(parent) +{ + setupUi(this); +} diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp new file mode 100644 index 000000000..cc36ee485 --- /dev/null +++ b/apps/wizard/componentselectionpage.hpp @@ -0,0 +1,21 @@ +#ifndef COMPONENTSELECTIONPAGE_HPP +#define COMPONENTSELECTIONPAGE_HPP + +#include + +#include "ui_componentselectionpage.h" + +namespace Wizard +{ + + class ComponentSelectionPage : public QWizardPage, private Ui::ComponentSelectionPage + { + Q_OBJECT + public: + ComponentSelectionPage(QWidget *parent = 0); + + }; + +} + +#endif // COMPONENTSELECTIONPAGE_HPP diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp new file mode 100644 index 000000000..2913e431d --- /dev/null +++ b/apps/wizard/conclusionpage.cpp @@ -0,0 +1,7 @@ +#include "conclusionpage.hpp" + +Wizard::ConclusionPage::ConclusionPage(QWidget *parent) : + QWizardPage(parent) +{ + setupUi(this); +} diff --git a/apps/wizard/conclusionpage.hpp b/apps/wizard/conclusionpage.hpp new file mode 100644 index 000000000..37c477b44 --- /dev/null +++ b/apps/wizard/conclusionpage.hpp @@ -0,0 +1,21 @@ +#ifndef CONCLUSIONPAGE_HPP +#define CONCLUSIONPAGE_HPP + +#include + +#include "ui_conclusionpage.h" + +namespace Wizard +{ + + class ConclusionPage : public QWizardPage, private Ui::ConclusionPage + { + Q_OBJECT + public: + ConclusionPage(QWidget *parent = 0); + + }; + +} + +#endif // CONCLUSIONPAGE_HPP diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp new file mode 100644 index 000000000..ad540de73 --- /dev/null +++ b/apps/wizard/existinginstallationpage.cpp @@ -0,0 +1,7 @@ +#include "existinginstallationpage.hpp" + +Wizard::ExistingInstallationPage::ExistingInstallationPage(QWidget *parent) : + QWizardPage(parent) +{ + setupUi(this); +} diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp new file mode 100644 index 000000000..5048a14d4 --- /dev/null +++ b/apps/wizard/existinginstallationpage.hpp @@ -0,0 +1,21 @@ +#ifndef EXISTINGINSTALLATIONPAGE_HPP +#define EXISTINGINSTALLATIONPAGE_HPP + +#include + +#include "ui_existinginstallationpage.h" + +namespace Wizard +{ + + class ExistingInstallationPage : public QWizardPage, private Ui::ExistingInstallationPage + { + Q_OBJECT + public: + ExistingInstallationPage(QWidget *parent = 0); + + }; + +} + +#endif // EXISTINGINSTALLATIONPAGE_HPP diff --git a/apps/wizard/importpage.cpp b/apps/wizard/importpage.cpp new file mode 100644 index 000000000..2ac92efd0 --- /dev/null +++ b/apps/wizard/importpage.cpp @@ -0,0 +1,7 @@ +#include "importpage.hpp" + +Wizard::ImportPage::ImportPage(QWidget *parent) : + QWizardPage(parent) +{ + setupUi(this); +} diff --git a/apps/wizard/importpage.hpp b/apps/wizard/importpage.hpp new file mode 100644 index 000000000..e3917c837 --- /dev/null +++ b/apps/wizard/importpage.hpp @@ -0,0 +1,21 @@ +#ifndef IMPORTPAGE_HPP +#define IMPORTPAGE_HPP + +#include + +#include "ui_importpage.h" + +namespace Wizard +{ + + class ImportPage : public QWizardPage, private Ui::ImportPage + { + Q_OBJECT + public: + ImportPage(QWidget *parent = 0); + + }; + +} + +#endif // IMPORTPAGE_HPP diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp new file mode 100644 index 000000000..20737ebfb --- /dev/null +++ b/apps/wizard/installationpage.cpp @@ -0,0 +1,7 @@ +#include "installationpage.hpp" + +Wizard::InstallationPage::InstallationPage(QWidget *parent) : + QWizardPage(parent) +{ + setupUi(this); +} diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp new file mode 100644 index 000000000..3c4470047 --- /dev/null +++ b/apps/wizard/installationpage.hpp @@ -0,0 +1,21 @@ +#ifndef INSTALLATIONPAGE_HPP +#define INSTALLATIONPAGE_HPP + +#include + +#include "ui_installationpage.h" + +namespace Wizard +{ + + class InstallationPage : public QWizardPage, private Ui::InstallationPage + { + Q_OBJECT + public: + InstallationPage(QWidget *parent = 0); + + }; + +} + +#endif // INSTALLATIONPAGE_HPP diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp new file mode 100644 index 000000000..7632b2331 --- /dev/null +++ b/apps/wizard/installationtargetpage.cpp @@ -0,0 +1,7 @@ +#include "installationtargetpage.hpp" + +Wizard::InstallationTargetPage::InstallationTargetPage(QWidget *parent) : + QWizardPage(parent) +{ + setupUi(this); +} diff --git a/apps/wizard/installationtargetpage.hpp b/apps/wizard/installationtargetpage.hpp new file mode 100644 index 000000000..266d1adc7 --- /dev/null +++ b/apps/wizard/installationtargetpage.hpp @@ -0,0 +1,21 @@ +#ifndef INSTALLATIONTARGETPAGE_HPP +#define INSTALLATIONTARGETPAGE_HPP + +#include + +#include "ui_installationtargetpage.h" + +namespace Wizard +{ + + class InstallationTargetPage : public QWizardPage, private Ui::InstallationTargetPage + { + Q_OBJECT + public: + InstallationTargetPage(QWidget *parent = 0); + + }; + +} + +#endif // INSTALLATIONTARGETPAGE_HPP diff --git a/apps/wizard/intropage.cpp b/apps/wizard/intropage.cpp new file mode 100644 index 000000000..7b390f059 --- /dev/null +++ b/apps/wizard/intropage.cpp @@ -0,0 +1,7 @@ +#include "intropage.hpp" + +Wizard::IntroPage::IntroPage(QWidget *parent) : + QWizardPage(parent) +{ + setupUi(this); +} diff --git a/apps/wizard/intropage.hpp b/apps/wizard/intropage.hpp new file mode 100644 index 000000000..b247efd89 --- /dev/null +++ b/apps/wizard/intropage.hpp @@ -0,0 +1,21 @@ +#ifndef INTROPAGE_HPP +#define INTROPAGE_HPP + +#include + +#include "ui_intropage.h" + +namespace Wizard +{ + + class IntroPage : public QWizardPage, private Ui::IntroPage + { + Q_OBJECT + public: + IntroPage(QWidget *parent = 0); + + }; + +} + +#endif // INTROPAGE_HPP diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index ed3f3a6ea..e6a94118a 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -3,6 +3,8 @@ #include #include +#include "mainwizard.hpp" + #ifdef MAC_OS_X_VERSION_MIN_REQUIRED #undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 @@ -39,13 +41,8 @@ int main(int argc, char *argv[]) // Support non-latin characters QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); -// Launcher::MainDialog mainWin; - -// if (mainWin.setup()) { -// mainWin.show(); -// } else { -// return 0; -// } + Wizard::MainWizard wizard; + wizard.show(); return app.exec(); } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp new file mode 100644 index 000000000..438d9e576 --- /dev/null +++ b/apps/wizard/mainwizard.cpp @@ -0,0 +1,36 @@ +#include "mainwizard.hpp" +#include "intropage.hpp" +#include "methodselectionpage.hpp" +#include "existinginstallationpage.hpp" +#include "installationtargetpage.hpp" +#include "componentselectionpage.hpp" +#include "installationpage.hpp" +#include "importpage.hpp" +#include "conclusionpage.hpp" + +Wizard::MainWizard::MainWizard(QWidget *parent) : + QWizard(parent) +{ + +#ifndef Q_OS_MAC + setWizardStyle(QWizard::ModernStyle); +#else + setWizardStyle(QWizard::ClassicStyle); +#endif + + setWindowTitle(tr("OpenMW Wizard")); + setupPages(); +} + +void Wizard::MainWizard::setupPages() +{ + setPage(Page_Intro, new IntroPage); + setPage(Page_MethodSelection, new MethodSelectionPage); + setPage(Page_ExistingInstallation, new ExistingInstallationPage); + setPage(Page_InstallationTarget, new InstallationTargetPage); + setPage(Page_ComponentSelection, new ComponentSelectionPage); + setPage(Page_Installation, new InstallationPage); + setPage(Page_Import, new ImportPage); + setPage(Page_Conclusion, new ConclusionPage); + setStartId(Page_Intro); +} diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp new file mode 100644 index 000000000..e5cdc866b --- /dev/null +++ b/apps/wizard/mainwizard.hpp @@ -0,0 +1,33 @@ +#ifndef MAINWIZARD_HPP +#define MAINWIZARD_HPP + +#include + +namespace Wizard +{ + + class MainWizard : public QWizard + { + Q_OBJECT + + public: + enum { + Page_Intro, + Page_MethodSelection, + Page_ExistingInstallation, + Page_InstallationTarget, + Page_ComponentSelection, + Page_Installation, + Page_Import, + Page_Conclusion + }; + + MainWizard(QWidget *parent = 0); + + private: + void setupPages(); + }; + +} + +#endif // MAINWIZARD_HPP diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp new file mode 100644 index 000000000..bd9997367 --- /dev/null +++ b/apps/wizard/methodselectionpage.cpp @@ -0,0 +1,7 @@ +#include "methodselectionpage.hpp" + +Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : + QWizardPage(parent) +{ + setupUi(this); +} diff --git a/apps/wizard/methodselectionpage.hpp b/apps/wizard/methodselectionpage.hpp new file mode 100644 index 000000000..cc32a825e --- /dev/null +++ b/apps/wizard/methodselectionpage.hpp @@ -0,0 +1,21 @@ +#ifndef METHODSELECTIONPAGE_HPP +#define METHODSELECTIONPAGE_HPP + +#include + +#include "ui_methodselectionpage.h" + +namespace Wizard +{ + + class MethodSelectionPage : public QWizardPage, private Ui::MethodSelectionPage + { + Q_OBJECT + public: + MethodSelectionPage(QWidget *parent = 0); + + }; + +} + +#endif // METHODSELECTIONPAGE_HPP diff --git a/apps/wizard/wizard.cpp b/apps/wizard/wizard.cpp deleted file mode 100644 index 8d1c8b69c..000000000 --- a/apps/wizard/wizard.cpp +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/wizard/wizard.hpp b/apps/wizard/wizard.hpp deleted file mode 100644 index 8d1c8b69c..000000000 --- a/apps/wizard/wizard.hpp +++ /dev/null @@ -1 +0,0 @@ - diff --git a/files/ui/wizard/configurationpage.ui b/files/ui/wizard/configurationpage.ui deleted file mode 100644 index 8340b2ebf..000000000 --- a/files/ui/wizard/configurationpage.ui +++ /dev/null @@ -1,58 +0,0 @@ - - - WizardPage - - - - 0 - 0 - 440 - 300 - - - - WizardPage - - - - - - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly. Select the steps you want to be taken and click Next when you are ready to continue.</p><p><span style=" font-weight:600;">Note:</span> it is possible to import settings later, by re-running this Wizard.</p><p/></body></html> - - - true - - - - - - - Import settings from Morrowind.ini - - - - - - - Import selected add-ons (creates a new Cotent List in the launcher) - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - diff --git a/files/ui/wizard/installationtargetpage.ui b/files/ui/wizard/installationtargetpage.ui index 6bdac5592..c87214d8d 100644 --- a/files/ui/wizard/installationtargetpage.ui +++ b/files/ui/wizard/installationtargetpage.ui @@ -53,16 +53,6 @@ - - - - Click Next to continue. If you would like to select a different location, click Browse. - - - true - - - From 0e1d3237fe280a07a228eda14008bc116e36463a Mon Sep 17 00:00:00 2001 From: pvdk Date: Sun, 8 Dec 2013 22:58:29 +0100 Subject: [PATCH 0004/1983] Added some logic to the page switching --- apps/wizard/componentselectionpage.cpp | 7 +++++++ apps/wizard/componentselectionpage.hpp | 2 ++ apps/wizard/conclusionpage.cpp | 5 +++++ apps/wizard/conclusionpage.hpp | 2 ++ apps/wizard/existinginstallationpage.cpp | 7 +++++++ apps/wizard/existinginstallationpage.hpp | 2 ++ apps/wizard/importpage.cpp | 7 +++++++ apps/wizard/importpage.hpp | 2 ++ apps/wizard/installationpage.cpp | 7 +++++++ apps/wizard/installationpage.hpp | 2 ++ apps/wizard/installationtargetpage.cpp | 7 +++++++ apps/wizard/installationtargetpage.hpp | 2 ++ apps/wizard/intropage.cpp | 7 +++++++ apps/wizard/intropage.hpp | 2 ++ apps/wizard/methodselectionpage.cpp | 11 +++++++++++ apps/wizard/methodselectionpage.hpp | 2 ++ 16 files changed, 74 insertions(+) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index dbfa9412e..0293bdc05 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -1,7 +1,14 @@ #include "componentselectionpage.hpp" +#include "mainwizard.hpp" + Wizard::ComponentSelectionPage::ComponentSelectionPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); } + +int Wizard::ComponentSelectionPage::nextId() const +{ + return MainWizard::Page_Installation; +} diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp index cc36ee485..277845774 100644 --- a/apps/wizard/componentselectionpage.hpp +++ b/apps/wizard/componentselectionpage.hpp @@ -14,6 +14,8 @@ namespace Wizard public: ComponentSelectionPage(QWidget *parent = 0); + int nextId() const; + }; } diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 2913e431d..567d325b3 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -5,3 +5,8 @@ Wizard::ConclusionPage::ConclusionPage(QWidget *parent) : { setupUi(this); } + +int Wizard::ConclusionPage::nextId() const +{ + return -1; +} diff --git a/apps/wizard/conclusionpage.hpp b/apps/wizard/conclusionpage.hpp index 37c477b44..e00fc0fe3 100644 --- a/apps/wizard/conclusionpage.hpp +++ b/apps/wizard/conclusionpage.hpp @@ -14,6 +14,8 @@ namespace Wizard public: ConclusionPage(QWidget *parent = 0); + int nextId() const; + }; } diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index ad540de73..d6dea0af9 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -1,7 +1,14 @@ #include "existinginstallationpage.hpp" +#include "mainwizard.hpp" + Wizard::ExistingInstallationPage::ExistingInstallationPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); } + +int Wizard::ExistingInstallationPage::nextId() const +{ + return MainWizard::Page_ComponentSelection; +} diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp index 5048a14d4..823bc2140 100644 --- a/apps/wizard/existinginstallationpage.hpp +++ b/apps/wizard/existinginstallationpage.hpp @@ -14,6 +14,8 @@ namespace Wizard public: ExistingInstallationPage(QWidget *parent = 0); + int nextId() const; + }; } diff --git a/apps/wizard/importpage.cpp b/apps/wizard/importpage.cpp index 2ac92efd0..29db6919d 100644 --- a/apps/wizard/importpage.cpp +++ b/apps/wizard/importpage.cpp @@ -1,7 +1,14 @@ #include "importpage.hpp" +#include "mainwizard.hpp" + Wizard::ImportPage::ImportPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); } + +int Wizard::ImportPage::nextId() const +{ + return MainWizard::Page_Conclusion; +} diff --git a/apps/wizard/importpage.hpp b/apps/wizard/importpage.hpp index e3917c837..f1cc11e03 100644 --- a/apps/wizard/importpage.hpp +++ b/apps/wizard/importpage.hpp @@ -14,6 +14,8 @@ namespace Wizard public: ImportPage(QWidget *parent = 0); + int nextId() const; + }; } diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 20737ebfb..092cf62e0 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -1,7 +1,14 @@ #include "installationpage.hpp" +#include "mainwizard.hpp" + Wizard::InstallationPage::InstallationPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); } + +int Wizard::InstallationPage::nextId() const +{ + return MainWizard::Page_Import; +} diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index 3c4470047..125fcc2e1 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -14,6 +14,8 @@ namespace Wizard public: InstallationPage(QWidget *parent = 0); + int nextId() const; + }; } diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 7632b2331..fa178f913 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -1,7 +1,14 @@ #include "installationtargetpage.hpp" +#include "mainwizard.hpp" + Wizard::InstallationTargetPage::InstallationTargetPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); } + +int Wizard::InstallationTargetPage::nextId() const +{ + return MainWizard::Page_ComponentSelection; +} diff --git a/apps/wizard/installationtargetpage.hpp b/apps/wizard/installationtargetpage.hpp index 266d1adc7..4e399b707 100644 --- a/apps/wizard/installationtargetpage.hpp +++ b/apps/wizard/installationtargetpage.hpp @@ -14,6 +14,8 @@ namespace Wizard public: InstallationTargetPage(QWidget *parent = 0); + int nextId() const; + }; } diff --git a/apps/wizard/intropage.cpp b/apps/wizard/intropage.cpp index 7b390f059..72c769ae6 100644 --- a/apps/wizard/intropage.cpp +++ b/apps/wizard/intropage.cpp @@ -1,7 +1,14 @@ #include "intropage.hpp" +#include "mainwizard.hpp" + Wizard::IntroPage::IntroPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); } + +int Wizard::IntroPage::nextId() const +{ + return MainWizard::Page_MethodSelection; +} diff --git a/apps/wizard/intropage.hpp b/apps/wizard/intropage.hpp index b247efd89..603d283fa 100644 --- a/apps/wizard/intropage.hpp +++ b/apps/wizard/intropage.hpp @@ -14,6 +14,8 @@ namespace Wizard public: IntroPage(QWidget *parent = 0); + int nextId() const; + }; } diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index bd9997367..59125f251 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -1,7 +1,18 @@ #include "methodselectionpage.hpp" +#include "mainwizard.hpp" + Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); } + +int Wizard::MethodSelectionPage::nextId() const +{ + if (newLocationRadioButton->isChecked()) { + return MainWizard::Page_InstallationTarget; + } else { + return MainWizard::Page_ExistingInstallation; + } +} diff --git a/apps/wizard/methodselectionpage.hpp b/apps/wizard/methodselectionpage.hpp index cc32a825e..1555ea157 100644 --- a/apps/wizard/methodselectionpage.hpp +++ b/apps/wizard/methodselectionpage.hpp @@ -14,6 +14,8 @@ namespace Wizard public: MethodSelectionPage(QWidget *parent = 0); + int nextId() const; + }; } From d7f96041400c8398da4fd061b3218671e598d81a Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 13 Dec 2013 13:38:49 +0100 Subject: [PATCH 0005/1983] WIP: Working on the installation selection and added the ui for the language page --- apps/wizard/componentselectionpage.cpp | 107 +++++++++++++++++++- apps/wizard/componentselectionpage.hpp | 12 ++- apps/wizard/conclusionpage.cpp | 21 +++- apps/wizard/conclusionpage.hpp | 9 +- apps/wizard/existinginstallationpage.cpp | 97 +++++++++++++++++- apps/wizard/existinginstallationpage.hpp | 15 ++- apps/wizard/importpage.cpp | 5 +- apps/wizard/importpage.hpp | 6 +- apps/wizard/installationpage.cpp | 13 ++- apps/wizard/installationpage.hpp | 9 +- apps/wizard/installationtargetpage.cpp | 33 +++++- apps/wizard/installationtargetpage.hpp | 12 ++- apps/wizard/intropage.cpp | 6 +- apps/wizard/intropage.hpp | 5 +- apps/wizard/mainwizard.cpp | 69 +++++++++++-- apps/wizard/mainwizard.hpp | 13 +++ apps/wizard/methodselectionpage.cpp | 11 +- apps/wizard/methodselectionpage.hpp | 6 +- files/ui/wizard/conclusionpage.ui | 8 +- files/ui/wizard/existinginstallationpage.ui | 9 +- files/ui/wizard/languageselectionpage.ui | 72 +++++++++++++ files/wizard/wizard.qrc | 7 +- 22 files changed, 499 insertions(+), 46 deletions(-) create mode 100644 files/ui/wizard/languageselectionpage.ui diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 0293bdc05..1ec1380b1 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -1,14 +1,115 @@ #include "componentselectionpage.hpp" +#include +#include + #include "mainwizard.hpp" -Wizard::ComponentSelectionPage::ComponentSelectionPage(QWidget *parent) : - QWizardPage(parent) +Wizard::ComponentSelectionPage::ComponentSelectionPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) { setupUi(this); + + setCommitPage(true); + setButtonText(QWizard::CommitButton, tr("&Install")); + + connect(componentsList, SIGNAL(itemChanged(QListWidgetItem*)), + this, SLOT(updateButton(QListWidgetItem*))); +} + +void Wizard::ComponentSelectionPage::updateButton(QListWidgetItem *item) +{ + if (field("installation.new").toBool() == true) + return; // Morrowind is always checked here + + bool unchecked = true; + + for (int i =0; i < componentsList->count(); ++i) { + QListWidgetItem *item = componentsList->item(i); + + qDebug() << "item is " << item->text(); + + if (!item) + continue; + + if (item->checkState() == Qt::Checked) { + unchecked = false; + } + } + + if (unchecked) { + setCommitPage(false); + setButtonText(QWizard::NextButton, tr("&Skip")); + } else { + setCommitPage(true); + } +} + +void Wizard::ComponentSelectionPage::initializePage() +{ + componentsList->clear(); + + QString path = field("installation.path").toString(); + + QListWidgetItem *morrowindItem = new QListWidgetItem(QString("Morrowind")); + QListWidgetItem *tribunalItem = new QListWidgetItem(QString("Tribunal")); + QListWidgetItem *bloodmoonItem = new QListWidgetItem(QString("Bloodmoon")); + + if (field("installation.new").toBool() == true) + { + morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); + componentsList->addItem(morrowindItem); + + tribunalItem->setFlags(tribunalItem->flags() | Qt::ItemIsUserCheckable); + tribunalItem->setData(Qt::CheckStateRole, Qt::Checked); + componentsList->addItem(tribunalItem); + + bloodmoonItem->setFlags(bloodmoonItem->flags() | Qt::ItemIsUserCheckable); + bloodmoonItem->setData(Qt::CheckStateRole, Qt::Checked); + componentsList->addItem(bloodmoonItem); + } else { + + if (mWizard->mInstallations[path]->hasMorrowind == true) { + morrowindItem->setText(tr("Morrowind\t\t(installed)")); + morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + morrowindItem->setData(Qt::CheckStateRole, Qt::Unchecked); + } else { + morrowindItem->setText(tr("Morrowind")); + morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); + } + + componentsList->addItem(morrowindItem); + + if (mWizard->mInstallations[path]->hasTribunal == true) { + tribunalItem->setText(tr("Tribunal\t\t(installed)")); + tribunalItem->setFlags(tribunalItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + tribunalItem->setData(Qt::CheckStateRole, Qt::Unchecked); + } else { + tribunalItem->setText(tr("Tribunal")); + tribunalItem->setData(Qt::CheckStateRole, Qt::Checked); + } + + componentsList->addItem(tribunalItem); + + if (mWizard->mInstallations[path]->hasBloodmoon == true) { + bloodmoonItem->setText(tr("Bloodmoon\t\t(installed)")); + bloodmoonItem->setFlags(bloodmoonItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + bloodmoonItem->setData(Qt::CheckStateRole, Qt::Unchecked); + } else { + bloodmoonItem->setText(tr("Bloodmoon")); + bloodmoonItem->setData(Qt::CheckStateRole, Qt::Checked); + } + + componentsList->addItem(bloodmoonItem); + } } int Wizard::ComponentSelectionPage::nextId() const { - return MainWizard::Page_Installation; + if (isCommitPage()) + return MainWizard::Page_Installation; + + return MainWizard::Page_Import; } diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp index 277845774..8b4c186d0 100644 --- a/apps/wizard/componentselectionpage.hpp +++ b/apps/wizard/componentselectionpage.hpp @@ -7,15 +7,25 @@ namespace Wizard { + class MainWizard; class ComponentSelectionPage : public QWizardPage, private Ui::ComponentSelectionPage { Q_OBJECT public: - ComponentSelectionPage(QWidget *parent = 0); + ComponentSelectionPage(MainWizard *wizard); int nextId() const; + private slots: + void updateButton(QListWidgetItem *item); + + private: + MainWizard *mWizard; + + protected: + void initializePage(); + }; } diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 567d325b3..55b03b912 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -1,11 +1,28 @@ #include "conclusionpage.hpp" -Wizard::ConclusionPage::ConclusionPage(QWidget *parent) : - QWizardPage(parent) +#include + +#include "mainwizard.hpp" + +Wizard::ConclusionPage::ConclusionPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) { setupUi(this); } +void Wizard::ConclusionPage::initializePage() +{ + if (field("installation.new").toBool() == true) + { + textLabel->setText(tr("The OpenMW Wizard successfully installed Morrowind on your computer.\n\n") + + tr("Click Finish to close the Wizard.")); + } else { + textLabel->setText(tr("The OpenMW Wizard successfully modified your existing Morrowind installation.\n\n") + + tr("Click Finish to close the Wizard.")); + } +} + int Wizard::ConclusionPage::nextId() const { return -1; diff --git a/apps/wizard/conclusionpage.hpp b/apps/wizard/conclusionpage.hpp index e00fc0fe3..e050684e1 100644 --- a/apps/wizard/conclusionpage.hpp +++ b/apps/wizard/conclusionpage.hpp @@ -7,15 +7,22 @@ namespace Wizard { + class MainWizard; class ConclusionPage : public QWizardPage, private Ui::ConclusionPage { Q_OBJECT public: - ConclusionPage(QWidget *parent = 0); + ConclusionPage(MainWizard *wizard); int nextId() const; + private: + MainWizard *mWizard; + + protected: + void initializePage(); + }; } diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index d6dea0af9..f9eb7c283 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -1,14 +1,105 @@ #include "existinginstallationpage.hpp" +#include +#include + #include "mainwizard.hpp" -Wizard::ExistingInstallationPage::ExistingInstallationPage(QWidget *parent) : - QWizardPage(parent) +Wizard::ExistingInstallationPage::ExistingInstallationPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) { setupUi(this); + + connect(detectedList, SIGNAL(currentTextChanged(QString)), + this, SLOT(textChanged(QString))); + + connect(detectedList,SIGNAL(itemSelectionChanged()), + this, SIGNAL(completeChanged())); +} + +void Wizard::ExistingInstallationPage::on_browseButton_clicked() +{ + QString selectedFile = QFileDialog::getOpenFileName( + this, + tr("Select master file"), + QDir::currentPath(), + QString(tr("Morrowind master file (*.esm)")), + NULL, + QFileDialog::DontResolveSymlinks); + + QFileInfo info(selectedFile); + if (!info.exists()) + return; + + QDir dir(info.absolutePath()); + if (!dir.cdUp()) + return; // Cannot move out of the Data Files directory + + QString path = QDir::toNativeSeparators(dir.absolutePath()); + QList items = detectedList->findItems(path, Qt::MatchExactly); + + if (items.isEmpty()) + { + // Path is not yet in the list, add it + QListWidgetItem *item = new QListWidgetItem(path); + detectedList->addItem(item); + detectedList->setCurrentItem(item); // Select it too + } else { + detectedList->setCurrentItem(items.first()); + } + +} + +void Wizard::ExistingInstallationPage::textChanged(const QString &text) +{ + // Set the installation path manually, as registerField doesn't work + if (!text.isEmpty()) + wizard()->setField("installation.path", text); +} + +void Wizard::ExistingInstallationPage::initializePage() +{ + + QStringList paths = mWizard->mInstallations.keys(); + + if (paths.isEmpty()) + return; + + detectedList->clear(); + + foreach (const QString &path, paths) { + QListWidgetItem *item = new QListWidgetItem(path); + detectedList->addItem(item); + } + +} + +bool Wizard::ExistingInstallationPage::isComplete() const +{ + if (detectedList->selectionModel()->hasSelection()) { + return true; + } else { + return false; + } } int Wizard::ExistingInstallationPage::nextId() const { - return MainWizard::Page_ComponentSelection; + QString path = field("installation.path").toString(); + + if (path.isEmpty()) + return MainWizard::Page_ComponentSelection; + + if (!mWizard->mInstallations.contains(path)) + return MainWizard::Page_ComponentSelection; + + if (mWizard->mInstallations[path]->hasMorrowind == true && + mWizard->mInstallations[path]->hasTribunal == true && + mWizard->mInstallations[path]->hasBloodmoon == true) + { + return MainWizard::Page_Import; + } else { + return MainWizard::Page_ComponentSelection; + } } diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp index 823bc2140..88d6913bc 100644 --- a/apps/wizard/existinginstallationpage.hpp +++ b/apps/wizard/existinginstallationpage.hpp @@ -7,14 +7,27 @@ namespace Wizard { + class MainWizard; class ExistingInstallationPage : public QWizardPage, private Ui::ExistingInstallationPage { Q_OBJECT public: - ExistingInstallationPage(QWidget *parent = 0); + ExistingInstallationPage(MainWizard *wizard); int nextId() const; + virtual bool isComplete() const; + + private slots: + void on_browseButton_clicked(); + void textChanged(const QString &text); + + + private: + MainWizard *mWizard; + + protected: + void initializePage(); }; diff --git a/apps/wizard/importpage.cpp b/apps/wizard/importpage.cpp index 29db6919d..059683cf0 100644 --- a/apps/wizard/importpage.cpp +++ b/apps/wizard/importpage.cpp @@ -2,8 +2,9 @@ #include "mainwizard.hpp" -Wizard::ImportPage::ImportPage(QWidget *parent) : - QWizardPage(parent) +Wizard::ImportPage::ImportPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) { setupUi(this); } diff --git a/apps/wizard/importpage.hpp b/apps/wizard/importpage.hpp index f1cc11e03..2eae08531 100644 --- a/apps/wizard/importpage.hpp +++ b/apps/wizard/importpage.hpp @@ -7,15 +7,19 @@ namespace Wizard { + class MainWizard; class ImportPage : public QWizardPage, private Ui::ImportPage { Q_OBJECT public: - ImportPage(QWidget *parent = 0); + ImportPage(MainWizard *wizard); int nextId() const; + private: + MainWizard *mWizard; + }; } diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 092cf62e0..de0720ecb 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -1,13 +1,22 @@ #include "installationpage.hpp" +#include + #include "mainwizard.hpp" -Wizard::InstallationPage::InstallationPage(QWidget *parent) : - QWizardPage(parent) +Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) { setupUi(this); } +void Wizard::InstallationPage::initializePage() +{ + qDebug() << "installing to: " << field("installation.path").toString(); + logTextEdit->setText(QString("Installing to %1").arg(field("installation.path").toString())); +} + int Wizard::InstallationPage::nextId() const { return MainWizard::Page_Import; diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index 125fcc2e1..cb206e225 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -7,15 +7,22 @@ namespace Wizard { + class MainWizard; class InstallationPage : public QWizardPage, private Ui::InstallationPage { Q_OBJECT public: - InstallationPage(QWidget *parent = 0); + InstallationPage(MainWizard *wizard); int nextId() const; + private: + MainWizard *mWizard; + + protected: + void initializePage(); + }; } diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index fa178f913..43941f0dc 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -1,11 +1,40 @@ #include "installationtargetpage.hpp" +#include +#include + #include "mainwizard.hpp" -Wizard::InstallationTargetPage::InstallationTargetPage(QWidget *parent) : - QWizardPage(parent) +Wizard::InstallationTargetPage::InstallationTargetPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) { setupUi(this); + + registerField("installation.path*", targetLineEdit); +} + +void Wizard::InstallationTargetPage::initializePage() +{ + +} + +void Wizard::InstallationTargetPage::on_browseButton_clicked() +{ + QString selectedPath = QFileDialog::getExistingDirectory( + this, + tr("Select where to install Morrowind"), + QDir::currentPath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + + qDebug() << selectedPath; + QFileInfo info(selectedPath); + if (!info.exists()) + return; + + if (info.isWritable()) + targetLineEdit->setText(info.absoluteFilePath()); + } int Wizard::InstallationTargetPage::nextId() const diff --git a/apps/wizard/installationtargetpage.hpp b/apps/wizard/installationtargetpage.hpp index 4e399b707..ccaac5969 100644 --- a/apps/wizard/installationtargetpage.hpp +++ b/apps/wizard/installationtargetpage.hpp @@ -7,15 +7,25 @@ namespace Wizard { + class MainWizard; class InstallationTargetPage : public QWizardPage, private Ui::InstallationTargetPage { Q_OBJECT public: - InstallationTargetPage(QWidget *parent = 0); + InstallationTargetPage(MainWizard *wizard); int nextId() const; + private slots: + void on_browseButton_clicked(); + + private: + MainWizard *mWizard; + + protected: + void initializePage(); + }; } diff --git a/apps/wizard/intropage.cpp b/apps/wizard/intropage.cpp index 72c769ae6..93510cd21 100644 --- a/apps/wizard/intropage.cpp +++ b/apps/wizard/intropage.cpp @@ -2,8 +2,10 @@ #include "mainwizard.hpp" -Wizard::IntroPage::IntroPage(QWidget *parent) : - QWizardPage(parent) +Wizard::IntroPage::IntroPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) + { setupUi(this); } diff --git a/apps/wizard/intropage.hpp b/apps/wizard/intropage.hpp index 603d283fa..78bac3155 100644 --- a/apps/wizard/intropage.hpp +++ b/apps/wizard/intropage.hpp @@ -7,15 +7,18 @@ namespace Wizard { + class MainWizard; class IntroPage : public QWizardPage, private Ui::IntroPage { Q_OBJECT public: - IntroPage(QWidget *parent = 0); + IntroPage(MainWizard *wizard); int nextId() const; + private: + MainWizard *mWizard; }; } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 438d9e576..a741546fd 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -1,4 +1,8 @@ #include "mainwizard.hpp" + +#include +#include + #include "intropage.hpp" #include "methodselectionpage.hpp" #include "existinginstallationpage.hpp" @@ -19,18 +23,67 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : #endif setWindowTitle(tr("OpenMW Wizard")); + setupInstallations(); setupPages(); + + QDir dir("/home/pvdk/data"); + QFileInfo info(dir.absoluteFilePath("../Morrowind.ini")); + + qDebug() << "exists? " << info.exists(); +} + +void Wizard::MainWizard::setupInstallations() +{ + // TODO: detect existing installations + QStringList paths; + paths << QString("/home/pvdk/.wine/drive_c/Program Files/Bethesda Softworks/Morrowind"); + paths << QString("/home/pvdk/openmw/Data Files"); + paths << QString("/usr/games/morrowind"); + + foreach (const QString &path, paths) + { + Installation* install = new Installation(); + + install->hasMorrowind = (findFiles(QString("Morrowind"), path)); + install->hasTribunal = true; + install->hasBloodmoon = false; + + mInstallations.insert(QDir::toNativeSeparators(path), install); + } + } void Wizard::MainWizard::setupPages() { - setPage(Page_Intro, new IntroPage); - setPage(Page_MethodSelection, new MethodSelectionPage); - setPage(Page_ExistingInstallation, new ExistingInstallationPage); - setPage(Page_InstallationTarget, new InstallationTargetPage); - setPage(Page_ComponentSelection, new ComponentSelectionPage); - setPage(Page_Installation, new InstallationPage); - setPage(Page_Import, new ImportPage); - setPage(Page_Conclusion, new ConclusionPage); + setPage(Page_Intro, new IntroPage(this)); + setPage(Page_MethodSelection, new MethodSelectionPage(this)); + setPage(Page_ExistingInstallation, new ExistingInstallationPage(this)); + setPage(Page_InstallationTarget, new InstallationTargetPage(this)); + setPage(Page_ComponentSelection, new ComponentSelectionPage(this)); + setPage(Page_Installation, new InstallationPage(this)); + setPage(Page_Import, new ImportPage(this)); + setPage(Page_Conclusion, new ConclusionPage(this)); setStartId(Page_Intro); } + +bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) +{ + QDir dir(path); + + if (!dir.exists()) + return false; + + if (!dir.cd(QString("Data Files"))) + return false; + + qDebug() << "name: " << name + QString(".esm") << dir.absolutePath(); + + // TODO: add MIME handling to make sure the files are real + if (dir.exists(name + QString(".esm")) && dir.exists(name + QString(".bsa"))) + { + return true; + } else { + return false; + } + +} diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index e5cdc866b..15706e8e3 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -2,6 +2,7 @@ #define MAINWIZARD_HPP #include +#include namespace Wizard { @@ -11,6 +12,12 @@ namespace Wizard Q_OBJECT public: + struct Installation { + bool hasMorrowind; + bool hasTribunal; + bool hasBloodmoon; + }; + enum { Page_Intro, Page_MethodSelection, @@ -24,8 +31,14 @@ namespace Wizard MainWizard(QWidget *parent = 0); + static bool findFiles(const QString &name, const QString &path); + QMap mInstallations; + private: + void setupInstallations(); void setupPages(); + + }; } diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index 59125f251..1efffc13d 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -1,18 +1,23 @@ #include "methodselectionpage.hpp" - +#include #include "mainwizard.hpp" -Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : - QWizardPage(parent) +Wizard::MethodSelectionPage::MethodSelectionPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) { setupUi(this); + + registerField("installation.new", newLocationRadioButton); } int Wizard::MethodSelectionPage::nextId() const { if (newLocationRadioButton->isChecked()) { + //wizard()->setField("installation.new", true); return MainWizard::Page_InstallationTarget; } else { + //wizard()->setField("installation.new", false); return MainWizard::Page_ExistingInstallation; } } diff --git a/apps/wizard/methodselectionpage.hpp b/apps/wizard/methodselectionpage.hpp index 1555ea157..de34ff555 100644 --- a/apps/wizard/methodselectionpage.hpp +++ b/apps/wizard/methodselectionpage.hpp @@ -7,15 +7,19 @@ namespace Wizard { + class MainWizard; class MethodSelectionPage : public QWizardPage, private Ui::MethodSelectionPage { Q_OBJECT public: - MethodSelectionPage(QWidget *parent = 0); + MethodSelectionPage(MainWizard *wizard); int nextId() const; + private: + MainWizard *mWizard; + }; } diff --git a/files/ui/wizard/conclusionpage.ui b/files/ui/wizard/conclusionpage.ui index bedad3e2a..a27f66789 100644 --- a/files/ui/wizard/conclusionpage.ui +++ b/files/ui/wizard/conclusionpage.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 398 + 298 @@ -20,9 +20,7 @@ - The OpenMW Wizard successfully installed Morrowind on your computer. - -Click Finish to close the Wizard. + Placeholder true diff --git a/files/ui/wizard/existinginstallationpage.ui b/files/ui/wizard/existinginstallationpage.ui index 579ddbe67..3ca831636 100644 --- a/files/ui/wizard/existinginstallationpage.ui +++ b/files/ui/wizard/existinginstallationpage.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 396 + 296 @@ -23,7 +23,7 @@ - Detected Installations: + Detected installations: @@ -33,6 +33,9 @@ No existing installations detected + + NoItemFlags + diff --git a/files/ui/wizard/languageselectionpage.ui b/files/ui/wizard/languageselectionpage.ui new file mode 100644 index 000000000..134a9de86 --- /dev/null +++ b/files/ui/wizard/languageselectionpage.ui @@ -0,0 +1,72 @@ + + + WizardPage + + + + 0 + 0 + 398 + 298 + + + + WizardPage + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + What is the language of the Morrowind installation? + + + true + + + + + + + + + + + + Qt::Vertical + + + + 20 + 230 + + + + + + + + + + + diff --git a/files/wizard/wizard.qrc b/files/wizard/wizard.qrc index 86b3795c4..b3bb722c9 100644 --- a/files/wizard/wizard.qrc +++ b/files/wizard/wizard.qrc @@ -1,7 +1,8 @@ - - + + + icons/tango/48x48/preferences-desktop-locale.png icons/tango/index.theme icons/tango/48x48/folder.png icons/tango/48x48/system-installer.png - + From c82f0ec35fb6464fd1485e61f0633cb51c8a8660 Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 13 Dec 2013 15:30:49 +0100 Subject: [PATCH 0006/1983] Implemented the language selection page --- apps/wizard/CMakeLists.txt | 4 ++ apps/wizard/languageselectionpage.cpp | 18 +++++ apps/wizard/languageselectionpage.hpp | 25 +++++++ apps/wizard/mainwizard.cpp | 2 + apps/wizard/mainwizard.hpp | 1 + apps/wizard/methodselectionpage.cpp | 8 +-- files/ui/wizard/languageselectionpage.ui | 88 ++++++++++++++++++++++-- 7 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 apps/wizard/languageselectionpage.cpp create mode 100644 apps/wizard/languageselectionpage.hpp diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 3ff41c206..a62327d85 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -6,6 +6,7 @@ set(WIZARD installationpage.cpp installationtargetpage.cpp intropage.cpp + languageselectionpage.cpp main.cpp mainwizard.cpp methodselectionpage.cpp @@ -22,6 +23,7 @@ set(WIZARD_HEADER installationpage.hpp installationtargetpage.hpp intropage.hpp + languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp ) @@ -39,6 +41,7 @@ set(WIZARD_HEADER_MOC installationpage.hpp installationtargetpage.hpp intropage.hpp + languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp ) @@ -56,6 +59,7 @@ set(WIZARD_UI ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationtargetpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/intropage.ui + ${CMAKE_SOURCE_DIR}/files/ui/wizard/languageselectionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/methodselectionpage.ui ) diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp new file mode 100644 index 000000000..dc64f1ae1 --- /dev/null +++ b/apps/wizard/languageselectionpage.cpp @@ -0,0 +1,18 @@ +#include "languageselectionpage.hpp" + +#include "mainwizard.hpp" + +Wizard::LanguageSelectionPage::LanguageSelectionPage(MainWizard *wizard) : + QWizardPage(wizard), + mWizard(wizard) +{ + setupUi(this); +} + +int Wizard::LanguageSelectionPage::nextId() const +{ + if (field("installation.new").toBool() == true) + return MainWizard::Page_InstallationTarget; + + return MainWizard::Page_ExistingInstallation; +} diff --git a/apps/wizard/languageselectionpage.hpp b/apps/wizard/languageselectionpage.hpp new file mode 100644 index 000000000..aa7a81ff1 --- /dev/null +++ b/apps/wizard/languageselectionpage.hpp @@ -0,0 +1,25 @@ +#ifndef LANGUAGESELECTIONPAGE_HPP +#define LANGUAGESELECTIONPAGE_HPP + +#include + +#include "ui_languageselectionpage.h" + +namespace Wizard +{ + class MainWizard; + + class LanguageSelectionPage : public QWizardPage, private Ui::LanguageSelectionPage + { + Q_OBJECT + public: + LanguageSelectionPage(MainWizard *wizard); + + int nextId() const; + + private: + MainWizard *mWizard; + }; +} + +#endif // LANGUAGESELECTIONPAGE_HPP diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index a741546fd..77f17499e 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -5,6 +5,7 @@ #include "intropage.hpp" #include "methodselectionpage.hpp" +#include "languageselectionpage.hpp" #include "existinginstallationpage.hpp" #include "installationtargetpage.hpp" #include "componentselectionpage.hpp" @@ -57,6 +58,7 @@ void Wizard::MainWizard::setupPages() { setPage(Page_Intro, new IntroPage(this)); setPage(Page_MethodSelection, new MethodSelectionPage(this)); + setPage(Page_LanguageSelection, new LanguageSelectionPage(this)); setPage(Page_ExistingInstallation, new ExistingInstallationPage(this)); setPage(Page_InstallationTarget, new InstallationTargetPage(this)); setPage(Page_ComponentSelection, new ComponentSelectionPage(this)); diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 15706e8e3..c6b541d35 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -21,6 +21,7 @@ namespace Wizard enum { Page_Intro, Page_MethodSelection, + Page_LanguageSelection, Page_ExistingInstallation, Page_InstallationTarget, Page_ComponentSelection, diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index 1efffc13d..4185fa2bc 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -13,11 +13,5 @@ Wizard::MethodSelectionPage::MethodSelectionPage(MainWizard *wizard) : int Wizard::MethodSelectionPage::nextId() const { - if (newLocationRadioButton->isChecked()) { - //wizard()->setField("installation.new", true); - return MainWizard::Page_InstallationTarget; - } else { - //wizard()->setField("installation.new", false); - return MainWizard::Page_ExistingInstallation; - } + return MainWizard::Page_LanguageSelection; } diff --git a/files/ui/wizard/languageselectionpage.ui b/files/ui/wizard/languageselectionpage.ui index 134a9de86..a76e3004b 100644 --- a/files/ui/wizard/languageselectionpage.ui +++ b/files/ui/wizard/languageselectionpage.ui @@ -1,7 +1,7 @@ - WizardPage - + LanguageSelectionPage + 0 @@ -13,11 +13,17 @@ WizardPage + + Select Morrowind Language + + + What is the language of the Morrowind installation? + - + 0 @@ -38,7 +44,7 @@ - What is the language of the Morrowind installation? + Select the language of the Morrowind installation. true @@ -48,7 +54,79 @@ - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 250 + 0 + + + + + English + + + + + French + + + + + Russian + + + + + Polish + + + + + Italian + + + + + Spanish + + + + + German + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + From 7132b9b0c590574e4d603c605a7307ba23c1151c Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 13 Dec 2013 23:48:55 +0100 Subject: [PATCH 0007/1983] Implement default installation path support for all platforms --- apps/wizard/componentselectionpage.cpp | 6 +++--- apps/wizard/installationtargetpage.cpp | 28 +++++++++++++++++++++++++- apps/wizard/languageselectionpage.cpp | 2 ++ apps/wizard/mainwizard.cpp | 5 ----- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 1ec1380b1..046f34076 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -52,9 +52,9 @@ void Wizard::ComponentSelectionPage::initializePage() QString path = field("installation.path").toString(); - QListWidgetItem *morrowindItem = new QListWidgetItem(QString("Morrowind")); - QListWidgetItem *tribunalItem = new QListWidgetItem(QString("Tribunal")); - QListWidgetItem *bloodmoonItem = new QListWidgetItem(QString("Bloodmoon")); + QListWidgetItem *morrowindItem = new QListWidgetItem(QLatin1String("Morrowind")); + QListWidgetItem *tribunalItem = new QListWidgetItem(QLatin1String("Tribunal")); + QListWidgetItem *bloodmoonItem = new QListWidgetItem(QLatin1String("Bloodmoon")); if (field("installation.new").toBool() == true) { diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 43941f0dc..0952abdcb 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "mainwizard.hpp" @@ -11,12 +12,37 @@ Wizard::InstallationTargetPage::InstallationTargetPage(MainWizard *wizard) : { setupUi(this); - registerField("installation.path*", targetLineEdit); + registerField(QLatin1String("installation.path*"), targetLineEdit); } void Wizard::InstallationTargetPage::initializePage() { +#ifdef Q_OS_WIN + QString path = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); +#endif +#ifdef Q_OS_MAC + QString path = QDesktopServices::storageLocation(QDesktopServices::DataLocation); +#endif + +#if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) + QString path = QFile::decodeName(qgetenv("XDG_DATA_HOME")); + + if (path.isEmpty()) + path = QDir::homePath() + QLatin1String("/.local/share"); +#endif + + path.append(QLatin1String("/openmw/data")); + + if (!QFile::exists(path)) { + QDir dir; + dir.mkpath(path); + } + + QDir dir(path); + targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); + + qDebug() << path; } void Wizard::InstallationTargetPage::on_browseButton_clicked() diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index dc64f1ae1..903ab175f 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -7,6 +7,8 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(MainWizard *wizard) : mWizard(wizard) { setupUi(this); + + registerField(QLatin1String("installation.language"), languagesComboBox); } int Wizard::LanguageSelectionPage::nextId() const diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 77f17499e..e6cb140d0 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -26,11 +26,6 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setWindowTitle(tr("OpenMW Wizard")); setupInstallations(); setupPages(); - - QDir dir("/home/pvdk/data"); - QFileInfo info(dir.absoluteFilePath("../Morrowind.ini")); - - qDebug() << "exists? " << info.exists(); } void Wizard::MainWizard::setupInstallations() From 77fe73799af9e3638528d79e2c72443c884fcde0 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sun, 15 Dec 2013 13:12:48 +0100 Subject: [PATCH 0008/1983] Enabled language setting to be accessable from different pages --- apps/wizard/componentselectionpage.cpp | 2 -- apps/wizard/installationpage.cpp | 2 ++ apps/wizard/installationtargetpage.cpp | 2 ++ apps/wizard/mainwizard.cpp | 4 ++++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 046f34076..b5618c677 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -28,8 +28,6 @@ void Wizard::ComponentSelectionPage::updateButton(QListWidgetItem *item) for (int i =0; i < componentsList->count(); ++i) { QListWidgetItem *item = componentsList->item(i); - qDebug() << "item is " << item->text(); - if (!item) continue; diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index de0720ecb..1053c3240 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -13,6 +13,8 @@ Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : void Wizard::InstallationPage::initializePage() { + QString path = field("installation.path").toString(); + qDebug() << "installing to: " << field("installation.path").toString(); logTextEdit->setText(QString("Installing to %1").arg(field("installation.path").toString())); } diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 0952abdcb..60acfd5f7 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -17,6 +17,8 @@ Wizard::InstallationTargetPage::InstallationTargetPage(MainWizard *wizard) : void Wizard::InstallationTargetPage::initializePage() { + qDebug() << mWizard->field("installation.language"); + #ifdef Q_OS_WIN QString path = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); #endif diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index e6cb140d0..58ef2e061 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -24,6 +24,10 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : #endif setWindowTitle(tr("OpenMW Wizard")); + + // Set the property for comboboxes to the text instead of index + setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); + setupInstallations(); setupPages(); } From 30710bad704cc9fa2ad7c4efd534c3cd7d3b5cd7 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sun, 15 Dec 2013 15:13:49 +0100 Subject: [PATCH 0009/1983] Modified components widget so the checked items can be stored for all pages to access --- apps/wizard/CMakeLists.txt | 6 +++ apps/wizard/componentselectionpage.cpp | 12 +++++- apps/wizard/componentselectionpage.hpp | 1 + apps/wizard/installationpage.cpp | 6 ++- apps/wizard/mainwizard.cpp | 2 + apps/wizard/utils/componentlistwidget.cpp | 46 +++++++++++++++++++++++ apps/wizard/utils/componentlistwidget.hpp | 29 ++++++++++++++ files/ui/wizard/componentselectionpage.ui | 13 +++++-- 8 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 apps/wizard/utils/componentlistwidget.cpp create mode 100644 apps/wizard/utils/componentlistwidget.hpp diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index a62327d85..741f790b5 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -10,6 +10,8 @@ set(WIZARD main.cpp mainwizard.cpp methodselectionpage.cpp + + utils/componentlistwidget.cpp ) # if(NOT WIN32) # LIST(APPEND WIZARD unshieldthread.cpp) @@ -26,6 +28,8 @@ set(WIZARD_HEADER languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp + + utils/componentlistwidget.hpp ) # if(NOT WIN32) # LIST(APPEND WIZARD_HEADER unshieldthread.hpp) @@ -44,6 +48,8 @@ set(WIZARD_HEADER_MOC languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp + + utils/componentlistwidget.hpp ) # if(NOT WIN32) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index b5618c677..50e6e8ef4 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -14,8 +14,16 @@ Wizard::ComponentSelectionPage::ComponentSelectionPage(MainWizard *wizard) : setCommitPage(true); setButtonText(QWizard::CommitButton, tr("&Install")); - connect(componentsList, SIGNAL(itemChanged(QListWidgetItem*)), - this, SLOT(updateButton(QListWidgetItem*))); + registerField("installation.components", componentsList); + + connect(componentsList, SIGNAL(itemChanged(QListWidgetItem *)), + this, SLOT(updateButton(QListWidgetItem *))); + +} + +void Wizard::ComponentSelectionPage::debugMe(QString &text) +{ + qDebug() << "Debug Me" << text; } void Wizard::ComponentSelectionPage::updateButton(QListWidgetItem *item) diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp index 8b4c186d0..f5e955782 100644 --- a/apps/wizard/componentselectionpage.hpp +++ b/apps/wizard/componentselectionpage.hpp @@ -19,6 +19,7 @@ namespace Wizard private slots: void updateButton(QListWidgetItem *item); + void debugMe(QString &text); private: MainWizard *mWizard; diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 1053c3240..632b9c6a6 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -14,9 +14,11 @@ Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : void Wizard::InstallationPage::initializePage() { QString path = field("installation.path").toString(); + QStringList components = field("installation.components").toStringList(); + + logTextEdit->append(QString("Installing to %1").arg(path)); + logTextEdit->append(QString("Installing %1.").arg(components.join(", "))); - qDebug() << "installing to: " << field("installation.path").toString(); - logTextEdit->setText(QString("Installing to %1").arg(field("installation.path").toString())); } int Wizard::InstallationPage::nextId() const diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 58ef2e061..e752d0455 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -28,6 +28,8 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : // Set the property for comboboxes to the text instead of index setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); + setDefaultProperty("ComponentListWidget", "mCheckedItems", "checkedItemsChanged"); + setupInstallations(); setupPages(); } diff --git a/apps/wizard/utils/componentlistwidget.cpp b/apps/wizard/utils/componentlistwidget.cpp new file mode 100644 index 000000000..c9a1c1940 --- /dev/null +++ b/apps/wizard/utils/componentlistwidget.cpp @@ -0,0 +1,46 @@ +#include "componentlistwidget.hpp" + +#include +#include + +ComponentListWidget::ComponentListWidget(QWidget *parent) : + QListWidget(parent) +{ + mCheckedItems = QStringList(); + + connect(this, SIGNAL(itemChanged(QListWidgetItem *)), + this, SLOT(updateCheckedItems(QListWidgetItem *))); +} + +void ComponentListWidget::addItem(QListWidgetItem *item) +{ + // The model does not emit a dataChanged signal when items are added + // So we need to update manually + QListWidget::insertItem(count(), item); + updateCheckedItems(item); + +} + +QStringList ComponentListWidget::checkedItems() +{ + mCheckedItems.removeDuplicates(); + return mCheckedItems; +} + +void ComponentListWidget::updateCheckedItems(QListWidgetItem *item) +{ + QString text = item->text(); + + if (item->checkState() == Qt::Checked) { + if (!mCheckedItems.contains(text)) + mCheckedItems.append(text); + } else { + if (mCheckedItems.contains(text)) + mCheckedItems.removeAll(text); + } + + mCheckedItems.removeDuplicates(); + + emit checkedItemsChanged(mCheckedItems); + +} diff --git a/apps/wizard/utils/componentlistwidget.hpp b/apps/wizard/utils/componentlistwidget.hpp new file mode 100644 index 000000000..c9c782d45 --- /dev/null +++ b/apps/wizard/utils/componentlistwidget.hpp @@ -0,0 +1,29 @@ +#ifndef COMPONENTLISTWIDGET_HPP +#define COMPONENTLISTWIDGET_HPP + +#include + +class ComponentListWidget : public QListWidget +{ + Q_OBJECT + + Q_PROPERTY(QStringList mCheckedItems READ checkedItems) + +public: + ComponentListWidget(QWidget *parent = 0); + + QStringList mCheckedItems; + QStringList checkedItems(); + + void addItem(QListWidgetItem *item); + +signals: + void checkedItemsChanged(const QStringList &items); + +private slots: + void updateCheckedItems(QListWidgetItem *item); +}; + + + +#endif // COMPONENTLISTWIDGET_HPP diff --git a/files/ui/wizard/componentselectionpage.ui b/files/ui/wizard/componentselectionpage.ui index c3333752a..026fcdff6 100644 --- a/files/ui/wizard/componentselectionpage.ui +++ b/files/ui/wizard/componentselectionpage.ui @@ -6,8 +6,8 @@ 0 0 - 454 - 393 + 448 + 387 @@ -38,7 +38,7 @@ - + @@ -55,6 +55,13 @@ + + + ComponentListWidget + QListWidget +
apps/wizard/utils/componentlistwidget.hpp
+
+
From 8ea31e5050f5ed83009b68a41effb57903218ee3 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 24 Dec 2013 19:38:21 +0100 Subject: [PATCH 0010/1983] Working on a Morrowind.ini reader --- apps/wizard/CMakeLists.txt | 32 ++++----------- apps/wizard/inisettings.cpp | 68 ++++++++++++++++++++++++++++++++ apps/wizard/inisettings.hpp | 45 +++++++++++++++++++++ apps/wizard/installationpage.cpp | 64 ++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 apps/wizard/inisettings.cpp create mode 100644 apps/wizard/inisettings.hpp diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 741f790b5..7639cc8e1 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -3,6 +3,7 @@ set(WIZARD conclusionpage.cpp existinginstallationpage.cpp importpage.cpp + inisettings.cpp installationpage.cpp installationtargetpage.cpp intropage.cpp @@ -10,31 +11,27 @@ set(WIZARD main.cpp mainwizard.cpp methodselectionpage.cpp + unshieldthread.cpp utils/componentlistwidget.cpp ) -# if(NOT WIN32) -# LIST(APPEND WIZARD unshieldthread.cpp) -# endif(NOT WIN32) set(WIZARD_HEADER componentselectionpage.hpp conclusionpage.hpp existinginstallationpage.hpp importpage.hpp + inisettings.hpp installationpage.hpp installationtargetpage.hpp intropage.hpp languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp + unshieldthread.hpp utils/componentlistwidget.hpp ) -# if(NOT WIN32) -# LIST(APPEND WIZARD_HEADER unshieldthread.hpp) -# endif(NOT WIN32) - # Headers that must be pre-processed set(WIZARD_HEADER_MOC @@ -48,15 +45,11 @@ set(WIZARD_HEADER_MOC languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp + unshieldthread.hpp utils/componentlistwidget.hpp ) -# if(NOT WIN32) -# LIST(APPEND WIZARD_HEADER_MOC unshieldthread.hpp) -# endif(NOT WIN32) - - set(WIZARD_UI ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui @@ -86,10 +79,7 @@ QT4_WRAP_UI(UI_HDRS ${WIZARD_UI}) include(${QT_USE_FILE}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) -if(NOT WIN32) - include_directories(${LIBUNSHIELD_INCLUDE}) -endif(NOT WIN32) +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LIBUNSHIELD_INCLUDE}) add_executable(openmw-wizard ${GUI_TYPE} @@ -101,16 +91,10 @@ add_executable(openmw-wizard ) target_link_libraries(openmw-wizard -# ${Boost_LIBRARIES} + ${Boost_LIBRARIES} ${QT_LIBRARIES} + ${LIBUNSHIELD_LIBRARY} ) -if(NOT WIN32) - target_link_libraries(openmw-wizard - ${LIBUNSHIELD_LIBRARY} - ) -endif(NOT WIN32) - - if(DPKG_PROGRAM) INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION games COMPONENT openmw-wizard) diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp new file mode 100644 index 000000000..8bdac5ad0 --- /dev/null +++ b/apps/wizard/inisettings.cpp @@ -0,0 +1,68 @@ +#include "inisettings.hpp" + + +#include +#include +#include +#include +#include + +Wizard::IniSettings::IniSettings() +{ +} + +Wizard::IniSettings::~IniSettings() +{ +} + +bool Wizard::IniSettings::readFile(QTextStream &stream) +{ + // Look for a square bracket, "'\\[" + // that has one or more "not nothing" in it, "([^]]+)" + // and is closed with a square bracket, "\\]" + QRegExp sectionRe("^\\[([^]]+)\\]"); + + // Find any character(s) that is/are not equal sign(s), "[^=]+" + // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" + // and one or more periods, "(.+)" + QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); + + QString currentSection; + + while (!stream.atEnd()) + { + const QString &line = stream.readLine(); + + if (line.isEmpty() || line.startsWith(";")) + continue; + + if (sectionRe.exactMatch(line)) + { + currentSection = sectionRe.cap(1); + } + else if (keyRe.indexIn(line) != -1) + { + QString key = keyRe.cap(1).trimmed(); + QString value = keyRe.cap(2).trimmed(); + + // Append the section, but only if there is one + if (!currentSection.isEmpty()) + key = currentSection + QLatin1Char('/') + key; + + mSettings[key] = QVariant(value); + } + } + + return true; +} + +bool Wizard::IniSettings::writeFile(QTextStream &stream) +{ + qDebug() << "test! " << stream.readAll(); + + while (!stream.atEnd()) { + qDebug() << "test! " << stream.readLine(); + } + + return true; +} diff --git a/apps/wizard/inisettings.hpp b/apps/wizard/inisettings.hpp new file mode 100644 index 000000000..66fe96ec1 --- /dev/null +++ b/apps/wizard/inisettings.hpp @@ -0,0 +1,45 @@ +#ifndef INISETTINGS_HPP +#define INISETTINGS_HPP + +#include +#include + +class QTextStream; + +namespace Wizard +{ + + typedef QHash SettingsMap; + + class IniSettings + { + public: + explicit IniSettings(); + ~IniSettings(); + + inline QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const + { + return mSettings.value(key, defaultValue); + } + + inline void setValue(const QString &key, const QVariant &value) + { + mSettings.insert(key, value); + } + + inline void remove(const QString &key) + { + mSettings.remove(key); + } + + bool readFile(QTextStream &stream); + bool writeFile(QTextStream &stream); + + private: + + SettingsMap mSettings; + }; + +} + +#endif // INISETTINGS_HPP diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 632b9c6a6..5d943dbfa 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -1,8 +1,12 @@ #include "installationpage.hpp" #include +#include +#include +#include #include "mainwizard.hpp" +#include "inisettings.hpp" Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : QWizardPage(wizard), @@ -19,6 +23,66 @@ void Wizard::InstallationPage::initializePage() logTextEdit->append(QString("Installing to %1").arg(path)); logTextEdit->append(QString("Installing %1.").arg(components.join(", "))); + installProgressBar->setMinimum(0); + + // Set the progressbar maximum to a multiple of 100 + // That way installing all three components would yield 300% + // When one component is done the bar will be filled by 33% + + if (field("installation.new").toBool() == true) + { + installProgressBar->setMaximum((components.count() * 100)); + } + else + { + if (components.contains("Tribunal") && mWizard->mInstallations[path]->hasTribunal == false) + installProgressBar->setMaximum(100); + + if (components.contains("Bloodmoon") && mWizard->mInstallations[path]->hasBloodmoon == false) + installProgressBar->setMaximum(installProgressBar->maximum() + 100); + } + + installProgressBar->setValue(100); + + // Test settings + IniSettings iniSettings; + + QFile file("/home/pvdk/.wine/drive_c/Program Files/Bethesda Softworks/Morrowind/Morrowind.ini"); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + } + + QTextStream stream(&file); + + QString language = field("installation.language").toString(); + + if (language == QLatin1String("English") || + language == QLatin1String("German") || + language == QLatin1String("French")) + { + stream.setCodec(QTextCodec::codecForName("windows-1252")); + } + else if (language == QLatin1String("Russian")) + { + stream.setCodec(QTextCodec::codecForName("windows-1251")); + } + else if (language == QLatin1String("Polish")) + { + stream.setCodec(QTextCodec::codecForName("windows-1250")); + } + + iniSettings.readFile(stream); + + qDebug() << iniSettings.value("Game Files/GameFile0"); + } int Wizard::InstallationPage::nextId() const From 445f96434e537613234f7641ebaf28e9e0708a6f Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 24 Dec 2013 23:09:31 +0100 Subject: [PATCH 0011/1983] Added Morrowind.ini detection logic --- apps/wizard/componentselectionpage.cpp | 5 -- apps/wizard/componentselectionpage.hpp | 1 - apps/wizard/existinginstallationpage.cpp | 64 ++++++++++++++++++++---- apps/wizard/existinginstallationpage.hpp | 2 +- apps/wizard/inisettings.cpp | 2 +- apps/wizard/installationpage.cpp | 37 ++++++++------ apps/wizard/installationpage.hpp | 3 ++ apps/wizard/languageselectionpage.cpp | 16 +++++- apps/wizard/languageselectionpage.hpp | 3 ++ apps/wizard/mainwizard.cpp | 41 ++++++++++----- apps/wizard/mainwizard.hpp | 6 ++- files/ui/wizard/languageselectionpage.ui | 45 ++--------------- 12 files changed, 135 insertions(+), 90 deletions(-) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 50e6e8ef4..7d934fe84 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -21,11 +21,6 @@ Wizard::ComponentSelectionPage::ComponentSelectionPage(MainWizard *wizard) : } -void Wizard::ComponentSelectionPage::debugMe(QString &text) -{ - qDebug() << "Debug Me" << text; -} - void Wizard::ComponentSelectionPage::updateButton(QListWidgetItem *item) { if (field("installation.new").toBool() == true) diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp index f5e955782..8b4c186d0 100644 --- a/apps/wizard/componentselectionpage.hpp +++ b/apps/wizard/componentselectionpage.hpp @@ -19,7 +19,6 @@ namespace Wizard private slots: void updateButton(QListWidgetItem *item); - void debugMe(QString &text); private: MainWizard *mWizard; diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index f9eb7c283..aeaa5a1f6 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -1,7 +1,10 @@ #include "existinginstallationpage.hpp" #include +#include #include +#include +#include #include "mainwizard.hpp" @@ -32,16 +35,14 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() if (!info.exists()) return; - QDir dir(info.absolutePath()); - if (!dir.cdUp()) - return; // Cannot move out of the Data Files directory - - QString path = QDir::toNativeSeparators(dir.absolutePath()); + QString path(QDir::toNativeSeparators(info.absolutePath())); QList items = detectedList->findItems(path, Qt::MatchExactly); if (items.isEmpty()) { // Path is not yet in the list, add it + mWizard->addInstallation(path); + QListWidgetItem *item = new QListWidgetItem(path); detectedList->addItem(item); detectedList->setCurrentItem(item); // Select it too @@ -55,13 +56,13 @@ void Wizard::ExistingInstallationPage::textChanged(const QString &text) { // Set the installation path manually, as registerField doesn't work if (!text.isEmpty()) - wizard()->setField("installation.path", text); + mWizard->setField("installation.path", text); } void Wizard::ExistingInstallationPage::initializePage() { - QStringList paths = mWizard->mInstallations.keys(); + QStringList paths(mWizard->mInstallations.keys()); if (paths.isEmpty()) return; @@ -75,6 +76,50 @@ void Wizard::ExistingInstallationPage::initializePage() } +bool Wizard::ExistingInstallationPage::validatePage() +{ + // See if Morrowind.ini is detected, if not, ask the user + // It can be missing entirely + // Or failed to be detected due to the target being a symlink + + QString path(field("installation.path").toString()); + QFile file(mWizard->mInstallations[path]->iniPath); + + if (!file.exists()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error detecting Morrowind configuration")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(QObject::tr("
Could not find Morrowind.ini

\ + The Wizard needs to update settings in this file.

\ + Press \"Browse...\" to specify the location manually.
")); + + QAbstractButton *browseButton = + msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); + + msgBox.exec(); + + QString iniFile; + if (msgBox.clickedButton() == browseButton) { + iniFile = QFileDialog::getOpenFileName( + NULL, + QObject::tr("Select configuration file"), + QDir::currentPath(), + QString(tr("Morrowind configuration file (*.ini)"))); + } + + if (iniFile.isEmpty()) { + return false; // Cancel was clicked; + } + + // A proper Morrowind.ini was selected, set it + QFileInfo info(iniFile); + mWizard->mInstallations[path]->iniPath = info.absoluteFilePath(); + } + + return true; +} + bool Wizard::ExistingInstallationPage::isComplete() const { if (detectedList->selectionModel()->hasSelection()) { @@ -86,14 +131,11 @@ bool Wizard::ExistingInstallationPage::isComplete() const int Wizard::ExistingInstallationPage::nextId() const { - QString path = field("installation.path").toString(); + QString path(field("installation.path").toString()); if (path.isEmpty()) return MainWizard::Page_ComponentSelection; - if (!mWizard->mInstallations.contains(path)) - return MainWizard::Page_ComponentSelection; - if (mWizard->mInstallations[path]->hasMorrowind == true && mWizard->mInstallations[path]->hasTribunal == true && mWizard->mInstallations[path]->hasBloodmoon == true) diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp index 88d6913bc..d243bb868 100644 --- a/apps/wizard/existinginstallationpage.hpp +++ b/apps/wizard/existinginstallationpage.hpp @@ -17,6 +17,7 @@ namespace Wizard int nextId() const; virtual bool isComplete() const; + virtual bool validatePage(); private slots: void on_browseButton_clicked(); @@ -28,7 +29,6 @@ namespace Wizard protected: void initializePage(); - }; } diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index 8bdac5ad0..2c099acec 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -31,7 +31,7 @@ bool Wizard::IniSettings::readFile(QTextStream &stream) while (!stream.atEnd()) { - const QString &line = stream.readLine(); + QString line(stream.readLine()); if (line.isEmpty() || line.startsWith(";")) continue; diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 5d943dbfa..e98d75ba4 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -1,7 +1,9 @@ #include "installationpage.hpp" #include +#include #include +#include #include #include @@ -17,8 +19,8 @@ Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : void Wizard::InstallationPage::initializePage() { - QString path = field("installation.path").toString(); - QStringList components = field("installation.components").toStringList(); + QString path(field("installation.path").toString()); + QStringList components(field("installation.components").toStringList()); logTextEdit->append(QString("Installing to %1").arg(path)); logTextEdit->append(QString("Installing %1.").arg(components.join(", "))); @@ -44,45 +46,48 @@ void Wizard::InstallationPage::initializePage() installProgressBar->setValue(100); + if (field("installation.new").toBool() == false) + setupSettings(); +} + +void Wizard::InstallationPage::setupSettings() +{ // Test settings IniSettings iniSettings; - QFile file("/home/pvdk/.wine/drive_c/Program Files/Bethesda Softworks/Morrowind/Morrowind.ini"); + QString path(field("installation.path").toString()); + QFile file(mWizard->mInstallations[path]->iniPath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setWindowTitle(tr("Error opening Morrowind configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(QObject::tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); msgBox.exec(); + mWizard->close(); + return; } QTextStream stream(&file); - QString language = field("installation.language").toString(); + QString language(field("installation.language").toString()); - if (language == QLatin1String("English") || - language == QLatin1String("German") || - language == QLatin1String("French")) - { - stream.setCodec(QTextCodec::codecForName("windows-1252")); + if (language == QLatin1String("Polish")) { + stream.setCodec(QTextCodec::codecForName("windows-1250")); } - else if (language == QLatin1String("Russian")) - { + else if (language == QLatin1String("Russian")) { stream.setCodec(QTextCodec::codecForName("windows-1251")); } - else if (language == QLatin1String("Polish")) - { - stream.setCodec(QTextCodec::codecForName("windows-1250")); + else { + stream.setCodec(QTextCodec::codecForName("windows-1252")); } iniSettings.readFile(stream); qDebug() << iniSettings.value("Game Files/GameFile0"); - } int Wizard::InstallationPage::nextId() const diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index cb206e225..b4fecc3ba 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -20,9 +20,12 @@ namespace Wizard private: MainWizard *mWizard; + void setupSettings(); + protected: void initializePage(); + }; } diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 903ab175f..102a4c39a 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -8,7 +8,21 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(MainWizard *wizard) : { setupUi(this); - registerField(QLatin1String("installation.language"), languagesComboBox); + registerField(QLatin1String("installation.language"), languageComboBox); +} + +void Wizard::LanguageSelectionPage::initializePage() +{ + QStringList languages; + languages << "English" + << "French" + << "German" + << "Italian" + << "Polish" + << "Russian" + << "Spanish"; + + languageComboBox->addItems(languages); } int Wizard::LanguageSelectionPage::nextId() const diff --git a/apps/wizard/languageselectionpage.hpp b/apps/wizard/languageselectionpage.hpp index aa7a81ff1..3c17514f9 100644 --- a/apps/wizard/languageselectionpage.hpp +++ b/apps/wizard/languageselectionpage.hpp @@ -19,6 +19,9 @@ namespace Wizard private: MainWizard *mWizard; + + protected: + void initializePage(); }; } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index e752d0455..9b133cee1 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -38,21 +38,41 @@ void Wizard::MainWizard::setupInstallations() { // TODO: detect existing installations QStringList paths; - paths << QString("/home/pvdk/.wine/drive_c/Program Files/Bethesda Softworks/Morrowind"); + paths << QString("/home/pvdk/.wine/drive_c/Program Files/Bethesda Softworks/Morrowind/Data Files"); paths << QString("/home/pvdk/openmw/Data Files"); paths << QString("/usr/games/morrowind"); foreach (const QString &path, paths) { - Installation* install = new Installation(); + addInstallation(path); + } + +} - install->hasMorrowind = (findFiles(QString("Morrowind"), path)); - install->hasTribunal = true; - install->hasBloodmoon = false; +void Wizard::MainWizard::addInstallation(const QString &path) +{ + qDebug() << "add installation in: " << path; + Installation* install = new Installation(); + + install->hasMorrowind = findFiles(QLatin1String("Morrowind"), path); + install->hasTribunal = findFiles(QLatin1String("Tribunal"), path); + install->hasBloodmoon = findFiles(QLatin1String("Bloodmoon"), path); - mInstallations.insert(QDir::toNativeSeparators(path), install); + // Try to autodetect the Morrowind.ini location + QDir dir(path); + QFile file(dir.filePath("Morrowind.ini")); + + // Try the parent directory + // In normal Morrowind installations that's where Morrowind.ini is + if (!file.exists()) { + dir.cdUp(); + file.setFileName(dir.filePath(QLatin1String("Morrowind.ini"))); } + if (file.exists()) + install->iniPath = file.fileName(); + + mInstallations.insert(QDir::toNativeSeparators(path), install); } void Wizard::MainWizard::setupPages() @@ -76,16 +96,13 @@ bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) if (!dir.exists()) return false; - if (!dir.cd(QString("Data Files"))) - return false; - - qDebug() << "name: " << name + QString(".esm") << dir.absolutePath(); - // TODO: add MIME handling to make sure the files are real - if (dir.exists(name + QString(".esm")) && dir.exists(name + QString(".bsa"))) + if (dir.exists(name + QLatin1String(".esm")) && dir.exists(name + QLatin1String(".bsa"))) { + qDebug() << name << " exists!"; return true; } else { + qDebug() << name << " doesn't exist!"; return false; } diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index c6b541d35..c025c729e 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -16,6 +16,8 @@ namespace Wizard bool hasMorrowind; bool hasTribunal; bool hasBloodmoon; + + QString iniPath; }; enum { @@ -32,7 +34,9 @@ namespace Wizard MainWizard(QWidget *parent = 0); - static bool findFiles(const QString &name, const QString &path); + bool findFiles(const QString &name, const QString &path); + void addInstallation(const QString &path); + QMap mInstallations; private: diff --git a/files/ui/wizard/languageselectionpage.ui b/files/ui/wizard/languageselectionpage.ui index a76e3004b..fccd2aa42 100644 --- a/files/ui/wizard/languageselectionpage.ui +++ b/files/ui/wizard/languageselectionpage.ui @@ -6,8 +6,8 @@ 0 0 - 398 - 298 + 396 + 296 @@ -69,48 +69,13 @@ - + 250 0 - - - English - - - - - French - - - - - Russian - - - - - Polish - - - - - Italian - - - - - Spanish - - - - - German - - @@ -143,8 +108,6 @@ - - - + From 3a37761d76147bad3467e74c84911fe111ead020 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 24 Dec 2013 23:47:04 +0100 Subject: [PATCH 0012/1983] Some minor cleanups --- apps/wizard/existinginstallationpage.cpp | 19 ++++++++++--------- apps/wizard/mainwizard.cpp | 1 + apps/wizard/utils/componentlistwidget.hpp | 2 -- files/ui/wizard/existinginstallationpage.ui | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index aeaa5a1f6..5c66ceb4f 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -14,10 +14,10 @@ Wizard::ExistingInstallationPage::ExistingInstallationPage(MainWizard *wizard) : { setupUi(this); - connect(detectedList, SIGNAL(currentTextChanged(QString)), + connect(installationsList, SIGNAL(currentTextChanged(QString)), this, SLOT(textChanged(QString))); - connect(detectedList,SIGNAL(itemSelectionChanged()), + connect(installationsList,SIGNAL(itemSelectionChanged()), this, SIGNAL(completeChanged())); } @@ -36,7 +36,7 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() return; QString path(QDir::toNativeSeparators(info.absolutePath())); - QList items = detectedList->findItems(path, Qt::MatchExactly); + QList items = installationsList->findItems(path, Qt::MatchExactly); if (items.isEmpty()) { @@ -44,10 +44,10 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() mWizard->addInstallation(path); QListWidgetItem *item = new QListWidgetItem(path); - detectedList->addItem(item); - detectedList->setCurrentItem(item); // Select it too + installationsList->addItem(item); + installationsList->setCurrentItem(item); // Select it too } else { - detectedList->setCurrentItem(items.first()); + installationsList->setCurrentItem(items.first()); } } @@ -55,6 +55,7 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() void Wizard::ExistingInstallationPage::textChanged(const QString &text) { // Set the installation path manually, as registerField doesn't work + // Because it doesn't accept two widgets operating on a single field if (!text.isEmpty()) mWizard->setField("installation.path", text); } @@ -67,11 +68,11 @@ void Wizard::ExistingInstallationPage::initializePage() if (paths.isEmpty()) return; - detectedList->clear(); + installationsList->clear(); foreach (const QString &path, paths) { QListWidgetItem *item = new QListWidgetItem(path); - detectedList->addItem(item); + installationsList->addItem(item); } } @@ -122,7 +123,7 @@ bool Wizard::ExistingInstallationPage::validatePage() bool Wizard::ExistingInstallationPage::isComplete() const { - if (detectedList->selectionModel()->hasSelection()) { + if (installationsList->selectionModel()->hasSelection()) { return true; } else { return false; diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 9b133cee1..fde82e998 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -29,6 +29,7 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); setDefaultProperty("ComponentListWidget", "mCheckedItems", "checkedItemsChanged"); + setDefaultProperty("InstallationListWidget", "mCurrentText", "currentTextChanged"); setupInstallations(); setupPages(); diff --git a/apps/wizard/utils/componentlistwidget.hpp b/apps/wizard/utils/componentlistwidget.hpp index c9c782d45..6869629d3 100644 --- a/apps/wizard/utils/componentlistwidget.hpp +++ b/apps/wizard/utils/componentlistwidget.hpp @@ -24,6 +24,4 @@ private slots: void updateCheckedItems(QListWidgetItem *item); }; - - #endif // COMPONENTLISTWIDGET_HPP diff --git a/files/ui/wizard/existinginstallationpage.ui b/files/ui/wizard/existinginstallationpage.ui index 3ca831636..b887fb06c 100644 --- a/files/ui/wizard/existinginstallationpage.ui +++ b/files/ui/wizard/existinginstallationpage.ui @@ -6,8 +6,8 @@ 0 0 - 396 - 296 + 394 + 294 @@ -21,14 +21,14 @@ - + Detected installations: - + No existing installations detected From 095ff4e17a54176716ee35b3f4c14082b5da6567 Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 25 Dec 2013 00:50:25 +0100 Subject: [PATCH 0013/1983] Moved launcher settings stuff into components, so they can be reused in the wizard --- apps/launcher/CMakeLists.txt | 5 ----- apps/launcher/datafilespage.cpp | 12 +++++------- apps/launcher/datafilespage.hpp | 12 ++++++------ apps/launcher/maindialog.hpp | 9 +++++---- apps/launcher/settings/graphicssettings.hpp | 4 ++-- components/CMakeLists.txt | 13 +++++++++---- .../config}/gamesettings.cpp | 14 +++++++------- .../config}/gamesettings.hpp | 2 +- .../config}/launchersettings.cpp | 10 +++++----- .../config}/launchersettings.hpp | 2 +- .../config}/settingsbase.hpp | 2 +- 11 files changed, 42 insertions(+), 43 deletions(-) rename {apps/launcher/settings => components/config}/gamesettings.cpp (91%) rename {apps/launcher/settings => components/config}/gamesettings.hpp (98%) rename {apps/launcher/settings => components/config}/launchersettings.cpp (86%) rename {apps/launcher/settings => components/config}/launchersettings.hpp (96%) rename {apps/launcher/settings => components/config}/settingsbase.hpp (99%) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 18c555a24..279474406 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -6,9 +6,7 @@ set(LAUNCHER playpage.cpp textslotmsgbox.cpp - settings/gamesettings.cpp settings/graphicssettings.cpp - settings/launchersettings.cpp utils/checkablemessagebox.cpp utils/profilescombobox.cpp @@ -28,10 +26,7 @@ set(LAUNCHER_HEADER playpage.hpp textslotmsgbox.hpp - settings/gamesettings.hpp settings/graphicssettings.hpp - settings/launchersettings.hpp - settings/settingsbase.hpp utils/checkablemessagebox.hpp utils/profilescombobox.hpp diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 362d7562c..db5975102 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -9,18 +9,16 @@ #include #include - #include +#include + +#include +#include #include "utils/textinputdialog.hpp" #include "utils/profilescombobox.hpp" -#include "settings/gamesettings.hpp" -#include "settings/launchersettings.hpp" - -#include "components/contentselector/view/contentselector.hpp" - -Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) +Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent) : mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 37603a210..642668cb7 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -14,12 +14,12 @@ class QMenu; namespace Files { struct ConfigurationManager; } namespace ContentSelectorView { class ContentSelector; } +namespace Config { class GameSettings; + class LauncherSettings; } namespace Launcher { class TextInputDialog; - class GameSettings; - class LauncherSettings; class ProfilesComboBox; class DataFilesPage : public QWidget @@ -30,8 +30,8 @@ namespace Launcher Ui::DataFilesPage ui; public: - explicit DataFilesPage (Files::ConfigurationManager &cfg, GameSettings &gameSettings, - LauncherSettings &launcherSettings, QWidget *parent = 0); + explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, QWidget *parent = 0); QAbstractItemModel* profilesModel() const; @@ -62,8 +62,8 @@ namespace Launcher Files::ConfigurationManager &mCfgMgr; - GameSettings &mGameSettings; - LauncherSettings &mLauncherSettings; + Config::GameSettings &mGameSettings; + Config::LauncherSettings &mLauncherSettings; QString mDataLocal; diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 5b8e4908e..a4fb4cd9a 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -5,9 +5,10 @@ #ifndef Q_MOC_RUN #include #endif -#include "settings/gamesettings.hpp" +#include +#include + #include "settings/graphicssettings.hpp" -#include "settings/launchersettings.hpp" #include "ui_mainwindow.h" @@ -60,9 +61,9 @@ namespace Launcher Files::ConfigurationManager mCfgMgr; - GameSettings mGameSettings; + Config::GameSettings mGameSettings; GraphicsSettings mGraphicsSettings; - LauncherSettings mLauncherSettings; + Config::LauncherSettings mLauncherSettings; }; } diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp index 6f7c13547..a52e0aa84 100644 --- a/apps/launcher/settings/graphicssettings.hpp +++ b/apps/launcher/settings/graphicssettings.hpp @@ -1,11 +1,11 @@ #ifndef GRAPHICSSETTINGS_HPP #define GRAPHICSSETTINGS_HPP -#include "settingsbase.hpp" +#include namespace Launcher { - class GraphicsSettings : public SettingsBase > + class GraphicsSettings : public Config::SettingsBase > { public: GraphicsSettings(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 59fb084a8..bfa25072a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -75,8 +75,8 @@ add_component_dir (loadinglistener ) add_component_dir (ogreinit - ogreinit ogreplugin - ) + ogreinit ogreplugin + ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) @@ -84,11 +84,16 @@ 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 + add_component_qt_dir (contentselector model/modelitem model/esmfile model/naturalsort model/contentmodel view/combobox view/contentselector - ) + ) + add_component_qt_dir (config + gamesettings + launchersettings + settingsbase + ) include(${QT_USE_FILE}) QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) diff --git a/apps/launcher/settings/gamesettings.cpp b/components/config/gamesettings.cpp similarity index 91% rename from apps/launcher/settings/gamesettings.cpp rename to components/config/gamesettings.cpp index 41113c35a..62ae65df6 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -27,16 +27,16 @@ namespace boost #endif /* (BOOST_VERSION <= 104600) */ -Launcher::GameSettings::GameSettings(Files::ConfigurationManager &cfg) +Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg) : mCfgMgr(cfg) { } -Launcher::GameSettings::~GameSettings() +Config::GameSettings::~GameSettings() { } -void Launcher::GameSettings::validatePaths() +void Config::GameSettings::validatePaths() { if (mSettings.isEmpty() || !mDataDirs.isEmpty()) return; // Don't re-validate paths if they are already parsed @@ -82,14 +82,14 @@ void Launcher::GameSettings::validatePaths() } } -QStringList Launcher::GameSettings::values(const QString &key, const QStringList &defaultValues) +QStringList Config::GameSettings::values(const QString &key, const QStringList &defaultValues) { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); return defaultValues; } -bool Launcher::GameSettings::readFile(QTextStream &stream) +bool Config::GameSettings::readFile(QTextStream &stream) { QMap cache; QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); @@ -131,7 +131,7 @@ bool Launcher::GameSettings::readFile(QTextStream &stream) return true; } -bool Launcher::GameSettings::writeFile(QTextStream &stream) +bool Config::GameSettings::writeFile(QTextStream &stream) { // Iterate in reverse order to preserve insertion order QMapIterator i(mSettings); @@ -170,7 +170,7 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream) return true; } -bool Launcher::GameSettings::hasMaster() +bool Config::GameSettings::hasMaster() { bool result = false; QStringList content = mSettings.values(QString("content")); diff --git a/apps/launcher/settings/gamesettings.hpp b/components/config/gamesettings.hpp similarity index 98% rename from apps/launcher/settings/gamesettings.hpp rename to components/config/gamesettings.hpp index 60236200a..3ec32c1a5 100644 --- a/apps/launcher/settings/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -14,7 +14,7 @@ namespace Files struct ConfigurationManager; } -namespace Launcher +namespace Config { class GameSettings { diff --git a/apps/launcher/settings/launchersettings.cpp b/components/config/launchersettings.cpp similarity index 86% rename from apps/launcher/settings/launchersettings.cpp rename to components/config/launchersettings.cpp index 705453555..14d66bba7 100644 --- a/apps/launcher/settings/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -7,15 +7,15 @@ #include -Launcher::LauncherSettings::LauncherSettings() +Config::LauncherSettings::LauncherSettings() { } -Launcher::LauncherSettings::~LauncherSettings() +Config::LauncherSettings::~LauncherSettings() { } -QStringList Launcher::LauncherSettings::values(const QString &key, Qt::MatchFlags flags) +QStringList Config::LauncherSettings::values(const QString &key, Qt::MatchFlags flags) { QMap settings = SettingsBase::getSettings(); @@ -36,7 +36,7 @@ QStringList Launcher::LauncherSettings::values(const QString &key, Qt::MatchFlag return result; } -QStringList Launcher::LauncherSettings::subKeys(const QString &key) +QStringList Config::LauncherSettings::subKeys(const QString &key) { QMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); @@ -60,7 +60,7 @@ QStringList Launcher::LauncherSettings::subKeys(const QString &key) return result; } -bool Launcher::LauncherSettings::writeFile(QTextStream &stream) +bool Config::LauncherSettings::writeFile(QTextStream &stream) { QString sectionPrefix; QRegExp sectionRe("([^/]+)/(.+)$"); diff --git a/apps/launcher/settings/launchersettings.hpp b/components/config/launchersettings.hpp similarity index 96% rename from apps/launcher/settings/launchersettings.hpp rename to components/config/launchersettings.hpp index 8acc389a9..042823ca9 100644 --- a/apps/launcher/settings/launchersettings.hpp +++ b/components/config/launchersettings.hpp @@ -3,7 +3,7 @@ #include "settingsbase.hpp" -namespace Launcher +namespace Config { class LauncherSettings : public SettingsBase > { diff --git a/apps/launcher/settings/settingsbase.hpp b/components/config/settingsbase.hpp similarity index 99% rename from apps/launcher/settings/settingsbase.hpp rename to components/config/settingsbase.hpp index 3a1cf8e30..92ca34cdf 100644 --- a/apps/launcher/settings/settingsbase.hpp +++ b/components/config/settingsbase.hpp @@ -7,7 +7,7 @@ #include #include -namespace Launcher +namespace Config { template class SettingsBase From dddd9cba57833124994c19106ad2a32262904bf2 Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 25 Dec 2013 15:50:02 +0100 Subject: [PATCH 0014/1983] Added data path retrieval from openmw.cfg, to use as existing installs --- apps/wizard/CMakeLists.txt | 1 + apps/wizard/mainwizard.cpp | 88 +++++++++++++++++++++++++++++++++----- apps/wizard/mainwizard.hpp | 12 ++++++ 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 7639cc8e1..4d1563ee7 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -94,6 +94,7 @@ target_link_libraries(openmw-wizard ${Boost_LIBRARIES} ${QT_LIBRARIES} ${LIBUNSHIELD_LIBRARY} + components ) if(DPKG_PROGRAM) diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index fde82e998..cbc9253ed 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -1,6 +1,10 @@ #include "mainwizard.hpp" #include + +#include +#include +#include #include #include "intropage.hpp" @@ -14,6 +18,7 @@ #include "conclusionpage.hpp" Wizard::MainWizard::MainWizard(QWidget *parent) : + mGameSettings(mCfgMgr), QWizard(parent) { @@ -29,7 +34,6 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); setDefaultProperty("ComponentListWidget", "mCheckedItems", "checkedItemsChanged"); - setDefaultProperty("InstallationListWidget", "mCurrentText", "currentTextChanged"); setupInstallations(); setupPages(); @@ -37,15 +41,47 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : void Wizard::MainWizard::setupInstallations() { - // TODO: detect existing installations + QString userPath(QFile::decodeName(mCfgMgr.getUserPath().string().c_str())); + QString globalPath(QFile::decodeName(mCfgMgr.getGlobalPath().string().c_str())); + QStringList paths; - paths << QString("/home/pvdk/.wine/drive_c/Program Files/Bethesda Softworks/Morrowind/Data Files"); - paths << QString("/home/pvdk/openmw/Data Files"); - paths << QString("/usr/games/morrowind"); + paths.append(userPath + QLatin1String("openmw.cfg")); + paths.append(QLatin1String("openmw.cfg")); + paths.append(globalPath + QLatin1String("openmw.cfg")); + + foreach (const QString &path, paths) { + qDebug() << "Loading config file:" << qPrintable(path); + + QFile file(path); + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.readFile(stream); + } + file.close(); + } - foreach (const QString &path, paths) - { - addInstallation(path); + // Check if the paths actually contain data files + foreach (const QString path, mGameSettings.getDataDirs()) { + QDir dir(path); + QStringList filters; + filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; + + // Add to Wizard installations + if (!dir.entryList(filters).isEmpty()) + addInstallation(path); } } @@ -74,6 +110,10 @@ void Wizard::MainWizard::addInstallation(const QString &path) install->iniPath = file.fileName(); mInstallations.insert(QDir::toNativeSeparators(path), install); + + // Add it to the openmw.cfg too + mGameSettings.setMultiValue(QString("data"), path); + mGameSettings.addDataDir(path); } void Wizard::MainWizard::setupPages() @@ -89,6 +129,36 @@ void Wizard::MainWizard::setupPages() setPage(Page_Conclusion, new ConclusionPage(this)); setStartId(Page_Intro); } +void Wizard::MainWizard::accept() +{ + writeSettings(); + QWizard::accept(); +} + +void Wizard::MainWizard::writeSettings() +{ + QString userPath(QFile::decodeName(mCfgMgr.getUserPath().string().c_str())); + QFile file(userPath + QLatin1String("openmw.cfg")); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.writeFile(stream); + file.close(); +} bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) { @@ -100,10 +170,8 @@ bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) // TODO: add MIME handling to make sure the files are real if (dir.exists(name + QLatin1String(".esm")) && dir.exists(name + QLatin1String(".bsa"))) { - qDebug() << name << " exists!"; return true; } else { - qDebug() << name << " doesn't exist!"; return false; } diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index c025c729e..0d4c05fce 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -4,6 +4,9 @@ #include #include +#include +#include + namespace Wizard { @@ -39,10 +42,19 @@ namespace Wizard QMap mInstallations; + Files::ConfigurationManager mCfgMgr; + private: + void setupInstallations(); void setupPages(); + void writeSettings(); + + Config::GameSettings mGameSettings; + + private slots: + void accept(); }; From 40486370d951fd68a29e30d078e43f2e13c407e4 Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 25 Dec 2013 18:52:34 +0100 Subject: [PATCH 0015/1983] Working on the Unshield functionality --- apps/wizard/installationpage.cpp | 71 ++++++++++++++++++++++++++++- apps/wizard/installationpage.hpp | 6 +++ apps/wizard/unshieldthread.cpp | 61 +++++++++++++++++++++++++ apps/wizard/unshieldthread.hpp | 47 +++++++++++++++++++ files/ui/wizard/installationpage.ui | 6 +-- 5 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 apps/wizard/unshieldthread.cpp create mode 100644 apps/wizard/unshieldthread.hpp diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index e98d75ba4..bff465fee 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -9,6 +9,7 @@ #include "mainwizard.hpp" #include "inisettings.hpp" +#include "unshieldthread.hpp" Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : QWizardPage(wizard), @@ -37,10 +38,12 @@ void Wizard::InstallationPage::initializePage() } else { - if (components.contains("Tribunal") && mWizard->mInstallations[path]->hasTribunal == false) + if (components.contains(QLatin1String("Tribunal")) + && mWizard->mInstallations[path]->hasTribunal == false) installProgressBar->setMaximum(100); - if (components.contains("Bloodmoon") && mWizard->mInstallations[path]->hasBloodmoon == false) + if (components.contains(QLatin1String("Bloodmoon")) + && mWizard->mInstallations[path]->hasBloodmoon == false) installProgressBar->setMaximum(installProgressBar->maximum() + 100); } @@ -48,6 +51,9 @@ void Wizard::InstallationPage::initializePage() if (field("installation.new").toBool() == false) setupSettings(); + + startInstallation(); + } void Wizard::InstallationPage::setupSettings() @@ -90,6 +96,67 @@ void Wizard::InstallationPage::setupSettings() qDebug() << iniSettings.value("Game Files/GameFile0"); } +void Wizard::InstallationPage::startInstallation() +{ + QStringList components(field("installation.components").toStringList()); + QString path(field("installation.path").toString()); + + UnshieldThread *unshield = new UnshieldThread(); + + connect(unshield, SIGNAL(finished()), + unshield, SLOT(deleteLater())); + + connect(unshield, SIGNAL(finished()), + this, SLOT(installationFinished())); + + connect(unshield, SIGNAL(textChanged(QString)), + installProgressLabel, SLOT(setText(QString))); + + connect(unshield, SIGNAL(textChanged(QString)), + logTextEdit, SLOT(append(QString))); + + if (field("installation.new").toBool() == true) + { + // Always install Morrowind + unshield->setInstallMorrowind(true); + + if (components.contains(QLatin1String("Tribunal"))) + unshield->setInstallTribunal(true); + + if (components.contains(QLatin1String("Bloodmoon"))) + unshield->setInstallBloodmoon(true); + } else { + // Morrowind should already be installed + unshield->setInstallMorrowind(false); + + if (components.contains(QLatin1String("Tribunal")) + && mWizard->mInstallations[path]->hasTribunal == false) + unshield->setInstallTribunal(false); + + if (components.contains(QLatin1String("Bloodmoon")) + && mWizard->mInstallations[path]->hasBloodmoon == false) + unshield->setInstallBloodmoon(true); + } + + + // Set the installation target path + unshield->setPath(path); + + unshield->start(); + +} + +void Wizard::InstallationPage::installationFinished() +{ + qDebug() << "Installation finished!"; + mFinished = true; +} + +bool Wizard::InstallationPage::isComplete() const +{ + return mFinished; +} + int Wizard::InstallationPage::nextId() const { return MainWizard::Page_Import; diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index b4fecc3ba..022b34c64 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -16,11 +16,17 @@ namespace Wizard InstallationPage(MainWizard *wizard); int nextId() const; + virtual bool isComplete() const; private: MainWizard *mWizard; + bool mFinished; void setupSettings(); + void startInstallation(); + + private slots: + void installationFinished(); protected: void initializePage(); diff --git a/apps/wizard/unshieldthread.cpp b/apps/wizard/unshieldthread.cpp new file mode 100644 index 000000000..23257e2d8 --- /dev/null +++ b/apps/wizard/unshieldthread.cpp @@ -0,0 +1,61 @@ +#include "unshieldthread.hpp" + +#include +#include + +Wizard::UnshieldThread::UnshieldThread(QObject *parent) : + QThread(parent) +{ + unshield_set_log_level(0); + + mInstallMorrowind = false; + mInstallTribunal = false; + mInstallBloodmoon = false; + +} + +void Wizard::UnshieldThread::setInstallMorrowind(bool install) +{ + mInstallMorrowind = install; +} + +void Wizard::UnshieldThread::setInstallTribunal(bool install) +{ + mInstallTribunal = install; +} + +void Wizard::UnshieldThread::setInstallBloodmoon(bool install) +{ + mInstallBloodmoon = install; +} + +void Wizard::UnshieldThread::setPath(const QString &path) +{ + mPath = path; +} + +void Wizard::UnshieldThread::extract() +{ + emit textChanged(QLatin1String("Starting installation")); + emit textChanged(QLatin1String("Installation target: ") + mPath); + + QStringList components; + if (mInstallMorrowind) + components << QLatin1String("Morrowind"); + + if (mInstallTribunal) + components << QLatin1String("Tribunal"); + + if (mInstallBloodmoon) + components << QLatin1String("Bloodmoon"); + + emit textChanged(QLatin1String("Components: ") + components.join(QLatin1String(", "))); + +} + +void Wizard::UnshieldThread::run() +{ + qDebug() << "From worker thread: " << currentThreadId(); + + extract(); +} diff --git a/apps/wizard/unshieldthread.hpp b/apps/wizard/unshieldthread.hpp new file mode 100644 index 000000000..764a99e6a --- /dev/null +++ b/apps/wizard/unshieldthread.hpp @@ -0,0 +1,47 @@ +#ifndef UNSHIELDTHREAD_HPP +#define UNSHIELDTHREAD_HPP + +#include + +#include + +namespace Wizard +{ + + class UnshieldThread : public QThread + { + Q_OBJECT + public: + explicit UnshieldThread(QObject *parent = 0); + + void setInstallMorrowind(bool install); + void setInstallTribunal(bool install); + void setInstallBloodmoon(bool install); + + void setPath(const QString &path); + + private: + void extract(); + + void extractCab(const QString &cabFile, + const QString &outputDir, bool extractIni); + //void extractFile(Unshield *unshield, + // ) + + bool mInstallMorrowind; + bool mInstallTribunal; + bool mInstallBloodmoon; + + QString mPath; + + protected: + virtual void run(); + + signals: + void textChanged(const QString &text); + + }; + +} + +#endif // UNSHIELDTHREAD_HPP diff --git a/files/ui/wizard/installationpage.ui b/files/ui/wizard/installationpage.ui index 3e94c1e30..6c1a3c493 100644 --- a/files/ui/wizard/installationpage.ui +++ b/files/ui/wizard/installationpage.ui @@ -6,8 +6,8 @@ 0 0 - 518 - 423 + 516 + 421
@@ -21,7 +21,7 @@ - + Extracting: %1 From 3a55d88bac1ed3cbb4a10b02a9698f8e74d0cc23 Mon Sep 17 00:00:00 2001 From: pvdk Date: Thu, 26 Dec 2013 18:02:34 +0100 Subject: [PATCH 0016/1983] Working on the installshield thread --- apps/wizard/installationpage.cpp | 74 ++++-------- apps/wizard/installationpage.hpp | 3 +- apps/wizard/unshieldthread.cpp | 187 ++++++++++++++++++++++++++++++- apps/wizard/unshieldthread.hpp | 22 +++- 4 files changed, 229 insertions(+), 57 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index bff465fee..3ac9b05c8 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -1,11 +1,7 @@ #include "installationpage.hpp" #include -#include -#include -#include #include -#include #include "mainwizard.hpp" #include "inisettings.hpp" @@ -16,6 +12,8 @@ Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : mWizard(wizard) { setupUi(this); + + mFinished = false; } void Wizard::InstallationPage::initializePage() @@ -49,51 +47,7 @@ void Wizard::InstallationPage::initializePage() installProgressBar->setValue(100); - if (field("installation.new").toBool() == false) - setupSettings(); - startInstallation(); - -} - -void Wizard::InstallationPage::setupSettings() -{ - // Test settings - IniSettings iniSettings; - - QString path(field("installation.path").toString()); - QFile file(mWizard->mInstallations[path]->iniPath); - - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening Morrowind configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - mWizard->close(); - return; - } - - QTextStream stream(&file); - - QString language(field("installation.language").toString()); - - if (language == QLatin1String("Polish")) { - stream.setCodec(QTextCodec::codecForName("windows-1250")); - } - else if (language == QLatin1String("Russian")) { - stream.setCodec(QTextCodec::codecForName("windows-1251")); - } - else { - stream.setCodec(QTextCodec::codecForName("windows-1252")); - } - - iniSettings.readFile(stream); - - qDebug() << iniSettings.value("Game Files/GameFile0"); } void Wizard::InstallationPage::startInstallation() @@ -115,6 +69,9 @@ void Wizard::InstallationPage::startInstallation() connect(unshield, SIGNAL(textChanged(QString)), logTextEdit, SLOT(append(QString))); + connect(unshield, SIGNAL(progressChanged(int)), + installProgressBar, SLOT(setValue(int))); + if (field("installation.new").toBool() == true) { // Always install Morrowind @@ -131,25 +88,40 @@ void Wizard::InstallationPage::startInstallation() if (components.contains(QLatin1String("Tribunal")) && mWizard->mInstallations[path]->hasTribunal == false) - unshield->setInstallTribunal(false); + unshield->setInstallTribunal(true); if (components.contains(QLatin1String("Bloodmoon")) && mWizard->mInstallations[path]->hasBloodmoon == false) unshield->setInstallBloodmoon(true); - } + // Set the location of the Morrowind.ini to update + unshield->setIniPath(mWizard->mInstallations[path]->iniPath); + } // Set the installation target path unshield->setPath(path); + // Set the right codec to use for Morrowind.ini + QString language(field("installation.language").toString()); + + if (language == QLatin1String("Polish")) { + unshield->setIniCodec(QTextCodec::codecForName("windows-1250")); + } + else if (language == QLatin1String("Russian")) { + unshield->setIniCodec(QTextCodec::codecForName("windows-1251")); + } + else { + unshield->setIniCodec(QTextCodec::codecForName("windows-1252")); + } + unshield->start(); } void Wizard::InstallationPage::installationFinished() { - qDebug() << "Installation finished!"; mFinished = true; + emit completeChanged(); } bool Wizard::InstallationPage::isComplete() const diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index 022b34c64..42b2be819 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -4,10 +4,12 @@ #include #include "ui_installationpage.h" +#include "inisettings.hpp" namespace Wizard { class MainWizard; + class IniSettings; class InstallationPage : public QWizardPage, private Ui::InstallationPage { @@ -22,7 +24,6 @@ namespace Wizard MainWizard *mWizard; bool mFinished; - void setupSettings(); void startInstallation(); private slots: diff --git a/apps/wizard/unshieldthread.cpp b/apps/wizard/unshieldthread.cpp index 23257e2d8..d759b56a6 100644 --- a/apps/wizard/unshieldthread.cpp +++ b/apps/wizard/unshieldthread.cpp @@ -1,13 +1,28 @@ #include "unshieldthread.hpp" #include + +#include #include +#include +#include +#include +#include + +#include Wizard::UnshieldThread::UnshieldThread(QObject *parent) : - QThread(parent) + QThread(parent), + mIniSettings() { unshield_set_log_level(0); + mPath = QString(); + mIniPath = QString(); + + // Default to Latin encoding + mIniCodec = QTextCodec::codecForName("windows-1252"); + mInstallMorrowind = false; mInstallTribunal = false; mInstallBloodmoon = false; @@ -34,6 +49,43 @@ void Wizard::UnshieldThread::setPath(const QString &path) mPath = path; } +void Wizard::UnshieldThread::setIniPath(const QString &path) +{ + mIniPath = path; +} + +void Wizard::UnshieldThread::setIniCodec(QTextCodec *codec) +{ + mIniCodec = codec; +} + +void Wizard::UnshieldThread::setupSettings() +{ + // Create Morrowind.ini settings map + if (mIniPath.isEmpty()) + return; + + QFile file(mIniPath); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + // TODO: Emit error signal + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening Morrowind configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return; + } + + QTextStream stream(&file); + stream.setCodec(mIniCodec); + + mIniSettings.readFile(stream); +} + void Wizard::UnshieldThread::extract() { emit textChanged(QLatin1String("Starting installation")); @@ -51,11 +103,144 @@ void Wizard::UnshieldThread::extract() emit textChanged(QLatin1String("Components: ") + components.join(QLatin1String(", "))); + emit textChanged(QLatin1String("Updating Morrowind.ini: ") + mIniPath); + + //emit progressChanged(45); + + /// +// bfs::path outputDataFilesDir = mOutputPath; +// outputDataFilesDir /= "Data Files"; +// bfs::path extractPath = mOutputPath; +// extractPath /= "extract-temp"; + + // Create temporary extract directory + // TODO: Use QTemporaryDir in Qt 5.0 + QString tempPath(mPath + QLatin1String("/extract-temp")); + QDir dir; + dir.mkpath(tempPath); + + if (mInstallMorrowind) + { + QString morrowindTempPath(tempPath + QLatin1String("/morrowind")); + QString morrowindCab(QLatin1String("/mnt/cdrom/data1.hdr")); + + extractCab(morrowindCab, morrowindTempPath); + + // TODO: Throw error; + // Move the files from the temporary path to the destination folder + + qDebug() << "rename: " << morrowindTempPath << " to: " << mPath; + if (!dir.rename(morrowindTempPath, mPath)) + qDebug() << "failed!"; + + } + +// if(!mMorrowindDone && mMorrowindPath.string().length() > 0) +// { +// mMorrowindDone = true; + +// bfs::path mwExtractPath = extractPath / "morrowind"; +// extract_cab(mMorrowindPath, mwExtractPath, true); + +// bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path(); + +// installToPath(dFilesDir, outputDataFilesDir); + +// install_dfiles_outside(mwExtractPath, outputDataFilesDir); + +// // Videos are often kept uncompressed on the cd +// bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false); +// if(videosPath.string() != "") +// { +// emit signalGUI(QString("Installing Videos...")); +// installToPath(videosPath, outputDataFilesDir / "Video", true); +// } + +// bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false); +// if(cdDFiles.string() != "") +// { +// emit signalGUI(QString("Installing Uncompressed Data files from CD...")); +// installToPath(cdDFiles, outputDataFilesDir, true); +// } + + +// bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini"); + +// mTribunalDone = contains(outputDataFilesDir, "tribunal.esm"); +// mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); + +// } + + + + /// + +} + +bool Wizard::UnshieldThread::extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter) +{ + bool success; + QString path(outputDir); + path.append('/'); + + int directory = unshield_file_directory(unshield, index); + + if (!prefix.isEmpty()) + path.append(prefix + QLatin1Char('/')); + + if (directory >= 0) + path.append(QString::fromLatin1(unshield_directory_name(unshield, directory)) + QLatin1Char('/')); + + // Ensure the target path exists + QDir dir; + dir.mkpath(path); + + QString fileName(path); + fileName.append(QString::fromLatin1(unshield_file_name(unshield, index))); + + // Calculate the percentage done + int progress = qCeil(((float) counter / (float) unshield_file_count(unshield)) * 100); + + qDebug() << progress << counter << unshield_file_count(unshield); + + emit textChanged(QLatin1String("Extracting: ") + QString::fromLatin1(unshield_file_name(unshield, index))); + emit progressChanged(progress); + + success = unshield_file_save(unshield, index, fileName.toLatin1().constData()); + + if (!success) + dir.remove(fileName); + + return success; +} + +void Wizard::UnshieldThread::extractCab(const QString &cabFile, const QString &outputDir) +{ + Unshield *unshield; + unshield = unshield_open(cabFile.toLatin1().constData()); + + int counter = 0; + + for (int i=0; ifirst_file; j<=group->last_file; ++j) + { + if (unshield_file_is_valid(unshield, j)) { + extractFile(unshield, outputDir, group->name, j, counter); + ++counter; + } + } + } + + unshield_close(unshield); } void Wizard::UnshieldThread::run() { qDebug() << "From worker thread: " << currentThreadId(); + setupSettings(); extract(); } diff --git a/apps/wizard/unshieldthread.hpp b/apps/wizard/unshieldthread.hpp index 764a99e6a..7f7250875 100644 --- a/apps/wizard/unshieldthread.hpp +++ b/apps/wizard/unshieldthread.hpp @@ -5,6 +5,10 @@ #include +#include "inisettings.hpp" + +class QTextCodec; + namespace Wizard { @@ -19,26 +23,36 @@ namespace Wizard void setInstallBloodmoon(bool install); void setPath(const QString &path); + void setIniPath(const QString &path); + + void setIniCodec(QTextCodec *codec); private: + + void setupSettings(); void extract(); - void extractCab(const QString &cabFile, - const QString &outputDir, bool extractIni); - //void extractFile(Unshield *unshield, - // ) + void extractCab(const QString &cabFile, const QString &outputDir); + bool extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter); + bool mInstallMorrowind; bool mInstallTribunal; bool mInstallBloodmoon; QString mPath; + QString mIniPath; + + IniSettings mIniSettings; + + QTextCodec *mIniCodec; protected: virtual void run(); signals: void textChanged(const QString &text); + void progressChanged(int progress); }; From a2ca7b92a227530579c442cf553631dcccdb7303 Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 1 Jan 2014 17:15:34 +0100 Subject: [PATCH 0017/1983] Working on the unshield implementation, added support for moving temp files --- apps/wizard/unshieldthread.cpp | 140 +++++++++++++++++++++++++++++++-- apps/wizard/unshieldthread.hpp | 5 ++ 2 files changed, 140 insertions(+), 5 deletions(-) diff --git a/apps/wizard/unshieldthread.cpp b/apps/wizard/unshieldthread.cpp index d759b56a6..0f8dbc477 100644 --- a/apps/wizard/unshieldthread.cpp +++ b/apps/wizard/unshieldthread.cpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include #include @@ -86,6 +88,91 @@ void Wizard::UnshieldThread::setupSettings() mIniSettings.readFile(stream); } +bool Wizard::UnshieldThread::removeDirectory(const QString &dirName) +{ + bool result = true; + QDir dir(dirName); + + if (dir.exists(dirName)) + { + QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | + QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, QDir::DirsFirst)); + foreach(QFileInfo info, list) { + if (info.isDir()) { + result = removeDirectory(info.absoluteFilePath()); + } else { + result = QFile::remove(info.absoluteFilePath()); + } + + if (!result) + return result; + } + + result = dir.rmdir(dirName); + } + return result; +} + +bool Wizard::UnshieldThread::moveFile(const QString &source, const QString &destination) +{ + QDir dir; + QFile file; + + if (dir.rename(source, destination)) { + return true; + } else { + if (file.copy(source, destination)) { + return file.remove(source); + } else { + qDebug() << "copy failed! " << file.errorString(); + } + } + + return false; +} + +bool Wizard::UnshieldThread::moveDirectory(const QString &source, const QString &destination) +{ + QDir sourceDir(source); + QDir destDir(destination); + + if (!destDir.exists()) { + sourceDir.mkpath(destination); + destDir.refresh(); + } + + if (!destDir.exists()) + return false; + + bool result = true; + + QFileInfoList list(sourceDir.entryInfoList(QDir::NoDotAndDotDot | + QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, QDir::DirsFirst)); + + foreach (const QFileInfo &info, list) { + QString relativePath(info.absoluteFilePath()); + relativePath.remove(source); + + if (info.isSymLink()) + continue; + + if (info.isDir()) { + result = moveDirectory(info.absoluteFilePath(), destDir.absolutePath() + relativePath); + } else { + qDebug() << "moving: " << info.absoluteFilePath() << " to: " << destDir.absolutePath() + relativePath; + + result = moveFile(info.absoluteFilePath(), destDir.absolutePath() + relativePath); + } + + //if (!result) + // return result; + } + + return result && removeDirectory(sourceDir.absolutePath()); +} + void Wizard::UnshieldThread::extract() { emit textChanged(QLatin1String("Starting installation")); @@ -129,10 +216,48 @@ void Wizard::UnshieldThread::extract() // TODO: Throw error; // Move the files from the temporary path to the destination folder - qDebug() << "rename: " << morrowindTempPath << " to: " << mPath; - if (!dir.rename(morrowindTempPath, mPath)) + //qDebug() << "rename: " << morrowindTempPath << " to: " << mPath; + morrowindTempPath.append(QDir::separator() + QLatin1String("Data Files")); + if (!moveDirectory(morrowindTempPath, mPath)) qDebug() << "failed!"; + QDir source(QLatin1String("/mnt/cdrom/")); + QStringList directories; + directories << QLatin1String("Fonts") + << QLatin1String("Music") + << QLatin1String("Sound") + << QLatin1String("Splash"); + + QFileInfoList list(sourceDir.entryInfoList(QDir::NoDotAndDotDot | + QDir::System | QDir::Hidden | + QDir::AllDirs)); + + + foreach(QFileInfo info, list) { + if (info.isSymLink()) + continue; + + if (directories.contains(info.fileName())) + copyDirectory(info.absoluteFilePath(), mPath); + } + + + bfs::path fonts = findFile(from, "fonts", false); + if(fonts.string() != "") + installToPath(fonts, dFiles / "Fonts"); + + bfs::path music = findFile(from, "music", false); + if(music.string() != "") + installToPath(music, dFiles / "Music"); + + bfs::path sound = findFile(from, "sound", false); + if(sound.string() != "") + installToPath(sound, dFiles / "Sound"); + + bfs::path splash = findFile(from, "splash", false); + if(splash.string() != "") + installToPath(splash, dFiles / "Splash"); + } // if(!mMorrowindDone && mMorrowindPath.string().length() > 0) @@ -181,16 +306,21 @@ bool Wizard::UnshieldThread::extractFile(Unshield *unshield, const QString &outp { bool success; QString path(outputDir); - path.append('/'); + path.append(QDir::separator()); int directory = unshield_file_directory(unshield, index); if (!prefix.isEmpty()) - path.append(prefix + QLatin1Char('/')); + path.append(prefix + QDir::separator()); if (directory >= 0) - path.append(QString::fromLatin1(unshield_directory_name(unshield, directory)) + QLatin1Char('/')); + path.append(QString::fromLatin1(unshield_directory_name(unshield, directory)) + QDir::separator()); + + // Ensure the path has the right separators + path.replace(QLatin1Char('\\'), QDir::separator()); + path = QDir::toNativeSeparators(path); + qDebug() << "path is: " << path << QString::fromLatin1(unshield_directory_name(unshield, directory)) + QLatin1Char('/'); // Ensure the target path exists QDir dir; dir.mkpath(path); diff --git a/apps/wizard/unshieldthread.hpp b/apps/wizard/unshieldthread.hpp index 7f7250875..cc3740ad8 100644 --- a/apps/wizard/unshieldthread.hpp +++ b/apps/wizard/unshieldthread.hpp @@ -29,6 +29,11 @@ namespace Wizard private: + bool removeDirectory(const QString &dirName); + + bool moveFile(const QString &source, const QString &destination); + bool moveDirectory(const QString &source, const QString &destination); + void setupSettings(); void extract(); From 691e007524591eec04d43fb8faabc38cc0046973 Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 1 Jan 2014 22:46:29 +0100 Subject: [PATCH 0018/1983] Redid the unshield thread stuff, this time the way it should be done --- apps/wizard/CMakeLists.txt | 9 ++- apps/wizard/installationpage.cpp | 19 ++++- .../unshieldworker.cpp} | 79 ++++++++----------- .../unshieldworker.hpp} | 28 ++++--- 4 files changed, 69 insertions(+), 66 deletions(-) rename apps/wizard/{unshieldthread.cpp => unshield/unshieldworker.cpp} (82%) rename apps/wizard/{unshieldthread.hpp => unshield/unshieldworker.hpp} (78%) diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 4d1563ee7..f2defd031 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -11,7 +11,8 @@ set(WIZARD main.cpp mainwizard.cpp methodselectionpage.cpp - unshieldthread.cpp + + unshield/unshieldworker.cpp utils/componentlistwidget.cpp ) @@ -28,7 +29,8 @@ set(WIZARD_HEADER languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp - unshieldthread.hpp + + unshield/unshieldworker.hpp utils/componentlistwidget.hpp ) @@ -45,7 +47,8 @@ set(WIZARD_HEADER_MOC languageselectionpage.hpp mainwizard.hpp methodselectionpage.hpp - unshieldthread.hpp + + unshield/unshieldworker.hpp utils/componentlistwidget.hpp ) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 3ac9b05c8..374c8180c 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -5,7 +5,7 @@ #include "mainwizard.hpp" #include "inisettings.hpp" -#include "unshieldthread.hpp" +#include "unshield/unshieldworker.hpp" Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : QWizardPage(wizard), @@ -55,11 +55,23 @@ void Wizard::InstallationPage::startInstallation() QStringList components(field("installation.components").toStringList()); QString path(field("installation.path").toString()); - UnshieldThread *unshield = new UnshieldThread(); + QThread* thread = new QThread(); + UnshieldWorker* unshield = new UnshieldWorker(); + + unshield->moveToThread(thread); + + connect(thread, SIGNAL(started()), + unshield, SLOT(extract())); + + connect(unshield, SIGNAL(finished()), + thread, SLOT(quit())); connect(unshield, SIGNAL(finished()), unshield, SLOT(deleteLater())); + connect(unshield, SIGNAL(finished()), + thread, SLOT(deleteLater())); + connect(unshield, SIGNAL(finished()), this, SLOT(installationFinished())); @@ -114,12 +126,13 @@ void Wizard::InstallationPage::startInstallation() unshield->setIniCodec(QTextCodec::codecForName("windows-1252")); } - unshield->start(); + thread->start(); } void Wizard::InstallationPage::installationFinished() { + qDebug() << "finished!"; mFinished = true; emit completeChanged(); } diff --git a/apps/wizard/unshieldthread.cpp b/apps/wizard/unshield/unshieldworker.cpp similarity index 82% rename from apps/wizard/unshieldthread.cpp rename to apps/wizard/unshield/unshieldworker.cpp index 0f8dbc477..71beaf9b8 100644 --- a/apps/wizard/unshieldthread.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -1,4 +1,4 @@ -#include "unshieldthread.hpp" +#include "unshieldworker.hpp" #include @@ -10,11 +10,12 @@ #include #include #include +#include #include -Wizard::UnshieldThread::UnshieldThread(QObject *parent) : - QThread(parent), +Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : + QObject(parent), mIniSettings() { unshield_set_log_level(0); @@ -28,40 +29,44 @@ Wizard::UnshieldThread::UnshieldThread(QObject *parent) : mInstallMorrowind = false; mInstallTribunal = false; mInstallBloodmoon = false; +} + +Wizard::UnshieldWorker::~UnshieldWorker() +{ } -void Wizard::UnshieldThread::setInstallMorrowind(bool install) +void Wizard::UnshieldWorker::setInstallMorrowind(bool install) { mInstallMorrowind = install; } -void Wizard::UnshieldThread::setInstallTribunal(bool install) +void Wizard::UnshieldWorker::setInstallTribunal(bool install) { mInstallTribunal = install; } -void Wizard::UnshieldThread::setInstallBloodmoon(bool install) +void Wizard::UnshieldWorker::setInstallBloodmoon(bool install) { mInstallBloodmoon = install; } -void Wizard::UnshieldThread::setPath(const QString &path) +void Wizard::UnshieldWorker::setPath(const QString &path) { mPath = path; } -void Wizard::UnshieldThread::setIniPath(const QString &path) +void Wizard::UnshieldWorker::setIniPath(const QString &path) { mIniPath = path; } -void Wizard::UnshieldThread::setIniCodec(QTextCodec *codec) +void Wizard::UnshieldWorker::setIniCodec(QTextCodec *codec) { mIniCodec = codec; } -void Wizard::UnshieldThread::setupSettings() +void Wizard::UnshieldWorker::setupSettings() { // Create Morrowind.ini settings map if (mIniPath.isEmpty()) @@ -88,7 +93,7 @@ void Wizard::UnshieldThread::setupSettings() mIniSettings.readFile(stream); } -bool Wizard::UnshieldThread::removeDirectory(const QString &dirName) +bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) { bool result = true; QDir dir(dirName); @@ -114,7 +119,7 @@ bool Wizard::UnshieldThread::removeDirectory(const QString &dirName) return result; } -bool Wizard::UnshieldThread::moveFile(const QString &source, const QString &destination) +bool Wizard::UnshieldWorker::moveFile(const QString &source, const QString &destination) { QDir dir; QFile file; @@ -132,7 +137,7 @@ bool Wizard::UnshieldThread::moveFile(const QString &source, const QString &dest return false; } -bool Wizard::UnshieldThread::moveDirectory(const QString &source, const QString &destination) +bool Wizard::UnshieldWorker::moveDirectory(const QString &source, const QString &destination) { QDir sourceDir(source); QDir destDir(destination); @@ -173,7 +178,7 @@ bool Wizard::UnshieldThread::moveDirectory(const QString &source, const QString return result && removeDirectory(sourceDir.absolutePath()); } -void Wizard::UnshieldThread::extract() +void Wizard::UnshieldWorker::extract() { emit textChanged(QLatin1String("Starting installation")); emit textChanged(QLatin1String("Installation target: ") + mPath); @@ -211,22 +216,23 @@ void Wizard::UnshieldThread::extract() QString morrowindTempPath(tempPath + QLatin1String("/morrowind")); QString morrowindCab(QLatin1String("/mnt/cdrom/data1.hdr")); - extractCab(morrowindCab, morrowindTempPath); + //extractCab(morrowindCab, morrowindTempPath); // TODO: Throw error; // Move the files from the temporary path to the destination folder //qDebug() << "rename: " << morrowindTempPath << " to: " << mPath; morrowindTempPath.append(QDir::separator() + QLatin1String("Data Files")); - if (!moveDirectory(morrowindTempPath, mPath)) - qDebug() << "failed!"; +// if (!moveDirectory(morrowindTempPath, mPath)) +// qDebug() << "failed!"; - QDir source(QLatin1String("/mnt/cdrom/")); + QDir sourceDir(QLatin1String("/mnt/cdrom/")); QStringList directories; directories << QLatin1String("Fonts") << QLatin1String("Music") << QLatin1String("Sound") - << QLatin1String("Splash"); + << QLatin1String("Splash") + << QLatin1String("Video"); QFileInfoList list(sourceDir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | @@ -237,27 +243,13 @@ void Wizard::UnshieldThread::extract() if (info.isSymLink()) continue; + qDebug() << "not found " << info.fileName(); + if (directories.contains(info.fileName())) - copyDirectory(info.absoluteFilePath(), mPath); + qDebug() << "found " << info.fileName(); +// copyDirectory(info.absoluteFilePath(), mPath); } - - bfs::path fonts = findFile(from, "fonts", false); - if(fonts.string() != "") - installToPath(fonts, dFiles / "Fonts"); - - bfs::path music = findFile(from, "music", false); - if(music.string() != "") - installToPath(music, dFiles / "Music"); - - bfs::path sound = findFile(from, "sound", false); - if(sound.string() != "") - installToPath(sound, dFiles / "Sound"); - - bfs::path splash = findFile(from, "splash", false); - if(splash.string() != "") - installToPath(splash, dFiles / "Splash"); - } // if(!mMorrowindDone && mMorrowindPath.string().length() > 0) @@ -299,10 +291,11 @@ void Wizard::UnshieldThread::extract() /// + emit finished(); } -bool Wizard::UnshieldThread::extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter) +bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter) { bool success; QString path(outputDir); @@ -344,7 +337,7 @@ bool Wizard::UnshieldThread::extractFile(Unshield *unshield, const QString &outp return success; } -void Wizard::UnshieldThread::extractCab(const QString &cabFile, const QString &outputDir) +void Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &outputDir) { Unshield *unshield; unshield = unshield_open(cabFile.toLatin1().constData()); @@ -366,11 +359,3 @@ void Wizard::UnshieldThread::extractCab(const QString &cabFile, const QString &o unshield_close(unshield); } - -void Wizard::UnshieldThread::run() -{ - qDebug() << "From worker thread: " << currentThreadId(); - - setupSettings(); - extract(); -} diff --git a/apps/wizard/unshieldthread.hpp b/apps/wizard/unshield/unshieldworker.hpp similarity index 78% rename from apps/wizard/unshieldthread.hpp rename to apps/wizard/unshield/unshieldworker.hpp index cc3740ad8..61e021865 100644 --- a/apps/wizard/unshieldthread.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -1,22 +1,22 @@ -#ifndef UNSHIELDTHREAD_HPP -#define UNSHIELDTHREAD_HPP +#ifndef UNSHIELDWORKER_HPP +#define UNSHIELDWORKER_HPP +#include #include #include -#include "inisettings.hpp" - -class QTextCodec; +#include "../inisettings.hpp" namespace Wizard { - - class UnshieldThread : public QThread + class UnshieldWorker : public QObject { Q_OBJECT + public: - explicit UnshieldThread(QObject *parent = 0); + UnshieldWorker(QObject *parent = 0); + ~UnshieldWorker(); void setInstallMorrowind(bool install); void setInstallTribunal(bool install); @@ -35,7 +35,6 @@ namespace Wizard bool moveDirectory(const QString &source, const QString &destination); void setupSettings(); - void extract(); void extractCab(const QString &cabFile, const QString &outputDir); bool extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter); @@ -52,15 +51,18 @@ namespace Wizard QTextCodec *mIniCodec; - protected: - virtual void run(); + + public slots: + void extract(); signals: + void finished(); void textChanged(const QString &text); + void error(const QString &text); void progressChanged(int progress); - }; + }; } -#endif // UNSHIELDTHREAD_HPP +#endif // UNSHIELDWORKER_HPP From 0f7f3391f7528ec01882d433ac369f8195eb3e64 Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 17 Jan 2014 13:21:44 +0100 Subject: [PATCH 0019/1983] WIP: Working on the installation of addons --- apps/wizard/installationpage.cpp | 11 +- apps/wizard/installationpage.hpp | 1 + apps/wizard/unshield/unshieldworker.cpp | 275 ++++++++++++++++-------- apps/wizard/unshield/unshieldworker.hpp | 11 + files/ui/wizard/installationpage.ui | 6 +- 5 files changed, 215 insertions(+), 89 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 374c8180c..045138ab7 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -45,8 +45,6 @@ void Wizard::InstallationPage::initializePage() installProgressBar->setMaximum(installProgressBar->maximum() + 100); } - installProgressBar->setValue(100); - startInstallation(); } @@ -75,6 +73,9 @@ void Wizard::InstallationPage::startInstallation() connect(unshield, SIGNAL(finished()), this, SLOT(installationFinished())); + connect(unshield, SIGNAL(error(QString)), + this, SLOT(installationError(QString))); + connect(unshield, SIGNAL(textChanged(QString)), installProgressLabel, SLOT(setText(QString))); @@ -135,6 +136,12 @@ void Wizard::InstallationPage::installationFinished() qDebug() << "finished!"; mFinished = true; emit completeChanged(); + +} + +void Wizard::InstallationPage::installationError(const QString &text) +{ + qDebug() << "error: " << text; } bool Wizard::InstallationPage::isComplete() const diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index 42b2be819..f55ab8ab5 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -28,6 +28,7 @@ namespace Wizard private slots: void installationFinished(); + void installationError(const QString &text); protected: void initializePage(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 71beaf9b8..4f92b1cf9 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -29,6 +29,10 @@ Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : mInstallMorrowind = false; mInstallTribunal = false; mInstallBloodmoon = false; + + mMorrowindDone = false; + mTribunalDone = false; + mBloodmoonDone = false; } Wizard::UnshieldWorker::~UnshieldWorker() @@ -119,25 +123,30 @@ bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) return result; } -bool Wizard::UnshieldWorker::moveFile(const QString &source, const QString &destination) +bool Wizard::UnshieldWorker::copyFile(const QString &source, const QString &destination, bool keepSource) { QDir dir; QFile file; - if (dir.rename(source, destination)) { - return true; - } else { - if (file.copy(source, destination)) { + QFileInfo info(destination); + + if (info.exists()) + dir.remove(info.absoluteFilePath()); + + if (file.copy(source, destination)) { + if (!keepSource) { return file.remove(source); } else { - qDebug() << "copy failed! " << file.errorString(); + return true; } + } else { + qDebug() << "copy failed! " << file.errorString(); } return false; } -bool Wizard::UnshieldWorker::moveDirectory(const QString &source, const QString &destination) +bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString &destination, bool keepSource) { QDir sourceDir(source); QDir destDir(destination); @@ -170,127 +179,217 @@ bool Wizard::UnshieldWorker::moveDirectory(const QString &source, const QString result = moveFile(info.absoluteFilePath(), destDir.absolutePath() + relativePath); } - - //if (!result) - // return result; } - return result && removeDirectory(sourceDir.absolutePath()); + if (!keepSource) + return result && removeDirectory(sourceDir.absolutePath()); + + return result; } -void Wizard::UnshieldWorker::extract() +bool Wizard::UnshieldWorker::moveFile(const QString &source, const QString &destination) { - emit textChanged(QLatin1String("Starting installation")); - emit textChanged(QLatin1String("Installation target: ") + mPath); + return copyFile(source, destination, false); +} - QStringList components; - if (mInstallMorrowind) - components << QLatin1String("Morrowind"); +bool Wizard::UnshieldWorker::moveDirectory(const QString &source, const QString &destination) +{ + return copyDirectory(source, destination, false); +} - if (mInstallTribunal) - components << QLatin1String("Tribunal"); +void Wizard::UnshieldWorker::installDirectories(const QString &source) +{ + QDir dir(source); - if (mInstallBloodmoon) - components << QLatin1String("Bloodmoon"); + if (!dir.exists()) + return; - emit textChanged(QLatin1String("Components: ") + components.join(QLatin1String(", "))); + QStringList directories; + directories << QLatin1String("Fonts") + << QLatin1String("Music") + << QLatin1String("Sound") + << QLatin1String("Splash") + << QLatin1String("Video"); + + QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | + QDir::System | QDir::Hidden | + QDir::AllDirs)); + foreach(QFileInfo info, list) { + if (info.isSymLink()) + continue; - emit textChanged(QLatin1String("Updating Morrowind.ini: ") + mIniPath); + if (directories.contains(info.fileName())) { + qDebug() << "found " << info.fileName(); + emit textChanged(tr("Extracting: %1 directory").arg(info.fileName())); + copyDirectory(info.absoluteFilePath(), mPath + QDir::separator() + info.fileName()); + } + } +} - //emit progressChanged(45); +void Wizard::UnshieldWorker::extract() +{ + emit textChanged(QLatin1String("Starting installation")); + emit textChanged(QLatin1String("Installation target: ") + mPath); - /// -// bfs::path outputDataFilesDir = mOutputPath; -// outputDataFilesDir /= "Data Files"; -// bfs::path extractPath = mOutputPath; -// extractPath /= "extract-temp"; + QString diskPath("/mnt/cdrom/"); + QDir disk(diskPath); // Create temporary extract directory // TODO: Use QTemporaryDir in Qt 5.0 - QString tempPath(mPath + QLatin1String("/extract-temp")); - QDir dir; - dir.mkpath(tempPath); + QString tempPath(mPath + QDir::separator() + QLatin1String("extract-temp")); + QDir temp; + + // Make sure the temporary folder is empty + removeDirectory(tempPath); + + if (!temp.mkpath(tempPath)) { + qDebug() << "Can't make path"; + return; + } + + temp.setPath(tempPath); + disk.setPath(diskPath); if (mInstallMorrowind) { - QString morrowindTempPath(tempPath + QLatin1String("/morrowind")); - QString morrowindCab(QLatin1String("/mnt/cdrom/data1.hdr")); + emit textChanged(QLatin1String("Installing Morrowind\n")); - //extractCab(morrowindCab, morrowindTempPath); + if (!temp.mkdir(QLatin1String("morrowind"))) { + qDebug() << "Can't make dir"; + return; + } + + if (!temp.cd(QLatin1String("morrowind"))) { + qDebug() << "Can't cd to dir"; + return; + } + + if (!disk.exists(QLatin1String("data1.hdr"))) { + qDebug() << "No data found!"; + return; + } + + // Extract the installation files + extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); // TODO: Throw error; // Move the files from the temporary path to the destination folder //qDebug() << "rename: " << morrowindTempPath << " to: " << mPath; - morrowindTempPath.append(QDir::separator() + QLatin1String("Data Files")); -// if (!moveDirectory(morrowindTempPath, mPath)) -// qDebug() << "failed!"; + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) + qDebug() << "failed!"; - QDir sourceDir(QLatin1String("/mnt/cdrom/")); - QStringList directories; - directories << QLatin1String("Fonts") - << QLatin1String("Music") - << QLatin1String("Sound") - << QLatin1String("Splash") - << QLatin1String("Video"); + // Install files outside of cab archives + qDebug() << temp.absolutePath() << disk.absolutePath(); + installDirectories(disk.absolutePath()); - QFileInfoList list(sourceDir.entryInfoList(QDir::NoDotAndDotDot | - QDir::System | QDir::Hidden | - QDir::AllDirs)); + // Copy Morrowind configuration file + QString iniPath(temp.absoluteFilePath(QLatin1String("App Executables"))); + iniPath.append(QDir::separator() + QLatin1String("Morrowind.ini")); + QFileInfo info(iniPath); - foreach(QFileInfo info, list) { - if (info.isSymLink()) - continue; + qDebug() << info.absoluteFilePath() << mPath; + + if (info.exists()) { + emit textChanged(tr("Extracting: Morrowind.ini")); + moveFile(info.absoluteFilePath(), mPath + QDir::separator() + QLatin1String("Morrowind.ini")); + } else { + qDebug() << "Could not find ini file!"; + } + + mMorrowindDone = true; + + } + + temp.setPath(tempPath); + disk.setPath(diskPath); + + if (mInstallTribunal) + { + emit textChanged(QLatin1String("Installing Tribunal\n")); + + if (!temp.mkdir(QLatin1String("tribunal"))) { + qDebug() << "Can't make dir"; + return; + } - qDebug() << "not found " << info.fileName(); + if (!temp.cd(QLatin1String("tribunal"))) { + qDebug() << "Can't cd to dir"; + return; + } + + if (!disk.cd(QLatin1String("Tribunal"))) + qDebug() << "Show file selector"; - if (directories.contains(info.fileName())) - qDebug() << "found " << info.fileName(); -// copyDirectory(info.absoluteFilePath(), mPath); + if (!disk.exists(QLatin1String("data1.hdr"))) { + qDebug() << "No data found!"; + return; } + // Extract the installation files + extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); + + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) + qDebug() << "failed!"; + + // Install files outside of cab archives + installDirectories(disk.absolutePath()); + + mTribunalDone = true; } -// if(!mMorrowindDone && mMorrowindPath.string().length() > 0) -// { -// mMorrowindDone = true; + temp.setPath(tempPath); + disk.setPath(diskPath); -// bfs::path mwExtractPath = extractPath / "morrowind"; -// extract_cab(mMorrowindPath, mwExtractPath, true); + if (mInstallBloodmoon) + { + emit textChanged(QLatin1String("Installing Bloodmoon\n")); -// bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path(); + if (!temp.mkdir(QLatin1String("bloodmoon"))) { + qDebug() << "Can't make dir"; + return; + } -// installToPath(dFilesDir, outputDataFilesDir); + if (!temp.cd(QLatin1String("bloodmoon"))) { + qDebug() << "Can't cd to dir"; + return; + } -// install_dfiles_outside(mwExtractPath, outputDataFilesDir); + if (!disk.cd(QLatin1String("Bloodmoon"))) + qDebug() << "Show file selector"; -// // Videos are often kept uncompressed on the cd -// bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false); -// if(videosPath.string() != "") -// { -// emit signalGUI(QString("Installing Videos...")); -// installToPath(videosPath, outputDataFilesDir / "Video", true); -// } + if (!disk.exists(QLatin1String("data1.hdr"))) { + qDebug() << "No data found!"; + return; + } -// bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false); -// if(cdDFiles.string() != "") -// { -// emit signalGUI(QString("Installing Uncompressed Data files from CD...")); -// installToPath(cdDFiles, outputDataFilesDir, true); -// } + // Extract the installation files + extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) + qDebug() << "failed!"; -// bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini"); + // Install files outside of cab archives + installDirectories(disk.absolutePath()); -// mTribunalDone = contains(outputDataFilesDir, "tribunal.esm"); -// mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); + mBloodmoonDone = true; + } -// } + int total = 0; + if (mInstallMorrowind) + total = 100; - /// + if (mInstallTribunal) + total = total + 100; + + if (mInstallBloodmoon) + total = total + 100; + + emit textChanged(tr("Installation finished!")); + emit progressChanged(total); emit finished(); } @@ -322,17 +421,25 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp fileName.append(QString::fromLatin1(unshield_file_name(unshield, index))); // Calculate the percentage done - int progress = qCeil(((float) counter / (float) unshield_file_count(unshield)) * 100); + int progress = qFloor(((float) counter / (float) unshield_file_count(unshield)) * 100); + + if (mMorrowindDone) + progress = progress + 100; + + if (mTribunalDone) + progress = progress + 100; qDebug() << progress << counter << unshield_file_count(unshield); - emit textChanged(QLatin1String("Extracting: ") + QString::fromLatin1(unshield_file_name(unshield, index))); + emit textChanged(tr("Extracting: %1").arg(QString::fromLatin1(unshield_file_name(unshield, index)))); emit progressChanged(progress); success = unshield_file_save(unshield, index, fileName.toLatin1().constData()); - if (!success) + if (!success) { + emit error(tr("Failed to extract %1").arg(fileName)); dir.remove(fileName); + } return success; } diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 61e021865..b268727ce 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -31,6 +31,9 @@ namespace Wizard bool removeDirectory(const QString &dirName); + bool copyFile(const QString &source, const QString &destination, bool keepSource = true); + bool copyDirectory(const QString &source, const QString &destination, bool keepSource = true); + bool moveFile(const QString &source, const QString &destination); bool moveDirectory(const QString &source, const QString &destination); @@ -39,11 +42,17 @@ namespace Wizard void extractCab(const QString &cabFile, const QString &outputDir); bool extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter); + void installDirectories(const QString &source); + bool mInstallMorrowind; bool mInstallTribunal; bool mInstallBloodmoon; + bool mMorrowindDone; + bool mTribunalDone; + bool mBloodmoonDone; + QString mPath; QString mIniPath; @@ -58,6 +67,8 @@ namespace Wizard signals: void finished(); void textChanged(const QString &text); + void logTextChanged(const QString &text); + void error(const QString &text); void progressChanged(int progress); diff --git a/files/ui/wizard/installationpage.ui b/files/ui/wizard/installationpage.ui index 6c1a3c493..664e10556 100644 --- a/files/ui/wizard/installationpage.ui +++ b/files/ui/wizard/installationpage.ui @@ -6,8 +6,8 @@ 0 0 - 516 - 421 + 514 + 419 @@ -30,7 +30,7 @@ - 24 + 0 From cdbf5c68b0fccceeb9a3a22fc654934228958f89 Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 24 Jan 2014 22:25:22 +0100 Subject: [PATCH 0020/1983] Implemented selection of installation media and added forgotten file --- apps/wizard/installationpage.cpp | 129 ++++- apps/wizard/installationpage.hpp | 8 + apps/wizard/unshield/unshieldworker.cpp | 506 ++++++++++++++---- apps/wizard/unshield/unshieldworker.hpp | 34 ++ .../48x48/preferences-desktop-locale.png | Bin 0 -> 1761 bytes 5 files changed, 546 insertions(+), 131 deletions(-) create mode 100644 files/wizard/icons/tango/48x48/preferences-desktop-locale.png diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 045138ab7..860cfbb56 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "mainwizard.hpp" #include "inisettings.hpp" @@ -53,82 +55,161 @@ void Wizard::InstallationPage::startInstallation() QStringList components(field("installation.components").toStringList()); QString path(field("installation.path").toString()); - QThread* thread = new QThread(); - UnshieldWorker* unshield = new UnshieldWorker(); + QThread *thread = new QThread(); + mUnshield = new UnshieldWorker(); - unshield->moveToThread(thread); + mUnshield->moveToThread(thread); connect(thread, SIGNAL(started()), - unshield, SLOT(extract())); + mUnshield, SLOT(extract())); - connect(unshield, SIGNAL(finished()), + connect(mUnshield, SIGNAL(finished()), thread, SLOT(quit())); - connect(unshield, SIGNAL(finished()), - unshield, SLOT(deleteLater())); + connect(mUnshield, SIGNAL(finished()), + mUnshield, SLOT(deleteLater())); - connect(unshield, SIGNAL(finished()), + connect(mUnshield, SIGNAL(finished()), thread, SLOT(deleteLater())); - connect(unshield, SIGNAL(finished()), + connect(mUnshield, SIGNAL(finished()), this, SLOT(installationFinished())); - connect(unshield, SIGNAL(error(QString)), + connect(mUnshield, SIGNAL(error(QString)), this, SLOT(installationError(QString))); - connect(unshield, SIGNAL(textChanged(QString)), + connect(mUnshield, SIGNAL(textChanged(QString)), installProgressLabel, SLOT(setText(QString))); - connect(unshield, SIGNAL(textChanged(QString)), + connect(mUnshield, SIGNAL(textChanged(QString)), logTextEdit, SLOT(append(QString))); - connect(unshield, SIGNAL(progressChanged(int)), + connect(mUnshield, SIGNAL(progressChanged(int)), installProgressBar, SLOT(setValue(int))); + connect(mUnshield, SIGNAL(requestFileDialog(QString)), + this, SLOT(showFileDialog(QString))); + if (field("installation.new").toBool() == true) { // Always install Morrowind - unshield->setInstallMorrowind(true); + mUnshield->setInstallMorrowind(true); if (components.contains(QLatin1String("Tribunal"))) - unshield->setInstallTribunal(true); + mUnshield->setInstallTribunal(true); if (components.contains(QLatin1String("Bloodmoon"))) - unshield->setInstallBloodmoon(true); + mUnshield->setInstallBloodmoon(true); } else { // Morrowind should already be installed - unshield->setInstallMorrowind(false); + mUnshield->setInstallMorrowind(false); if (components.contains(QLatin1String("Tribunal")) && mWizard->mInstallations[path]->hasTribunal == false) - unshield->setInstallTribunal(true); + mUnshield->setInstallTribunal(true); if (components.contains(QLatin1String("Bloodmoon")) && mWizard->mInstallations[path]->hasBloodmoon == false) - unshield->setInstallBloodmoon(true); + mUnshield->setInstallBloodmoon(true); // Set the location of the Morrowind.ini to update - unshield->setIniPath(mWizard->mInstallations[path]->iniPath); + mUnshield->setIniPath(mWizard->mInstallations[path]->iniPath); } // Set the installation target path - unshield->setPath(path); + mUnshield->setPath(path); // Set the right codec to use for Morrowind.ini QString language(field("installation.language").toString()); if (language == QLatin1String("Polish")) { - unshield->setIniCodec(QTextCodec::codecForName("windows-1250")); + mUnshield->setIniCodec(QTextCodec::codecForName("windows-1250")); } else if (language == QLatin1String("Russian")) { - unshield->setIniCodec(QTextCodec::codecForName("windows-1251")); + mUnshield->setIniCodec(QTextCodec::codecForName("windows-1251")); } else { - unshield->setIniCodec(QTextCodec::codecForName("windows-1252")); + mUnshield->setIniCodec(QTextCodec::codecForName("windows-1252")); } thread->start(); +} + + + +//void Wizard::InstallationPage::installAddons() +//{ +// qDebug() << "component finished"; + +// QStringList components(field("installation.components").toStringList()); + +// if (components.contains(QLatin1String("Tribunal")) && !mUnshield->tribunalDone()) +// { +// QString fileName = QFileDialog::getOpenFileName( +// this, +// tr("Select Tribunal installation file"), +// QDir::rootPath(), +// tr("InstallShield header files (*.hdr)")); + +// if (fileName.isEmpty()) { +// qDebug() << "Cancel was clicked!"; +// return; +// } + +// QFileInfo info(fileName); +// mUnshield->installTribunal(info.absolutePath()); +// } +// if (components.contains(QLatin1String("Bloodmoon")) && !mUnshield->bloodmoonDone()) +// { +// QString fileName = QFileDialog::getOpenFileName( +// this, +// tr("Select Bloodmoon installation file"), +// QDir::rootPath(), +// tr("InstallShield header files (*.hdr)")); + +// if (fileName.isEmpty()) { +// qDebug() << "Cancel was clicked!"; +// return; +// } + +// QFileInfo info(fileName); +// mUnshield->installBloodmoon(info.absolutePath()); +// } +//} + +void Wizard::InstallationPage::showFileDialog(const QString &component) +{ + QString fileName; + + if (field("installation.new").toBool() == true) + { + fileName = QFileDialog::getOpenFileName( + this, + tr("Select %0 installation file").arg(component), + QDir::rootPath(), + tr("InstallShield header files (*.hdr)")); + + if (fileName.isEmpty()) { + qDebug() << "Cancel was clicked!"; + return; + } + + QFileInfo info(fileName); + + if (component == QLatin1String("Morrowind")) + { + mUnshield->setMorrowindPath(info.absolutePath()); + } + else if (component == QLatin1String("Tribunal")) + { + mUnshield->setTribunalPath(info.absolutePath()); + } + else if (component == QLatin1String("Bloodmoon")) + { + mUnshield->setBloodmoonPath(info.absolutePath()); + } + } } void Wizard::InstallationPage::installationFinished() diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index f55ab8ab5..e125eadc4 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -6,10 +6,13 @@ #include "ui_installationpage.h" #include "inisettings.hpp" +class QThread; + namespace Wizard { class MainWizard; class IniSettings; + class UnshieldWorker; class InstallationPage : public QWizardPage, private Ui::InstallationPage { @@ -24,9 +27,14 @@ namespace Wizard MainWizard *mWizard; bool mFinished; + QThread* mThread; + UnshieldWorker *mUnshield; + void startInstallation(); private slots: + void showFileDialog(const QString &component); + void installationFinished(); void installationError(const QString &text); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 4f92b1cf9..7d536affa 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -20,6 +21,10 @@ Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : { unshield_set_log_level(0); + mMorrowindPath = QString(); + mTribunalPath = QString(); + mBloodmoonPath = QString(); + mPath = QString(); mIniPath = QString(); @@ -42,19 +47,101 @@ Wizard::UnshieldWorker::~UnshieldWorker() void Wizard::UnshieldWorker::setInstallMorrowind(bool install) { +// QMutexLocker locker(&mMutex); mInstallMorrowind = install; } void Wizard::UnshieldWorker::setInstallTribunal(bool install) { +// QMutexLocker locker(&mMutex); mInstallTribunal = install; } void Wizard::UnshieldWorker::setInstallBloodmoon(bool install) { +// QMutexLocker locker(&mMutex); mInstallBloodmoon = install; } +bool Wizard::UnshieldWorker::getInstallMorrowind() +{ +// QMutexLocker locker(&mMutex); + return mInstallMorrowind; +} + +bool Wizard::UnshieldWorker::getInstallTribunal() +{ +// QMutexLocker locker(&mMutex); + return mInstallTribunal; +} + +bool Wizard::UnshieldWorker::getInstallBloodmoon() +{ +// QMutexLocker locker(&mMutex); + return mInstallBloodmoon; +} + +//bool Wizard::UnshieldWorker::getMorrowindDone() +//{ +// QMutexLocker locker(&mMutex); +// return mMorrowindDone; +//} + +//bool Wizard::UnshieldWorker::tribunalDone() +//{ +// QMutexLocker locker(&mMutex); +// return mTribunalDone; +//} + +//bool Wizard::UnshieldWorker::bloodmoonDone() +//{ +// return mBloodmoonDone; +//} + +void Wizard::UnshieldWorker::setMorrowindPath(const QString &path) +{ + qDebug() << "setmorrowindpath!"; + QMutexLocker locker(&mMutex); + mMorrowindPath = path; + mWait.wakeAll(); +} + +void Wizard::UnshieldWorker::setTribunalPath(const QString &path) +{ + //QMutexLocker locker(&mMutex); + mTribunalPath = path; + mWait.wakeAll(); + +} + +void Wizard::UnshieldWorker::setBloodmoonPath(const QString &path) +{ + //QMutexLocker locker(&mMutex); + mBloodmoonPath = path; + mWait.wakeAll(); + +} + +QString Wizard::UnshieldWorker::getMorrowindPath() +{ + qDebug() << "getmorrowindpath!"; + //QMutexLocker locker(&mMutex); + //mWait.wakeAll(); + return mMorrowindPath; +} + +QString Wizard::UnshieldWorker::getTribunalPath() +{ + //QMutexLocker locker(&mMutex); + return mTribunalPath; +} + +QString Wizard::UnshieldWorker::getBloodmoonPath() +{ + //QMutexLocker locker(&mMutex); + return mBloodmoonPath; +} + void Wizard::UnshieldWorker::setPath(const QString &path) { mPath = path; @@ -224,15 +311,173 @@ void Wizard::UnshieldWorker::installDirectories(const QString &source) copyDirectory(info.absoluteFilePath(), mPath + QDir::separator() + info.fileName()); } } + + // Copy the Data Files dir too, but only the subdirectories + QFileInfo info(dir.absoluteFilePath("Data Files")); + if (info.exists()) { + emit textChanged(tr("Extracting: Data Files directory")); + copyDirectory(info.absoluteFilePath(), mPath); + } + } + void Wizard::UnshieldWorker::extract() { - emit textChanged(QLatin1String("Starting installation")); - emit textChanged(QLatin1String("Installation target: ") + mPath); + qDebug() << "extract!"; + + QMutexLocker locker(&mMutex); + + QDir disk; + + qDebug() << "hi!"; + + if (getInstallMorrowind()) + { + while (!mMorrowindDone) + { + if (getMorrowindPath().isEmpty()) { + qDebug() << "request file dialog"; + emit requestFileDialog(QLatin1String("Morrowind")); + mWait.wait(&mMutex); + } + + if (!mMorrowindDone && !getMorrowindPath().isEmpty()) { + disk.setPath(getMorrowindPath()); + + if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Morrowind.bsa"))) { + qDebug() << "found"; + emit requestFileDialog(QLatin1String("Morrowind")); + mWait.wait(&mMutex); + + } else { + qDebug() << "install morrowind!"; + + if (installMorrowind()) { + mMorrowindDone = true; + } else { + qDebug() << "Erorr installing Morrowind"; + return; + } + } + } + } + } + + if (getInstallTribunal()) + { + while (!mTribunalDone) + { + QDir tribunal(disk); + + if (!tribunal.cd(QLatin1String("Tribunal"))) { + qDebug() << "not found on cd!"; + emit requestFileDialog(QLatin1String("Tribunal")); + mWait.wait(&mMutex); + + } else if (tribunal.exists(QLatin1String("data1.hdr"))) { + qDebug() << "Exists! " << tribunal.absolutePath(); + mTribunalPath = tribunal.absolutePath(); + } + + if (getTribunalPath().isEmpty()) { + qDebug() << "request file dialog"; + emit requestFileDialog(QLatin1String("Tribunal")); + mWait.wait(&mMutex); + } + + // Make sure the dir is up-to-date + tribunal.setPath(getTribunalPath()); + + if (!findFile(tribunal.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa"))) { + qDebug() << "file not found!"; + emit requestFileDialog(QLatin1String("Tribunal")); + mWait.wait(&mMutex); + + } else { + qDebug() << "install tribunal!"; + if (installTribunal()) { + mTribunalDone = true; + } else { + qDebug() << "Erorr installing Tribunal"; + return; + } + } + } + } + + if (getInstallBloodmoon()) + { + while (!mBloodmoonDone) + { + QDir bloodmoon(disk); + + qDebug() << "bloodmoon!: " << bloodmoon.absolutePath(); + + if (!bloodmoon.cd(QLatin1String("Bloodmoon"))) { + emit requestFileDialog(QLatin1String("Bloodmoon")); + mWait.wait(&mMutex); + + } else if (bloodmoon.exists(QLatin1String("data1.hdr"))) { + mBloodmoonPath = bloodmoon.absolutePath(); + } + + if (getBloodmoonPath().isEmpty()) { + qDebug() << "request file dialog"; + emit requestFileDialog(QLatin1String("Bloodmoon")); + mWait.wait(&mMutex); + } + + // Make sure the dir is up-to-date + bloodmoon.setPath(getBloodmoonPath()); + + if (!findFile(bloodmoon.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) { + emit requestFileDialog(QLatin1String("Bloodmoon")); + mWait.wait(&mMutex); + + } else { + qDebug() << "install bloodmoon!"; + if (installBloodmoon()) { + mBloodmoonDone = true; + } else { + qDebug() << "Erorr installing Bloodmoon"; + return; + } + } + } + } + + // Remove the temporary directory + removeDirectory(mPath + QDir::separator() + QLatin1String("extract-temp")); + + locker.unlock(); + + int total = 0; + + if (mInstallMorrowind) + total = 100; + + if (mInstallTribunal) + total = total + 100; + + if (mInstallBloodmoon) + total = total + 100; + + emit textChanged(tr("Installation finished!")); + emit progressChanged(total); + emit finished(); + + qDebug() << "installation finished!"; +} + +bool Wizard::UnshieldWorker::installMorrowind() +{ + emit textChanged(QLatin1String("Installing Morrowind\n")); - QString diskPath("/mnt/cdrom/"); - QDir disk(diskPath); + QDir disk(getMorrowindPath()); + + if (!disk.exists()) + return false; // Create temporary extract directory // TODO: Use QTemporaryDir in Qt 5.0 @@ -244,156 +489,168 @@ void Wizard::UnshieldWorker::extract() if (!temp.mkpath(tempPath)) { qDebug() << "Can't make path"; - return; + return false; } temp.setPath(tempPath); - disk.setPath(diskPath); - if (mInstallMorrowind) - { - emit textChanged(QLatin1String("Installing Morrowind\n")); + QString cabFile(disk.absoluteFilePath(QLatin1String("data1.hdr"))); - if (!temp.mkdir(QLatin1String("morrowind"))) { - qDebug() << "Can't make dir"; - return; - } + if (!temp.mkdir(QLatin1String("morrowind"))) { + qDebug() << "Can't make dir"; + return false; + } - if (!temp.cd(QLatin1String("morrowind"))) { - qDebug() << "Can't cd to dir"; - return; - } + if (!temp.cd(QLatin1String("morrowind"))) { + qDebug() << "Can't cd to dir"; + return false; + } - if (!disk.exists(QLatin1String("data1.hdr"))) { - qDebug() << "No data found!"; - return; - } + // Extract the installation files + extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); - // Extract the installation files - extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); + // TODO: Throw error; + // Move the files from the temporary path to the destination folder + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) { + qDebug() << "failed to move files!"; + return false; + } - // TODO: Throw error; - // Move the files from the temporary path to the destination folder + // Install files outside of cab archives + installDirectories(disk.absolutePath()); - //qDebug() << "rename: " << morrowindTempPath << " to: " << mPath; - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) - qDebug() << "failed!"; + // Copy Morrowind configuration file + QString iniPath(temp.absoluteFilePath(QLatin1String("App Executables"))); + iniPath.append(QDir::separator() + QLatin1String("Morrowind.ini")); - // Install files outside of cab archives - qDebug() << temp.absolutePath() << disk.absolutePath(); - installDirectories(disk.absolutePath()); + QFileInfo info(iniPath); - // Copy Morrowind configuration file - QString iniPath(temp.absoluteFilePath(QLatin1String("App Executables"))); - iniPath.append(QDir::separator() + QLatin1String("Morrowind.ini")); + qDebug() << info.absoluteFilePath() << mPath; - QFileInfo info(iniPath); + if (info.exists()) { + emit textChanged(tr("Extracting: Morrowind.ini")); + moveFile(info.absoluteFilePath(), mPath + QDir::separator() + QLatin1String("Morrowind.ini")); + } else { + qDebug() << "Could not find ini file!"; + return false; + } - qDebug() << info.absoluteFilePath() << mPath; + return true; +} - if (info.exists()) { - emit textChanged(tr("Extracting: Morrowind.ini")); - moveFile(info.absoluteFilePath(), mPath + QDir::separator() + QLatin1String("Morrowind.ini")); - } else { - qDebug() << "Could not find ini file!"; - } +bool Wizard::UnshieldWorker::installTribunal() +{ + emit textChanged(QLatin1String("Installing Tribunal")); - mMorrowindDone = true; + QDir disk(getTribunalPath()); + if (!disk.exists()) { + qDebug() << "disk does not exist! " << disk.absolutePath() << getTribunalPath(); + return false; } - temp.setPath(tempPath); - disk.setPath(diskPath); - - if (mInstallTribunal) - { - emit textChanged(QLatin1String("Installing Tribunal\n")); - - if (!temp.mkdir(QLatin1String("tribunal"))) { - qDebug() << "Can't make dir"; - return; - } + // Create temporary extract directory + // TODO: Use QTemporaryDir in Qt 5.0 + QString tempPath(mPath + QDir::separator() + QLatin1String("extract-temp")); + QDir temp; - if (!temp.cd(QLatin1String("tribunal"))) { - qDebug() << "Can't cd to dir"; - return; - } + // Make sure the temporary folder is empty + removeDirectory(tempPath); - if (!disk.cd(QLatin1String("Tribunal"))) - qDebug() << "Show file selector"; + if (!temp.mkpath(tempPath)) { + qDebug() << "Can't make path"; + return false; + } - if (!disk.exists(QLatin1String("data1.hdr"))) { - qDebug() << "No data found!"; - return; - } + temp.setPath(tempPath); - // Extract the installation files - extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); + if (!temp.mkdir(QLatin1String("tribunal"))) { + qDebug() << "Can't make dir"; + return false; + } - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) - qDebug() << "failed!"; + if (!temp.cd(QLatin1String("tribunal"))) { + qDebug() << "Can't cd to dir"; + return false; + } - // Install files outside of cab archives - installDirectories(disk.absolutePath()); + // Extract the installation files + extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); - mTribunalDone = true; + // TODO: Throw error; + // Move the files from the temporary path to the destination folder + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) { + qDebug() << "failed to move files!"; + return false; } - temp.setPath(tempPath); - disk.setPath(diskPath); + // Install files outside of cab archives + installDirectories(disk.absolutePath()); - if (mInstallBloodmoon) - { - emit textChanged(QLatin1String("Installing Bloodmoon\n")); + return true; +} - if (!temp.mkdir(QLatin1String("bloodmoon"))) { - qDebug() << "Can't make dir"; - return; - } +bool Wizard::UnshieldWorker::installBloodmoon() +{ + emit textChanged(QLatin1String("Installing Bloodmoon")); - if (!temp.cd(QLatin1String("bloodmoon"))) { - qDebug() << "Can't cd to dir"; - return; - } + QDir disk(getBloodmoonPath()); - if (!disk.cd(QLatin1String("Bloodmoon"))) - qDebug() << "Show file selector"; + if (!disk.exists()) { + qDebug() << "disk does not exist! " << disk.absolutePath() << getTribunalPath(); + return false; + } - if (!disk.exists(QLatin1String("data1.hdr"))) { - qDebug() << "No data found!"; - return; - } + // Create temporary extract directory + // TODO: Use QTemporaryDir in Qt 5.0 + QString tempPath(mPath + QDir::separator() + QLatin1String("extract-temp")); + QDir temp; - // Extract the installation files - extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); + // Make sure the temporary folder is empty + removeDirectory(tempPath); - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) - qDebug() << "failed!"; + if (!temp.mkpath(tempPath)) { + qDebug() << "Can't make path"; + return false; + } - // Install files outside of cab archives - installDirectories(disk.absolutePath()); + temp.setPath(tempPath); - mBloodmoonDone = true; + if (!temp.mkdir(QLatin1String("bloodmoon"))) { + qDebug() << "Can't make dir"; + return false; } + if (!temp.cd(QLatin1String("bloodmoon"))) { + qDebug() << "Can't cd to dir"; + return false; + } - int total = 0; + // Extract the installation files + extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); - if (mInstallMorrowind) - total = 100; + // TODO: Throw error; + // Move the files from the temporary path to the destination folder + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) { + qDebug() << "failed to move files!"; + return false; + } - if (mInstallTribunal) - total = total + 100; + // Install files outside of cab archives + installDirectories(disk.absolutePath()); - if (mInstallBloodmoon) - total = total + 100; + QFileInfo patch(temp.absoluteFilePath(QLatin1String("Tribunal Patch") + QDir::separator() + QLatin1String("Tribunal.esm"))); + QFileInfo original(mPath + QDir::separator() + QLatin1String("Tribunal.esm")); - emit textChanged(tr("Installation finished!")); - emit progressChanged(total); - emit finished(); + if (original.exists() && patch.exists()) { + emit textChanged(tr("Extracting: Tribunal patch")); + copyFile(patch.absoluteFilePath(), original.absoluteFilePath()); + } + return true; } + bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter) { bool success; @@ -444,11 +701,46 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp return success; } +bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fileName) +{ + Unshield *unshield; + unshield = unshield_open(cabFile.toLatin1().constData()); + + // TODO: Proper error + if (!unshield) { + emit error(tr("Failed to open %1").arg(cabFile)); + return false; + } + + for (int i=0; ifirst_file; j<=group->last_file; ++j) + { + QString current(QString::fromLatin1(unshield_file_name(unshield, j))); + + qDebug() << "File is: " << unshield_file_name(unshield, j); + if (current == fileName) + return true; // File is found! + } + } + + unshield_close(unshield); + return false; +} + void Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &outputDir) { Unshield *unshield; unshield = unshield_open(cabFile.toLatin1().constData()); + // TODO: Proper error + if (!unshield) { + emit error(tr("Failed to open %1").arg(cabFile)); + return; + } + int counter = 0; for (int i=0; i #include +#include +#include #include @@ -22,6 +24,18 @@ namespace Wizard void setInstallTribunal(bool install); void setInstallBloodmoon(bool install); + bool getInstallMorrowind(); + bool getInstallTribunal(); + bool getInstallBloodmoon(); + + void setMorrowindPath(const QString &path); + void setTribunalPath(const QString &path); + void setBloodmoonPath(const QString &path); + + QString getMorrowindPath(); + QString getTribunalPath(); + QString getBloodmoonPath(); + void setPath(const QString &path); void setIniPath(const QString &path); @@ -29,6 +43,14 @@ namespace Wizard private: +// void setMorrowindDone(bool done); +// void setTribunalDone(bool done); +// void setBloodmoonDone(bool done); + +// bool getMorrowindDone(); +// bool getTribunalDone(); +// bool getBloodmoonDone(); + bool removeDirectory(const QString &dirName); bool copyFile(const QString &source, const QString &destination, bool keepSource = true); @@ -41,9 +63,13 @@ namespace Wizard void extractCab(const QString &cabFile, const QString &outputDir); bool extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter); + bool findFile(const QString &cabFile, const QString &fileName); void installDirectories(const QString &source); + bool installMorrowind(); + bool installTribunal(); + bool installBloodmoon(); bool mInstallMorrowind; bool mInstallTribunal; @@ -53,6 +79,10 @@ namespace Wizard bool mTribunalDone; bool mBloodmoonDone; + QString mMorrowindPath; + QString mTribunalPath; + QString mBloodmoonPath; + QString mPath; QString mIniPath; @@ -60,12 +90,16 @@ namespace Wizard QTextCodec *mIniCodec; + QWaitCondition mWait; + QMutex mMutex; public slots: void extract(); signals: void finished(); + void requestFileDialog(const QString &component); + void textChanged(const QString &text); void logTextChanged(const QString &text); diff --git a/files/wizard/icons/tango/48x48/preferences-desktop-locale.png b/files/wizard/icons/tango/48x48/preferences-desktop-locale.png new file mode 100644 index 0000000000000000000000000000000000000000..f56497bd23989040f0a2d23080b8103ad086cfbf GIT binary patch literal 1761 zcmV<71|Io|P)f8XQYxp&4h_IP4DiIX;ILTG3tgaC<(1skfaDuhs3pe_p%ELpSg zo((HNVgnl%tyF=ET2vukf$$J%)uuF+lR9pi_-)U_Gk5Oed$8~jEaW&-M~1PKBVFB@ zJ6D?X`@VDj=bR57*&}->6Z`%6!sovINg>SBGSFX?LOm|v1W?9%oO9L-(FVU^jd|C@ zycGTeTlfzyymk7cIp*B|1>pIoo)6{gZIO`BjAP`8Q$chZ>4Axt`a)fa}RE?qJr6vv%WPvw2}>zDue!|r|n zdiNJ4)Xy({_Q|QK6H|0HIxMU&5(Xhb5D)@T^e?8{>BTh3zgA>6H<_{48s#u0mG7*mkfBA|CDDsB3&Jow(6;uv3F&4^mZvDGP*M|UO%%n%QA}707#ojbyo_-kZ+3z~58l;EvGC#T zt}Pz?U;2Ng7T$aEEXNg&G*M`&?>VcaMyZ3jB9FR^%DppN9BqIjzXL}b!22TwpzmAW z_TlitvK8N8y*5vK>?T>MNh&*dX~BbV8VZffG`?BGY>eZ3Rp?EidUZe@GQfSTueNx5 z;#EG3|H5qJERWAV!R*wtjK>YeN|Qu-44kB}1*XX9q+6_RFS6dc!|LV=&GsT&=^C<7 z1f5C3%^9NYX`=2lQ95-Xu%Q8N)?eqwKbkWdQXc86EXzPV7|=qDn-0EFRerNADFM)%qCYljDpxYIqN6FU8?U9>D&a zLIsK&?=6@+x9%2cKMTEZEB-})auj7~H-G_n^UrUmX+JGGY3H@u8w1qg+fp7qz9z3Is2*A~) zs|h=!i5mc@!8>9nKitSTe*G0+As=Yf%%8@xjT@?(XD_x@fwNTDdJIu$iZMNbf_F|-J<(vhh? znM~-*Z4%L;ZB{8L4qt`#v3v3}89qiMJWWM6=x1HJ?S$>TiM1O@uZ%zwm#VT7HstZ} zJeBe|VJ)JauhC5JvejRr*lpFaXY5`fbmZyMIhqR+fafwDW{p z`L4hvwPFt$LxtH?f7Um$md(V@%!t{&}?jg Date: Mon, 27 Jan 2014 15:12:02 +0100 Subject: [PATCH 0021/1983] Made the getter/setter functions thread-safe --- apps/wizard/installationpage.cpp | 12 +- apps/wizard/unshield/unshieldworker.cpp | 273 ++++++++++++------------ apps/wizard/unshield/unshieldworker.hpp | 18 +- 3 files changed, 155 insertions(+), 148 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 860cfbb56..9b14b5918 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -73,22 +73,22 @@ void Wizard::InstallationPage::startInstallation() thread, SLOT(deleteLater())); connect(mUnshield, SIGNAL(finished()), - this, SLOT(installationFinished())); + this, SLOT(installationFinished()), Qt::QueuedConnection); connect(mUnshield, SIGNAL(error(QString)), - this, SLOT(installationError(QString))); + this, SLOT(installationError(QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(textChanged(QString)), - installProgressLabel, SLOT(setText(QString))); + installProgressLabel, SLOT(setText(QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(textChanged(QString)), - logTextEdit, SLOT(append(QString))); + logTextEdit, SLOT(append(QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(progressChanged(int)), - installProgressBar, SLOT(setValue(int))); + installProgressBar, SLOT(setValue(int)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(requestFileDialog(QString)), - this, SLOT(showFileDialog(QString))); + this, SLOT(showFileDialog(QString)), Qt::QueuedConnection); if (field("installation.new").toBool() == true) { diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 7d536affa..5c22df446 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -2,10 +2,11 @@ #include +#include +#include #include #include #include -#include #include #include #include @@ -47,68 +48,50 @@ Wizard::UnshieldWorker::~UnshieldWorker() void Wizard::UnshieldWorker::setInstallMorrowind(bool install) { -// QMutexLocker locker(&mMutex); + QWriteLocker writeLock(&mLock); mInstallMorrowind = install; } void Wizard::UnshieldWorker::setInstallTribunal(bool install) { -// QMutexLocker locker(&mMutex); + QWriteLocker writeLock(&mLock); mInstallTribunal = install; } void Wizard::UnshieldWorker::setInstallBloodmoon(bool install) { -// QMutexLocker locker(&mMutex); + QWriteLocker writeLock(&mLock); mInstallBloodmoon = install; } bool Wizard::UnshieldWorker::getInstallMorrowind() { -// QMutexLocker locker(&mMutex); + QReadLocker readLock(&mLock); return mInstallMorrowind; } bool Wizard::UnshieldWorker::getInstallTribunal() { -// QMutexLocker locker(&mMutex); + QReadLocker readLock(&mLock); return mInstallTribunal; } bool Wizard::UnshieldWorker::getInstallBloodmoon() { -// QMutexLocker locker(&mMutex); + QReadLocker readLock(&mLock); return mInstallBloodmoon; } -//bool Wizard::UnshieldWorker::getMorrowindDone() -//{ -// QMutexLocker locker(&mMutex); -// return mMorrowindDone; -//} - -//bool Wizard::UnshieldWorker::tribunalDone() -//{ -// QMutexLocker locker(&mMutex); -// return mTribunalDone; -//} - -//bool Wizard::UnshieldWorker::bloodmoonDone() -//{ -// return mBloodmoonDone; -//} - void Wizard::UnshieldWorker::setMorrowindPath(const QString &path) { - qDebug() << "setmorrowindpath!"; - QMutexLocker locker(&mMutex); + QWriteLocker writeLock(&mLock); mMorrowindPath = path; mWait.wakeAll(); } void Wizard::UnshieldWorker::setTribunalPath(const QString &path) { - //QMutexLocker locker(&mMutex); + QWriteLocker writeLock(&mLock); mTribunalPath = path; mWait.wakeAll(); @@ -116,7 +99,7 @@ void Wizard::UnshieldWorker::setTribunalPath(const QString &path) void Wizard::UnshieldWorker::setBloodmoonPath(const QString &path) { - //QMutexLocker locker(&mMutex); + QWriteLocker writeLock(&mLock); mBloodmoonPath = path; mWait.wakeAll(); @@ -124,57 +107,99 @@ void Wizard::UnshieldWorker::setBloodmoonPath(const QString &path) QString Wizard::UnshieldWorker::getMorrowindPath() { - qDebug() << "getmorrowindpath!"; - //QMutexLocker locker(&mMutex); - //mWait.wakeAll(); + QReadLocker readLock(&mLock); return mMorrowindPath; } QString Wizard::UnshieldWorker::getTribunalPath() { - //QMutexLocker locker(&mMutex); + QReadLocker readLock(&mLock); return mTribunalPath; } QString Wizard::UnshieldWorker::getBloodmoonPath() { - //QMutexLocker locker(&mMutex); + QReadLocker readLock(&mLock); return mBloodmoonPath; } void Wizard::UnshieldWorker::setPath(const QString &path) { + QWriteLocker writeLock(&mLock); mPath = path; } void Wizard::UnshieldWorker::setIniPath(const QString &path) { + QWriteLocker writeLock(&mLock); mIniPath = path; } +QString Wizard::UnshieldWorker::getPath() +{ + QReadLocker readLock(&mLock); + return mPath; +} + +QString Wizard::UnshieldWorker::getIniPath() +{ + QReadLocker readLock(&mLock); + return mIniPath; +} + void Wizard::UnshieldWorker::setIniCodec(QTextCodec *codec) { + QWriteLocker writeLock(&mLock); mIniCodec = codec; } +void Wizard::UnshieldWorker::setMorrowindDone(bool done) +{ + QWriteLocker writeLock(&mLock); + mMorrowindDone = done; +} + +void Wizard::UnshieldWorker::setTribunalDone(bool done) +{ + QWriteLocker writeLock(&mLock); + mTribunalDone = done; +} + +void Wizard::UnshieldWorker::setBloodmoonDone(bool done) +{ + QWriteLocker writeLock(&mLock); + mBloodmoonDone = done; +} + +bool Wizard::UnshieldWorker::getMorrowindDone() +{ + QReadLocker readLock(&mLock); + return mMorrowindDone; +} + +bool Wizard::UnshieldWorker::getTribunalDone() +{ + QReadLocker readLock(&mLock); + return mTribunalDone; +} + +bool Wizard::UnshieldWorker::getBloodmoonDone() +{ + QReadLocker readLock(&mLock); + return mBloodmoonDone; +} + void Wizard::UnshieldWorker::setupSettings() { // Create Morrowind.ini settings map - if (mIniPath.isEmpty()) + if (getIniPath().isEmpty()) return; - QFile file(mIniPath); + QFile file(getIniPath()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { // TODO: Emit error signal - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening Morrowind configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + qDebug() << "Error opening .ini file!"; return; } @@ -308,7 +333,7 @@ void Wizard::UnshieldWorker::installDirectories(const QString &source) if (directories.contains(info.fileName())) { qDebug() << "found " << info.fileName(); emit textChanged(tr("Extracting: %1 directory").arg(info.fileName())); - copyDirectory(info.absoluteFilePath(), mPath + QDir::separator() + info.fileName()); + copyDirectory(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName()); } } @@ -316,7 +341,7 @@ void Wizard::UnshieldWorker::installDirectories(const QString &source) QFileInfo info(dir.absoluteFilePath("Data Files")); if (info.exists()) { emit textChanged(tr("Extracting: Data Files directory")); - copyDirectory(info.absoluteFilePath(), mPath); + copyDirectory(info.absoluteFilePath(), getPath()); } } @@ -325,36 +350,32 @@ void Wizard::UnshieldWorker::installDirectories(const QString &source) void Wizard::UnshieldWorker::extract() { qDebug() << "extract!"; - - QMutexLocker locker(&mMutex); - QDir disk; - qDebug() << "hi!"; - if (getInstallMorrowind()) { - while (!mMorrowindDone) + while (!getMorrowindDone()) { if (getMorrowindPath().isEmpty()) { qDebug() << "request file dialog"; + QReadLocker readLock(&mLock); emit requestFileDialog(QLatin1String("Morrowind")); - mWait.wait(&mMutex); + mWait.wait(&mLock); } - if (!mMorrowindDone && !getMorrowindPath().isEmpty()) { + if (!getMorrowindPath().isEmpty()) { disk.setPath(getMorrowindPath()); - if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Morrowind.bsa"))) { - qDebug() << "found"; + if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Morrowind.bsa")) + | findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa")) + | findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) + { + QReadLocker readLock(&mLock); emit requestFileDialog(QLatin1String("Morrowind")); - mWait.wait(&mMutex); - + mWait.wait(&mLock); } else { - qDebug() << "install morrowind!"; - if (installMorrowind()) { - mMorrowindDone = true; + setMorrowindDone(true); } else { qDebug() << "Erorr installing Morrowind"; return; @@ -366,41 +387,31 @@ void Wizard::UnshieldWorker::extract() if (getInstallTribunal()) { - while (!mTribunalDone) + while (!getTribunalDone()) { - QDir tribunal(disk); - - if (!tribunal.cd(QLatin1String("Tribunal"))) { - qDebug() << "not found on cd!"; - emit requestFileDialog(QLatin1String("Tribunal")); - mWait.wait(&mMutex); - - } else if (tribunal.exists(QLatin1String("data1.hdr"))) { - qDebug() << "Exists! " << tribunal.absolutePath(); - mTribunalPath = tribunal.absolutePath(); - } - if (getTribunalPath().isEmpty()) { qDebug() << "request file dialog"; + QReadLocker locker(&mLock); emit requestFileDialog(QLatin1String("Tribunal")); - mWait.wait(&mMutex); + mWait.wait(&mLock); } - // Make sure the dir is up-to-date - tribunal.setPath(getTribunalPath()); - - if (!findFile(tribunal.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa"))) { - qDebug() << "file not found!"; - emit requestFileDialog(QLatin1String("Tribunal")); - mWait.wait(&mMutex); + if (!getTribunalPath().isEmpty()) { + disk.setPath(getTribunalPath()); - } else { - qDebug() << "install tribunal!"; - if (installTribunal()) { - mTribunalDone = true; + if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa"))) + { + qDebug() << "found"; + QReadLocker locker(&mLock); + emit requestFileDialog(QLatin1String("Tribunal")); + mWait.wait(&mLock); } else { - qDebug() << "Erorr installing Tribunal"; - return; + if (installTribunal()) { + setTribunalDone(true); + } else { + qDebug() << "Erorr installing Tribunal"; + return; + } } } } @@ -408,50 +419,35 @@ void Wizard::UnshieldWorker::extract() if (getInstallBloodmoon()) { - while (!mBloodmoonDone) + while (!getBloodmoonDone()) { - QDir bloodmoon(disk); - - qDebug() << "bloodmoon!: " << bloodmoon.absolutePath(); - - if (!bloodmoon.cd(QLatin1String("Bloodmoon"))) { - emit requestFileDialog(QLatin1String("Bloodmoon")); - mWait.wait(&mMutex); - - } else if (bloodmoon.exists(QLatin1String("data1.hdr"))) { - mBloodmoonPath = bloodmoon.absolutePath(); - } - if (getBloodmoonPath().isEmpty()) { qDebug() << "request file dialog"; + QReadLocker locker(&mLock); emit requestFileDialog(QLatin1String("Bloodmoon")); - mWait.wait(&mMutex); + mWait.wait(&mLock); } - // Make sure the dir is up-to-date - bloodmoon.setPath(getBloodmoonPath()); - - if (!findFile(bloodmoon.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) { - emit requestFileDialog(QLatin1String("Bloodmoon")); - mWait.wait(&mMutex); + if (!getBloodmoonPath().isEmpty()) { + disk.setPath(getBloodmoonPath()); - } else { - qDebug() << "install bloodmoon!"; - if (installBloodmoon()) { - mBloodmoonDone = true; + if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) + { + QReadLocker locker(&mLock); + emit requestFileDialog(QLatin1String("Bloodmoon")); + mWait.wait(&mLock); } else { - qDebug() << "Erorr installing Bloodmoon"; - return; + if (installBloodmoon()) { + setBloodmoonDone(true); + } else { + qDebug() << "Erorr installing Bloodmoon"; + return; + } } } } } - // Remove the temporary directory - removeDirectory(mPath + QDir::separator() + QLatin1String("extract-temp")); - - locker.unlock(); - int total = 0; if (mInstallMorrowind) @@ -472,16 +468,19 @@ void Wizard::UnshieldWorker::extract() bool Wizard::UnshieldWorker::installMorrowind() { - emit textChanged(QLatin1String("Installing Morrowind\n")); + qDebug() << "install morrowind!"; + emit textChanged(QLatin1String("Installing Morrowind")); QDir disk(getMorrowindPath()); - if (!disk.exists()) + if (!disk.exists()) { + qDebug() << "getMorrowindPath: " << getMorrowindPath(); return false; + } // Create temporary extract directory // TODO: Use QTemporaryDir in Qt 5.0 - QString tempPath(mPath + QDir::separator() + QLatin1String("extract-temp")); + QString tempPath(getPath() + QDir::separator() + QLatin1String("extract-temp")); QDir temp; // Make sure the temporary folder is empty @@ -494,8 +493,6 @@ bool Wizard::UnshieldWorker::installMorrowind() temp.setPath(tempPath); - QString cabFile(disk.absoluteFilePath(QLatin1String("data1.hdr"))); - if (!temp.mkdir(QLatin1String("morrowind"))) { qDebug() << "Can't make dir"; return false; @@ -511,7 +508,8 @@ bool Wizard::UnshieldWorker::installMorrowind() // TODO: Throw error; // Move the files from the temporary path to the destination folder - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) { + emit textChanged(tr("Moving installation files")); + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { qDebug() << "failed to move files!"; return false; } @@ -525,16 +523,17 @@ bool Wizard::UnshieldWorker::installMorrowind() QFileInfo info(iniPath); - qDebug() << info.absoluteFilePath() << mPath; + qDebug() << info.absoluteFilePath() << getPath(); if (info.exists()) { emit textChanged(tr("Extracting: Morrowind.ini")); - moveFile(info.absoluteFilePath(), mPath + QDir::separator() + QLatin1String("Morrowind.ini")); + moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); } else { qDebug() << "Could not find ini file!"; return false; } + emit textChanged(tr("Morrowind installation finished!")); return true; } @@ -551,7 +550,7 @@ bool Wizard::UnshieldWorker::installTribunal() // Create temporary extract directory // TODO: Use QTemporaryDir in Qt 5.0 - QString tempPath(mPath + QDir::separator() + QLatin1String("extract-temp")); + QString tempPath(getPath() + QDir::separator() + QLatin1String("extract-temp")); QDir temp; // Make sure the temporary folder is empty @@ -579,7 +578,8 @@ bool Wizard::UnshieldWorker::installTribunal() // TODO: Throw error; // Move the files from the temporary path to the destination folder - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) { + emit textChanged(tr("Moving installation files")); + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { qDebug() << "failed to move files!"; return false; } @@ -587,6 +587,7 @@ bool Wizard::UnshieldWorker::installTribunal() // Install files outside of cab archives installDirectories(disk.absolutePath()); + emit textChanged(tr("Tribunal installation finished!")); return true; } @@ -597,13 +598,12 @@ bool Wizard::UnshieldWorker::installBloodmoon() QDir disk(getBloodmoonPath()); if (!disk.exists()) { - qDebug() << "disk does not exist! " << disk.absolutePath() << getTribunalPath(); return false; } // Create temporary extract directory // TODO: Use QTemporaryDir in Qt 5.0 - QString tempPath(mPath + QDir::separator() + QLatin1String("extract-temp")); + QString tempPath(getPath() + QDir::separator() + QLatin1String("extract-temp")); QDir temp; // Make sure the temporary folder is empty @@ -631,7 +631,8 @@ bool Wizard::UnshieldWorker::installBloodmoon() // TODO: Throw error; // Move the files from the temporary path to the destination folder - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), mPath)) { + emit textChanged(tr("Moving installation files")); + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { qDebug() << "failed to move files!"; return false; } @@ -640,13 +641,14 @@ bool Wizard::UnshieldWorker::installBloodmoon() installDirectories(disk.absolutePath()); QFileInfo patch(temp.absoluteFilePath(QLatin1String("Tribunal Patch") + QDir::separator() + QLatin1String("Tribunal.esm"))); - QFileInfo original(mPath + QDir::separator() + QLatin1String("Tribunal.esm")); + QFileInfo original(getPath() + QDir::separator() + QLatin1String("Tribunal.esm")); if (original.exists() && patch.exists()) { emit textChanged(tr("Extracting: Tribunal patch")); copyFile(patch.absoluteFilePath(), original.absoluteFilePath()); } + emit textChanged(tr("Bloodmoon installation finished!")); return true; } @@ -669,7 +671,6 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp path.replace(QLatin1Char('\\'), QDir::separator()); path = QDir::toNativeSeparators(path); - qDebug() << "path is: " << path << QString::fromLatin1(unshield_directory_name(unshield, directory)) + QLatin1Char('/'); // Ensure the target path exists QDir dir; dir.mkpath(path); @@ -678,12 +679,12 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp fileName.append(QString::fromLatin1(unshield_file_name(unshield, index))); // Calculate the percentage done - int progress = qFloor(((float) counter / (float) unshield_file_count(unshield)) * 100); + int progress = (((float) counter / (float) unshield_file_count(unshield)) * 100); - if (mMorrowindDone) + if (getMorrowindDone()) progress = progress + 100; - if (mTribunalDone) + if (getTribunalDone()) progress = progress + 100; qDebug() << progress << counter << unshield_file_count(unshield); diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 17c0973b0..bdaf44198 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -39,17 +40,20 @@ namespace Wizard void setPath(const QString &path); void setIniPath(const QString &path); + QString getPath(); + QString getIniPath(); + void setIniCodec(QTextCodec *codec); private: -// void setMorrowindDone(bool done); -// void setTribunalDone(bool done); -// void setBloodmoonDone(bool done); + void setMorrowindDone(bool done); + void setTribunalDone(bool done); + void setBloodmoonDone(bool done); -// bool getMorrowindDone(); -// bool getTribunalDone(); -// bool getBloodmoonDone(); + bool getMorrowindDone(); + bool getTribunalDone(); + bool getBloodmoonDone(); bool removeDirectory(const QString &dirName); @@ -93,6 +97,8 @@ namespace Wizard QWaitCondition mWait; QMutex mMutex; + QReadWriteLock mLock; + public slots: void extract(); From 3fd88aca594040a756ba811d6ac06fc4da8de523 Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 27 Jan 2014 16:51:22 +0100 Subject: [PATCH 0022/1983] Re-added support for GoTY disks and added a messagebox, displayed when done --- apps/wizard/installationpage.cpp | 53 ++++------------- apps/wizard/unshield/unshieldworker.cpp | 78 +++++++++++++++++-------- 2 files changed, 64 insertions(+), 67 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 9b14b5918..8690cd2bc 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "mainwizard.hpp" #include "inisettings.hpp" @@ -135,49 +136,6 @@ void Wizard::InstallationPage::startInstallation() thread->start(); } - - -//void Wizard::InstallationPage::installAddons() -//{ -// qDebug() << "component finished"; - -// QStringList components(field("installation.components").toStringList()); - -// if (components.contains(QLatin1String("Tribunal")) && !mUnshield->tribunalDone()) -// { -// QString fileName = QFileDialog::getOpenFileName( -// this, -// tr("Select Tribunal installation file"), -// QDir::rootPath(), -// tr("InstallShield header files (*.hdr)")); - -// if (fileName.isEmpty()) { -// qDebug() << "Cancel was clicked!"; -// return; -// } - -// QFileInfo info(fileName); -// mUnshield->installTribunal(info.absolutePath()); -// } - -// if (components.contains(QLatin1String("Bloodmoon")) && !mUnshield->bloodmoonDone()) -// { -// QString fileName = QFileDialog::getOpenFileName( -// this, -// tr("Select Bloodmoon installation file"), -// QDir::rootPath(), -// tr("InstallShield header files (*.hdr)")); - -// if (fileName.isEmpty()) { -// qDebug() << "Cancel was clicked!"; -// return; -// } - -// QFileInfo info(fileName); -// mUnshield->installBloodmoon(info.absolutePath()); -// } -//} - void Wizard::InstallationPage::showFileDialog(const QString &component) { QString fileName; @@ -215,6 +173,15 @@ void Wizard::InstallationPage::showFileDialog(const QString &component) void Wizard::InstallationPage::installationFinished() { qDebug() << "finished!"; + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Installation finished")); + msgBox.setIcon(QMessageBox::Information); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("Installation completed sucessfully!")); + + msgBox.exec(); + mFinished = true; emit completeChanged(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 5c22df446..2ab68dde0 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -287,7 +287,7 @@ bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString if (info.isDir()) { result = moveDirectory(info.absoluteFilePath(), destDir.absolutePath() + relativePath); } else { - qDebug() << "moving: " << info.absoluteFilePath() << " to: " << destDir.absolutePath() + relativePath; + qDebug() << "moving: " << info.absoluteFilePath() << " to: " << destDir.absolutePath() + relativePath; result = moveFile(info.absoluteFilePath(), destDir.absolutePath() + relativePath); } @@ -389,6 +389,19 @@ void Wizard::UnshieldWorker::extract() { while (!getTribunalDone()) { + QDir tribunal(disk); + + if (!tribunal.cd(QLatin1String("Tribunal"))) { + qDebug() << "not found on cd!"; + QReadLocker locker(&mLock); + emit requestFileDialog(QLatin1String("Tribunal")); + mWait.wait(&mLock); + + } else if (tribunal.exists(QLatin1String("data1.hdr"))) { + qDebug() << "Exists! " << tribunal.absolutePath(); + setTribunalPath(tribunal.absolutePath()); + } + if (getTribunalPath().isEmpty()) { qDebug() << "request file dialog"; QReadLocker locker(&mLock); @@ -396,10 +409,12 @@ void Wizard::UnshieldWorker::extract() mWait.wait(&mLock); } + // Make sure the dir is up-to-date + tribunal.setPath(getTribunalPath()); + if (!getTribunalPath().isEmpty()) { - disk.setPath(getTribunalPath()); - if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa"))) + if (!findFile(tribunal.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa"))) { qDebug() << "found"; QReadLocker locker(&mLock); @@ -421,6 +436,21 @@ void Wizard::UnshieldWorker::extract() { while (!getBloodmoonDone()) { + QDir bloodmoon(disk); + + qDebug() << "Test!: " << bloodmoon.absolutePath(); + + if (!bloodmoon.cd(QLatin1String("Bloodmoon"))) { + qDebug() << "not found on cd!"; + QReadLocker locker(&mLock); + emit requestFileDialog(QLatin1String("Bloodmoon")); + mWait.wait(&mLock); + + } else if (bloodmoon.exists(QLatin1String("data1.hdr"))) { + qDebug() << "Exists! " << bloodmoon.absolutePath(); + setBloodmoonPath(bloodmoon.absolutePath()); + } + if (getBloodmoonPath().isEmpty()) { qDebug() << "request file dialog"; QReadLocker locker(&mLock); @@ -428,35 +458,38 @@ void Wizard::UnshieldWorker::extract() mWait.wait(&mLock); } - if (!getBloodmoonPath().isEmpty()) { - disk.setPath(getBloodmoonPath()); + // Make sure the dir is up-to-date + bloodmoon.setPath(getBloodmoonPath()); - if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) - { - QReadLocker locker(&mLock); - emit requestFileDialog(QLatin1String("Bloodmoon")); - mWait.wait(&mLock); + if (!findFile(bloodmoon.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) + { + QReadLocker locker(&mLock); + emit requestFileDialog(QLatin1String("Bloodmoon")); + mWait.wait(&mLock); + } else { + if (installBloodmoon()) { + setBloodmoonDone(true); } else { - if (installBloodmoon()) { - setBloodmoonDone(true); - } else { - qDebug() << "Erorr installing Bloodmoon"; - return; - } + qDebug() << "Erorr installing Bloodmoon"; + return; } } } } + // Remove the temporary directory + removeDirectory(mPath + QDir::separator() + QLatin1String("extract-temp")); + + // Fill the progress bar int total = 0; - if (mInstallMorrowind) + if (getInstallMorrowind()) total = 100; - if (mInstallTribunal) + if (getInstallTribunal()) total = total + 100; - if (mInstallBloodmoon) + if (getInstallBloodmoon()) total = total + 100; emit textChanged(tr("Installation finished!")); @@ -523,8 +556,6 @@ bool Wizard::UnshieldWorker::installMorrowind() QFileInfo info(iniPath); - qDebug() << info.absoluteFilePath() << getPath(); - if (info.exists()) { emit textChanged(tr("Extracting: Morrowind.ini")); moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); @@ -579,7 +610,8 @@ bool Wizard::UnshieldWorker::installTribunal() // TODO: Throw error; // Move the files from the temporary path to the destination folder emit textChanged(tr("Moving installation files")); - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) + { qDebug() << "failed to move files!"; return false; } @@ -687,8 +719,6 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp if (getTribunalDone()) progress = progress + 100; - qDebug() << progress << counter << unshield_file_count(unshield); - emit textChanged(tr("Extracting: %1").arg(QString::fromLatin1(unshield_file_name(unshield, index)))); emit progressChanged(progress); From 802448b217477578a20d5543194623455b45c1b2 Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 27 Jan 2014 20:14:02 +0100 Subject: [PATCH 0023/1983] Added a settings page to the launcher --- apps/launcher/CMakeLists.txt | 4 + apps/launcher/maindialog.cpp | 28 +-- apps/launcher/maindialog.hpp | 7 + apps/launcher/settingspage.cpp | 18 ++ apps/launcher/settingspage.hpp | 20 +++ .../icons/tango/{ => 16x16}/document-new.png | Bin .../icons/tango/{ => 16x16}/edit-copy.png | Bin .../icons/tango/{ => 16x16}/edit-delete.png | Bin .../icons/tango/{ => 16x16}/go-bottom.png | Bin .../icons/tango/{ => 16x16}/go-down.png | Bin .../icons/tango/{ => 16x16}/go-top.png | Bin .../icons/tango/{ => 16x16}/go-up.png | Bin .../icons/tango/48x48/preferences-system.png | Bin 0 -> 3718 bytes .../icons/tango/{ => 48x48}/video-display.png | Bin files/launcher/icons/tango/index.theme | 7 +- files/launcher/launcher.qrc | 17 +- files/ui/mainwindow.ui | 10 +- files/ui/settingspage.ui | 159 ++++++++++++++++++ 18 files changed, 248 insertions(+), 22 deletions(-) create mode 100644 apps/launcher/settingspage.cpp create mode 100644 apps/launcher/settingspage.hpp rename files/launcher/icons/tango/{ => 16x16}/document-new.png (100%) rename files/launcher/icons/tango/{ => 16x16}/edit-copy.png (100%) rename files/launcher/icons/tango/{ => 16x16}/edit-delete.png (100%) rename files/launcher/icons/tango/{ => 16x16}/go-bottom.png (100%) rename files/launcher/icons/tango/{ => 16x16}/go-down.png (100%) rename files/launcher/icons/tango/{ => 16x16}/go-top.png (100%) rename files/launcher/icons/tango/{ => 16x16}/go-up.png (100%) create mode 100644 files/launcher/icons/tango/48x48/preferences-system.png rename files/launcher/icons/tango/{ => 48x48}/video-display.png (100%) create mode 100644 files/ui/settingspage.ui diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 279474406..d24c8e377 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -5,6 +5,7 @@ set(LAUNCHER maindialog.cpp playpage.cpp textslotmsgbox.cpp + settingspage.cpp settings/graphicssettings.cpp @@ -25,6 +26,7 @@ set(LAUNCHER_HEADER maindialog.hpp playpage.hpp textslotmsgbox.hpp + settingspage.hpp settings/graphicssettings.hpp @@ -45,6 +47,7 @@ set(LAUNCHER_HEADER_MOC maindialog.hpp playpage.hpp textslotmsgbox.hpp + settingspage.hpp utils/textinputdialog.hpp utils/checkablemessagebox.hpp @@ -64,6 +67,7 @@ set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui + ${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 4012a1fbd..f81d15e9c 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -23,6 +23,7 @@ #include "playpage.hpp" #include "graphicspage.hpp" #include "datafilespage.hpp" +#include "settingspage.hpp" Launcher::MainDialog::MainDialog(QWidget *parent) : mGameSettings(mCfgMgr), QMainWindow (parent) @@ -75,27 +76,30 @@ void Launcher::MainDialog::createIcons() if (!QIcon::hasThemeIcon("document-new")) QIcon::setThemeName("tango"); - // We create a fallback icon because the default fallback doesn't work - QIcon graphicsIcon = QIcon(":/icons/tango/video-display.png"); - QListWidgetItem *playButton = new QListWidgetItem(iconWidget); playButton->setIcon(QIcon(":/images/openmw.png")); playButton->setText(tr("Play")); playButton->setTextAlignment(Qt::AlignCenter); playButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); - graphicsButton->setIcon(QIcon::fromTheme("video-display", graphicsIcon)); - graphicsButton->setText(tr("Graphics")); - graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); - graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - QListWidgetItem *dataFilesButton = new QListWidgetItem(iconWidget); dataFilesButton->setIcon(QIcon(":/images/openmw-plugin.png")); dataFilesButton->setText(tr("Data Files")); dataFilesButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); + graphicsButton->setIcon(QIcon::fromTheme("video-display")); + graphicsButton->setText(tr("Graphics")); + graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); + graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + + QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget); + settingsButton->setIcon(QIcon::fromTheme("preferences-system")); + settingsButton->setText(tr("Settings")); + settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); + settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + connect(iconWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); @@ -105,8 +109,9 @@ void Launcher::MainDialog::createIcons() void Launcher::MainDialog::createPages() { mPlayPage = new PlayPage(this); - mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); + mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); + mSettingsPage = new SettingsPage(this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); @@ -114,8 +119,9 @@ void Launcher::MainDialog::createPages() // Add the pages to the stacked widget pagesWidget->addWidget(mPlayPage); - pagesWidget->addWidget(mGraphicsPage); pagesWidget->addWidget(mDataFilesPage); + pagesWidget->addWidget(mGraphicsPage); + pagesWidget->addWidget(mSettingsPage); // Select the first page iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select); diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index a4fb4cd9a..718acb5a9 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -13,6 +13,10 @@ #include "ui_mainwindow.h" class QListWidgetItem; +class QStackedWidget; +class QStringList; +class QStringListModel; +class QString; namespace Launcher { @@ -20,6 +24,7 @@ namespace Launcher class GraphicsPage; class DataFilesPage; class UnshieldThread; + class SettingsPage; #ifndef WIN32 bool expansions(Launcher::UnshieldThread& cd); @@ -58,6 +63,8 @@ namespace Launcher PlayPage *mPlayPage; GraphicsPage *mGraphicsPage; DataFilesPage *mDataFilesPage; + SettingsPage *mSettingsPage; + Files::ConfigurationManager mCfgMgr; diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp new file mode 100644 index 000000000..96dd6e529 --- /dev/null +++ b/apps/launcher/settingspage.cpp @@ -0,0 +1,18 @@ +#include "settingspage.hpp" + +Launcher::SettingsPage::SettingsPage(QWidget *parent) : QWidget(parent) +{ + setupUi(this); + + QStringList languages; + languages << "English" + << "French" + << "German" + << "Italian" + << "Polish" + << "Russian" + << "Spanish"; + + languageComboBox->addItems(languages); +} + diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp new file mode 100644 index 000000000..c87f6e46f --- /dev/null +++ b/apps/launcher/settingspage.hpp @@ -0,0 +1,20 @@ +#ifndef SETTINGSPAGE_HPP +#define SETTINGSPAGE_HPP + +#include + +#include "ui_settingspage.h" + +namespace Launcher +{ + + class SettingsPage : public QWidget, private Ui::SettingsPage + { + Q_OBJECT + public: + SettingsPage(QWidget *parent = 0); + + }; +} + +#endif // SETTINGSPAGE_HPP diff --git a/files/launcher/icons/tango/document-new.png b/files/launcher/icons/tango/16x16/document-new.png similarity index 100% rename from files/launcher/icons/tango/document-new.png rename to files/launcher/icons/tango/16x16/document-new.png diff --git a/files/launcher/icons/tango/edit-copy.png b/files/launcher/icons/tango/16x16/edit-copy.png similarity index 100% rename from files/launcher/icons/tango/edit-copy.png rename to files/launcher/icons/tango/16x16/edit-copy.png diff --git a/files/launcher/icons/tango/edit-delete.png b/files/launcher/icons/tango/16x16/edit-delete.png similarity index 100% rename from files/launcher/icons/tango/edit-delete.png rename to files/launcher/icons/tango/16x16/edit-delete.png diff --git a/files/launcher/icons/tango/go-bottom.png b/files/launcher/icons/tango/16x16/go-bottom.png similarity index 100% rename from files/launcher/icons/tango/go-bottom.png rename to files/launcher/icons/tango/16x16/go-bottom.png diff --git a/files/launcher/icons/tango/go-down.png b/files/launcher/icons/tango/16x16/go-down.png similarity index 100% rename from files/launcher/icons/tango/go-down.png rename to files/launcher/icons/tango/16x16/go-down.png diff --git a/files/launcher/icons/tango/go-top.png b/files/launcher/icons/tango/16x16/go-top.png similarity index 100% rename from files/launcher/icons/tango/go-top.png rename to files/launcher/icons/tango/16x16/go-top.png diff --git a/files/launcher/icons/tango/go-up.png b/files/launcher/icons/tango/16x16/go-up.png similarity index 100% rename from files/launcher/icons/tango/go-up.png rename to files/launcher/icons/tango/16x16/go-up.png diff --git a/files/launcher/icons/tango/48x48/preferences-system.png b/files/launcher/icons/tango/48x48/preferences-system.png new file mode 100644 index 0000000000000000000000000000000000000000..a1620e991663d37d6e95ed11be7abb9927d73dfc GIT binary patch literal 3718 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 literal 0 HcmV?d00001 diff --git a/files/launcher/icons/tango/video-display.png b/files/launcher/icons/tango/48x48/video-display.png similarity index 100% rename from files/launcher/icons/tango/video-display.png rename to files/launcher/icons/tango/48x48/video-display.png diff --git a/files/launcher/icons/tango/index.theme b/files/launcher/icons/tango/index.theme index 1f54489eb..8ec560f85 100644 --- a/files/launcher/icons/tango/index.theme +++ b/files/launcher/icons/tango/index.theme @@ -2,7 +2,10 @@ Name=Tango Comment=Tango Theme Inherits=default -Directories=16x16 +Directories=16x16,48x48 [16x16] -Size=16 \ No newline at end of file +Size=16 + +[48x48] +Size=48 \ No newline at end of file diff --git a/files/launcher/launcher.qrc b/files/launcher/launcher.qrc index 19b1c5a6f..4f55fccd5 100644 --- a/files/launcher/launcher.qrc +++ b/files/launcher/launcher.qrc @@ -9,13 +9,14 @@ icons/tango/index.theme - icons/tango/video-display.png - icons/tango/document-new.png - icons/tango/edit-copy.png - icons/tango/edit-delete.png - icons/tango/go-bottom.png - icons/tango/go-down.png - icons/tango/go-top.png - icons/tango/go-up.png + icons/tango/48x48/video-display.png + icons/tango/48x48/preferences-system.png + icons/tango/16x16/document-new.png + icons/tango/16x16/edit-copy.png + icons/tango/16x16/edit-delete.png + icons/tango/16x16/go-bottom.png + icons/tango/16x16/go-down.png + icons/tango/16x16/go-top.png + icons/tango/16x16/go-up.png diff --git a/files/ui/mainwindow.ui b/files/ui/mainwindow.ui index a1dfb172b..368e5cc5f 100644 --- a/files/ui/mainwindow.ui +++ b/files/ui/mainwindow.ui @@ -2,9 +2,17 @@ MainWindow + + + 0 + 0 + 635 + 575 + + - 575 + 635 535 diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui new file mode 100644 index 000000000..95b62761e --- /dev/null +++ b/files/ui/settingspage.ui @@ -0,0 +1,159 @@ + + + SettingsPage + + + + 0 + 0 + 518 + 401 + + + + Form + + + + + + General + + + + + + Morrowind installation language: + + + + + + + + 250 + 0 + + + + + + + + + + + Morrowind Installation Wizard + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Run &Installation Wizard + + + + + + + + + + Morrowind Settings Importer + + + + + + + + File to import settings from: + + + + + + + + /home/user/.local/share/openmw/data/Morrowind.ini + + + + + + + + Browse... + + + + + + + + + Import previously selected add-ons (creates a new Content List) + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Run &Settings Importer + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + From 2736527e5a37ecf15b38474dbfd5624b61898394 Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 27 Jan 2014 22:54:14 +0100 Subject: [PATCH 0024/1983] Did some cleanup work on the unshield thread: more code re-use --- apps/wizard/installationpage.cpp | 59 +-- apps/wizard/installationpage.hpp | 3 +- apps/wizard/unshield/unshieldworker.cpp | 496 +++++++++--------------- apps/wizard/unshield/unshieldworker.hpp | 42 +- 4 files changed, 229 insertions(+), 371 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 8690cd2bc..47d10c45f 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -8,7 +8,6 @@ #include "mainwizard.hpp" #include "inisettings.hpp" -#include "unshield/unshieldworker.hpp" Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : QWizardPage(wizard), @@ -58,9 +57,10 @@ void Wizard::InstallationPage::startInstallation() QThread *thread = new QThread(); mUnshield = new UnshieldWorker(); - mUnshield->moveToThread(thread); + qRegisterMetaType("Wizard::Component"); + connect(thread, SIGNAL(started()), mUnshield, SLOT(extract())); @@ -88,30 +88,30 @@ void Wizard::InstallationPage::startInstallation() connect(mUnshield, SIGNAL(progressChanged(int)), installProgressBar, SLOT(setValue(int)), Qt::QueuedConnection); - connect(mUnshield, SIGNAL(requestFileDialog(QString)), - this, SLOT(showFileDialog(QString)), Qt::QueuedConnection); + connect(mUnshield, SIGNAL(requestFileDialog(Wizard::Component)), + this, SLOT(showFileDialog(Wizard::Component)), Qt::QueuedConnection); if (field("installation.new").toBool() == true) { // Always install Morrowind - mUnshield->setInstallMorrowind(true); + mUnshield->setInstallComponent(Wizard::Component_Morrowind, true); if (components.contains(QLatin1String("Tribunal"))) - mUnshield->setInstallTribunal(true); + mUnshield->setInstallComponent(Wizard::Component_Tribunal, true); if (components.contains(QLatin1String("Bloodmoon"))) - mUnshield->setInstallBloodmoon(true); + mUnshield->setInstallComponent(Wizard::Component_Bloodmoon, true); } else { // Morrowind should already be installed - mUnshield->setInstallMorrowind(false); + mUnshield->setInstallComponent(Wizard::Component_Morrowind, false); if (components.contains(QLatin1String("Tribunal")) - && mWizard->mInstallations[path]->hasTribunal == false) - mUnshield->setInstallTribunal(true); + && !mWizard->mInstallations[path]->hasTribunal) + mUnshield->setInstallComponent(Wizard::Component_Tribunal, true); if (components.contains(QLatin1String("Bloodmoon")) - && mWizard->mInstallations[path]->hasBloodmoon == false) - mUnshield->setInstallBloodmoon(true); + && !mWizard->mInstallations[path]->hasBloodmoon) + mUnshield->setInstallComponent(Wizard::Component_Bloodmoon, true); // Set the location of the Morrowind.ini to update mUnshield->setIniPath(mWizard->mInstallations[path]->iniPath); @@ -136,38 +136,21 @@ void Wizard::InstallationPage::startInstallation() thread->start(); } -void Wizard::InstallationPage::showFileDialog(const QString &component) +void Wizard::InstallationPage::showFileDialog(Wizard::Component component) { - QString fileName; - - if (field("installation.new").toBool() == true) - { - fileName = QFileDialog::getOpenFileName( + QString fileName = QFileDialog::getOpenFileName( this, - tr("Select %0 installation file").arg(component), + tr("Select installation file"), QDir::rootPath(), tr("InstallShield header files (*.hdr)")); - if (fileName.isEmpty()) { - qDebug() << "Cancel was clicked!"; - return; - } - - QFileInfo info(fileName); - - if (component == QLatin1String("Morrowind")) - { - mUnshield->setMorrowindPath(info.absolutePath()); - } - else if (component == QLatin1String("Tribunal")) - { - mUnshield->setTribunalPath(info.absolutePath()); - } - else if (component == QLatin1String("Bloodmoon")) - { - mUnshield->setBloodmoonPath(info.absolutePath()); - } + if (fileName.isEmpty()) { + qDebug() << "Cancel was clicked!"; + return; } + + QFileInfo info(fileName); + mUnshield->setComponentPath(component, info.absolutePath()); } void Wizard::InstallationPage::installationFinished() diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index e125eadc4..3989177ab 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -3,6 +3,7 @@ #include +#include "unshield/unshieldworker.hpp" #include "ui_installationpage.h" #include "inisettings.hpp" @@ -33,7 +34,7 @@ namespace Wizard void startInstallation(); private slots: - void showFileDialog(const QString &component); + void showFileDialog(Wizard::Component component); void installationFinished(); void installationError(const QString &text); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 2ab68dde0..9c635818b 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -14,8 +14,6 @@ #include #include -#include - Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : QObject(parent), mIniSettings() @@ -46,81 +44,106 @@ Wizard::UnshieldWorker::~UnshieldWorker() } -void Wizard::UnshieldWorker::setInstallMorrowind(bool install) +void Wizard::UnshieldWorker::setInstallComponent(Wizard::Component component, bool install) { QWriteLocker writeLock(&mLock); - mInstallMorrowind = install; -} + switch (component) { -void Wizard::UnshieldWorker::setInstallTribunal(bool install) -{ - QWriteLocker writeLock(&mLock); - mInstallTribunal = install; -} - -void Wizard::UnshieldWorker::setInstallBloodmoon(bool install) -{ - QWriteLocker writeLock(&mLock); - mInstallBloodmoon = install; + case Wizard::Component_Morrowind: + mInstallMorrowind = install; + break; + case Wizard::Component_Tribunal: + mInstallTribunal = install; + break; + case Wizard::Component_Bloodmoon: + mInstallBloodmoon = install; + break; + } } -bool Wizard::UnshieldWorker::getInstallMorrowind() +bool Wizard::UnshieldWorker::getInstallComponent(Component component) { QReadLocker readLock(&mLock); - return mInstallMorrowind; -} + switch (component) { -bool Wizard::UnshieldWorker::getInstallTribunal() -{ - QReadLocker readLock(&mLock); - return mInstallTribunal; -} + case Wizard::Component_Morrowind: + return mInstallMorrowind; + case Wizard::Component_Tribunal: + return mInstallTribunal; + case Wizard::Component_Bloodmoon: + return mInstallBloodmoon; + } -bool Wizard::UnshieldWorker::getInstallBloodmoon() -{ - QReadLocker readLock(&mLock); - return mInstallBloodmoon; + return false; } -void Wizard::UnshieldWorker::setMorrowindPath(const QString &path) +void Wizard::UnshieldWorker::setComponentPath(Wizard::Component component, const QString &path) { - QWriteLocker writeLock(&mLock); - mMorrowindPath = path; - mWait.wakeAll(); + QWriteLocker writeLock(&mLock); + switch (component) { + + case Wizard::Component_Morrowind: + mMorrowindPath = path; + break; + case Wizard::Component_Tribunal: + mTribunalPath = path; + break; + case Wizard::Component_Bloodmoon: + mBloodmoonPath = path; + break; + } + + mWait.wakeAll(); } -void Wizard::UnshieldWorker::setTribunalPath(const QString &path) +QString Wizard::UnshieldWorker::getComponentPath(Component component) { - QWriteLocker writeLock(&mLock); - mTribunalPath = path; - mWait.wakeAll(); + QReadLocker readLock(&mLock); + switch (component) { + + case Wizard::Component_Morrowind: + return mMorrowindPath; + case Wizard::Component_Tribunal: + return mTribunalPath; + case Wizard::Component_Bloodmoon: + return mBloodmoonPath; + } + return QString(); } -void Wizard::UnshieldWorker::setBloodmoonPath(const QString &path) +void Wizard::UnshieldWorker::setComponentDone(Component component, bool done) { QWriteLocker writeLock(&mLock); - mBloodmoonPath = path; - mWait.wakeAll(); + switch (component) { + case Wizard::Component_Morrowind: + mMorrowindDone = done; + break; + case Wizard::Component_Tribunal: + mTribunalDone = done; + break; + case Wizard::Component_Bloodmoon: + mBloodmoonDone = done; + break; + } } -QString Wizard::UnshieldWorker::getMorrowindPath() +bool Wizard::UnshieldWorker::getComponentDone(Component component) { QReadLocker readLock(&mLock); - return mMorrowindPath; -} + switch (component) + { -QString Wizard::UnshieldWorker::getTribunalPath() -{ - QReadLocker readLock(&mLock); - return mTribunalPath; -} + case Wizard::Component_Morrowind: + return mMorrowindDone; + case Wizard::Component_Tribunal: + return mTribunalDone; + case Wizard::Component_Bloodmoon: + return mBloodmoonDone; + } -QString Wizard::UnshieldWorker::getBloodmoonPath() -{ - QReadLocker readLock(&mLock); - return mBloodmoonPath; + return false; } void Wizard::UnshieldWorker::setPath(const QString &path) @@ -153,42 +176,6 @@ void Wizard::UnshieldWorker::setIniCodec(QTextCodec *codec) mIniCodec = codec; } -void Wizard::UnshieldWorker::setMorrowindDone(bool done) -{ - QWriteLocker writeLock(&mLock); - mMorrowindDone = done; -} - -void Wizard::UnshieldWorker::setTribunalDone(bool done) -{ - QWriteLocker writeLock(&mLock); - mTribunalDone = done; -} - -void Wizard::UnshieldWorker::setBloodmoonDone(bool done) -{ - QWriteLocker writeLock(&mLock); - mBloodmoonDone = done; -} - -bool Wizard::UnshieldWorker::getMorrowindDone() -{ - QReadLocker readLock(&mLock); - return mMorrowindDone; -} - -bool Wizard::UnshieldWorker::getTribunalDone() -{ - QReadLocker readLock(&mLock); - return mTribunalDone; -} - -bool Wizard::UnshieldWorker::getBloodmoonDone() -{ - QReadLocker readLock(&mLock); - return mBloodmoonDone; -} - void Wizard::UnshieldWorker::setupSettings() { // Create Morrowind.ini settings map @@ -287,8 +274,7 @@ bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString if (info.isDir()) { result = moveDirectory(info.absoluteFilePath(), destDir.absolutePath() + relativePath); } else { - qDebug() << "moving: " << info.absoluteFilePath() << " to: " << destDir.absolutePath() + relativePath; - +// qDebug() << "moving: " << info.absoluteFilePath() << " to: " << destDir.absolutePath() + relativePath; result = moveFile(info.absoluteFilePath(), destDir.absolutePath() + relativePath); } } @@ -346,36 +332,35 @@ void Wizard::UnshieldWorker::installDirectories(const QString &source) } - void Wizard::UnshieldWorker::extract() { qDebug() << "extract!"; QDir disk; - if (getInstallMorrowind()) + if (getInstallComponent(Wizard::Component_Morrowind)) { - while (!getMorrowindDone()) + while (!getComponentDone(Wizard::Component_Morrowind)) { - if (getMorrowindPath().isEmpty()) { + if (getComponentPath(Wizard::Component_Morrowind).isEmpty()) { qDebug() << "request file dialog"; QReadLocker readLock(&mLock); - emit requestFileDialog(QLatin1String("Morrowind")); + emit requestFileDialog(Wizard::Component_Morrowind); mWait.wait(&mLock); } - if (!getMorrowindPath().isEmpty()) { - disk.setPath(getMorrowindPath()); + if (!getComponentPath(Wizard::Component_Morrowind).isEmpty()) { + disk.setPath(getComponentPath(Wizard::Component_Morrowind)); if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Morrowind.bsa")) | findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa")) | findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) { QReadLocker readLock(&mLock); - emit requestFileDialog(QLatin1String("Morrowind")); + emit requestFileDialog(Wizard::Component_Morrowind); mWait.wait(&mLock); } else { - if (installMorrowind()) { - setMorrowindDone(true); + if (installComponent(Wizard::Component_Morrowind)) { + setComponentDone(Wizard::Component_Morrowind, true); } else { qDebug() << "Erorr installing Morrowind"; return; @@ -385,111 +370,29 @@ void Wizard::UnshieldWorker::extract() } } - if (getInstallTribunal()) + if (getInstallComponent(Wizard::Component_Tribunal)) { - while (!getTribunalDone()) - { - QDir tribunal(disk); - - if (!tribunal.cd(QLatin1String("Tribunal"))) { - qDebug() << "not found on cd!"; - QReadLocker locker(&mLock); - emit requestFileDialog(QLatin1String("Tribunal")); - mWait.wait(&mLock); - - } else if (tribunal.exists(QLatin1String("data1.hdr"))) { - qDebug() << "Exists! " << tribunal.absolutePath(); - setTribunalPath(tribunal.absolutePath()); - } - - if (getTribunalPath().isEmpty()) { - qDebug() << "request file dialog"; - QReadLocker locker(&mLock); - emit requestFileDialog(QLatin1String("Tribunal")); - mWait.wait(&mLock); - } - - // Make sure the dir is up-to-date - tribunal.setPath(getTribunalPath()); - - if (!getTribunalPath().isEmpty()) { - - if (!findFile(tribunal.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa"))) - { - qDebug() << "found"; - QReadLocker locker(&mLock); - emit requestFileDialog(QLatin1String("Tribunal")); - mWait.wait(&mLock); - } else { - if (installTribunal()) { - setTribunalDone(true); - } else { - qDebug() << "Erorr installing Tribunal"; - return; - } - } - } - } + setupAddon(Wizard::Component_Tribunal); } - if (getInstallBloodmoon()) + if (getInstallComponent(Wizard::Component_Bloodmoon)) { - while (!getBloodmoonDone()) - { - QDir bloodmoon(disk); - - qDebug() << "Test!: " << bloodmoon.absolutePath(); - - if (!bloodmoon.cd(QLatin1String("Bloodmoon"))) { - qDebug() << "not found on cd!"; - QReadLocker locker(&mLock); - emit requestFileDialog(QLatin1String("Bloodmoon")); - mWait.wait(&mLock); - - } else if (bloodmoon.exists(QLatin1String("data1.hdr"))) { - qDebug() << "Exists! " << bloodmoon.absolutePath(); - setBloodmoonPath(bloodmoon.absolutePath()); - } - - if (getBloodmoonPath().isEmpty()) { - qDebug() << "request file dialog"; - QReadLocker locker(&mLock); - emit requestFileDialog(QLatin1String("Bloodmoon")); - mWait.wait(&mLock); - } - - // Make sure the dir is up-to-date - bloodmoon.setPath(getBloodmoonPath()); - - if (!findFile(bloodmoon.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) - { - QReadLocker locker(&mLock); - emit requestFileDialog(QLatin1String("Bloodmoon")); - mWait.wait(&mLock); - } else { - if (installBloodmoon()) { - setBloodmoonDone(true); - } else { - qDebug() << "Erorr installing Bloodmoon"; - return; - } - } - } + setupAddon(Wizard::Component_Bloodmoon); } // Remove the temporary directory - removeDirectory(mPath + QDir::separator() + QLatin1String("extract-temp")); + removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); // Fill the progress bar int total = 0; - if (getInstallMorrowind()) + if (getInstallComponent(Wizard::Component_Morrowind)) total = 100; - if (getInstallTribunal()) + if (getInstallComponent(Wizard::Component_Tribunal)) total = total + 100; - if (getInstallBloodmoon()) + if (getInstallComponent(Wizard::Component_Bloodmoon)) total = total + 100; emit textChanged(tr("Installation finished!")); @@ -499,83 +402,87 @@ void Wizard::UnshieldWorker::extract() qDebug() << "installation finished!"; } -bool Wizard::UnshieldWorker::installMorrowind() +void Wizard::UnshieldWorker::setupAddon(Component component) { - qDebug() << "install morrowind!"; - emit textChanged(QLatin1String("Installing Morrowind")); - - QDir disk(getMorrowindPath()); + while (!getComponentDone(component)) + { + QDir disk(getComponentPath(Wizard::Component_Morrowind)); + QString name; + if (component == Wizard::Component_Tribunal) + name = QLatin1String("Tribunal"); + + if (component == Wizard::Component_Bloodmoon) + name = QLatin1String("Bloodmoon"); + + if (name.isEmpty()) + return; // Not a valid addon + + if (!disk.cd(name)) { + qDebug() << "not found on cd!"; + QReadLocker locker(&mLock); + emit requestFileDialog(component); + mWait.wait(&mLock); + + } else if (disk.exists(QLatin1String("data1.hdr"))) { + qDebug() << "Exists! " << disk.absolutePath(); + setComponentPath(component, disk.absolutePath()); + } - if (!disk.exists()) { - qDebug() << "getMorrowindPath: " << getMorrowindPath(); - return false; - } + if (getComponentPath(component).isEmpty()) { + qDebug() << "request file dialog"; + QReadLocker locker(&mLock); + emit requestFileDialog(Wizard::Component_Tribunal); + mWait.wait(&mLock); + } - // Create temporary extract directory - // TODO: Use QTemporaryDir in Qt 5.0 - QString tempPath(getPath() + QDir::separator() + QLatin1String("extract-temp")); - QDir temp; + // Make sure the dir is up-to-date + disk.setPath(getComponentPath(component)); - // Make sure the temporary folder is empty - removeDirectory(tempPath); + if (!getComponentPath(component).isEmpty()) { - if (!temp.mkpath(tempPath)) { - qDebug() << "Can't make path"; - return false; + if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) + { + QReadLocker locker(&mLock); + emit requestFileDialog(component); + mWait.wait(&mLock); + } else { + // Now do the actual installing + if (installComponent(component)) { + setComponentDone(component, true); + } else { + qDebug() << "Error installing " << name; + return; + } + } + } } +} - temp.setPath(tempPath); - - if (!temp.mkdir(QLatin1String("morrowind"))) { - qDebug() << "Can't make dir"; - return false; - } +bool Wizard::UnshieldWorker::installComponent(Component component) +{ + QString name; + switch (component) { - if (!temp.cd(QLatin1String("morrowind"))) { - qDebug() << "Can't cd to dir"; - return false; + case Wizard::Component_Morrowind: + name = QLatin1String("Morrowind"); + break; + case Wizard::Component_Tribunal: + name = QLatin1String("Tribunal"); + break; + case Wizard::Component_Bloodmoon: + name = QLatin1String("Bloodmoon"); + break; } - // Extract the installation files - extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); - - // TODO: Throw error; - // Move the files from the temporary path to the destination folder - emit textChanged(tr("Moving installation files")); - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { - qDebug() << "failed to move files!"; + if (name.isEmpty()) return false; - } - - // Install files outside of cab archives - installDirectories(disk.absolutePath()); - // Copy Morrowind configuration file - QString iniPath(temp.absoluteFilePath(QLatin1String("App Executables"))); - iniPath.append(QDir::separator() + QLatin1String("Morrowind.ini")); + emit textChanged(tr("Installing %0").arg(name)); - QFileInfo info(iniPath); - - if (info.exists()) { - emit textChanged(tr("Extracting: Morrowind.ini")); - moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); - } else { - qDebug() << "Could not find ini file!"; - return false; - } - - emit textChanged(tr("Morrowind installation finished!")); - return true; -} - -bool Wizard::UnshieldWorker::installTribunal() -{ - emit textChanged(QLatin1String("Installing Tribunal")); - - QDir disk(getTribunalPath()); + QDir disk(getComponentPath(component)); if (!disk.exists()) { - qDebug() << "disk does not exist! " << disk.absolutePath() << getTribunalPath(); + qDebug() << "Component path not set: " << getComponentPath(Wizard::Component_Morrowind); return false; } @@ -594,12 +501,12 @@ bool Wizard::UnshieldWorker::installTribunal() temp.setPath(tempPath); - if (!temp.mkdir(QLatin1String("tribunal"))) { + if (!temp.mkdir(name)) { qDebug() << "Can't make dir"; return false; } - if (!temp.cd(QLatin1String("tribunal"))) { + if (!temp.cd(name)) { qDebug() << "Can't cd to dir"; return false; } @@ -610,8 +517,7 @@ bool Wizard::UnshieldWorker::installTribunal() // TODO: Throw error; // Move the files from the temporary path to the destination folder emit textChanged(tr("Moving installation files")); - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) - { + if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { qDebug() << "failed to move files!"; return false; } @@ -619,71 +525,39 @@ bool Wizard::UnshieldWorker::installTribunal() // Install files outside of cab archives installDirectories(disk.absolutePath()); - emit textChanged(tr("Tribunal installation finished!")); - return true; -} - -bool Wizard::UnshieldWorker::installBloodmoon() -{ - emit textChanged(QLatin1String("Installing Bloodmoon")); - - QDir disk(getBloodmoonPath()); - - if (!disk.exists()) { - return false; - } - - // Create temporary extract directory - // TODO: Use QTemporaryDir in Qt 5.0 - QString tempPath(getPath() + QDir::separator() + QLatin1String("extract-temp")); - QDir temp; - - // Make sure the temporary folder is empty - removeDirectory(tempPath); - - if (!temp.mkpath(tempPath)) { - qDebug() << "Can't make path"; - return false; - } - - temp.setPath(tempPath); - - if (!temp.mkdir(QLatin1String("bloodmoon"))) { - qDebug() << "Can't make dir"; - return false; - } - - if (!temp.cd(QLatin1String("bloodmoon"))) { - qDebug() << "Can't cd to dir"; - return false; - } + if (component == Wizard::Component_Morrowind) + { + // Copy Morrowind configuration file + QString iniPath(temp.absoluteFilePath(QLatin1String("App Executables"))); + iniPath.append(QDir::separator() + QLatin1String("Morrowind.ini")); - // Extract the installation files - extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); + QFileInfo info(iniPath); - // TODO: Throw error; - // Move the files from the temporary path to the destination folder - emit textChanged(tr("Moving installation files")); - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { - qDebug() << "failed to move files!"; - return false; + if (info.exists()) { + emit textChanged(tr("Extracting: Morrowind.ini")); + moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); + } else { + qDebug() << "Could not find ini file!"; + return false; + } } - // Install files outside of cab archives - installDirectories(disk.absolutePath()); + if (component == Wizard::Component_Bloodmoon) + { + QFileInfo patch(temp.absoluteFilePath(QLatin1String("Tribunal Patch") + QDir::separator() + QLatin1String("Tribunal.esm"))); + QFileInfo original(getPath() + QDir::separator() + QLatin1String("Tribunal.esm")); - QFileInfo patch(temp.absoluteFilePath(QLatin1String("Tribunal Patch") + QDir::separator() + QLatin1String("Tribunal.esm"))); - QFileInfo original(getPath() + QDir::separator() + QLatin1String("Tribunal.esm")); + if (original.exists() && patch.exists()) { + emit textChanged(tr("Extracting: Tribunal patch")); + copyFile(patch.absoluteFilePath(), original.absoluteFilePath()); + } - if (original.exists() && patch.exists()) { - emit textChanged(tr("Extracting: Tribunal patch")); - copyFile(patch.absoluteFilePath(), original.absoluteFilePath()); } - emit textChanged(tr("Bloodmoon installation finished!")); + emit textChanged(tr("%0 installation finished!").arg(name)); return true; -} +} bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter) { @@ -713,10 +587,10 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp // Calculate the percentage done int progress = (((float) counter / (float) unshield_file_count(unshield)) * 100); - if (getMorrowindDone()) + if (getComponentDone(Wizard::Component_Morrowind)) progress = progress + 100; - if (getTribunalDone()) + if (getComponentDone(Wizard::Component_Tribunal)) progress = progress + 100; emit textChanged(tr("Extracting: %1").arg(QString::fromLatin1(unshield_file_name(unshield, index)))); diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index bdaf44198..fb55bc88f 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -11,31 +11,29 @@ #include "../inisettings.hpp" + namespace Wizard { + enum Component { + Component_Morrowind, + Component_Tribunal, + Component_Bloodmoon + }; + + //Q_DECLARE_METATYPE(Wizard::Component) + class UnshieldWorker : public QObject { Q_OBJECT + Q_ENUMS(Wizard::Component) public: UnshieldWorker(QObject *parent = 0); ~UnshieldWorker(); - void setInstallMorrowind(bool install); - void setInstallTribunal(bool install); - void setInstallBloodmoon(bool install); + void setInstallComponent(Wizard::Component component, bool install); - bool getInstallMorrowind(); - bool getInstallTribunal(); - bool getInstallBloodmoon(); - - void setMorrowindPath(const QString &path); - void setTribunalPath(const QString &path); - void setBloodmoonPath(const QString &path); - - QString getMorrowindPath(); - QString getTribunalPath(); - QString getBloodmoonPath(); + void setComponentPath(Wizard::Component component, const QString &path); void setPath(const QString &path); void setIniPath(const QString &path); @@ -47,13 +45,12 @@ namespace Wizard private: - void setMorrowindDone(bool done); - void setTribunalDone(bool done); - void setBloodmoonDone(bool done); + bool getInstallComponent(Component component); - bool getMorrowindDone(); - bool getTribunalDone(); - bool getBloodmoonDone(); + QString getComponentPath(Component component); + + void setComponentDone(Component component, bool done = true); + bool getComponentDone(Component component); bool removeDirectory(const QString &dirName); @@ -75,6 +72,9 @@ namespace Wizard bool installTribunal(); bool installBloodmoon(); + bool installComponent(Component component); + void setupAddon(Component component); + bool mInstallMorrowind; bool mInstallTribunal; bool mInstallBloodmoon; @@ -104,7 +104,7 @@ namespace Wizard signals: void finished(); - void requestFileDialog(const QString &component); + void requestFileDialog(Wizard::Component component); void textChanged(const QString &text); void logTextChanged(const QString &text); From 1262eab03ab51c7629a20f8c225c5c490d6ad7e3 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 28 Jan 2014 01:03:47 +0100 Subject: [PATCH 0025/1983] Added some nice error messages to the installation process --- apps/wizard/conclusionpage.cpp | 17 ++++++--- apps/wizard/installationpage.cpp | 48 +++++++++++++++++++------ apps/wizard/installationpage.hpp | 2 +- apps/wizard/mainwizard.hpp | 2 ++ apps/wizard/unshield/unshieldworker.cpp | 45 ++++++++++++++--------- apps/wizard/unshield/unshieldworker.hpp | 4 +-- files/ui/wizard/conclusionpage.ui | 6 ++++ files/ui/wizard/installationpage.ui | 4 +-- 8 files changed, 92 insertions(+), 36 deletions(-) diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 55b03b912..61d58eaba 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -13,13 +13,20 @@ Wizard::ConclusionPage::ConclusionPage(MainWizard *wizard) : void Wizard::ConclusionPage::initializePage() { - if (field("installation.new").toBool() == true) + if (!mWizard->mError) { - textLabel->setText(tr("The OpenMW Wizard successfully installed Morrowind on your computer.\n\n") + - tr("Click Finish to close the Wizard.")); + if (field("installation.new").toBool() == true) + { + textLabel->setText(tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

\ +

Click Finish to close the Wizard.

")); + } else { + textLabel->setText(tr("

The OpenMW Wizard successfully modified your existing Morrowind installation.

\ +

Click Finish to close the Wizard.

")); + } } else { - textLabel->setText(tr("The OpenMW Wizard successfully modified your existing Morrowind installation.\n\n") + - tr("Click Finish to close the Wizard.")); + textLabel->setText(tr("

The OpenMW Wizard failed to install Morrowind on your computer.

\ +

Please report any bugs you might have encountered to our \ + bug tracker.
Make sure to include the installation log.


")); } } diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 47d10c45f..fc527ed3f 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -23,8 +23,8 @@ void Wizard::InstallationPage::initializePage() QString path(field("installation.path").toString()); QStringList components(field("installation.components").toStringList()); - logTextEdit->append(QString("Installing to %1").arg(path)); - logTextEdit->append(QString("Installing %1.").arg(components.join(", "))); + logTextEdit->appendPlainText(QString("Installing to %1").arg(path)); + logTextEdit->appendPlainText(QString("Installing %1.").arg(components.join(", "))); installProgressBar->setMinimum(0); @@ -39,11 +39,11 @@ void Wizard::InstallationPage::initializePage() else { if (components.contains(QLatin1String("Tribunal")) - && mWizard->mInstallations[path]->hasTribunal == false) + && !mWizard->mInstallations[path]->hasTribunal) installProgressBar->setMaximum(100); if (components.contains(QLatin1String("Bloodmoon")) - && mWizard->mInstallations[path]->hasBloodmoon == false) + && !mWizard->mInstallations[path]->hasBloodmoon) installProgressBar->setMaximum(installProgressBar->maximum() + 100); } @@ -76,14 +76,14 @@ void Wizard::InstallationPage::startInstallation() connect(mUnshield, SIGNAL(finished()), this, SLOT(installationFinished()), Qt::QueuedConnection); - connect(mUnshield, SIGNAL(error(QString)), - this, SLOT(installationError(QString)), Qt::QueuedConnection); + connect(mUnshield, SIGNAL(error(QString, QString)), + this, SLOT(installationError(QString, QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(textChanged(QString)), installProgressLabel, SLOT(setText(QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(textChanged(QString)), - logTextEdit, SLOT(append(QString)), Qt::QueuedConnection); + logTextEdit, SLOT(appendPlainText(QString)), Qt::QueuedConnection); connect(mUnshield, SIGNAL(progressChanged(int)), installProgressBar, SLOT(setValue(int)), Qt::QueuedConnection); @@ -170,17 +170,45 @@ void Wizard::InstallationPage::installationFinished() } -void Wizard::InstallationPage::installationError(const QString &text) +void Wizard::InstallationPage::installationError(const QString &text, const QString &details) { qDebug() << "error: " << text; + + installProgressLabel->setText(tr("Installation failed!")); + + logTextEdit->appendHtml(tr("Error: %1").arg(text)); + logTextEdit->appendHtml(tr("%1").arg(details)); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("An error occurred")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

The Wizard has encountered an error

\ +

The error reported was:

%1

\ +

Press "Show Details..." for more information.

").arg(text)); + + msgBox.setDetailedText(details); + msgBox.exec(); + + mWizard->mError = true; + emit completeChanged(); + } bool Wizard::InstallationPage::isComplete() const { - return mFinished; + if (!mWizard->mError) { + return mFinished; + } else { + return true; + } } int Wizard::InstallationPage::nextId() const { - return MainWizard::Page_Import; + if (!mWizard->mError) { + return MainWizard::Page_Import; + } else { + return MainWizard::Page_Conclusion; + } } diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index 3989177ab..7001b2630 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -37,7 +37,7 @@ namespace Wizard void showFileDialog(Wizard::Component component); void installationFinished(); - void installationError(const QString &text); + void installationError(const QString &text, const QString &details); protected: void initializePage(); diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 0d4c05fce..31a93ffbb 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -44,6 +44,8 @@ namespace Wizard Files::ConfigurationManager mCfgMgr; + bool mError; + private: void setupInstallations(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 9c635818b..7becec101 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -185,8 +185,8 @@ void Wizard::UnshieldWorker::setupSettings() QFile file(getIniPath()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - // TODO: Emit error signal qDebug() << "Error opening .ini file!"; + emit error(tr("Failed to open Morrowind configuration file!"), tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); return; } @@ -240,6 +240,7 @@ bool Wizard::UnshieldWorker::copyFile(const QString &source, const QString &dest } } else { qDebug() << "copy failed! " << file.errorString(); + emit error(tr("Failed to copy file!"), tr("Copying %1 to %2 failed: %3.").arg(source, destination, file.errorString())); } return false; @@ -274,7 +275,6 @@ bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString if (info.isDir()) { result = moveDirectory(info.absoluteFilePath(), destDir.absolutePath() + relativePath); } else { -// qDebug() << "moving: " << info.absoluteFilePath() << " to: " << destDir.absolutePath() + relativePath; result = moveFile(info.absoluteFilePath(), destDir.absolutePath() + relativePath); } } @@ -363,6 +363,7 @@ void Wizard::UnshieldWorker::extract() setComponentDone(Wizard::Component_Morrowind, true); } else { qDebug() << "Erorr installing Morrowind"; + return; } } @@ -414,8 +415,10 @@ void Wizard::UnshieldWorker::setupAddon(Component component) if (component == Wizard::Component_Bloodmoon) name = QLatin1String("Bloodmoon"); - if (name.isEmpty()) - return; // Not a valid addon + if (name.isEmpty()) { + emit error(tr("Component parameter is invalid!"), tr("An invalid component parameter was supplied.")); + return; + } if (!disk.cd(name)) { qDebug() << "not found on cd!"; @@ -474,8 +477,10 @@ bool Wizard::UnshieldWorker::installComponent(Component component) break; } - if (name.isEmpty()) + if (name.isEmpty()) { + emit error(tr("Component parameter is invalid!"), tr("An invalid component parameter was supplied.")); return false; + } emit textChanged(tr("Installing %0").arg(name)); @@ -483,6 +488,7 @@ bool Wizard::UnshieldWorker::installComponent(Component component) if (!disk.exists()) { qDebug() << "Component path not set: " << getComponentPath(Wizard::Component_Morrowind); + emit error(tr("Component path not set!"), tr("The source path for %0 was not set.").arg(name)); return false; } @@ -496,29 +502,34 @@ bool Wizard::UnshieldWorker::installComponent(Component component) if (!temp.mkpath(tempPath)) { qDebug() << "Can't make path"; + emit error(tr("Cannot create temporary directory!"), tr("Failed to create %0.").arg(tempPath)); return false; } temp.setPath(tempPath); if (!temp.mkdir(name)) { - qDebug() << "Can't make dir"; + emit error(tr("Cannot create temporary directory!"), tr("Failed to create %0.").arg(temp.absoluteFilePath(name))); return false; } if (!temp.cd(name)) { qDebug() << "Can't cd to dir"; + emit error(tr("Cannot move into temporary directory!"), tr("Failed to move into %0.").arg(temp.absoluteFilePath(name))); return false; } // Extract the installation files - extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath()); + if (!extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath())) + return false; - // TODO: Throw error; // Move the files from the temporary path to the destination folder emit textChanged(tr("Moving installation files")); if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { qDebug() << "failed to move files!"; + emit error(tr("Moving extracted files failed!"), + tr("Failed to move files from %0 to %1.").arg(temp.absoluteFilePath(QLatin1String("Data Files")), + getPath())); return false; } @@ -538,6 +549,7 @@ bool Wizard::UnshieldWorker::installComponent(Component component) moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); } else { qDebug() << "Could not find ini file!"; + emit error(tr("Could not find Morrowind configuration file!"), tr("Failed to find %0.").arg(iniPath)); return false; } } @@ -599,7 +611,7 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp success = unshield_file_save(unshield, index, fileName.toLatin1().constData()); if (!success) { - emit error(tr("Failed to extract %1").arg(fileName)); + emit error(tr("Failed to extract %1.").arg(QString::fromLatin1(unshield_file_name(unshield, index))), tr("Complete path: %1.").arg(fileName)); dir.remove(fileName); } @@ -611,9 +623,8 @@ bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fil Unshield *unshield; unshield = unshield_open(cabFile.toLatin1().constData()); - // TODO: Proper error if (!unshield) { - emit error(tr("Failed to open %1").arg(cabFile)); + emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); return false; } @@ -635,15 +646,16 @@ bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fil return false; } -void Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &outputDir) +bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &outputDir) { + bool success; + Unshield *unshield; unshield = unshield_open(cabFile.toLatin1().constData()); - // TODO: Proper error if (!unshield) { - emit error(tr("Failed to open %1").arg(cabFile)); - return; + emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); + return false; } int counter = 0; @@ -655,11 +667,12 @@ void Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &o for (size_t j=group->first_file; j<=group->last_file; ++j) { if (unshield_file_is_valid(unshield, j)) { - extractFile(unshield, outputDir, group->name, j, counter); + success = extractFile(unshield, outputDir, group->name, j, counter); ++counter; } } } unshield_close(unshield); + return success; } diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index fb55bc88f..501daaf30 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -62,7 +62,7 @@ namespace Wizard void setupSettings(); - void extractCab(const QString &cabFile, const QString &outputDir); + bool extractCab(const QString &cabFile, const QString &outputDir); bool extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter); bool findFile(const QString &cabFile, const QString &fileName); @@ -109,7 +109,7 @@ namespace Wizard void textChanged(const QString &text); void logTextChanged(const QString &text); - void error(const QString &text); + void error(const QString &text, const QString &details); void progressChanged(int progress); diff --git a/files/ui/wizard/conclusionpage.ui b/files/ui/wizard/conclusionpage.ui index a27f66789..eb6828b38 100644 --- a/files/ui/wizard/conclusionpage.ui +++ b/files/ui/wizard/conclusionpage.ui @@ -22,9 +22,15 @@ Placeholder + + Qt::RichText + true + + true +
diff --git a/files/ui/wizard/installationpage.ui b/files/ui/wizard/installationpage.ui index 664e10556..6877f1e58 100644 --- a/files/ui/wizard/installationpage.ui +++ b/files/ui/wizard/installationpage.ui @@ -23,7 +23,7 @@ - Extracting: %1 + @@ -35,7 +35,7 @@
- + false From 2ccf4d61124d0e62826e62252648fa3195792b80 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 28 Jan 2014 11:51:13 +0100 Subject: [PATCH 0026/1983] Post-merge cleanups, using confignmanager for XDG paths now, thanks scrawl! --- apps/wizard/installationtargetpage.cpp | 28 +++++--------------------- apps/wizard/installationtargetpage.hpp | 8 +++++++- apps/wizard/mainwizard.cpp | 6 +++--- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 60acfd5f7..459e7145d 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -2,13 +2,13 @@ #include #include -#include #include "mainwizard.hpp" -Wizard::InstallationTargetPage::InstallationTargetPage(MainWizard *wizard) : +Wizard::InstallationTargetPage::InstallationTargetPage(MainWizard *wizard, const Files::ConfigurationManager &cfg) : QWizardPage(wizard), - mWizard(wizard) + mWizard(wizard), + mCfgMgr(cfg) { setupUi(this); @@ -17,24 +17,8 @@ Wizard::InstallationTargetPage::InstallationTargetPage(MainWizard *wizard) : void Wizard::InstallationTargetPage::initializePage() { - qDebug() << mWizard->field("installation.language"); - -#ifdef Q_OS_WIN - QString path = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); -#endif - -#ifdef Q_OS_MAC - QString path = QDesktopServices::storageLocation(QDesktopServices::DataLocation); -#endif - -#if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) - QString path = QFile::decodeName(qgetenv("XDG_DATA_HOME")); - - if (path.isEmpty()) - path = QDir::homePath() + QLatin1String("/.local/share"); -#endif - - path.append(QLatin1String("/openmw/data")); + QString path(QFile::decodeName(mCfgMgr.getUserDataPath().string().c_str())); + path.append(QDir::separator() + QLatin1String("data")); if (!QFile::exists(path)) { QDir dir; @@ -43,8 +27,6 @@ void Wizard::InstallationTargetPage::initializePage() QDir dir(path); targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); - - qDebug() << path; } void Wizard::InstallationTargetPage::on_browseButton_clicked() diff --git a/apps/wizard/installationtargetpage.hpp b/apps/wizard/installationtargetpage.hpp index ccaac5969..f6d28b013 100644 --- a/apps/wizard/installationtargetpage.hpp +++ b/apps/wizard/installationtargetpage.hpp @@ -5,6 +5,11 @@ #include "ui_installationtargetpage.h" +namespace Files +{ + struct ConfigurationManager; +} + namespace Wizard { class MainWizard; @@ -13,7 +18,7 @@ namespace Wizard { Q_OBJECT public: - InstallationTargetPage(MainWizard *wizard); + InstallationTargetPage(MainWizard *wizard, const Files::ConfigurationManager &cfg); int nextId() const; @@ -22,6 +27,7 @@ namespace Wizard private: MainWizard *mWizard; + const Files::ConfigurationManager &mCfgMgr; protected: void initializePage(); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index cbc9253ed..aef59fa4e 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -41,7 +41,7 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : void Wizard::MainWizard::setupInstallations() { - QString userPath(QFile::decodeName(mCfgMgr.getUserPath().string().c_str())); + QString userPath(QFile::decodeName(mCfgMgr.getUserConfigPath().string().c_str())); QString globalPath(QFile::decodeName(mCfgMgr.getGlobalPath().string().c_str())); QStringList paths; @@ -122,7 +122,7 @@ void Wizard::MainWizard::setupPages() setPage(Page_MethodSelection, new MethodSelectionPage(this)); setPage(Page_LanguageSelection, new LanguageSelectionPage(this)); setPage(Page_ExistingInstallation, new ExistingInstallationPage(this)); - setPage(Page_InstallationTarget, new InstallationTargetPage(this)); + setPage(Page_InstallationTarget, new InstallationTargetPage(this, mCfgMgr)); setPage(Page_ComponentSelection, new ComponentSelectionPage(this)); setPage(Page_Installation, new InstallationPage(this)); setPage(Page_Import, new ImportPage(this)); @@ -137,7 +137,7 @@ void Wizard::MainWizard::accept() void Wizard::MainWizard::writeSettings() { - QString userPath(QFile::decodeName(mCfgMgr.getUserPath().string().c_str())); + QString userPath(QFile::decodeName(mCfgMgr.getUserConfigPath().string().c_str())); QFile file(userPath + QLatin1String("openmw.cfg")); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { From 5024f1bf7763ccaf569c483b8e1a6a3c36029e65 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 28 Jan 2014 13:27:09 +0100 Subject: [PATCH 0027/1983] Added a warning when trying to install tribunal after bloodmoon --- apps/wizard/componentselectionpage.cpp | 61 ++++++++++++++++++++--- apps/wizard/componentselectionpage.hpp | 1 + apps/wizard/existinginstallationpage.cpp | 6 +-- apps/wizard/installationpage.cpp | 16 +++++- apps/wizard/installationtargetpage.cpp | 4 +- apps/wizard/languageselectionpage.cpp | 5 +- apps/wizard/mainwizard.cpp | 17 +++---- apps/wizard/methodselectionpage.cpp | 6 ++- apps/wizard/utils/componentlistwidget.cpp | 18 ++++--- apps/wizard/utils/componentlistwidget.hpp | 3 +- 10 files changed, 99 insertions(+), 38 deletions(-) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 7d934fe84..87779e93d 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -1,7 +1,9 @@ #include "componentselectionpage.hpp" #include +#include #include +#include #include "mainwizard.hpp" @@ -51,7 +53,7 @@ void Wizard::ComponentSelectionPage::initializePage() { componentsList->clear(); - QString path = field("installation.path").toString(); + QString path(field("installation.path").toString()); QListWidgetItem *morrowindItem = new QListWidgetItem(QLatin1String("Morrowind")); QListWidgetItem *tribunalItem = new QListWidgetItem(QLatin1String("Tribunal")); @@ -72,7 +74,7 @@ void Wizard::ComponentSelectionPage::initializePage() componentsList->addItem(bloodmoonItem); } else { - if (mWizard->mInstallations[path]->hasMorrowind == true) { + if (mWizard->mInstallations[path]->hasMorrowind) { morrowindItem->setText(tr("Morrowind\t\t(installed)")); morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Unchecked); @@ -83,7 +85,7 @@ void Wizard::ComponentSelectionPage::initializePage() componentsList->addItem(morrowindItem); - if (mWizard->mInstallations[path]->hasTribunal == true) { + if (mWizard->mInstallations[path]->hasTribunal) { tribunalItem->setText(tr("Tribunal\t\t(installed)")); tribunalItem->setFlags(tribunalItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); tribunalItem->setData(Qt::CheckStateRole, Qt::Unchecked); @@ -94,7 +96,7 @@ void Wizard::ComponentSelectionPage::initializePage() componentsList->addItem(tribunalItem); - if (mWizard->mInstallations[path]->hasBloodmoon == true) { + if (mWizard->mInstallations[path]->hasBloodmoon) { bloodmoonItem->setText(tr("Bloodmoon\t\t(installed)")); bloodmoonItem->setFlags(bloodmoonItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); bloodmoonItem->setData(Qt::CheckStateRole, Qt::Unchecked); @@ -107,10 +109,55 @@ void Wizard::ComponentSelectionPage::initializePage() } } +bool Wizard::ComponentSelectionPage::validatePage() +{ + QStringList components(field("installation.components").toStringList()); + QString path(field("installation.path").toString()); + + qDebug() << components << path << mWizard->mInstallations[path]; + + if (field("installation.new").toBool() == false) { + if (components.contains(QLatin1String("Tribunal")) && !components.contains(QLatin1String("Bloodmoon"))) + { + if (mWizard->mInstallations[path]->hasBloodmoon) + { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("About to install Tribunal after Bloodmoon")); + msgBox.setIcon(QMessageBox::Information); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("

You are about to install Tribunal

\ +

Bloodmoon is already installed on your computer.

\ +

However, it is recommended that you install Tribunal before Bloodmoon.

\ +

Would you like to re-install Bloodmoon?

")); + + QAbstractButton *reinstallButton = msgBox.addButton(tr("Re-install &Bloodmoon"), QMessageBox::ActionRole); + msgBox.exec(); + + + if (msgBox.clickedButton() == reinstallButton) { + // Force reinstallation + mWizard->mInstallations[path]->hasBloodmoon = false; + QList items = componentsList->findItems(QLatin1String("Bloodmoon"), Qt::MatchStartsWith); + + foreach (QListWidgetItem *item, items) { + item->setText(QLatin1String("Bloodmoon")); + item->setCheckState(Qt::Checked); + } + + return true; + } + } + } + } + + return true; +} + int Wizard::ComponentSelectionPage::nextId() const { - if (isCommitPage()) + if (isCommitPage()) { return MainWizard::Page_Installation; - - return MainWizard::Page_Import; + } else { + return MainWizard::Page_Import; + } } diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp index 8b4c186d0..ed007fd08 100644 --- a/apps/wizard/componentselectionpage.hpp +++ b/apps/wizard/componentselectionpage.hpp @@ -16,6 +16,7 @@ namespace Wizard ComponentSelectionPage(MainWizard *wizard); int nextId() const; + virtual bool validatePage(); private slots: void updateButton(QListWidgetItem *item); diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 5c66ceb4f..c1dbf8e9b 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -57,7 +57,7 @@ void Wizard::ExistingInstallationPage::textChanged(const QString &text) // Set the installation path manually, as registerField doesn't work // Because it doesn't accept two widgets operating on a single field if (!text.isEmpty()) - mWizard->setField("installation.path", text); + mWizard->setField(QLatin1String("installation.path"), text); } void Wizard::ExistingInstallationPage::initializePage() @@ -135,7 +135,7 @@ int Wizard::ExistingInstallationPage::nextId() const QString path(field("installation.path").toString()); if (path.isEmpty()) - return MainWizard::Page_ComponentSelection; + return MainWizard::Page_LanguageSelection; if (mWizard->mInstallations[path]->hasMorrowind == true && mWizard->mInstallations[path]->hasTribunal == true && @@ -143,6 +143,6 @@ int Wizard::ExistingInstallationPage::nextId() const { return MainWizard::Page_Import; } else { - return MainWizard::Page_ComponentSelection; + return MainWizard::Page_LanguageSelection; } } diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index fc527ed3f..e838a19c1 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -138,9 +138,23 @@ void Wizard::InstallationPage::startInstallation() void Wizard::InstallationPage::showFileDialog(Wizard::Component component) { + QString name; + switch (component) { + + case Wizard::Component_Morrowind: + name = QLatin1String("Morrowind"); + break; + case Wizard::Component_Tribunal: + name = QLatin1String("Tribunal"); + break; + case Wizard::Component_Bloodmoon: + name = QLatin1String("Bloodmoon"); + break; + } + QString fileName = QFileDialog::getOpenFileName( this, - tr("Select installation file"), + tr("Select %1 installation file").arg(name), QDir::rootPath(), tr("InstallShield header files (*.hdr)")); diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 459e7145d..900a978cf 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -34,7 +34,7 @@ void Wizard::InstallationTargetPage::on_browseButton_clicked() QString selectedPath = QFileDialog::getExistingDirectory( this, tr("Select where to install Morrowind"), - QDir::currentPath(), + QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); qDebug() << selectedPath; @@ -49,5 +49,5 @@ void Wizard::InstallationTargetPage::on_browseButton_clicked() int Wizard::InstallationTargetPage::nextId() const { - return MainWizard::Page_ComponentSelection; + return MainWizard::Page_LanguageSelection; } diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 102a4c39a..b921d5ddb 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -27,8 +27,5 @@ void Wizard::LanguageSelectionPage::initializePage() int Wizard::LanguageSelectionPage::nextId() const { - if (field("installation.new").toBool() == true) - return MainWizard::Page_InstallationTarget; - - return MainWizard::Page_ExistingInstallation; + return MainWizard::Page_ComponentSelection; } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index aef59fa4e..0913cf9e4 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -73,14 +73,10 @@ void Wizard::MainWizard::setupInstallations() file.close(); } - // Check if the paths actually contain data files + // Check if the paths actually contains a Morrowind installation foreach (const QString path, mGameSettings.getDataDirs()) { - QDir dir(path); - QStringList filters; - filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; - // Add to Wizard installations - if (!dir.entryList(filters).isEmpty()) + if (findFiles(QLatin1String("Morrowind"), path)) addInstallation(path); } @@ -112,8 +108,10 @@ void Wizard::MainWizard::addInstallation(const QString &path) mInstallations.insert(QDir::toNativeSeparators(path), install); // Add it to the openmw.cfg too - mGameSettings.setMultiValue(QString("data"), path); - mGameSettings.addDataDir(path); + if (!mGameSettings.getDataDirs().contains(path)) { + mGameSettings.setMultiValue(QString("data"), path); + mGameSettings.addDataDir(path); + } } void Wizard::MainWizard::setupPages() @@ -168,8 +166,7 @@ bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) return false; // TODO: add MIME handling to make sure the files are real - if (dir.exists(name + QLatin1String(".esm")) && dir.exists(name + QLatin1String(".bsa"))) - { + if (dir.exists(name + QLatin1String(".esm")) && dir.exists(name + QLatin1String(".bsa"))) { return true; } else { return false; diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index 4185fa2bc..6db0779a6 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -13,5 +13,9 @@ Wizard::MethodSelectionPage::MethodSelectionPage(MainWizard *wizard) : int Wizard::MethodSelectionPage::nextId() const { - return MainWizard::Page_LanguageSelection; + if (field("installation.new").toBool() == true) { + return MainWizard::Page_InstallationTarget; + } else { + return MainWizard::Page_ExistingInstallation; + } } diff --git a/apps/wizard/utils/componentlistwidget.cpp b/apps/wizard/utils/componentlistwidget.cpp index c9a1c1940..6a5d019b5 100644 --- a/apps/wizard/utils/componentlistwidget.cpp +++ b/apps/wizard/utils/componentlistwidget.cpp @@ -10,15 +10,9 @@ ComponentListWidget::ComponentListWidget(QWidget *parent) : connect(this, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(updateCheckedItems(QListWidgetItem *))); -} - -void ComponentListWidget::addItem(QListWidgetItem *item) -{ - // The model does not emit a dataChanged signal when items are added - // So we need to update manually - QListWidget::insertItem(count(), item); - updateCheckedItems(item); + connect(model(), SIGNAL(rowsInserted(QModelIndex, int, int)), + this, SLOT(updateCheckedItems(QModelIndex, int, int))); } QStringList ComponentListWidget::checkedItems() @@ -27,8 +21,16 @@ QStringList ComponentListWidget::checkedItems() return mCheckedItems; } +void ComponentListWidget::updateCheckedItems(const QModelIndex &index, int start, int end) +{ + updateCheckedItems(item(start)); +} + void ComponentListWidget::updateCheckedItems(QListWidgetItem *item) { + if (!item) + return; + QString text = item->text(); if (item->checkState() == Qt::Checked) { diff --git a/apps/wizard/utils/componentlistwidget.hpp b/apps/wizard/utils/componentlistwidget.hpp index 6869629d3..23965f8a6 100644 --- a/apps/wizard/utils/componentlistwidget.hpp +++ b/apps/wizard/utils/componentlistwidget.hpp @@ -15,13 +15,12 @@ public: QStringList mCheckedItems; QStringList checkedItems(); - void addItem(QListWidgetItem *item); - signals: void checkedItemsChanged(const QStringList &items); private slots: void updateCheckedItems(QListWidgetItem *item); + void updateCheckedItems(const QModelIndex &index, int start, int end); }; #endif // COMPONENTLISTWIDGET_HPP From ceb66d0790ed8aa1c62b6ca2564db311a2921d38 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 28 Jan 2014 13:36:14 +0100 Subject: [PATCH 0028/1983] Also install the separate Sounds directory for Tribunal --- apps/wizard/unshield/unshieldworker.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 7becec101..d7cda05a0 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -554,6 +554,17 @@ bool Wizard::UnshieldWorker::installComponent(Component component) } } + if (component == Wizard::Component_Tribunal) + { + QFileInfo sounds(temp.absoluteFilePath(QLatin1String("Sounds"))); + + if (sounds.exists()) { + emit textChanged(tr("Extracting: Sound directory")); + copyDirectory(sounds.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Sound")); + } + + } + if (component == Wizard::Component_Bloodmoon) { QFileInfo patch(temp.absoluteFilePath(QLatin1String("Tribunal Patch") + QDir::separator() + QLatin1String("Tribunal.esm"))); From e8170adde5b02fe1a66a471831a8d117f6f4b277 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sat, 8 Feb 2014 00:09:25 +0100 Subject: [PATCH 0029/1983] Some minor changes, implemented suggestions --- apps/wizard/installationpage.cpp | 15 ++++----------- apps/wizard/installationtargetpage.cpp | 22 ++++++++++++++++++++++ apps/wizard/installationtargetpage.hpp | 1 + apps/wizard/unshield/unshieldworker.cpp | 2 ++ apps/wizard/unshield/unshieldworker.hpp | 2 -- files/ui/wizard/importpage.ui | 6 +++--- files/ui/wizard/methodselectionpage.ui | 12 +++++------- 7 files changed, 37 insertions(+), 23 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index e838a19c1..efa6880be 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -32,12 +32,9 @@ void Wizard::InstallationPage::initializePage() // That way installing all three components would yield 300% // When one component is done the bar will be filled by 33% - if (field("installation.new").toBool() == true) - { + if (field("installation.new").toBool() == true) { installProgressBar->setMaximum((components.count() * 100)); - } - else - { + } else { if (components.contains(QLatin1String("Tribunal")) && !mWizard->mInstallations[path]->hasTribunal) installProgressBar->setMaximum(100); @@ -59,8 +56,6 @@ void Wizard::InstallationPage::startInstallation() mUnshield = new UnshieldWorker(); mUnshield->moveToThread(thread); - qRegisterMetaType("Wizard::Component"); - connect(thread, SIGNAL(started()), mUnshield, SLOT(extract())); @@ -125,11 +120,9 @@ void Wizard::InstallationPage::startInstallation() if (language == QLatin1String("Polish")) { mUnshield->setIniCodec(QTextCodec::codecForName("windows-1250")); - } - else if (language == QLatin1String("Russian")) { + } else if (language == QLatin1String("Russian")) { mUnshield->setIniCodec(QTextCodec::codecForName("windows-1251")); - } - else { + } else { mUnshield->setIniCodec(QTextCodec::codecForName("windows-1252")); } diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 900a978cf..306e7221c 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "mainwizard.hpp" @@ -29,6 +30,27 @@ void Wizard::InstallationTargetPage::initializePage() targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); } +bool Wizard::InstallationTargetPage::validatePage() +{ + QString path(field("installation.path").toString()); + + qDebug() << "Validating path: " << path; + + if (mWizard->findFiles(QLatin1String("Morrowind"), path)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Destination not empty")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

The destination directory is not empty

\ +

An existing Morrowind installation is present in the specified location.

\ +

Please specify a different location, or go back and select the location as an existing installation.

")); + msgBox.exec(); + return false; + } + + return true; +} + void Wizard::InstallationTargetPage::on_browseButton_clicked() { QString selectedPath = QFileDialog::getExistingDirectory( diff --git a/apps/wizard/installationtargetpage.hpp b/apps/wizard/installationtargetpage.hpp index f6d28b013..0711c1c05 100644 --- a/apps/wizard/installationtargetpage.hpp +++ b/apps/wizard/installationtargetpage.hpp @@ -21,6 +21,7 @@ namespace Wizard InstallationTargetPage(MainWizard *wizard, const Files::ConfigurationManager &cfg); int nextId() const; + virtual bool validatePage(); private slots: void on_browseButton_clicked(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index d7cda05a0..02e50a7e3 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -37,6 +37,8 @@ Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : mMorrowindDone = false; mTribunalDone = false; mBloodmoonDone = false; + + qRegisterMetaType("Wizard::Component"); } Wizard::UnshieldWorker::~UnshieldWorker() diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 501daaf30..d110851f4 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -20,8 +20,6 @@ namespace Wizard Component_Bloodmoon }; - //Q_DECLARE_METATYPE(Wizard::Component) - class UnshieldWorker : public QObject { Q_OBJECT diff --git a/files/ui/wizard/importpage.ui b/files/ui/wizard/importpage.ui index 21037f3e8..5b078efca 100644 --- a/files/ui/wizard/importpage.ui +++ b/files/ui/wizard/importpage.ui @@ -6,8 +6,8 @@ 0 0 - 513 - 328 + 511 + 326 @@ -43,7 +43,7 @@ - Import previously selected add-ons (creates a new Content List in the launcher) + Import add-on and plugin selection (creates a new Content List in the launcher) diff --git a/files/ui/wizard/methodselectionpage.ui b/files/ui/wizard/methodselectionpage.ui index e77349f5c..122ef754e 100644 --- a/files/ui/wizard/methodselectionpage.ui +++ b/files/ui/wizard/methodselectionpage.ui @@ -6,8 +6,8 @@ 0 0 - 398 - 298 + 396 + 296 @@ -26,7 +26,7 @@ font-weight:bold; - Install to a new location + Install Morrowind to a new location true @@ -94,7 +94,7 @@ font-weight:bold - Select an existing installation + Select an existing Morrowind installation
@@ -149,8 +149,6 @@
- - - + From 6916c0bc9479a65cadbe384be6ff3bde72c2ce70 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 18 Feb 2014 11:55:26 +0100 Subject: [PATCH 0030/1983] Wizard now writes valid Morrowind.ini files --- apps/wizard/existinginstallationpage.cpp | 3 +- apps/wizard/inisettings.cpp | 180 ++++++++++++++++++++++- apps/wizard/inisettings.hpp | 13 +- apps/wizard/installationpage.cpp | 1 + apps/wizard/installationtargetpage.cpp | 12 +- apps/wizard/mainwizard.cpp | 1 + apps/wizard/unshield/unshieldworker.cpp | 66 ++++++++- apps/wizard/unshield/unshieldworker.hpp | 6 +- 8 files changed, 262 insertions(+), 20 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index c1dbf8e9b..34e771e3d 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -38,8 +38,7 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() QString path(QDir::toNativeSeparators(info.absolutePath())); QList items = installationsList->findItems(path, Qt::MatchExactly); - if (items.isEmpty()) - { + if (items.isEmpty()) { // Path is not yet in the list, add it mWizard->addInstallation(path); diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index 2c099acec..f34699742 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -1,8 +1,10 @@ #include "inisettings.hpp" +#include #include #include +#include #include #include #include @@ -15,25 +17,40 @@ Wizard::IniSettings::~IniSettings() { } +QStringList Wizard::IniSettings::findKeys(const QString &text) +{ + QStringList result; + + foreach (const QString &key, mSettings.keys()) { + + if (key.startsWith(text)) + result << key; + + } + + return result; +} + bool Wizard::IniSettings::readFile(QTextStream &stream) { + qDebug() << "readFile called!"; // Look for a square bracket, "'\\[" // that has one or more "not nothing" in it, "([^]]+)" // and is closed with a square bracket, "\\]" - QRegExp sectionRe("^\\[([^]]+)\\]"); + QRegExp sectionRe(QLatin1String("^\\[([^]]+)\\]")); // Find any character(s) that is/are not equal sign(s), "[^=]+" // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" // and one or more periods, "(.+)" - QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); + QRegExp keyRe(QLatin1String("^([^=]+)\\s*=\\s*(.+)$")); QString currentSection; while (!stream.atEnd()) { - QString line(stream.readLine()); + const QString line(stream.readLine()); - if (line.isEmpty() || line.startsWith(";")) + if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) continue; if (sectionRe.exactMatch(line)) @@ -49,6 +66,7 @@ bool Wizard::IniSettings::readFile(QTextStream &stream) if (!currentSection.isEmpty()) key = currentSection + QLatin1Char('/') + key; + qDebug() << "adding: " << key << value; mSettings[key] = QVariant(value); } } @@ -56,13 +74,161 @@ bool Wizard::IniSettings::readFile(QTextStream &stream) return true; } -bool Wizard::IniSettings::writeFile(QTextStream &stream) +bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream) { - qDebug() << "test! " << stream.readAll(); + // Look for a square bracket, "'\\[" + // that has one or more "not nothing" in it, "([^]]+)" + // and is closed with a square bracket, "\\]" + QRegExp sectionRe(QLatin1String("^\\[([^]]+)\\]")); + + // Find any character(s) that is/are not equal sign(s), "[^=]+" + // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" + // and one or more periods, "(.+)" + QRegExp keyRe(QLatin1String("^([^=]+)\\s*=\\s*(.+)$")); + + const QStringList keys(mSettings.keys()); + + QString currentSection; + QString buffer; + + qDebug() << "Keys! " << keys; while (!stream.atEnd()) { - qDebug() << "test! " << stream.readLine(); + + const QString line(stream.readLine()); + + if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) { + buffer.append(line + QLatin1String("\n")); + continue; + } + + if (sectionRe.exactMatch(line)) { + buffer.append(line + QLatin1String("\n")); + currentSection = sectionRe.cap(1); + } else if (keyRe.indexIn(line) != -1) { + QString key(keyRe.cap(1).trimmed()); + QString lookupKey(key); + + // Append the section, but only if there is one + if (!currentSection.isEmpty()) + lookupKey = currentSection + QLatin1Char('/') + key; + + buffer.append(key + QLatin1Char('=') + mSettings[lookupKey].toString() + QLatin1String("\n")); + mSettings.remove(lookupKey); + } + } + + // Add the new settings to the buffer + QHashIterator i(mSettings); + while (i.hasNext()) { + i.next(); + + QStringList fullKey(i.key().split(QLatin1Char('/'))); + QString section(fullKey.at(0)); + section.prepend(QLatin1Char('[')); + section.append(QLatin1Char(']')); + QString key(fullKey.at(1)); + + int index = buffer.lastIndexOf(section); + if (index != -1) { + // Append the new keys to the bottom of the section + index = buffer.indexOf(QLatin1Char('['), index + 1); + + if (index == -1 ) + { + // Beginning of next section not found, we are at the last section + if (buffer.lastIndexOf(QLatin1String("\n")) > (buffer.lastIndexOf(section) + section.length())) { + // There is a newline after the section + index = buffer.lastIndexOf(QLatin1String("\n")) - 1; + buffer.insert(index - 2, QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + continue; + } else { + // No newline found, or the last newline is before the last section + // Append the key to the bottom of the file + buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + continue; + } + } + + // Add the key at the index + buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + } else { + // Add the section to the end of the file, because it's not found + buffer.append(QString("\n%1\n").arg(section)); + i.previous(); + } } + // Now we reopen the file, this time we write + QFile file(path); + + if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { + QTextStream in(&file); + in.setCodec(stream.codec()); + + // Write the updated buffer to an empty file + in << buffer; + file.flush(); + file.close(); + } else { + return false; + } + + return true; +} + +bool Wizard::IniSettings::parseInx(const QString &path) +{ + QFile file(path); + + if (file.open(QIODevice::ReadOnly)) + { + + const QByteArray data(file.readAll()); + const QByteArray pattern("\x21\x00\x1A\x01\x04\x00\x04\x97\xFF\x06", 10); + + int i = 0; + while ((i = data.indexOf(pattern, i)) != -1) { + + int next = data.indexOf(pattern, i + 1); + if (next == -1) + break; + + QByteArray array(data.mid(i, (next - i))); + + // Skip some invalid entries + if (array.contains("\x04\x96\xFF")) { + ++i; + continue; + } + + // Remove the pattern from the beginning + array.remove(0, 12); + + int index = array.indexOf("\x06"); + const QString section(array.left(index)); + + // Figure how many characters to read for the key + int lenght = array.indexOf("\x06", section.length() + 3) - (section.length() + 3); + const QString key(array.mid(section.length() + 3, lenght)); + + QString value(array.mid(section.length() + key.length() + 6)); + //qDebug() << section << key << value; + + // Add the value + setValue(section + QLatin1Char('/') + key, QVariant(value)); + + ++i; + } + + file.close(); + } else { + qDebug() << "Failed to open INX file: " << path; + return false; + } + return true; } diff --git a/apps/wizard/inisettings.hpp b/apps/wizard/inisettings.hpp index 66fe96ec1..d425a9b1b 100644 --- a/apps/wizard/inisettings.hpp +++ b/apps/wizard/inisettings.hpp @@ -22,6 +22,11 @@ namespace Wizard return mSettings.value(key, defaultValue); } + inline QList values() const + { + return mSettings.values(); + } + inline void setValue(const QString &key, const QVariant &value) { mSettings.insert(key, value); @@ -32,11 +37,17 @@ namespace Wizard mSettings.remove(key); } + QStringList findKeys(const QString &text); + bool readFile(QTextStream &stream); - bool writeFile(QTextStream &stream); + bool writeFile(const QString &path, QTextStream &stream); + + bool parseInx(const QString &path); private: + int getLastNewline(const QString &buffer, int from); + SettingsMap mSettings; }; diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index efa6880be..05d1b4af8 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -110,6 +110,7 @@ void Wizard::InstallationPage::startInstallation() // Set the location of the Morrowind.ini to update mUnshield->setIniPath(mWizard->mInstallations[path]->iniPath); + mUnshield->setupSettings(); } // Set the installation target path diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 306e7221c..daf2c0846 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -21,11 +21,6 @@ void Wizard::InstallationTargetPage::initializePage() QString path(QFile::decodeName(mCfgMgr.getUserDataPath().string().c_str())); path.append(QDir::separator() + QLatin1String("data")); - if (!QFile::exists(path)) { - QDir dir; - dir.mkpath(path); - } - QDir dir(path); targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); } @@ -36,6 +31,13 @@ bool Wizard::InstallationTargetPage::validatePage() qDebug() << "Validating path: " << path; + // TODO: Check writeability + if (!QFile::exists(path)) { + QDir dir; + dir.mkpath(path); + return true; + } + if (mWizard->findFiles(QLatin1String("Morrowind"), path)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Destination not empty")); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 0913cf9e4..d9b2c3ba8 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -127,6 +127,7 @@ void Wizard::MainWizard::setupPages() setPage(Page_Conclusion, new ConclusionPage(this)); setStartId(Page_Intro); } + void Wizard::MainWizard::accept() { writeSettings(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 02e50a7e3..47d3c1be7 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -43,7 +43,6 @@ Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : Wizard::UnshieldWorker::~UnshieldWorker() { - } void Wizard::UnshieldWorker::setInstallComponent(Wizard::Component component, bool install) @@ -198,6 +197,29 @@ void Wizard::UnshieldWorker::setupSettings() mIniSettings.readFile(stream); } +void Wizard::UnshieldWorker::writeSettings() +{ + if (getIniPath().isEmpty()) + return; + + QFile file(getIniPath()); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + qDebug() << "Error opening .ini file!"; + emit error(tr("Failed to open Morrowind configuration file!"), + tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); + return; + } + + QTextStream stream(&file); + stream.setCodec(mIniCodec); + + if (!mIniSettings.writeFile(getIniPath(), stream)) { + emit error(tr("Failed to write Morrowind configuration file!"), + tr("Writing to %1 failed: %2.").arg(getIniPath(), file.errorString())); + } +} + bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) { bool result = true; @@ -383,6 +405,32 @@ void Wizard::UnshieldWorker::extract() setupAddon(Wizard::Component_Bloodmoon); } + // Update Morrowind configuration + if (getInstallComponent(Wizard::Component_Tribunal)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); + } + + if (getInstallComponent(Wizard::Component_Bloodmoon)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Bloodmoon.esm"))); + } + + if (getInstallComponent(Wizard::Component_Tribunal) && + getInstallComponent(Wizard::Component_Bloodmoon)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Archives/Archive1"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File2"), QVariant(QString("Bloodmoon.esm"))); + } + + + // Write the settings to the Morrowind config file + writeSettings(); + // Remove the temporary directory removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); @@ -554,6 +602,10 @@ bool Wizard::UnshieldWorker::installComponent(Component component) emit error(tr("Could not find Morrowind configuration file!"), tr("Failed to find %0.").arg(iniPath)); return false; } + + // Setup Morrowind configuration + setIniPath(getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); + setupSettings(); } if (component == Wizard::Component_Tribunal) @@ -564,7 +616,6 @@ bool Wizard::UnshieldWorker::installComponent(Component component) emit textChanged(tr("Extracting: Sound directory")); copyDirectory(sounds.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Sound")); } - } if (component == Wizard::Component_Bloodmoon) @@ -577,6 +628,15 @@ bool Wizard::UnshieldWorker::installComponent(Component component) copyFile(patch.absoluteFilePath(), original.absoluteFilePath()); } + // Load Morrowind configuration settings from the setup script + QFileInfo inx(disk.absoluteFilePath(QLatin1String("setup.inx"))); + + if (inx.exists()) { + emit textChanged(tr("Updating Morrowind configuration file")); + mIniSettings.parseInx(inx.absoluteFilePath()); + } else { + qDebug() << "setup.inx not found!"; + } } emit textChanged(tr("%0 installation finished!").arg(name)); @@ -649,7 +709,7 @@ bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fil { QString current(QString::fromLatin1(unshield_file_name(unshield, j))); - qDebug() << "File is: " << unshield_file_name(unshield, j); + qDebug() << "File is: " << current; if (current == fileName) return true; // File is found! } diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index d110851f4..8aa54c9c0 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -41,8 +41,12 @@ namespace Wizard void setIniCodec(QTextCodec *codec); + void setupSettings(); + private: + void writeSettings(); + bool getInstallComponent(Component component); QString getComponentPath(Component component); @@ -58,8 +62,6 @@ namespace Wizard bool moveFile(const QString &source, const QString &destination); bool moveDirectory(const QString &source, const QString &destination); - void setupSettings(); - bool extractCab(const QString &cabFile, const QString &outputDir); bool extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter); bool findFile(const QString &cabFile, const QString &fileName); From 755c99df3ccd14db12a550f5e38e429f48909935 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 18 Feb 2014 12:44:27 +0100 Subject: [PATCH 0031/1983] Some minor fixes --- apps/wizard/componentselectionpage.cpp | 14 +++++------ apps/wizard/conclusionpage.cpp | 8 ++++++- apps/wizard/existinginstallationpage.cpp | 4 ++-- apps/wizard/inisettings.cpp | 30 ++++++++---------------- apps/wizard/installationpage.cpp | 14 +++++------ apps/wizard/installationtargetpage.cpp | 2 +- apps/wizard/methodselectionpage.cpp | 4 ++-- 7 files changed, 36 insertions(+), 40 deletions(-) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 87779e93d..19a07438d 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -16,7 +16,7 @@ Wizard::ComponentSelectionPage::ComponentSelectionPage(MainWizard *wizard) : setCommitPage(true); setButtonText(QWizard::CommitButton, tr("&Install")); - registerField("installation.components", componentsList); + registerField(QLatin1String("installation.components"), componentsList); connect(componentsList, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(updateButton(QListWidgetItem *))); @@ -25,7 +25,7 @@ Wizard::ComponentSelectionPage::ComponentSelectionPage(MainWizard *wizard) : void Wizard::ComponentSelectionPage::updateButton(QListWidgetItem *item) { - if (field("installation.new").toBool() == true) + if (field(QLatin1String("installation.new")).toBool() == true) return; // Morrowind is always checked here bool unchecked = true; @@ -53,13 +53,13 @@ void Wizard::ComponentSelectionPage::initializePage() { componentsList->clear(); - QString path(field("installation.path").toString()); + QString path(field(QLatin1String("installation.path")).toString()); QListWidgetItem *morrowindItem = new QListWidgetItem(QLatin1String("Morrowind")); QListWidgetItem *tribunalItem = new QListWidgetItem(QLatin1String("Tribunal")); QListWidgetItem *bloodmoonItem = new QListWidgetItem(QLatin1String("Bloodmoon")); - if (field("installation.new").toBool() == true) + if (field(QLatin1String("installation.new")).toBool() == true) { morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); @@ -111,12 +111,12 @@ void Wizard::ComponentSelectionPage::initializePage() bool Wizard::ComponentSelectionPage::validatePage() { - QStringList components(field("installation.components").toStringList()); - QString path(field("installation.path").toString()); + QStringList components(field(QLatin1String("installation.components")).toStringList()); + QString path(field(QLatin1String("installation.path")).toString()); qDebug() << components << path << mWizard->mInstallations[path]; - if (field("installation.new").toBool() == false) { + if (field(QLatin1String("installation.new")).toBool() == false) { if (components.contains(QLatin1String("Tribunal")) && !components.contains(QLatin1String("Bloodmoon"))) { if (mWizard->mInstallations[path]->hasBloodmoon) diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 61d58eaba..d734d39ab 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -13,9 +13,15 @@ Wizard::ConclusionPage::ConclusionPage(MainWizard *wizard) : void Wizard::ConclusionPage::initializePage() { + // Write the path to openmw.cfg + if (field(QLatin1String("installation.new")).toBool() == true) { + QString path(field(QLatin1String("installation.path")).toString()); + mWizard->addInstallation(path); + } + if (!mWizard->mError) { - if (field("installation.new").toBool() == true) + if (field(QLatin1String("installation.new")).toBool() == true) { textLabel->setText(tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

\

Click Finish to close the Wizard.

")); diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 34e771e3d..f0f5c8573 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -82,7 +82,7 @@ bool Wizard::ExistingInstallationPage::validatePage() // It can be missing entirely // Or failed to be detected due to the target being a symlink - QString path(field("installation.path").toString()); + QString path(field(QLatin1String("installation.path")).toString()); QFile file(mWizard->mInstallations[path]->iniPath); if (!file.exists()) { @@ -131,7 +131,7 @@ bool Wizard::ExistingInstallationPage::isComplete() const int Wizard::ExistingInstallationPage::nextId() const { - QString path(field("installation.path").toString()); + QString path(field(QLatin1String("installation.path")).toString()); if (path.isEmpty()) return MainWizard::Page_LanguageSelection; diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index f34699742..a2c255684 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -131,30 +131,20 @@ bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream) int index = buffer.lastIndexOf(section); if (index != -1) { - // Append the new keys to the bottom of the section + // Look for the next section index = buffer.indexOf(QLatin1Char('['), index + 1); - if (index == -1 ) - { - // Beginning of next section not found, we are at the last section - if (buffer.lastIndexOf(QLatin1String("\n")) > (buffer.lastIndexOf(section) + section.length())) { - // There is a newline after the section - index = buffer.lastIndexOf(QLatin1String("\n")) - 1; - buffer.insert(index - 2, QString("\n%1=%2").arg(key, i.value().toString())); - mSettings.remove(i.key()); - continue; - } else { - // No newline found, or the last newline is before the last section - // Append the key to the bottom of the file - buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); - mSettings.remove(i.key()); - continue; - } + if (index == -1 ) { + // We are at the last section, append it to the bottom of the file + buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + continue; + } else { + // Not at last section, add the key at the index + buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); } - // Add the key at the index - buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); - mSettings.remove(i.key()); } else { // Add the section to the end of the file, because it's not found buffer.append(QString("\n%1\n").arg(section)); diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 05d1b4af8..774e02f3c 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -20,8 +20,8 @@ Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : void Wizard::InstallationPage::initializePage() { - QString path(field("installation.path").toString()); - QStringList components(field("installation.components").toStringList()); + QString path(field(QLatin1String("installation.path")).toString()); + QStringList components(field(QLatin1String("installation.components")).toStringList()); logTextEdit->appendPlainText(QString("Installing to %1").arg(path)); logTextEdit->appendPlainText(QString("Installing %1.").arg(components.join(", "))); @@ -32,7 +32,7 @@ void Wizard::InstallationPage::initializePage() // That way installing all three components would yield 300% // When one component is done the bar will be filled by 33% - if (field("installation.new").toBool() == true) { + if (field(QLatin1String("installation.new")).toBool() == true) { installProgressBar->setMaximum((components.count() * 100)); } else { if (components.contains(QLatin1String("Tribunal")) @@ -49,8 +49,8 @@ void Wizard::InstallationPage::initializePage() void Wizard::InstallationPage::startInstallation() { - QStringList components(field("installation.components").toStringList()); - QString path(field("installation.path").toString()); + QStringList components(field(QLatin1String("installation.components")).toStringList()); + QString path(field(QLatin1String("installation.path")).toString()); QThread *thread = new QThread(); mUnshield = new UnshieldWorker(); @@ -86,7 +86,7 @@ void Wizard::InstallationPage::startInstallation() connect(mUnshield, SIGNAL(requestFileDialog(Wizard::Component)), this, SLOT(showFileDialog(Wizard::Component)), Qt::QueuedConnection); - if (field("installation.new").toBool() == true) + if (field(QLatin1String("installation.new")).toBool() == true) { // Always install Morrowind mUnshield->setInstallComponent(Wizard::Component_Morrowind, true); @@ -117,7 +117,7 @@ void Wizard::InstallationPage::startInstallation() mUnshield->setPath(path); // Set the right codec to use for Morrowind.ini - QString language(field("installation.language").toString()); + QString language(field(QLatin1String("installation.language")).toString()); if (language == QLatin1String("Polish")) { mUnshield->setIniCodec(QTextCodec::codecForName("windows-1250")); diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index daf2c0846..521dc2cea 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -27,7 +27,7 @@ void Wizard::InstallationTargetPage::initializePage() bool Wizard::InstallationTargetPage::validatePage() { - QString path(field("installation.path").toString()); + QString path(field(QLatin1String("installation.path")).toString()); qDebug() << "Validating path: " << path; diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index 6db0779a6..b1483ac3a 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -8,12 +8,12 @@ Wizard::MethodSelectionPage::MethodSelectionPage(MainWizard *wizard) : { setupUi(this); - registerField("installation.new", newLocationRadioButton); + registerField(QLatin1String("installation.new"), newLocationRadioButton); } int Wizard::MethodSelectionPage::nextId() const { - if (field("installation.new").toBool() == true) { + if (field(QLatin1String("installation.new")).toBool() == true) { return MainWizard::Page_InstallationTarget; } else { return MainWizard::Page_ExistingInstallation; From 36d4287da423cac163f3cc817639deaff6a1a281 Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 24 Feb 2014 15:59:44 +0100 Subject: [PATCH 0032/1983] Fixed openmw.cfg handling: file should not be cleared anymore --- apps/wizard/mainwizard.cpp | 64 ++++++++++++++++++++----- apps/wizard/unshield/unshieldworker.cpp | 21 ++++---- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index d9b2c3ba8..eb1e3b304 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -41,9 +41,36 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : void Wizard::MainWizard::setupInstallations() { - QString userPath(QFile::decodeName(mCfgMgr.getUserConfigPath().string().c_str())); - QString globalPath(QFile::decodeName(mCfgMgr.getGlobalPath().string().c_str())); + QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + QString globalPath(QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str())); + QString message(tr("

Could not open %1 for reading

\ +

Please make sure you have the right permissions \ + and try again.

")); + + // Load the user config file first, separately + // So we can write it properly, uncontaminated + QString path(userPath + QLatin1String("openmw.cfg")); + QFile file(path); + + qDebug() << "Loading config file:" << qPrintable(path); + + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(message.arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.readUserFile(stream); + } + // Now the rest QStringList paths; paths.append(userPath + QLatin1String("openmw.cfg")); paths.append(QLatin1String("openmw.cfg")); @@ -59,10 +86,8 @@ void Wizard::MainWizard::setupInstallations() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + msgBox.setText(message.arg(file.fileName())); + return qApp->quit(); } QTextStream stream(&file); @@ -73,7 +98,7 @@ void Wizard::MainWizard::setupInstallations() file.close(); } - // Check if the paths actually contains a Morrowind installation + // Check if the paths actually contain a Morrowind installation foreach (const QString path, mGameSettings.getDataDirs()) { if (findFiles(QLatin1String("Morrowind"), path)) @@ -136,7 +161,24 @@ void Wizard::MainWizard::accept() void Wizard::MainWizard::writeSettings() { - QString userPath(QFile::decodeName(mCfgMgr.getUserConfigPath().string().c_str())); + QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + QDir dir(userPath); + + if (!dir.exists()) { + if (!dir.mkpath(userPath)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not create %1

\ +

Please make sure you have the right permissions \ + and try again.

").arg(userPath)); + msgBox.exec(); + return qApp->quit(); + } + } + + // Game settings QFile file(userPath + QLatin1String("openmw.cfg")); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { @@ -145,9 +187,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); + msgBox.setText(tr("

Could not open %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

").arg(file.fileName())); msgBox.exec(); return qApp->quit(); } diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 47d3c1be7..6a19673f3 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -656,7 +656,7 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp path.append(prefix + QDir::separator()); if (directory >= 0) - path.append(QString::fromLatin1(unshield_directory_name(unshield, directory)) + QDir::separator()); + path.append(QString::fromUtf8(unshield_directory_name(unshield, directory)) + QDir::separator()); // Ensure the path has the right separators path.replace(QLatin1Char('\\'), QDir::separator()); @@ -667,7 +667,7 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp dir.mkpath(path); QString fileName(path); - fileName.append(QString::fromLatin1(unshield_file_name(unshield, index))); + fileName.append(QString::fromUtf8(unshield_file_name(unshield, index))); // Calculate the percentage done int progress = (((float) counter / (float) unshield_file_count(unshield)) * 100); @@ -678,13 +678,14 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp if (getComponentDone(Wizard::Component_Tribunal)) progress = progress + 100; - emit textChanged(tr("Extracting: %1").arg(QString::fromLatin1(unshield_file_name(unshield, index)))); + emit textChanged(tr("Extracting: %1").arg(QString::fromUtf8(unshield_file_name(unshield, index)))); emit progressChanged(progress); - success = unshield_file_save(unshield, index, fileName.toLatin1().constData()); + QByteArray array(fileName.toUtf8()); + success = unshield_file_save(unshield, index, array.constData()); if (!success) { - emit error(tr("Failed to extract %1.").arg(QString::fromLatin1(unshield_file_name(unshield, index))), tr("Complete path: %1.").arg(fileName)); + emit error(tr("Failed to extract %1.").arg(QString::fromUtf8(unshield_file_name(unshield, index))), tr("Complete path: %1.").arg(fileName)); dir.remove(fileName); } @@ -693,8 +694,10 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fileName) { + QByteArray array(cabFile.toUtf8()); + Unshield *unshield; - unshield = unshield_open(cabFile.toLatin1().constData()); + unshield = unshield_open(array.constData()); if (!unshield) { emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); @@ -707,7 +710,7 @@ bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fil for (size_t j=group->first_file; j<=group->last_file; ++j) { - QString current(QString::fromLatin1(unshield_file_name(unshield, j))); + QString current(QString::fromUtf8(unshield_file_name(unshield, j))); qDebug() << "File is: " << current; if (current == fileName) @@ -723,8 +726,10 @@ bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &o { bool success; + QByteArray array(cabFile.toUtf8()); + Unshield *unshield; - unshield = unshield_open(cabFile.toLatin1().constData()); + unshield = unshield_open(array.constData()); if (!unshield) { emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); From 3792b301e9378127d5fae3ddf9b2f4eefa602e73 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 25 Feb 2014 15:33:30 +0100 Subject: [PATCH 0033/1983] Wizard now runs the ini-importer to import settings from Morrowind.ini --- apps/launcher/maindialog.cpp | 106 +--------------- apps/wizard/conclusionpage.cpp | 18 ++- apps/wizard/importpage.cpp | 3 + apps/wizard/inisettings.cpp | 154 ++++++++++++------------ apps/wizard/installationpage.cpp | 21 +++- apps/wizard/installationtargetpage.cpp | 29 ++++- apps/wizard/mainwizard.cpp | 83 +++++++++++-- apps/wizard/mainwizard.hpp | 2 + apps/wizard/unshield/unshieldworker.cpp | 27 +++-- components/CMakeLists.txt | 4 + components/config/gamesettings.hpp | 2 + components/process/processinvoker.cpp | 109 +++++++++++++++++ components/process/processinvoker.hpp | 23 ++++ files/ui/wizard/importpage.ui | 6 +- 14 files changed, 373 insertions(+), 214 deletions(-) create mode 100644 components/process/processinvoker.cpp create mode 100644 components/process/processinvoker.hpp diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 56b3186ff..3b7a3273a 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,6 +1,7 @@ #include "maindialog.hpp" #include +#include #include #include @@ -29,6 +30,8 @@ #include "graphicspage.hpp" #include "datafilespage.hpp" +using namespace Process; + Launcher::MainDialog::MainDialog(QWidget *parent) : mGameSettings(mCfgMgr), QMainWindow (parent) { @@ -273,7 +276,7 @@ bool Launcher::MainDialog::showFirstRunDialog() arguments.append(QString("--cfg")); arguments.append(path); - if (!startProgram(QString("mwiniimport"), arguments, false)) + if (!ProcessInvoker::startProcess(QLatin1String("mwiniimport"), arguments, false)) return false; // Re-read the game settings @@ -810,103 +813,6 @@ void Launcher::MainDialog::play() } // Launch the game detached - startProgram(QString("openmw"), true); - qApp->quit(); -} - -bool Launcher::MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached) -{ - QString path = name; -#ifdef Q_OS_WIN - path.append(QString(".exe")); -#elif defined(Q_OS_MAC) - QDir dir(QCoreApplication::applicationDirPath()); - path = dir.absoluteFilePath(name); -#else - path.prepend(QString("./")); -#endif - - QFile file(path); - - QProcess process; - QFileInfo info(file); - - if (!file.exists()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not find %1

\ - The application is not found.
\ - Please make sure OpenMW is installed correctly and try again.
").arg(info.fileName())); - msgBox.exec(); - - return false; - } - - if (!info.isExecutable()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not start %1

\ - The application is not executable.
\ - Please make sure you have the right permissions and try again.
").arg(info.fileName())); - msgBox.exec(); - - return false; - } - - // Start the executable - if (detached) { - if (!process.startDetached(path, arguments)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not start %1

\ - An error occurred while starting %1.

\ - Press \"Show Details...\" for more information.
").arg(info.fileName())); - msgBox.setDetailedText(process.errorString()); - msgBox.exec(); - - return false; - } - } else { - process.start(path, arguments); - if (!process.waitForFinished()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not start %1

\ - An error occurred while starting %1.

\ - Press \"Show Details...\" for more information.
").arg(info.fileName())); - msgBox.setDetailedText(process.errorString()); - msgBox.exec(); - - return false; - } - - if (process.exitCode() != 0 || process.exitStatus() == QProcess::CrashExit) { - QString error(process.readAllStandardError()); - error.append(tr("\nArguments:\n")); - error.append(arguments.join(" ")); - - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error running executable")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Executable %1 returned an error

\ - An error occurred while running %1.

\ - Press \"Show Details...\" for more information.
").arg(info.fileName())); - msgBox.setDetailedText(error); - msgBox.exec(); - - return false; - } - } - - return true; - + if (ProcessInvoker::startProcess(QLatin1String("openmw"), true)) + qApp->quit(); } diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index d734d39ab..3681459b7 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -13,10 +13,20 @@ Wizard::ConclusionPage::ConclusionPage(MainWizard *wizard) : void Wizard::ConclusionPage::initializePage() { - // Write the path to openmw.cfg - if (field(QLatin1String("installation.new")).toBool() == true) { - QString path(field(QLatin1String("installation.path")).toString()); - mWizard->addInstallation(path); +// // Write the path to openmw.cfg +// if (field(QLatin1String("installation.new")).toBool() == true) { +// QString path(field(QLatin1String("installation.path")).toString()); +// mWizard->addInstallation(path); +// } + + if (!mWizard->mError) + { + if ((field(QLatin1String("installation.new")).toBool() == true) + | (field(QLatin1String("installation.import-settings")).toBool() == true)) + { + qDebug() << "IMPORT SETTINGS"; + mWizard->runSettingsImporter(); + } } if (!mWizard->mError) diff --git a/apps/wizard/importpage.cpp b/apps/wizard/importpage.cpp index 059683cf0..b49105faa 100644 --- a/apps/wizard/importpage.cpp +++ b/apps/wizard/importpage.cpp @@ -7,6 +7,9 @@ Wizard::ImportPage::ImportPage(MainWizard *wizard) : mWizard(wizard) { setupUi(this); + + registerField(QLatin1String("installation.import-settings"), importCheckBox); + registerField(QLatin1String("installation.import-addons"), addonsCheckBox); } int Wizard::ImportPage::nextId() const diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index a2c255684..8c733ce3b 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -51,7 +51,7 @@ bool Wizard::IniSettings::readFile(QTextStream &stream) const QString line(stream.readLine()); if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) - continue; + continue; if (sectionRe.exactMatch(line)) { @@ -91,83 +91,81 @@ bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream) QString currentSection; QString buffer; - qDebug() << "Keys! " << keys; - - while (!stream.atEnd()) { - - const QString line(stream.readLine()); - - if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) { - buffer.append(line + QLatin1String("\n")); - continue; - } - - if (sectionRe.exactMatch(line)) { - buffer.append(line + QLatin1String("\n")); - currentSection = sectionRe.cap(1); - } else if (keyRe.indexIn(line) != -1) { - QString key(keyRe.cap(1).trimmed()); - QString lookupKey(key); - - // Append the section, but only if there is one - if (!currentSection.isEmpty()) - lookupKey = currentSection + QLatin1Char('/') + key; - - buffer.append(key + QLatin1Char('=') + mSettings[lookupKey].toString() + QLatin1String("\n")); - mSettings.remove(lookupKey); - } - } - - // Add the new settings to the buffer - QHashIterator i(mSettings); - while (i.hasNext()) { - i.next(); - - QStringList fullKey(i.key().split(QLatin1Char('/'))); - QString section(fullKey.at(0)); - section.prepend(QLatin1Char('[')); - section.append(QLatin1Char(']')); - QString key(fullKey.at(1)); - - int index = buffer.lastIndexOf(section); - if (index != -1) { - // Look for the next section - index = buffer.indexOf(QLatin1Char('['), index + 1); - - if (index == -1 ) { - // We are at the last section, append it to the bottom of the file - buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); - mSettings.remove(i.key()); - continue; - } else { - // Not at last section, add the key at the index - buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); - mSettings.remove(i.key()); - } - - } else { - // Add the section to the end of the file, because it's not found - buffer.append(QString("\n%1\n").arg(section)); - i.previous(); - } - } - - // Now we reopen the file, this time we write - QFile file(path); - - if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { - QTextStream in(&file); - in.setCodec(stream.codec()); - - // Write the updated buffer to an empty file - in << buffer; - file.flush(); - file.close(); - } else { - return false; - } - - return true; + while (!stream.atEnd()) { + + const QString line(stream.readLine()); + + if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) { + buffer.append(line + QLatin1String("\n")); + continue; + } + + if (sectionRe.exactMatch(line)) { + buffer.append(line + QLatin1String("\n")); + currentSection = sectionRe.cap(1); + } else if (keyRe.indexIn(line) != -1) { + QString key(keyRe.cap(1).trimmed()); + QString lookupKey(key); + + // Append the section, but only if there is one + if (!currentSection.isEmpty()) + lookupKey = currentSection + QLatin1Char('/') + key; + + buffer.append(key + QLatin1Char('=') + mSettings[lookupKey].toString() + QLatin1String("\n")); + mSettings.remove(lookupKey); + } + } + + // Add the new settings to the buffer + QHashIterator i(mSettings); + while (i.hasNext()) { + i.next(); + + QStringList fullKey(i.key().split(QLatin1Char('/'))); + QString section(fullKey.at(0)); + section.prepend(QLatin1Char('[')); + section.append(QLatin1Char(']')); + QString key(fullKey.at(1)); + + int index = buffer.lastIndexOf(section); + if (index != -1) { + // Look for the next section + index = buffer.indexOf(QLatin1Char('['), index + 1); + + if (index == -1 ) { + // We are at the last section, append it to the bottom of the file + buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + continue; + } else { + // Not at last section, add the key at the index + buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + } + + } else { + // Add the section to the end of the file, because it's not found + buffer.append(QString("\n%1\n").arg(section)); + i.previous(); + } + } + + // Now we reopen the file, this time we write + QFile file(path); + + if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { + QTextStream in(&file); + in.setCodec(stream.codec()); + + // Write the updated buffer to an empty file + in << buffer; + file.flush(); + file.close(); + } else { + return false; + } + + return true; } bool Wizard::IniSettings::parseInx(const QString &path) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 774e02f3c..f104c8195 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -154,6 +154,11 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) if (fileName.isEmpty()) { qDebug() << "Cancel was clicked!"; + + logTextEdit->appendHtml(tr("


\ + Error: The installation was aborted by the user

")); + mWizard->mError = true; + emit completeChanged(); return; } @@ -184,8 +189,10 @@ void Wizard::InstallationPage::installationError(const QString &text, const QStr installProgressLabel->setText(tr("Installation failed!")); - logTextEdit->appendHtml(tr("Error: %1").arg(text)); - logTextEdit->appendHtml(tr("%1").arg(details)); + logTextEdit->appendHtml(tr("


\ + Error: %1

").arg(text)); + logTextEdit->appendHtml(tr("

\ + %1

").arg(details)); QMessageBox msgBox; msgBox.setWindowTitle(tr("An error occurred")); @@ -214,9 +221,13 @@ bool Wizard::InstallationPage::isComplete() const int Wizard::InstallationPage::nextId() const { - if (!mWizard->mError) { - return MainWizard::Page_Import; - } else { + if (field(QLatin1String("installation.new")).toBool() == true) { return MainWizard::Page_Conclusion; + } else { + if (!mWizard->mError) { + return MainWizard::Page_Import; + } else { + return MainWizard::Page_Conclusion; + } } } diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 521dc2cea..d89d54a00 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -31,11 +31,34 @@ bool Wizard::InstallationTargetPage::validatePage() qDebug() << "Validating path: " << path; - // TODO: Check writeability if (!QFile::exists(path)) { QDir dir; - dir.mkpath(path); - return true; + + if (!dir.mkpath(path)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error creating destination")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not create the destination directory

\ +

Please make sure you have the right permissions \ + and try again, or specify a different location.

")); + msgBox.exec(); + return false; + } + } + + QFileInfo info(path); + + if (!info.isWritable()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Insufficient permissions")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not write to the destination directory

\ +

Please make sure you have the right permissions \ + and try again, or specify a different location.

")); + msgBox.exec(); + return false; } if (mWizard->findFiles(QLatin1String("Morrowind"), path)) { diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index eb1e3b304..fced2d39e 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -1,5 +1,7 @@ #include "mainwizard.hpp" +#include + #include #include @@ -17,6 +19,8 @@ #include "importpage.hpp" #include "conclusionpage.hpp" +using namespace Process; + Wizard::MainWizard::MainWizard(QWidget *parent) : mGameSettings(mCfgMgr), QWizard(parent) @@ -35,11 +39,12 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setDefaultProperty("ComponentListWidget", "mCheckedItems", "checkedItemsChanged"); + setupGameSettings(); setupInstallations(); setupPages(); } -void Wizard::MainWizard::setupInstallations() +void Wizard::MainWizard::setupGameSettings() { QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); QString globalPath(QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str())); @@ -97,14 +102,74 @@ void Wizard::MainWizard::setupInstallations() } file.close(); } +} +void Wizard::MainWizard::setupInstallations() +{ // Check if the paths actually contain a Morrowind installation foreach (const QString path, mGameSettings.getDataDirs()) { if (findFiles(QLatin1String("Morrowind"), path)) addInstallation(path); } +} +void Wizard::MainWizard::runSettingsImporter() +{ + QString path(field(QLatin1String("installation.path")).toString()); + + // Create the file if it doesn't already exist, else the importer will fail + QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + QFile file(userPath + QLatin1String("openmw.cfg")); + + if (!file.exists()) { + if (!file.open(QIODevice::ReadWrite)) { + // File cannot be created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not open or create %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

").arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + + file.close(); + } + + // Construct the arguments to run the importer + QStringList arguments; + + // Import plugin selection? + if (field(QLatin1String("installation.import-addons")).toBool() == true) + arguments.append(QLatin1String("--game-files")); + + arguments.append(QLatin1String("--encoding")); + + // Set encoding + QString language(field(QLatin1String("installation.language")).toString()); + + if (language == QLatin1String("Polish")) { + arguments.append(QLatin1String("windows-1250")); + } else if (language == QLatin1String("Russian")) { + arguments.append(QLatin1String("windows-1251")); + } else { + arguments.append(QLatin1String("windows-1252")); + } + + // Now the paths + arguments.append(QLatin1String("--ini")); + arguments.append(path + QDir::separator() + QLatin1String("Morrowind.ini")); + arguments.append(QLatin1String("--cfg")); + arguments.append(userPath + QLatin1String("openmw.cfg")); + + if (!ProcessInvoker::startProcess(QLatin1String("mwiniimport"), arguments, false)) + return qApp->quit();; + + // Re-read the game settings + setupGameSettings(); } void Wizard::MainWizard::addInstallation(const QString &path) @@ -134,7 +199,7 @@ void Wizard::MainWizard::addInstallation(const QString &path) // Add it to the openmw.cfg too if (!mGameSettings.getDataDirs().contains(path)) { - mGameSettings.setMultiValue(QString("data"), path); + mGameSettings.setMultiValue(QLatin1String("data"), path); mGameSettings.addDataDir(path); } } @@ -161,6 +226,12 @@ void Wizard::MainWizard::accept() void Wizard::MainWizard::writeSettings() { + QString path(field(QLatin1String("installation.path")).toString()); + + // Make sure the installation path is the last data= entry + mGameSettings.removeDataDir(path); + mGameSettings.addDataDir(path); + QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); QDir dir(userPath); @@ -209,10 +280,6 @@ bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) return false; // TODO: add MIME handling to make sure the files are real - if (dir.exists(name + QLatin1String(".esm")) && dir.exists(name + QLatin1String(".bsa"))) { - return true; - } else { - return false; - } - + return (dir.entryList().contains(name + QLatin1String(".esm"), Qt::CaseInsensitive) + && dir.entryList().contains(name + QLatin1String(".bsa"), Qt::CaseInsensitive)); } diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 31a93ffbb..7520ba637 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -39,6 +39,7 @@ namespace Wizard bool findFiles(const QString &name, const QString &path); void addInstallation(const QString &path); + void runSettingsImporter(); QMap mInstallations; @@ -48,6 +49,7 @@ namespace Wizard private: + void setupGameSettings(); void setupInstallations(); void setupPages(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 6a19673f3..64d15e92a 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -477,7 +477,6 @@ void Wizard::UnshieldWorker::setupAddon(Component component) mWait.wait(&mLock); } else if (disk.exists(QLatin1String("data1.hdr"))) { - qDebug() << "Exists! " << disk.absolutePath(); setComponentPath(component, disk.absolutePath()); } @@ -532,13 +531,13 @@ bool Wizard::UnshieldWorker::installComponent(Component component) return false; } - emit textChanged(tr("Installing %0").arg(name)); + emit textChanged(tr("Installing %1").arg(name)); QDir disk(getComponentPath(component)); if (!disk.exists()) { qDebug() << "Component path not set: " << getComponentPath(Wizard::Component_Morrowind); - emit error(tr("Component path not set!"), tr("The source path for %0 was not set.").arg(name)); + emit error(tr("Component path not set!"), tr("The source path for %1 was not set.").arg(name)); return false; } @@ -552,20 +551,20 @@ bool Wizard::UnshieldWorker::installComponent(Component component) if (!temp.mkpath(tempPath)) { qDebug() << "Can't make path"; - emit error(tr("Cannot create temporary directory!"), tr("Failed to create %0.").arg(tempPath)); + emit error(tr("Cannot create temporary directory!"), tr("Failed to create %1.").arg(tempPath)); return false; } temp.setPath(tempPath); if (!temp.mkdir(name)) { - emit error(tr("Cannot create temporary directory!"), tr("Failed to create %0.").arg(temp.absoluteFilePath(name))); + emit error(tr("Cannot create temporary directory!"), tr("Failed to create %1.").arg(temp.absoluteFilePath(name))); return false; } if (!temp.cd(name)) { qDebug() << "Can't cd to dir"; - emit error(tr("Cannot move into temporary directory!"), tr("Failed to move into %0.").arg(temp.absoluteFilePath(name))); + emit error(tr("Cannot move into temporary directory!"), tr("Failed to move into %1.").arg(temp.absoluteFilePath(name))); return false; } @@ -578,7 +577,7 @@ bool Wizard::UnshieldWorker::installComponent(Component component) if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { qDebug() << "failed to move files!"; emit error(tr("Moving extracted files failed!"), - tr("Failed to move files from %0 to %1.").arg(temp.absoluteFilePath(QLatin1String("Data Files")), + tr("Failed to move files from %1 to %2.").arg(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())); return false; } @@ -599,7 +598,7 @@ bool Wizard::UnshieldWorker::installComponent(Component component) moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); } else { qDebug() << "Could not find ini file!"; - emit error(tr("Could not find Morrowind configuration file!"), tr("Failed to find %0.").arg(iniPath)); + emit error(tr("Could not find Morrowind configuration file!"), tr("Failed to find %1.").arg(iniPath)); return false; } @@ -639,7 +638,7 @@ bool Wizard::UnshieldWorker::installComponent(Component component) } } - emit textChanged(tr("%0 installation finished!").arg(name)); + emit textChanged(tr("%1 installation finished!").arg(name)); return true; } @@ -710,11 +709,13 @@ bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fil for (size_t j=group->first_file; j<=group->last_file; ++j) { - QString current(QString::fromUtf8(unshield_file_name(unshield, j))); - qDebug() << "File is: " << current; - if (current == fileName) - return true; // File is found! + if (unshield_file_is_valid(unshield, j)) { + QString current(QString::fromUtf8(unshield_file_name(unshield, j))); + + if (current.toLower() == fileName.toLower()) + return true; // File is found! + } } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 711599787..9a1557963 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -105,6 +105,10 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) launchersettings settingsbase ) + + add_component_qt_dir (process + processinvoker + ) include(${QT_USE_FILE}) QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 41335707d..c4a6ead79 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -52,6 +52,8 @@ namespace Config } inline QStringList getDataDirs() { return mDataDirs; } + + inline void removeDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.removeAll(dir); } inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } inline QString getDataLocal() {return mDataLocal; } diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp new file mode 100644 index 000000000..e75daae9b --- /dev/null +++ b/components/process/processinvoker.cpp @@ -0,0 +1,109 @@ +#include "processinvoker.hpp" + +#include +#include +#include +#include +#include +#include +#include + +Process::ProcessInvoker::ProcessInvoker() +{ +} + +Process::ProcessInvoker::~ProcessInvoker() +{ +} + +bool Process::ProcessInvoker::startProcess(const QString &name, const QStringList &arguments, bool detached) +{ + QString path(name); +#ifdef Q_OS_WIN + path.append(QLatin1String(".exe")); +#elif defined(Q_OS_MAC) + QDir dir(QCoreApplication::applicationDirPath()); + path = dir.absoluteFilePath(name); +#else + path.prepend(QLatin1String("./")); +#endif + + QProcess process; + QFileInfo info(path); + + if (!info.exists()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not find %1

\ +

The application is not found.

\ +

Please make sure OpenMW is installed correctly and try again.

").arg(info.fileName())); + msgBox.exec(); + return false; + } + + if (!info.isExecutable()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not start %1

\ +

The application is not executable.

\ +

Please make sure you have the right permissions and try again.

").arg(info.fileName())); + msgBox.exec(); + return false; + } + + // Start the executable + if (detached) { + if (!process.startDetached(path, arguments)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not start %1

\ +

An error occurred while starting %1.

\ +

Press \"Show Details...\" for more information.

").arg(info.fileName())); + msgBox.setDetailedText(process.errorString()); + msgBox.exec(); + return false; + } + } else { + process.start(path, arguments); + if (!process.waitForFinished()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error starting executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not start %1

\ +

An error occurred while starting %1.

\ +

Press \"Show Details...\" for more information.

").arg(info.fileName())); + msgBox.setDetailedText(process.errorString()); + msgBox.exec(); + + return false; + } + + if (process.exitCode() != 0 || process.exitStatus() == QProcess::CrashExit) { + QString error(process.readAllStandardError()); + error.append(tr("\nArguments:\n")); + error.append(arguments.join(" ")); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error running executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Executable %1 returned an error

\ +

An error occurred while running %1.

\ +

Press \"Show Details...\" for more information.

").arg(info.fileName())); + msgBox.setDetailedText(error); + msgBox.exec(); + + return false; + } + } + + return true; + +} diff --git a/components/process/processinvoker.hpp b/components/process/processinvoker.hpp new file mode 100644 index 000000000..d59d2f012 --- /dev/null +++ b/components/process/processinvoker.hpp @@ -0,0 +1,23 @@ +#ifndef PROCESSINVOKER_HPP +#define PROCESSINVOKER_HPP + +#include +#include + +namespace Process +{ + class ProcessInvoker : public QObject + { + Q_OBJECT + + ProcessInvoker(); + ~ProcessInvoker(); + + public: + + inline static bool startProcess(const QString &name, bool detached = false) { return startProcess(name, QStringList(), detached); } + bool static startProcess(const QString &name, const QStringList &arguments, bool detached = false); + }; +} + +#endif // PROCESSINVOKER_HPP diff --git a/files/ui/wizard/importpage.ui b/files/ui/wizard/importpage.ui index 5b078efca..52f9ac4f2 100644 --- a/files/ui/wizard/importpage.ui +++ b/files/ui/wizard/importpage.ui @@ -6,8 +6,8 @@ 0 0 - 511 - 326 + 510 + 324 @@ -43,7 +43,7 @@ - Import add-on and plugin selection (creates a new Content List in the launcher) + Import add-on and plugin selection From d30c4edb14f789b9a5c3c69d95612a3bbe49d8ef Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 25 Feb 2014 17:23:26 +0100 Subject: [PATCH 0034/1983] Support separate Splash directory, found in some installation media cabinets --- apps/wizard/unshield/unshieldworker.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 64d15e92a..ea09650f6 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -587,6 +587,14 @@ bool Wizard::UnshieldWorker::installComponent(Component component) if (component == Wizard::Component_Morrowind) { + // Some installations have a separate Splash directory + QFileInfo splash(temp.absoluteFilePath(QLatin1String("Splash"))); + + if (splash.exists()) { + emit textChanged(tr("Extracting: Splash directory")); + copyDirectory(splash.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Splash")); + } + // Copy Morrowind configuration file QString iniPath(temp.absoluteFilePath(QLatin1String("App Executables"))); iniPath.append(QDir::separator() + QLatin1String("Morrowind.ini")); From 6b9082c6ba1098f95c96c76875cb112982a7d15e Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 25 Feb 2014 17:36:55 +0100 Subject: [PATCH 0035/1983] Support different Tribunal patch .esm location, as seen on German GoTY disk --- apps/wizard/unshield/unshieldworker.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index ea09650f6..617049daf 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -630,6 +630,10 @@ bool Wizard::UnshieldWorker::installComponent(Component component) QFileInfo patch(temp.absoluteFilePath(QLatin1String("Tribunal Patch") + QDir::separator() + QLatin1String("Tribunal.esm"))); QFileInfo original(getPath() + QDir::separator() + QLatin1String("Tribunal.esm")); + // Look for the patch in other places too, it's not always in "Tribunal Patch" + if (!patch.exists()) + patch = QFileInfo(temp.absoluteFilePath(QLatin1String("Tribunal") + QDir::separator() + QLatin1String("Tribunal.esm"))); + if (original.exists() && patch.exists()) { emit textChanged(tr("Extracting: Tribunal patch")); copyFile(patch.absoluteFilePath(), original.absoluteFilePath()); From 05f8af07576ae565969379f2074c5d732cf8b596 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 25 Feb 2014 18:15:29 +0100 Subject: [PATCH 0036/1983] Use the correct encoding for the ini importer --- apps/wizard/mainwizard.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index fced2d39e..8b95ecfa1 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -152,11 +152,11 @@ void Wizard::MainWizard::runSettingsImporter() QString language(field(QLatin1String("installation.language")).toString()); if (language == QLatin1String("Polish")) { - arguments.append(QLatin1String("windows-1250")); + arguments.append(QLatin1String("win1250")); } else if (language == QLatin1String("Russian")) { - arguments.append(QLatin1String("windows-1251")); + arguments.append(QLatin1String("win1251")); } else { - arguments.append(QLatin1String("windows-1252")); + arguments.append(QLatin1String("win1252")); } // Now the paths From b2156351d8852fc32ac6f83404651befa333fb33 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sun, 16 Mar 2014 20:07:54 +0100 Subject: [PATCH 0037/1983] Work in progress commit, working on data1.hdr autodetection --- apps/wizard/existinginstallationpage.cpp | 1 - apps/wizard/inisettings.cpp | 2 +- apps/wizard/installationpage.cpp | 2 + apps/wizard/installationpage.hpp | 1 - apps/wizard/mainwizard.cpp | 13 + apps/wizard/mainwizard.hpp | 1 + apps/wizard/unshield/unshieldworker.cpp | 475 +++++++++++++++-------- apps/wizard/unshield/unshieldworker.hpp | 12 +- 8 files changed, 328 insertions(+), 179 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index f0f5c8573..91d4f2df1 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -61,7 +61,6 @@ void Wizard::ExistingInstallationPage::textChanged(const QString &text) void Wizard::ExistingInstallationPage::initializePage() { - QStringList paths(mWizard->mInstallations.keys()); if (paths.isEmpty()) diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index 8c733ce3b..f67da0455 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -66,7 +66,7 @@ bool Wizard::IniSettings::readFile(QTextStream &stream) if (!currentSection.isEmpty()) key = currentSection + QLatin1Char('/') + key; - qDebug() << "adding: " << key << value; + //qDebug() << "adding: " << key << value; mSettings[key] = QVariant(value); } } diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index f104c8195..33ceadf8c 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -44,6 +44,8 @@ void Wizard::InstallationPage::initializePage() installProgressBar->setMaximum(installProgressBar->maximum() + 100); } + // Handle when cancel is clicked + startInstallation(); } diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index 7001b2630..5e58477be 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -42,7 +42,6 @@ namespace Wizard protected: void initializePage(); - }; } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 8b95ecfa1..e6dfc67d1 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -224,6 +224,19 @@ void Wizard::MainWizard::accept() QWizard::accept(); } +void Wizard::MainWizard::reject() +{ + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Quit Wizard")); + msgBox.setIcon(QMessageBox::Question); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setText(tr("Are you sure you want to exit the Wizard?")); + + if (msgBox.exec() == QMessageBox::Yes) { + QWizard::reject(); + } +} + void Wizard::MainWizard::writeSettings() { QString path(field(QLatin1String("installation.path")).toString()); diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 7520ba637..962571ac8 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -59,6 +59,7 @@ namespace Wizard private slots: void accept(); + void reject(); }; diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 617049daf..c23738943 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -319,144 +319,171 @@ bool Wizard::UnshieldWorker::moveDirectory(const QString &source, const QString return copyDirectory(source, destination, false); } -void Wizard::UnshieldWorker::installDirectories(const QString &source) +bool Wizard::UnshieldWorker::installFile(const QString &fileName, const QString &path) { - QDir dir(source); + qDebug() << "Attempting to find file: " << fileName << " in: " << path; - if (!dir.exists()) - return; + bool result = true; + QDir dir(path); - QStringList directories; - directories << QLatin1String("Fonts") - << QLatin1String("Music") - << QLatin1String("Sound") - << QLatin1String("Splash") - << QLatin1String("Video"); + if (!dir.exists()) + return false; QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | - QDir::System | QDir::Hidden | - QDir::AllDirs)); + QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, QDir::DirsFirst)); foreach(QFileInfo info, list) { - if (info.isSymLink()) - continue; + if (info.isDir()) { + result = installFile(fileName, info.absoluteFilePath()); + } else { + if (info.fileName() == fileName) { + qDebug() << "File found at: " << info.absoluteFilePath(); - if (directories.contains(info.fileName())) { - qDebug() << "found " << info.fileName(); - emit textChanged(tr("Extracting: %1 directory").arg(info.fileName())); - copyDirectory(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName()); + emit textChanged(tr("Installing: %1").arg(info.fileName())); + return moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName()); + } } } - // Copy the Data Files dir too, but only the subdirectories - QFileInfo info(dir.absoluteFilePath("Data Files")); - if (info.exists()) { - emit textChanged(tr("Extracting: Data Files directory")); - copyDirectory(info.absoluteFilePath(), getPath()); - } - + return result; } -void Wizard::UnshieldWorker::extract() +bool Wizard::UnshieldWorker::installDirectory(const QString &dirName, const QString &path, bool recursive) { - qDebug() << "extract!"; - QDir disk; - - if (getInstallComponent(Wizard::Component_Morrowind)) - { - while (!getComponentDone(Wizard::Component_Morrowind)) - { - if (getComponentPath(Wizard::Component_Morrowind).isEmpty()) { - qDebug() << "request file dialog"; - QReadLocker readLock(&mLock); - emit requestFileDialog(Wizard::Component_Morrowind); - mWait.wait(&mLock); - } + qDebug() << "Attempting to find: " << dirName << " in: " << path; + bool result = true; + QDir dir(path); - if (!getComponentPath(Wizard::Component_Morrowind).isEmpty()) { - disk.setPath(getComponentPath(Wizard::Component_Morrowind)); + if (!dir.exists()) + return false; - if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Morrowind.bsa")) - | findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Tribunal.bsa")) - | findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Bloodmoon.bsa"))) - { - QReadLocker readLock(&mLock); - emit requestFileDialog(Wizard::Component_Morrowind); - mWait.wait(&mLock); - } else { - if (installComponent(Wizard::Component_Morrowind)) { - setComponentDone(Wizard::Component_Morrowind, true); - } else { - qDebug() << "Erorr installing Morrowind"; + QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | + QDir::System | QDir::Hidden | + QDir::AllDirs)); + foreach(QFileInfo info, list) { + if (info.isSymLink()) + continue; - return; - } - } + if (info.isDir()) { + if (info.fileName() == dirName) { + qDebug() << "Directory found at: " << info.absoluteFilePath(); + emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); + return copyDirectory(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName()); + } else { + if (recursive) + result = installDirectory(dirName, info.absoluteFilePath()); } } } - if (getInstallComponent(Wizard::Component_Tribunal)) - { - setupAddon(Wizard::Component_Tribunal); - } - - if (getInstallComponent(Wizard::Component_Bloodmoon)) - { - setupAddon(Wizard::Component_Bloodmoon); - } - - // Update Morrowind configuration - if (getInstallComponent(Wizard::Component_Tribunal)) - { - mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); - mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); - } - - if (getInstallComponent(Wizard::Component_Bloodmoon)) - { - mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Bloodmoon.bsa"))); - mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Bloodmoon.esm"))); - } - - if (getInstallComponent(Wizard::Component_Tribunal) && - getInstallComponent(Wizard::Component_Bloodmoon)) - { - mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); - mIniSettings.setValue(QLatin1String("Archives/Archive1"), QVariant(QString("Bloodmoon.bsa"))); - mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); - mIniSettings.setValue(QLatin1String("Game Files/Game File2"), QVariant(QString("Bloodmoon.esm"))); - } - - - // Write the settings to the Morrowind config file - writeSettings(); - - // Remove the temporary directory - removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); - - // Fill the progress bar - int total = 0; - - if (getInstallComponent(Wizard::Component_Morrowind)) - total = 100; - - if (getInstallComponent(Wizard::Component_Tribunal)) - total = total + 100; - - if (getInstallComponent(Wizard::Component_Bloodmoon)) - total = total + 100; + return result; +} - emit textChanged(tr("Installation finished!")); - emit progressChanged(total); - emit finished(); +void Wizard::UnshieldWorker::extract() +{ + qDebug() << "extract!"; - qDebug() << "installation finished!"; + qDebug() << findFiles(QLatin1String("data1.hdr"), QLatin1String("/mnt/cdrom")); +// QDir disk; + +// if (getInstallComponent(Wizard::Component_Morrowind)) +// { +// if (!getComponentDone(Wizard::Component_Morrowind)) +// { +// if (getComponentPath(Wizard::Component_Morrowind).isEmpty()) { +// qDebug() << "request file dialog"; +// QReadLocker readLock(&mLock); +// emit requestFileDialog(Wizard::Component_Morrowind); +// mWait.wait(&mLock); +// } + +// if (!getComponentPath(Wizard::Component_Morrowind).isEmpty()) { +// disk.setPath(getComponentPath(Wizard::Component_Morrowind)); + +// if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Morrowind.bsa"))) +// { +// QReadLocker readLock(&mLock); +// emit requestFileDialog(Wizard::Component_Morrowind); +// mWait.wait(&mLock); +// } else { +// if (installComponent(Wizard::Component_Morrowind)) { +// setComponentDone(Wizard::Component_Morrowind, true); +// } else { +// qDebug() << "Erorr installing Morrowind"; + +// return; +// } +// } +// } +// } +// } + +// if (getInstallComponent(Wizard::Component_Tribunal)) +// { +// setupAddon(Wizard::Component_Tribunal); +// } + +// if (getInstallComponent(Wizard::Component_Bloodmoon)) +// { +// setupAddon(Wizard::Component_Bloodmoon); +// } + +// // Update Morrowind configuration +// if (getInstallComponent(Wizard::Component_Tribunal)) +// { +// mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); +// mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); +// } + +// if (getInstallComponent(Wizard::Component_Bloodmoon)) +// { +// mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Bloodmoon.bsa"))); +// mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Bloodmoon.esm"))); +// } + +// if (getInstallComponent(Wizard::Component_Tribunal) && +// getInstallComponent(Wizard::Component_Bloodmoon)) +// { +// mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); +// mIniSettings.setValue(QLatin1String("Archives/Archive1"), QVariant(QString("Bloodmoon.bsa"))); +// mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); +// mIniSettings.setValue(QLatin1String("Game Files/Game File2"), QVariant(QString("Bloodmoon.esm"))); +// } + + +// // Write the settings to the Morrowind config file +// writeSettings(); + +// // Remove the temporary directory +// //removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); + +// // Fill the progress bar +// int total = 0; + +// if (getInstallComponent(Wizard::Component_Morrowind)) +// total = 100; + +// if (getInstallComponent(Wizard::Component_Tribunal)) +// total = total + 100; + +// if (getInstallComponent(Wizard::Component_Bloodmoon)) +// total = total + 100; + +// emit textChanged(tr("Installation finished!")); +// emit progressChanged(total); +// emit finished(); + +// qDebug() << "installation finished!"; } void Wizard::UnshieldWorker::setupAddon(Component component) { - while (!getComponentDone(component)) + qDebug() << "SetupAddon!" << getComponentPath(component) << getComponentPath(Wizard::Component_Morrowind); + + if (!getComponentDone(component)) { + qDebug() << "Component not done!"; + QDir disk(getComponentPath(Wizard::Component_Morrowind)); QString name; if (component == Wizard::Component_Tribunal) @@ -470,43 +497,72 @@ void Wizard::UnshieldWorker::setupAddon(Component component) return; } - if (!disk.cd(name)) { - qDebug() << "not found on cd!"; - QReadLocker locker(&mLock); - emit requestFileDialog(component); - mWait.wait(&mLock); + qDebug() << "Determine if file is in current data1.hdr: " << name; - } else if (disk.exists(QLatin1String("data1.hdr"))) { - setComponentPath(component, disk.absolutePath()); - } + if (!disk.isEmpty()) { + if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) + { + if (!disk.cd(name)) { + qDebug() << "not found on cd!"; + QReadLocker locker(&mLock); + emit requestFileDialog(component); + mWait.wait(&mLock); + + } else if (disk.exists(QLatin1String("data1.hdr"))) { + if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) + { + QReadLocker locker(&mLock); + emit requestFileDialog(component); + mWait.wait(&mLock); + } else { + setComponentPath(component, disk.absolutePath()); + disk.setPath(getComponentPath(component)); + } + } + } - if (getComponentPath(component).isEmpty()) { - qDebug() << "request file dialog"; + } else { QReadLocker locker(&mLock); - emit requestFileDialog(Wizard::Component_Tribunal); + emit requestFileDialog(component); mWait.wait(&mLock); } - // Make sure the dir is up-to-date disk.setPath(getComponentPath(component)); - if (!getComponentPath(component).isEmpty()) { - - if (!findFile(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) - { + if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) + { + if (!disk.cd(name)) { + qDebug() << "not found on cd!"; QReadLocker locker(&mLock); emit requestFileDialog(component); mWait.wait(&mLock); - } else { - // Now do the actual installing - if (installComponent(component)) { - setComponentDone(component, true); + + } else if (disk.exists(QLatin1String("data1.hdr"))) { + if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) + { + QReadLocker locker(&mLock); + emit requestFileDialog(component); + mWait.wait(&mLock); } else { - qDebug() << "Error installing " << name; - return; + setComponentPath(component, disk.absolutePath()); + disk.setPath(getComponentPath(component)); } } + + // Make sure the dir is up-to-date + //disk.setPath(getComponentPath(component)); + } + + // Now do the actual installing + + if (installComponent(component)) { + setComponentDone(component, true); + } else { + qDebug() << "Error installing " << name; + return; } + + } } @@ -573,40 +629,60 @@ bool Wizard::UnshieldWorker::installComponent(Component component) return false; // Move the files from the temporary path to the destination folder +// emit textChanged(tr("Moving installation files")); +// if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { +// qDebug() << "failed to move files!"; +// emit error(tr("Moving extracted files failed!"), +// tr("Failed to move files from %1 to %2.").arg(temp.absoluteFilePath(QLatin1String("Data Files")), +// getPath())); +// return false; +// } emit textChanged(tr("Moving installation files")); - if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { - qDebug() << "failed to move files!"; - emit error(tr("Moving extracted files failed!"), - tr("Failed to move files from %1 to %2.").arg(temp.absoluteFilePath(QLatin1String("Data Files")), - getPath())); - return false; + + // Install extracted directories + QStringList directories; + directories << QLatin1String("BookArt") + << QLatin1String("Fonts") + << QLatin1String("Icons") + << QLatin1String("Meshes") + << QLatin1String("Music") + << QLatin1String("Sound") + << QLatin1String("Splash") + << QLatin1String("Textures") + << QLatin1String("Video"); + + foreach (const QString &dir, directories) { + installDirectory(dir, temp.absolutePath()); } - // Install files outside of cab archives - installDirectories(disk.absolutePath()); + // Install directories from disk + foreach (const QString &dir, directories) { + qDebug() << "\n\nDISK DIRS!"; + installDirectory(dir, disk.absolutePath(), false); + } + + QFileInfo info(disk.absoluteFilePath("Data Files")); + if (info.exists()) { + emit textChanged(tr("Installing: Data Files directory")); + copyDirectory(info.absoluteFilePath(), getPath()); + } if (component == Wizard::Component_Morrowind) { - // Some installations have a separate Splash directory - QFileInfo splash(temp.absoluteFilePath(QLatin1String("Splash"))); - - if (splash.exists()) { - emit textChanged(tr("Extracting: Splash directory")); - copyDirectory(splash.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Splash")); + QStringList files; + files << QLatin1String("Morrowind.esm") + << QLatin1String("Morrowin.bsa"); + + foreach (const QString &file, files) { + if (!installFile(file, temp.absolutePath())) { + emit error(tr("Could not find Morrowind data file!"), tr("Failed to find %1.").arg(file)); + return false; + } } // Copy Morrowind configuration file - QString iniPath(temp.absoluteFilePath(QLatin1String("App Executables"))); - iniPath.append(QDir::separator() + QLatin1String("Morrowind.ini")); - - QFileInfo info(iniPath); - - if (info.exists()) { - emit textChanged(tr("Extracting: Morrowind.ini")); - moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); - } else { - qDebug() << "Could not find ini file!"; - emit error(tr("Could not find Morrowind configuration file!"), tr("Failed to find %1.").arg(iniPath)); + if (!installFile(QLatin1String("Morrowind.ini"), temp.absolutePath())) { + emit error(tr("Could not find Morrowind configuration file!"), tr("Failed to find %1.").arg(QLatin1String("Morrowind.ini"))); return false; } @@ -620,23 +696,43 @@ bool Wizard::UnshieldWorker::installComponent(Component component) QFileInfo sounds(temp.absoluteFilePath(QLatin1String("Sounds"))); if (sounds.exists()) { - emit textChanged(tr("Extracting: Sound directory")); + emit textChanged(tr("Installing: Sound directory")); copyDirectory(sounds.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Sound")); } + + QStringList files; + files << QLatin1String("Tribunal.esm") + << QLatin1String("Tribunal.bsa"); + + foreach (const QString &file, files) { + if (!installFile(file, temp.absolutePath())) { + emit error(tr("Could not find Tribunal data file!"), tr("Failed to find %1.").arg(file)); + return false; + } + } + } if (component == Wizard::Component_Bloodmoon) { - QFileInfo patch(temp.absoluteFilePath(QLatin1String("Tribunal Patch") + QDir::separator() + QLatin1String("Tribunal.esm"))); QFileInfo original(getPath() + QDir::separator() + QLatin1String("Tribunal.esm")); - // Look for the patch in other places too, it's not always in "Tribunal Patch" - if (!patch.exists()) - patch = QFileInfo(temp.absoluteFilePath(QLatin1String("Tribunal") + QDir::separator() + QLatin1String("Tribunal.esm"))); + if (original.exists()) { + if (!installFile(QLatin1String("Tribunal.esm"), temp.absolutePath())) { + emit error(tr("Could not find Tribunal patch file!"), tr("Failed to find %1.").arg(QLatin1String("Tribunal.esm"))); + return false; + } + } - if (original.exists() && patch.exists()) { - emit textChanged(tr("Extracting: Tribunal patch")); - copyFile(patch.absoluteFilePath(), original.absoluteFilePath()); + QStringList files; + files << QLatin1String("Bloodmoon.esm") + << QLatin1String("Bloodmoon.bsa"); + + foreach (const QString &file, files) { + if (!installFile(file, temp.absolutePath())) { + emit error(tr("Could not find Bloodmoon data file!"), tr("Failed to find %1.").arg(file)); + return false; + } } // Load Morrowind configuration settings from the setup script @@ -655,10 +751,10 @@ bool Wizard::UnshieldWorker::installComponent(Component component) } -bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter) +bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter) { bool success; - QString path(outputDir); + QString path(destination); path.append(QDir::separator()); int directory = unshield_file_directory(unshield, index); @@ -703,7 +799,42 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &outp return success; } -bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fileName) +QString Wizard::UnshieldWorker::findFile(const QString &fileName, const QString &path) +{ + return findFiles(fileName, path).first(); +} + +QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QString &path) +{ + QStringList result; + QDir dir(source); + + if (!dir.exists()) + return QStringList(); + + QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | + QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, QDir::DirsFirst)); + foreach(QFileInfo info, list) { + + if (info.isSymLink()) + continue; + + if (info.isDir()) { + result = findFiles(file, info.absoluteFilePath()); + } else { + if (info.fileName() == fileName) { + qDebug() << "File found at: " << info.absoluteFilePath(); + result.append(info.absoluteFilePath()); + } + } + } + + return result; + +} + +bool Wizard::UnshieldWorker::findInCab(const QString &cabFile, const QString &fileName) { QByteArray array(cabFile.toUtf8()); @@ -724,7 +855,7 @@ bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fil if (unshield_file_is_valid(unshield, j)) { QString current(QString::fromUtf8(unshield_file_name(unshield, j))); - + qDebug() << "Current is: " << current; if (current.toLower() == fileName.toLower()) return true; // File is found! } @@ -735,7 +866,7 @@ bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fil return false; } -bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &outputDir) +bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &destination) { bool success; @@ -758,7 +889,7 @@ bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &o for (size_t j=group->first_file; j<=group->last_file; ++j) { if (unshield_file_is_valid(unshield, j)) { - success = extractFile(unshield, outputDir, group->name, j, counter); + success = extractFile(unshield, destination, group->name, j, counter); ++counter; } } diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 8aa54c9c0..74ae6c6d8 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -62,11 +62,15 @@ namespace Wizard bool moveFile(const QString &source, const QString &destination); bool moveDirectory(const QString &source, const QString &destination); - bool extractCab(const QString &cabFile, const QString &outputDir); - bool extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter); - bool findFile(const QString &cabFile, const QString &fileName); + bool extractCab(const QString &cabFile, const QString &destination); + bool extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter); + bool findInCab(const QString &cabFile, const QString &fileName); - void installDirectories(const QString &source); + QString findFile(const QString &fileName, const QString &path); + QStringList findFiles(const QString &fileName, const QString &path); + + bool installFile(const QString &fileName, const QString &path); + bool installDirectory(const QString &dirName, const QString &path, bool recursive = true); bool installMorrowind(); bool installTribunal(); From ef16b461483284cb5044cfc2348d2cd93f48339e Mon Sep 17 00:00:00 2001 From: pvdk Date: Sun, 16 Mar 2014 22:09:20 +0100 Subject: [PATCH 0038/1983] Wizard now autodetects correct installation archive --- apps/wizard/installationpage.cpp | 20 +- apps/wizard/unshield/unshieldworker.cpp | 405 ++++++++++-------------- apps/wizard/unshield/unshieldworker.hpp | 23 +- 3 files changed, 186 insertions(+), 262 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 33ceadf8c..3fcbd1fe2 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -148,13 +148,18 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) break; } - QString fileName = QFileDialog::getOpenFileName( - this, - tr("Select %1 installation file").arg(name), - QDir::rootPath(), - tr("InstallShield header files (*.hdr)")); +// QString fileName = QFileDialog::getOpenFileName( +// this, +// tr("Select %1 installation file").arg(name), +// QDir::rootPath(), +// tr("InstallShield header files (*.hdr)")); - if (fileName.isEmpty()) { + + QString path = QFileDialog::getExistingDirectory(this, + tr("Select %1 installation media").arg(name), + QDir::rootPath()); + + if (path.isEmpty()) { qDebug() << "Cancel was clicked!"; logTextEdit->appendHtml(tr("


\ @@ -164,8 +169,7 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) return; } - QFileInfo info(fileName); - mUnshield->setComponentPath(component, info.absolutePath()); + mUnshield->setDiskPath(path); } void Wizard::InstallationPage::installationFinished() diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index c23738943..223de41ce 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -20,12 +20,9 @@ Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : { unshield_set_log_level(0); - mMorrowindPath = QString(); - mTribunalPath = QString(); - mBloodmoonPath = QString(); - mPath = QString(); mIniPath = QString(); + mDiskPath = QString(); // Default to Latin encoding mIniCodec = QTextCodec::codecForName("windows-1252"); @@ -78,41 +75,6 @@ bool Wizard::UnshieldWorker::getInstallComponent(Component component) return false; } -void Wizard::UnshieldWorker::setComponentPath(Wizard::Component component, const QString &path) -{ - QWriteLocker writeLock(&mLock); - switch (component) { - - case Wizard::Component_Morrowind: - mMorrowindPath = path; - break; - case Wizard::Component_Tribunal: - mTribunalPath = path; - break; - case Wizard::Component_Bloodmoon: - mBloodmoonPath = path; - break; - } - - mWait.wakeAll(); -} - -QString Wizard::UnshieldWorker::getComponentPath(Component component) -{ - QReadLocker readLock(&mLock); - switch (component) { - - case Wizard::Component_Morrowind: - return mMorrowindPath; - case Wizard::Component_Tribunal: - return mTribunalPath; - case Wizard::Component_Bloodmoon: - return mBloodmoonPath; - } - - return QString(); -} - void Wizard::UnshieldWorker::setComponentDone(Component component, bool done) { QWriteLocker writeLock(&mLock); @@ -159,6 +121,13 @@ void Wizard::UnshieldWorker::setIniPath(const QString &path) mIniPath = path; } +void Wizard::UnshieldWorker::setDiskPath(const QString &path) +{ + QWriteLocker writeLock(&mLock); + mDiskPath = path; + mWait.wakeAll(); +} + QString Wizard::UnshieldWorker::getPath() { QReadLocker readLock(&mLock); @@ -171,6 +140,13 @@ QString Wizard::UnshieldWorker::getIniPath() return mIniPath; } +QString Wizard::UnshieldWorker::getDiskPath() +{ + QReadLocker readLock(&mLock); + return mDiskPath; +} + + void Wizard::UnshieldWorker::setIniCodec(QTextCodec *codec) { QWriteLocker writeLock(&mLock); @@ -332,7 +308,7 @@ bool Wizard::UnshieldWorker::installFile(const QString &fileName, const QString QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); - foreach(QFileInfo info, list) { + foreach(const QFileInfo &info, list) { if (info.isDir()) { result = installFile(fileName, info.absoluteFilePath()); } else { @@ -360,7 +336,7 @@ bool Wizard::UnshieldWorker::installDirectory(const QString &dirName, const QStr QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs)); - foreach(QFileInfo info, list) { + foreach(const QFileInfo &info, list) { if (info.isSymLink()) continue; @@ -383,190 +359,139 @@ void Wizard::UnshieldWorker::extract() { qDebug() << "extract!"; - qDebug() << findFiles(QLatin1String("data1.hdr"), QLatin1String("/mnt/cdrom")); -// QDir disk; - -// if (getInstallComponent(Wizard::Component_Morrowind)) -// { -// if (!getComponentDone(Wizard::Component_Morrowind)) -// { -// if (getComponentPath(Wizard::Component_Morrowind).isEmpty()) { -// qDebug() << "request file dialog"; -// QReadLocker readLock(&mLock); -// emit requestFileDialog(Wizard::Component_Morrowind); -// mWait.wait(&mLock); -// } - -// if (!getComponentPath(Wizard::Component_Morrowind).isEmpty()) { -// disk.setPath(getComponentPath(Wizard::Component_Morrowind)); - -// if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), QLatin1String("Morrowind.bsa"))) -// { -// QReadLocker readLock(&mLock); -// emit requestFileDialog(Wizard::Component_Morrowind); -// mWait.wait(&mLock); -// } else { -// if (installComponent(Wizard::Component_Morrowind)) { -// setComponentDone(Wizard::Component_Morrowind, true); -// } else { -// qDebug() << "Erorr installing Morrowind"; - -// return; -// } -// } -// } -// } -// } - -// if (getInstallComponent(Wizard::Component_Tribunal)) -// { -// setupAddon(Wizard::Component_Tribunal); -// } - -// if (getInstallComponent(Wizard::Component_Bloodmoon)) -// { -// setupAddon(Wizard::Component_Bloodmoon); -// } - -// // Update Morrowind configuration -// if (getInstallComponent(Wizard::Component_Tribunal)) -// { -// mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); -// mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); -// } - -// if (getInstallComponent(Wizard::Component_Bloodmoon)) -// { -// mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Bloodmoon.bsa"))); -// mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Bloodmoon.esm"))); -// } - -// if (getInstallComponent(Wizard::Component_Tribunal) && -// getInstallComponent(Wizard::Component_Bloodmoon)) -// { -// mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); -// mIniSettings.setValue(QLatin1String("Archives/Archive1"), QVariant(QString("Bloodmoon.bsa"))); -// mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); -// mIniSettings.setValue(QLatin1String("Game Files/Game File2"), QVariant(QString("Bloodmoon.esm"))); -// } - - -// // Write the settings to the Morrowind config file -// writeSettings(); - -// // Remove the temporary directory -// //removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); - -// // Fill the progress bar -// int total = 0; - -// if (getInstallComponent(Wizard::Component_Morrowind)) -// total = 100; - -// if (getInstallComponent(Wizard::Component_Tribunal)) -// total = total + 100; - -// if (getInstallComponent(Wizard::Component_Bloodmoon)) -// total = total + 100; - -// emit textChanged(tr("Installation finished!")); -// emit progressChanged(total); -// emit finished(); - -// qDebug() << "installation finished!"; -} + if (getInstallComponent(Wizard::Component_Morrowind)) + { + if (!getComponentDone(Wizard::Component_Morrowind)) + if (!setupComponent(Wizard::Component_Morrowind)) + return; + } -void Wizard::UnshieldWorker::setupAddon(Component component) -{ - qDebug() << "SetupAddon!" << getComponentPath(component) << getComponentPath(Wizard::Component_Morrowind); + if (getInstallComponent(Wizard::Component_Tribunal)) + { + if (!getComponentDone(Wizard::Component_Tribunal)) + if (!setupComponent(Wizard::Component_Tribunal)) + return; + } + + if (getInstallComponent(Wizard::Component_Bloodmoon)) + { + if (!getComponentDone(Wizard::Component_Bloodmoon)) + if (!setupComponent(Wizard::Component_Bloodmoon)) + return; + } + + // Update Morrowind configuration + if (getInstallComponent(Wizard::Component_Tribunal)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); + } - if (!getComponentDone(component)) + if (getInstallComponent(Wizard::Component_Bloodmoon)) { - qDebug() << "Component not done!"; + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Bloodmoon.esm"))); + } - QDir disk(getComponentPath(Wizard::Component_Morrowind)); - QString name; - if (component == Wizard::Component_Tribunal) - name = QLatin1String("Tribunal"); + if (getInstallComponent(Wizard::Component_Tribunal) && + getInstallComponent(Wizard::Component_Bloodmoon)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Archives/Archive1"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File2"), QVariant(QString("Bloodmoon.esm"))); + } - if (component == Wizard::Component_Bloodmoon) - name = QLatin1String("Bloodmoon"); + // Write the settings to the Morrowind config file + writeSettings(); - if (name.isEmpty()) { - emit error(tr("Component parameter is invalid!"), tr("An invalid component parameter was supplied.")); - return; - } + // Remove the temporary directory + removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); - qDebug() << "Determine if file is in current data1.hdr: " << name; - - if (!disk.isEmpty()) { - if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) - { - if (!disk.cd(name)) { - qDebug() << "not found on cd!"; - QReadLocker locker(&mLock); - emit requestFileDialog(component); - mWait.wait(&mLock); - - } else if (disk.exists(QLatin1String("data1.hdr"))) { - if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) - { - QReadLocker locker(&mLock); - emit requestFileDialog(component); - mWait.wait(&mLock); - } else { - setComponentPath(component, disk.absolutePath()); - disk.setPath(getComponentPath(component)); - } - } - } + // Fill the progress bar + int total = 0; - } else { - QReadLocker locker(&mLock); + if (getInstallComponent(Wizard::Component_Morrowind)) + total = 100; + + if (getInstallComponent(Wizard::Component_Tribunal)) + total = total + 100; + + if (getInstallComponent(Wizard::Component_Bloodmoon)) + total = total + 100; + + emit textChanged(tr("Installation finished!")); + emit progressChanged(total); + emit finished(); + + qDebug() << "installation finished!"; +} + +bool Wizard::UnshieldWorker::setupComponent(Component component) +{ + QString name; + switch (component) { + + case Wizard::Component_Morrowind: + name = QLatin1String("Morrowind"); + break; + case Wizard::Component_Tribunal: + name = QLatin1String("Tribunal"); + break; + case Wizard::Component_Bloodmoon: + name = QLatin1String("Bloodmoon"); + break; + } + + if (name.isEmpty()) { + emit error(tr("Component parameter is invalid!"), tr("An invalid component parameter was supplied.")); + return false; + } + + bool found = false; + QString cabFile; + QDir disk; + + // Keep showing the file dialog until we find the necessary install files + while (!found) { + if (getDiskPath().isEmpty()) { + QReadLocker readLock(&mLock); emit requestFileDialog(component); mWait.wait(&mLock); + disk.setPath(getDiskPath()); + } else { + disk.setPath(getDiskPath()); } - disk.setPath(getComponentPath(component)); + QStringList list(findFiles(QLatin1String("data1.hdr"), disk.absolutePath())); - if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) - { - if (!disk.cd(name)) { - qDebug() << "not found on cd!"; - QReadLocker locker(&mLock); - emit requestFileDialog(component); - mWait.wait(&mLock); - - } else if (disk.exists(QLatin1String("data1.hdr"))) { - if (!findInCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), name + QLatin1String(".bsa"))) - { - QReadLocker locker(&mLock); - emit requestFileDialog(component); - mWait.wait(&mLock); - } else { - setComponentPath(component, disk.absolutePath()); - disk.setPath(getComponentPath(component)); - } + foreach (const QString &file, list) { + qDebug() << "current cab file is: " << file; + if (findInCab(file, name + QLatin1String(".bsa"))) { + cabFile = file; + found = true; } - - // Make sure the dir is up-to-date - //disk.setPath(getComponentPath(component)); } - // Now do the actual installing - - if (installComponent(component)) { - setComponentDone(component, true); - } else { - qDebug() << "Error installing " << name; - return; + if (!found) { + QReadLocker readLock(&mLock); + emit requestFileDialog(component); + mWait.wait(&mLock); } + } - + if (installComponent(component, cabFile)) { + setComponentDone(component, true); + return true; + } else { + qDebug() << "Erorr installing " << name; + return false; } + + return true; } -bool Wizard::UnshieldWorker::installComponent(Component component) +bool Wizard::UnshieldWorker::installComponent(Component component, const QString &path) { QString name; switch (component) { @@ -587,13 +512,15 @@ bool Wizard::UnshieldWorker::installComponent(Component component) return false; } + qDebug() << "Install " << name << " from " << path; + + emit textChanged(tr("Installing %1").arg(name)); - QDir disk(getComponentPath(component)); + QFileInfo info(path); - if (!disk.exists()) { - qDebug() << "Component path not set: " << getComponentPath(Wizard::Component_Morrowind); - emit error(tr("Component path not set!"), tr("The source path for %1 was not set.").arg(name)); + if (!info.exists()) { + emit error(tr("Installation media path not set!"), tr("The source path for %1 was not set.").arg(name)); return false; } @@ -606,7 +533,6 @@ bool Wizard::UnshieldWorker::installComponent(Component component) removeDirectory(tempPath); if (!temp.mkpath(tempPath)) { - qDebug() << "Can't make path"; emit error(tr("Cannot create temporary directory!"), tr("Failed to create %1.").arg(tempPath)); return false; } @@ -619,24 +545,15 @@ bool Wizard::UnshieldWorker::installComponent(Component component) } if (!temp.cd(name)) { - qDebug() << "Can't cd to dir"; emit error(tr("Cannot move into temporary directory!"), tr("Failed to move into %1.").arg(temp.absoluteFilePath(name))); return false; } // Extract the installation files - if (!extractCab(disk.absoluteFilePath(QLatin1String("data1.hdr")), temp.absolutePath())) + if (!extractCab(info.absoluteFilePath(), temp.absolutePath())) return false; // Move the files from the temporary path to the destination folder -// emit textChanged(tr("Moving installation files")); -// if (!moveDirectory(temp.absoluteFilePath(QLatin1String("Data Files")), getPath())) { -// qDebug() << "failed to move files!"; -// emit error(tr("Moving extracted files failed!"), -// tr("Failed to move files from %1 to %2.").arg(temp.absoluteFilePath(QLatin1String("Data Files")), -// getPath())); -// return false; -// } emit textChanged(tr("Moving installation files")); // Install extracted directories @@ -658,20 +575,20 @@ bool Wizard::UnshieldWorker::installComponent(Component component) // Install directories from disk foreach (const QString &dir, directories) { qDebug() << "\n\nDISK DIRS!"; - installDirectory(dir, disk.absolutePath(), false); + installDirectory(dir, info.absolutePath(), false); } - QFileInfo info(disk.absoluteFilePath("Data Files")); - if (info.exists()) { + QFileInfo datafiles(info.absolutePath() + QDir::separator() + QLatin1String("Data Files")); + if (datafiles.exists()) { emit textChanged(tr("Installing: Data Files directory")); - copyDirectory(info.absoluteFilePath(), getPath()); + copyDirectory(datafiles.absoluteFilePath(), getPath()); } if (component == Wizard::Component_Morrowind) { QStringList files; files << QLatin1String("Morrowind.esm") - << QLatin1String("Morrowin.bsa"); + << QLatin1String("Morrowind.bsa"); foreach (const QString &file, files) { if (!installFile(file, temp.absolutePath())) { @@ -702,7 +619,9 @@ bool Wizard::UnshieldWorker::installComponent(Component component) QStringList files; files << QLatin1String("Tribunal.esm") - << QLatin1String("Tribunal.bsa"); + << QLatin1String("Tribunal.bsa") + << QLatin1String("Morrowind.esm") + << QLatin1String("Morrowind.bsa"); foreach (const QString &file, files) { if (!installFile(file, temp.absolutePath())) { @@ -710,7 +629,6 @@ bool Wizard::UnshieldWorker::installComponent(Component component) return false; } } - } if (component == Wizard::Component_Bloodmoon) @@ -726,7 +644,9 @@ bool Wizard::UnshieldWorker::installComponent(Component component) QStringList files; files << QLatin1String("Bloodmoon.esm") - << QLatin1String("Bloodmoon.bsa"); + << QLatin1String("Bloodmoon.bsa") + << QLatin1String("Morrowind.esm") + << QLatin1String("Morrowind.bsa"); foreach (const QString &file, files) { if (!installFile(file, temp.absolutePath())) { @@ -736,13 +656,12 @@ bool Wizard::UnshieldWorker::installComponent(Component component) } // Load Morrowind configuration settings from the setup script - QFileInfo inx(disk.absoluteFilePath(QLatin1String("setup.inx"))); + QStringList list(findFiles(QLatin1String("setup.inx"), getDiskPath())); - if (inx.exists()) { - emit textChanged(tr("Updating Morrowind configuration file")); - mIniSettings.parseInx(inx.absoluteFilePath()); - } else { - qDebug() << "setup.inx not found!"; + emit textChanged(tr("Updating Morrowind configuration file")); + + foreach (const QString &inx, list) { + mIniSettings.parseInx(inx); } } @@ -799,15 +718,23 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &dest return success; } -QString Wizard::UnshieldWorker::findFile(const QString &fileName, const QString &path) +QString Wizard::UnshieldWorker::findFile(const QString &fileName, const QString &path, int depth) { - return findFiles(fileName, path).first(); + return findFiles(fileName, path, depth).first(); } -QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QString &path) +QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QString &path, int depth) { + qDebug() << "Searching path: " << path << " for: " << fileName; + static const int MAXIMUM_DEPTH = 5; + + if (depth >= MAXIMUM_DEPTH) { + qWarning("Maximum directory depth limit reached."); + return QStringList(); + } + QStringList result; - QDir dir(source); + QDir dir(path); if (!dir.exists()) return QStringList(); @@ -821,7 +748,7 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt continue; if (info.isDir()) { - result = findFiles(file, info.absoluteFilePath()); + result.append(findFiles(fileName, info.absoluteFilePath(), depth + 1)); } else { if (info.fileName() == fileName) { qDebug() << "File found at: " << info.absoluteFilePath(); @@ -831,7 +758,6 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt } return result; - } bool Wizard::UnshieldWorker::findInCab(const QString &cabFile, const QString &fileName) @@ -855,7 +781,6 @@ bool Wizard::UnshieldWorker::findInCab(const QString &cabFile, const QString &fi if (unshield_file_is_valid(unshield, j)) { QString current(QString::fromUtf8(unshield_file_name(unshield, j))); - qDebug() << "Current is: " << current; if (current.toLower() == fileName.toLower()) return true; // File is found! } diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 74ae6c6d8..bda951456 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -31,7 +31,8 @@ namespace Wizard void setInstallComponent(Wizard::Component component, bool install); - void setComponentPath(Wizard::Component component, const QString &path); +// void setComponentPath(Wizard::Component component, const QString &path); + void setDiskPath(const QString &path); void setPath(const QString &path); void setIniPath(const QString &path); @@ -49,7 +50,8 @@ namespace Wizard bool getInstallComponent(Component component); - QString getComponentPath(Component component); + //QString getComponentPath(Component component); + QString getDiskPath(); void setComponentDone(Component component, bool done = true); bool getComponentDone(Component component); @@ -66,18 +68,14 @@ namespace Wizard bool extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter); bool findInCab(const QString &cabFile, const QString &fileName); - QString findFile(const QString &fileName, const QString &path); - QStringList findFiles(const QString &fileName, const QString &path); + QString findFile(const QString &fileName, const QString &path, int depth = 0); + QStringList findFiles(const QString &fileName, const QString &path, int depth = 0); bool installFile(const QString &fileName, const QString &path); bool installDirectory(const QString &dirName, const QString &path, bool recursive = true); - bool installMorrowind(); - bool installTribunal(); - bool installBloodmoon(); - - bool installComponent(Component component); - void setupAddon(Component component); + bool installComponent(Component component, const QString &path); + bool setupComponent(Component component); bool mInstallMorrowind; bool mInstallTribunal; @@ -87,12 +85,9 @@ namespace Wizard bool mTribunalDone; bool mBloodmoonDone; - QString mMorrowindPath; - QString mTribunalPath; - QString mBloodmoonPath; - QString mPath; QString mIniPath; + QString mDiskPath; IniSettings mIniSettings; From 254fe0a424edee364030970af3c4f4c8a8e19ee5 Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 17 Mar 2014 14:31:05 +0100 Subject: [PATCH 0039/1983] Made install functions use file finding methods and improved error handling --- apps/wizard/conclusionpage.cpp | 2 +- apps/wizard/installationpage.cpp | 30 +-- apps/wizard/mainwizard.cpp | 3 +- apps/wizard/unshield/unshieldworker.cpp | 266 ++++++++++++++---------- apps/wizard/unshield/unshieldworker.hpp | 25 ++- 5 files changed, 184 insertions(+), 142 deletions(-) diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 3681459b7..7b638813b 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -22,7 +22,7 @@ void Wizard::ConclusionPage::initializePage() if (!mWizard->mError) { if ((field(QLatin1String("installation.new")).toBool() == true) - | (field(QLatin1String("installation.import-settings")).toBool() == true)) + || (field(QLatin1String("installation.import-settings")).toBool() == true)) { qDebug() << "IMPORT SETTINGS"; mWizard->runSettingsImporter(); diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 3fcbd1fe2..74666c394 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -44,8 +44,6 @@ void Wizard::InstallationPage::initializePage() installProgressBar->setMaximum(installProgressBar->maximum() + 100); } - // Handle when cancel is clicked - startInstallation(); } @@ -54,21 +52,21 @@ void Wizard::InstallationPage::startInstallation() QStringList components(field(QLatin1String("installation.components")).toStringList()); QString path(field(QLatin1String("installation.path")).toString()); - QThread *thread = new QThread(); + mThread = new QThread(); mUnshield = new UnshieldWorker(); - mUnshield->moveToThread(thread); + mUnshield->moveToThread(mThread); - connect(thread, SIGNAL(started()), + connect(mThread, SIGNAL(started()), mUnshield, SLOT(extract())); connect(mUnshield, SIGNAL(finished()), - thread, SLOT(quit())); + mThread, SLOT(quit())); connect(mUnshield, SIGNAL(finished()), mUnshield, SLOT(deleteLater())); connect(mUnshield, SIGNAL(finished()), - thread, SLOT(deleteLater())); + mThread, SLOT(deleteLater()));; connect(mUnshield, SIGNAL(finished()), this, SLOT(installationFinished()), Qt::QueuedConnection); @@ -129,7 +127,7 @@ void Wizard::InstallationPage::startInstallation() mUnshield->setIniCodec(QTextCodec::codecForName("windows-1252")); } - thread->start(); + mThread->start(); } void Wizard::InstallationPage::showFileDialog(Wizard::Component component) @@ -148,23 +146,15 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) break; } -// QString fileName = QFileDialog::getOpenFileName( -// this, -// tr("Select %1 installation file").arg(name), -// QDir::rootPath(), -// tr("InstallShield header files (*.hdr)")); - - QString path = QFileDialog::getExistingDirectory(this, tr("Select %1 installation media").arg(name), QDir::rootPath()); if (path.isEmpty()) { - qDebug() << "Cancel was clicked!"; - logTextEdit->appendHtml(tr("


\ Error: The installation was aborted by the user

")); mWizard->mError = true; + emit completeChanged(); return; } @@ -174,8 +164,6 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) void Wizard::InstallationPage::installationFinished() { - qDebug() << "finished!"; - QMessageBox msgBox; msgBox.setWindowTitle(tr("Installation finished")); msgBox.setIcon(QMessageBox::Information); @@ -186,13 +174,10 @@ void Wizard::InstallationPage::installationFinished() mFinished = true; emit completeChanged(); - } void Wizard::InstallationPage::installationError(const QString &text, const QString &details) { - qDebug() << "error: " << text; - installProgressLabel->setText(tr("Installation failed!")); logTextEdit->appendHtml(tr("


\ @@ -213,7 +198,6 @@ void Wizard::InstallationPage::installationError(const QString &text, const QStr mWizard->mError = true; emit completeChanged(); - } bool Wizard::InstallationPage::isComplete() const diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index e6dfc67d1..2fec3cec3 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -143,7 +143,8 @@ void Wizard::MainWizard::runSettingsImporter() QStringList arguments; // Import plugin selection? - if (field(QLatin1String("installation.import-addons")).toBool() == true) + if (field(QLatin1String("installation.new")).toBool() == true + || field(QLatin1String("installation.import-addons")).toBool() == true) arguments.append(QLatin1String("--game-files")); arguments.append(QLatin1String("--encoding")); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 223de41ce..2319ca0df 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -162,8 +162,8 @@ void Wizard::UnshieldWorker::setupSettings() QFile file(getIniPath()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qDebug() << "Error opening .ini file!"; - emit error(tr("Failed to open Morrowind configuration file!"), tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); + emit error(tr("Failed to open Morrowind configuration file!"), + tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); return; } @@ -181,7 +181,6 @@ void Wizard::UnshieldWorker::writeSettings() QFile file(getIniPath()); if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { - qDebug() << "Error opening .ini file!"; emit error(tr("Failed to open Morrowind configuration file!"), tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); return; @@ -198,7 +197,7 @@ void Wizard::UnshieldWorker::writeSettings() bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) { - bool result = true; + bool result = false; QDir dir(dirName); if (dir.exists(dirName)) @@ -229,38 +228,42 @@ bool Wizard::UnshieldWorker::copyFile(const QString &source, const QString &dest QFileInfo info(destination); - if (info.exists()) - dir.remove(info.absoluteFilePath()); + if (info.exists()) { + if (!dir.remove(info.absoluteFilePath())) + return false; + } if (file.copy(source, destination)) { if (!keepSource) { - return file.remove(source); + if (!file.remove(source)) + return false; } else { return true; } } else { - qDebug() << "copy failed! " << file.errorString(); - emit error(tr("Failed to copy file!"), tr("Copying %1 to %2 failed: %3.").arg(source, destination, file.errorString())); + return false; } - return false; + return true; } bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString &destination, bool keepSource) { QDir sourceDir(source); QDir destDir(destination); + bool result = false; if (!destDir.exists()) { - sourceDir.mkpath(destination); + if (!sourceDir.mkpath(destination)) { + return false; + } + destDir.refresh(); } if (!destDir.exists()) return false; - bool result = true; - QFileInfoList list(sourceDir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); @@ -269,13 +272,15 @@ bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString QString relativePath(info.absoluteFilePath()); relativePath.remove(source); + QString destinationPath(destDir.absolutePath() + relativePath); + if (info.isSymLink()) continue; if (info.isDir()) { - result = moveDirectory(info.absoluteFilePath(), destDir.absolutePath() + relativePath); + result = copyDirectory(info.absoluteFilePath(), destinationPath); } else { - result = moveFile(info.absoluteFilePath(), destDir.absolutePath() + relativePath); + result = copyFile(info.absoluteFilePath(), destinationPath); } } @@ -285,80 +290,56 @@ bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString return result; } -bool Wizard::UnshieldWorker::moveFile(const QString &source, const QString &destination) +bool Wizard::UnshieldWorker::installFile(const QString &fileName, const QString &path, Qt::MatchFlags flags, bool keepSource) { - return copyFile(source, destination, false); + return installFiles(fileName, path, flags, true, keepSource); } -bool Wizard::UnshieldWorker::moveDirectory(const QString &source, const QString &destination) +bool Wizard::UnshieldWorker::installFiles(const QString &fileName, const QString &path, Qt::MatchFlags flags, bool keepSource, bool single) { - return copyDirectory(source, destination, false); -} - -bool Wizard::UnshieldWorker::installFile(const QString &fileName, const QString &path) -{ - qDebug() << "Attempting to find file: " << fileName << " in: " << path; - - bool result = true; QDir dir(path); if (!dir.exists()) return false; - QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | - QDir::System | QDir::Hidden | - QDir::AllDirs | QDir::Files, QDir::DirsFirst)); - foreach(const QFileInfo &info, list) { - if (info.isDir()) { - result = installFile(fileName, info.absoluteFilePath()); - } else { - if (info.fileName() == fileName) { - qDebug() << "File found at: " << info.absoluteFilePath(); + QStringList files(findFiles(fileName, path, flags)); - emit textChanged(tr("Installing: %1").arg(info.fileName())); - return moveFile(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName()); - } + foreach (const QString &file, files) { + QFileInfo info(file); + emit textChanged(tr("Installing: %1").arg(info.fileName())); + + if (single) { + return copyFile(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource); + } else { + if (!copyFile(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource)) + return false; } } - return result; + return true; } -bool Wizard::UnshieldWorker::installDirectory(const QString &dirName, const QString &path, bool recursive) +bool Wizard::UnshieldWorker::installDirectories(const QString &dirName, const QString &path, bool recursive, bool keepSource) { - qDebug() << "Attempting to find: " << dirName << " in: " << path; - bool result = true; QDir dir(path); if (!dir.exists()) return false; - QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | - QDir::System | QDir::Hidden | - QDir::AllDirs)); - foreach(const QFileInfo &info, list) { - if (info.isSymLink()) - continue; + QStringList directories(findDirectories(dirName, path, recursive)); - if (info.isDir()) { - if (info.fileName() == dirName) { - qDebug() << "Directory found at: " << info.absoluteFilePath(); - emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); - return copyDirectory(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName()); - } else { - if (recursive) - result = installDirectory(dirName, info.absoluteFilePath()); - } - } + foreach (const QString &dir, directories) { + QFileInfo info(dir); + emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); + if (!copyDirectory(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource)) + return false; } - return result; + return true; } void Wizard::UnshieldWorker::extract() { - qDebug() << "extract!"; - if (getInstallComponent(Wizard::Component_Morrowind)) { if (!getComponentDone(Wizard::Component_Morrowind)) @@ -383,23 +364,23 @@ void Wizard::UnshieldWorker::extract() // Update Morrowind configuration if (getInstallComponent(Wizard::Component_Tribunal)) { - mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); - mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); + mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Tribunal.esm"))); } if (getInstallComponent(Wizard::Component_Bloodmoon)) { - mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Bloodmoon.bsa"))); - mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Bloodmoon.esm"))); + mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Bloodmoon.esm"))); } if (getInstallComponent(Wizard::Component_Tribunal) && getInstallComponent(Wizard::Component_Bloodmoon)) { - mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); - mIniSettings.setValue(QLatin1String("Archives/Archive1"), QVariant(QString("Bloodmoon.bsa"))); - mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); - mIniSettings.setValue(QLatin1String("Game Files/Game File2"), QVariant(QString("Bloodmoon.esm"))); + mIniSettings.setValue(QLatin1String("Archives/Archive 0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Archives/Archive 1"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/GameFile1"), QVariant(QString("Tribunal.esm"))); + mIniSettings.setValue(QLatin1String("Game Files/GameFile2"), QVariant(QString("Bloodmoon.esm"))); } // Write the settings to the Morrowind config file @@ -423,8 +404,6 @@ void Wizard::UnshieldWorker::extract() emit textChanged(tr("Installation finished!")); emit progressChanged(total); emit finished(); - - qDebug() << "installation finished!"; } bool Wizard::UnshieldWorker::setupComponent(Component component) @@ -466,10 +445,26 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) QStringList list(findFiles(QLatin1String("data1.hdr"), disk.absolutePath())); foreach (const QString &file, list) { - qDebug() << "current cab file is: " << file; - if (findInCab(file, name + QLatin1String(".bsa"))) { - cabFile = file; - found = true; + + if (component == Wizard::Component_Morrowind) + { + bool morrowindFound = findInCab(file, QLatin1String("Morrowind.bsa")); + bool tribunalFound = findInCab(file, QLatin1String("Tribunal.bsa")); + bool bloodmoonFound = findInCab(file, QLatin1String("Bloodmoon.bsa")); + + if (morrowindFound) { + // Check if we have correct archive, other archives have Morrowind.bsa too + if ((tribunalFound && bloodmoonFound) + || (!tribunalFound && !bloodmoonFound)) { + cabFile = file; + found = true; // We have a GoTY disk + } + } + } else { + if (findInCab(file, name + QLatin1String(".bsa"))) { + cabFile = file; + found = true; + } } } @@ -484,7 +479,6 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) setComponentDone(component, true); return true; } else { - qDebug() << "Erorr installing " << name; return false; } @@ -512,9 +506,6 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString return false; } - qDebug() << "Install " << name << " from " << path; - - emit textChanged(tr("Installing %1").arg(name)); QFileInfo info(path); @@ -569,19 +560,34 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString << QLatin1String("Video"); foreach (const QString &dir, directories) { - installDirectory(dir, temp.absolutePath()); + if (!installDirectories(dir, temp.absolutePath())) { + emit error(tr("Could not install directory!"), + tr("Installing %1 to %2 failed.").arg(dir, temp.absolutePath())); + return false; + } } // Install directories from disk foreach (const QString &dir, directories) { - qDebug() << "\n\nDISK DIRS!"; - installDirectory(dir, info.absolutePath(), false); + if (!installDirectories(dir, info.absolutePath(), false, true)) { + emit error(tr("Could not install directory!"), + tr("Installing %1 to %2 failed.").arg(dir, info.absolutePath())); + } + } - QFileInfo datafiles(info.absolutePath() + QDir::separator() + QLatin1String("Data Files")); - if (datafiles.exists()) { - emit textChanged(tr("Installing: Data Files directory")); - copyDirectory(datafiles.absoluteFilePath(), getPath()); + // Install translation files + QStringList extensions; + extensions << QLatin1String(".cel") + << QLatin1String(".top") + << QLatin1String(".mrk"); + + foreach (const QString &extension, extensions) { + if (!installFiles(extension, info.absolutePath(), Qt::MatchEndsWith)) { + emit error(tr("Could not install translation file!"), + tr("Failed to install *%1 files.").arg(extension)); + return false; + } } if (component == Wizard::Component_Morrowind) @@ -592,14 +598,16 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString foreach (const QString &file, files) { if (!installFile(file, temp.absolutePath())) { - emit error(tr("Could not find Morrowind data file!"), tr("Failed to find %1.").arg(file)); + emit error(tr("Could not install Morrowind data file!"), + tr("Failed to install %1.").arg(file)); return false; } } // Copy Morrowind configuration file if (!installFile(QLatin1String("Morrowind.ini"), temp.absolutePath())) { - emit error(tr("Could not find Morrowind configuration file!"), tr("Failed to find %1.").arg(QLatin1String("Morrowind.ini"))); + emit error(tr("Could not install Morrowind configuration file!"), + tr("Failed to install %1.").arg(QLatin1String("Morrowind.ini"))); return false; } @@ -611,21 +619,25 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString if (component == Wizard::Component_Tribunal) { QFileInfo sounds(temp.absoluteFilePath(QLatin1String("Sounds"))); + QString dest(getPath() + QDir::separator() + QLatin1String("Sound")); if (sounds.exists()) { emit textChanged(tr("Installing: Sound directory")); - copyDirectory(sounds.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Sound")); + if (!copyDirectory(sounds.absoluteFilePath(), dest)) { + emit error(tr("Could not install directory!"), + tr("Installing %1 to %2 failed.").arg(sounds.absoluteFilePath(), dest)); + } + } QStringList files; files << QLatin1String("Tribunal.esm") - << QLatin1String("Tribunal.bsa") - << QLatin1String("Morrowind.esm") - << QLatin1String("Morrowind.bsa"); + << QLatin1String("Tribunal.bsa"); foreach (const QString &file, files) { if (!installFile(file, temp.absolutePath())) { - emit error(tr("Could not find Tribunal data file!"), tr("Failed to find %1.").arg(file)); + emit error(tr("Could not find Tribunal data file!"), + tr("Failed to find %1.").arg(file)); return false; } } @@ -637,20 +649,20 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString if (original.exists()) { if (!installFile(QLatin1String("Tribunal.esm"), temp.absolutePath())) { - emit error(tr("Could not find Tribunal patch file!"), tr("Failed to find %1.").arg(QLatin1String("Tribunal.esm"))); + emit error(tr("Could not find Tribunal patch file!"), + tr("Failed to find %1.").arg(QLatin1String("Tribunal.esm"))); return false; } } QStringList files; files << QLatin1String("Bloodmoon.esm") - << QLatin1String("Bloodmoon.bsa") - << QLatin1String("Morrowind.esm") - << QLatin1String("Morrowind.bsa"); + << QLatin1String("Bloodmoon.bsa"); foreach (const QString &file, files) { if (!installFile(file, temp.absolutePath())) { - emit error(tr("Could not find Bloodmoon data file!"), tr("Failed to find %1.").arg(file)); + emit error(tr("Could not find Bloodmoon data file!"), + tr("Failed to find %1.").arg(file)); return false; } } @@ -665,6 +677,21 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString } } + // Finally, install Data Files directories from temp and disk + QStringList datafiles(findDirectories(QLatin1String("Data Files"), temp.absolutePath())); + datafiles.append(findDirectories(QLatin1String("Data Files"), info.absolutePath())); + + foreach (const QString &dir, datafiles) { + QFileInfo info(dir); + emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); + + if (!copyDirectory(info.absoluteFilePath(), getPath())) { + emit error(tr("Could not install directory!"), + tr("Installing %1 to %2 failed.").arg(info.absoluteFilePath(), getPath())); + return false; + } + } + emit textChanged(tr("%1 installation finished!").arg(name)); return true; @@ -718,15 +745,15 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &dest return success; } -QString Wizard::UnshieldWorker::findFile(const QString &fileName, const QString &path, int depth) +QString Wizard::UnshieldWorker::findFile(const QString &fileName, const QString &path) { - return findFiles(fileName, path, depth).first(); + return findFiles(fileName, path).first(); } -QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QString &path, int depth) +QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QString &path, int depth, bool recursive, + bool directories, Qt::MatchFlags flags) { - qDebug() << "Searching path: " << path << " for: " << fileName; - static const int MAXIMUM_DEPTH = 5; + static const int MAXIMUM_DEPTH = 10; if (depth >= MAXIMUM_DEPTH) { qWarning("Maximum directory depth limit reached."); @@ -748,11 +775,31 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt continue; if (info.isDir()) { - result.append(findFiles(fileName, info.absoluteFilePath(), depth + 1)); + if (directories) + { + if (info.fileName() == fileName) { + result.append(info.absoluteFilePath()); + } else { + if (recursive) + result.append(findFiles(fileName, info.absoluteFilePath(), depth + 1, recursive, true)); + } + } else { + if (recursive) + result.append(findFiles(fileName, info.absoluteFilePath(), depth + 1)); + } } else { - if (info.fileName() == fileName) { - qDebug() << "File found at: " << info.absoluteFilePath(); - result.append(info.absoluteFilePath()); + if (directories) + break; + + switch (flags) { + case Qt::MatchExactly: + if (info.fileName() == fileName) + result.append(info.absoluteFilePath()); + break; + case Qt::MatchEndsWith: + if (info.fileName().endsWith(fileName)) + result.append(info.absoluteFilePath()); + break; } } } @@ -760,6 +807,11 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt return result; } +QStringList Wizard::UnshieldWorker::findDirectories(const QString &dirName, const QString &path, bool recursive) +{ + return findFiles(dirName, path, 0, true, true); +} + bool Wizard::UnshieldWorker::findInCab(const QString &cabFile, const QString &fileName) { QByteArray array(cabFile.toUtf8()); diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index bda951456..f23dc1aa7 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -31,7 +32,6 @@ namespace Wizard void setInstallComponent(Wizard::Component component, bool install); -// void setComponentPath(Wizard::Component component, const QString &path); void setDiskPath(const QString &path); void setPath(const QString &path); @@ -50,7 +50,6 @@ namespace Wizard bool getInstallComponent(Component component); - //QString getComponentPath(Component component); QString getDiskPath(); void setComponentDone(Component component, bool done = true); @@ -61,18 +60,25 @@ namespace Wizard bool copyFile(const QString &source, const QString &destination, bool keepSource = true); bool copyDirectory(const QString &source, const QString &destination, bool keepSource = true); - bool moveFile(const QString &source, const QString &destination); - bool moveDirectory(const QString &source, const QString &destination); - bool extractCab(const QString &cabFile, const QString &destination); bool extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter); bool findInCab(const QString &cabFile, const QString &fileName); - QString findFile(const QString &fileName, const QString &path, int depth = 0); - QStringList findFiles(const QString &fileName, const QString &path, int depth = 0); + QString findFile(const QString &fileName, const QString &path); + + QStringList findFiles(const QString &fileName, const QString &path, int depth = 0, bool recursive = true, + bool directories = false, Qt::MatchFlags flags = Qt::MatchExactly); + + QStringList findDirectories(const QString &dirName, const QString &path, bool recursive = true); - bool installFile(const QString &fileName, const QString &path); - bool installDirectory(const QString &dirName, const QString &path, bool recursive = true); + bool installFile(const QString &fileName, const QString &path, Qt::MatchFlags flags = Qt::MatchExactly, + bool keepSource = false); + + bool installFiles(const QString &fileName, const QString &path, Qt::MatchFlags flags = Qt::MatchExactly, + bool keepSource = false, bool single = false); + + bool installDirectories(const QString &dirName, const QString &path, + bool recursive = true, bool keepSource = false); bool installComponent(Component component, const QString &path); bool setupComponent(Component component); @@ -111,7 +117,6 @@ namespace Wizard void error(const QString &text, const QString &details); void progressChanged(int progress); - }; } From ae5f783a1650b8d65a2b65eeae149ab5729ef473 Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 17 Mar 2014 17:50:51 +0100 Subject: [PATCH 0040/1983] Added some eye-candy to the wizard: a watermark and a placeholder icon --- apps/wizard/intropage.cpp | 2 +- apps/wizard/mainwizard.cpp | 3 ++- files/ui/wizard/intropage.ui | 6 ++++-- files/wizard/images/intropage-background.png | Bin 0 -> 150614 bytes files/wizard/images/openmw-wizard.png | Bin 0 -> 72530 bytes files/wizard/wizard.qrc | 4 ++++ 6 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 files/wizard/images/intropage-background.png create mode 100644 files/wizard/images/openmw-wizard.png diff --git a/apps/wizard/intropage.cpp b/apps/wizard/intropage.cpp index 93510cd21..91e7d5dc0 100644 --- a/apps/wizard/intropage.cpp +++ b/apps/wizard/intropage.cpp @@ -5,9 +5,9 @@ Wizard::IntroPage::IntroPage(MainWizard *wizard) : QWizardPage(wizard), mWizard(wizard) - { setupUi(this); + setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/images/intropage-background.png"))); } int Wizard::IntroPage::nextId() const diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 2fec3cec3..6a72d71fb 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -25,7 +25,6 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : mGameSettings(mCfgMgr), QWizard(parent) { - #ifndef Q_OS_MAC setWizardStyle(QWizard::ModernStyle); #else @@ -33,6 +32,8 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : #endif setWindowTitle(tr("OpenMW Wizard")); + setWindowIcon(QIcon(QLatin1String(":/images/openmw-wizard.png"))); + setMinimumWidth(550); // Set the property for comboboxes to the text instead of index setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); diff --git a/files/ui/wizard/intropage.ui b/files/ui/wizard/intropage.ui index bb597e3a6..b06f7f170 100644 --- a/files/ui/wizard/intropage.ui +++ b/files/ui/wizard/intropage.ui @@ -20,7 +20,7 @@ - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + This Wizard will help you install Morrowindand its add-ons for OpenMW to use. true @@ -29,6 +29,8 @@ - + + + diff --git a/files/wizard/images/intropage-background.png b/files/wizard/images/intropage-background.png new file mode 100644 index 0000000000000000000000000000000000000000..0ce13804b4f5b3a2ad8338c8f6a5a88870acf778 GIT binary patch literal 150614 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�zTe*@)AGZt5mTa*$uAtDLDJ!rOVe5> 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 literal 0 HcmV?d00001 diff --git a/files/wizard/wizard.qrc b/files/wizard/wizard.qrc index b3bb722c9..99623e985 100644 --- a/files/wizard/wizard.qrc +++ b/files/wizard/wizard.qrc @@ -5,4 +5,8 @@ icons/tango/48x48/folder.png icons/tango/48x48/system-installer.png + + images/intropage-background.png + images/openmw-wizard.png + From 9e5e19f9cb6387f0d9f50bfe820ddc0e2ffcebd4 Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 17 Mar 2014 18:28:05 +0100 Subject: [PATCH 0041/1983] Improved existing installation handling --- apps/wizard/existinginstallationpage.cpp | 95 ++++++++++++--------- apps/wizard/existinginstallationpage.hpp | 3 + apps/wizard/mainwizard.hpp | 1 - files/ui/wizard/existinginstallationpage.ui | 11 +-- 4 files changed, 60 insertions(+), 50 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 91d4f2df1..56743eb44 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -14,6 +14,12 @@ Wizard::ExistingInstallationPage::ExistingInstallationPage(MainWizard *wizard) : { setupUi(this); + mEmptyItem = new QListWidgetItem(tr("No existing installations detected")); + mEmptyItem->setFlags(Qt::NoItemFlags); + installationsList->addItem(mEmptyItem); + mEmptyItem->setHidden(true); + + connect(installationsList, SIGNAL(currentTextChanged(QString)), this, SLOT(textChanged(QString))); @@ -21,51 +27,17 @@ Wizard::ExistingInstallationPage::ExistingInstallationPage(MainWizard *wizard) : this, SIGNAL(completeChanged())); } -void Wizard::ExistingInstallationPage::on_browseButton_clicked() -{ - QString selectedFile = QFileDialog::getOpenFileName( - this, - tr("Select master file"), - QDir::currentPath(), - QString(tr("Morrowind master file (*.esm)")), - NULL, - QFileDialog::DontResolveSymlinks); - - QFileInfo info(selectedFile); - if (!info.exists()) - return; - - QString path(QDir::toNativeSeparators(info.absolutePath())); - QList items = installationsList->findItems(path, Qt::MatchExactly); - - if (items.isEmpty()) { - // Path is not yet in the list, add it - mWizard->addInstallation(path); - - QListWidgetItem *item = new QListWidgetItem(path); - installationsList->addItem(item); - installationsList->setCurrentItem(item); // Select it too - } else { - installationsList->setCurrentItem(items.first()); - } - -} - -void Wizard::ExistingInstallationPage::textChanged(const QString &text) -{ - // Set the installation path manually, as registerField doesn't work - // Because it doesn't accept two widgets operating on a single field - if (!text.isEmpty()) - mWizard->setField(QLatin1String("installation.path"), text); -} - void Wizard::ExistingInstallationPage::initializePage() { QStringList paths(mWizard->mInstallations.keys()); - if (paths.isEmpty()) + if (paths.isEmpty()) { + mEmptyItem->setHidden(false); return; + } + // Make to clear list before adding items + // to prevent duplicates when going back and forth between pages installationsList->clear(); foreach (const QString &path, paths) { @@ -119,6 +91,51 @@ bool Wizard::ExistingInstallationPage::validatePage() return true; } +void Wizard::ExistingInstallationPage::on_browseButton_clicked() +{ + QString selectedFile = QFileDialog::getOpenFileName( + this, + tr("Select master file"), + QDir::currentPath(), + QString(tr("Morrowind master file (*.esm)")), + NULL, + QFileDialog::DontResolveSymlinks); + + QFileInfo info(selectedFile); + + if (!info.exists()) + return; + + if (!mWizard->findFiles(QLatin1String("Morrowind"), info.absolutePath())) + return; // No valid Morrowind installation found + + QString path(QDir::toNativeSeparators(info.absolutePath())); + QList items = installationsList->findItems(path, Qt::MatchExactly); + + if (items.isEmpty()) { + // Path is not yet in the list, add it + mWizard->addInstallation(path); + + // Hide the default item + mEmptyItem->setHidden(true); + + QListWidgetItem *item = new QListWidgetItem(path); + installationsList->addItem(item); + installationsList->setCurrentItem(item); // Select it too + } else { + installationsList->setCurrentItem(items.first()); + } + +} + +void Wizard::ExistingInstallationPage::textChanged(const QString &text) +{ + // Set the installation path manually, as registerField doesn't work + // Because it doesn't accept two widgets operating on a single field + if (!text.isEmpty()) + mWizard->setField(QLatin1String("installation.path"), text); +} + bool Wizard::ExistingInstallationPage::isComplete() const { if (installationsList->selectionModel()->hasSelection()) { diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp index d243bb868..04385893a 100644 --- a/apps/wizard/existinginstallationpage.hpp +++ b/apps/wizard/existinginstallationpage.hpp @@ -2,6 +2,7 @@ #define EXISTINGINSTALLATIONPAGE_HPP #include +#include #include "ui_existinginstallationpage.h" @@ -26,6 +27,8 @@ namespace Wizard private: MainWizard *mWizard; + QListWidgetItem *mEmptyItem; + protected: void initializePage(); diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 962571ac8..f55709bad 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -9,7 +9,6 @@ namespace Wizard { - class MainWizard : public QWizard { Q_OBJECT diff --git a/files/ui/wizard/existinginstallationpage.ui b/files/ui/wizard/existinginstallationpage.ui index b887fb06c..921b2eb2a 100644 --- a/files/ui/wizard/existinginstallationpage.ui +++ b/files/ui/wizard/existinginstallationpage.ui @@ -28,16 +28,7 @@ - - - - No existing installations detected - - - NoItemFlags - - - + From 2e1248537e73e28e5ee8c345507cf8c59cca893b Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 17 Mar 2014 21:33:44 +0100 Subject: [PATCH 0042/1983] Minor fixes, installation should stop after one error message now --- apps/wizard/conclusionpage.cpp | 10 +++++----- apps/wizard/unshield/unshieldworker.cpp | 17 +++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 7b638813b..932a9715c 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -13,11 +13,11 @@ Wizard::ConclusionPage::ConclusionPage(MainWizard *wizard) : void Wizard::ConclusionPage::initializePage() { -// // Write the path to openmw.cfg -// if (field(QLatin1String("installation.new")).toBool() == true) { -// QString path(field(QLatin1String("installation.path")).toString()); -// mWizard->addInstallation(path); -// } + // Write the path to openmw.cfg + if (field(QLatin1String("installation.new")).toBool() == true) { + QString path(field(QLatin1String("installation.path")).toString()); + mWizard->addInstallation(path); + } if (!mWizard->mError) { diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 2319ca0df..aceac69b8 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -251,16 +251,15 @@ bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString { QDir sourceDir(source); QDir destDir(destination); - bool result = false; + bool result = true; if (!destDir.exists()) { - if (!sourceDir.mkpath(destination)) { + if (!sourceDir.mkpath(destination)) return false; - } - - destDir.refresh(); } + destDir.refresh(); + if (!destDir.exists()) return false; @@ -387,7 +386,7 @@ void Wizard::UnshieldWorker::extract() writeSettings(); // Remove the temporary directory - removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); + //removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); // Fill the progress bar int total = 0; @@ -572,6 +571,7 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString if (!installDirectories(dir, info.absolutePath(), false, true)) { emit error(tr("Could not install directory!"), tr("Installing %1 to %2 failed.").arg(dir, info.absolutePath())); + return false; } } @@ -626,6 +626,7 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString if (!copyDirectory(sounds.absoluteFilePath(), dest)) { emit error(tr("Could not install directory!"), tr("Installing %1 to %2 failed.").arg(sounds.absoluteFilePath(), dest)); + return false; } } @@ -699,7 +700,7 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter) { - bool success; + bool success = false; QString path(destination); path.append(QDir::separator()); @@ -845,7 +846,7 @@ bool Wizard::UnshieldWorker::findInCab(const QString &cabFile, const QString &fi bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &destination) { - bool success; + bool success = false; QByteArray array(cabFile.toUtf8()); From 667b0de250149fcb19d03ba065a1dc077dde828e Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 17 Mar 2014 23:39:21 +0100 Subject: [PATCH 0043/1983] Fixed problems with cd content autodetection --- apps/wizard/unshield/unshieldworker.cpp | 111 ++++++++++++++---------- apps/wizard/unshield/unshieldworker.hpp | 4 +- 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index aceac69b8..679730715 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -386,7 +386,7 @@ void Wizard::UnshieldWorker::extract() writeSettings(); // Remove the temporary directory - //removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); + removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); // Fill the progress bar int total = 0; @@ -445,26 +445,39 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) foreach (const QString &file, list) { + qDebug() << "current archive: " << file; + + // Try to open the archive + Unshield *unshield = NULL; + unshield = openCab(file); + + if (!unshield) + return false; + if (component == Wizard::Component_Morrowind) { - bool morrowindFound = findInCab(file, QLatin1String("Morrowind.bsa")); - bool tribunalFound = findInCab(file, QLatin1String("Tribunal.bsa")); - bool bloodmoonFound = findInCab(file, QLatin1String("Bloodmoon.bsa")); + bool morrowindFound = findInCab(QLatin1String("Morrowind.bsa"), unshield); + bool tribunalFound = findInCab(QLatin1String("Tribunal.bsa"), unshield); + bool bloodmoonFound = findInCab(QLatin1String("Bloodmoon.bsa"), unshield); if (morrowindFound) { // Check if we have correct archive, other archives have Morrowind.bsa too if ((tribunalFound && bloodmoonFound) || (!tribunalFound && !bloodmoonFound)) { cabFile = file; - found = true; // We have a GoTY disk + found = true; // We have a GoTY disk or a Morrowind-only disk } } } else { - if (findInCab(file, name + QLatin1String(".bsa"))) { + + if (findInCab(name + QLatin1String(".bsa"), unshield)) { cabFile = file; found = true; } } + + // Close the current archive + unshield_close(unshield); } if (!found) { @@ -746,6 +759,39 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &dest return success; } +bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &destination) +{ + bool success = false; + + QByteArray array(cabFile.toUtf8()); + + Unshield *unshield; + unshield = unshield_open(array.constData()); + + if (!unshield) { + emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); + return false; + } + + int counter = 0; + + for (int i=0; ifirst_file; j<=group->last_file; ++j) + { + if (unshield_file_is_valid(unshield, j)) { + success = extractFile(unshield, destination, group->name, j, counter); + ++counter; + } + } + } + + unshield_close(unshield); + return success; +} + QString Wizard::UnshieldWorker::findFile(const QString &fileName, const QString &path) { return findFiles(fileName, path).first(); @@ -764,14 +810,16 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt QStringList result; QDir dir(path); + // Prevent parsing over the complete filesystem + if (dir == QDir::rootPath()) + return QStringList(); + if (!dir.exists()) return QStringList(); QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | - QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); foreach(QFileInfo info, list) { - if (info.isSymLink()) continue; @@ -813,7 +861,7 @@ QStringList Wizard::UnshieldWorker::findDirectories(const QString &dirName, cons return findFiles(dirName, path, 0, true, true); } -bool Wizard::UnshieldWorker::findInCab(const QString &cabFile, const QString &fileName) +Unshield* Wizard::UnshieldWorker::openCab(const QString &cabFile) { QByteArray array(cabFile.toUtf8()); @@ -822,43 +870,17 @@ bool Wizard::UnshieldWorker::findInCab(const QString &cabFile, const QString &fi if (!unshield) { emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); - return false; + unshield_close(unshield); + return NULL; } - for (int i=0; ifirst_file; j<=group->last_file; ++j) - { - - if (unshield_file_is_valid(unshield, j)) { - QString current(QString::fromUtf8(unshield_file_name(unshield, j))); - if (current.toLower() == fileName.toLower()) - return true; // File is found! - } - } - } - - unshield_close(unshield); - return false; + return unshield; } -bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &destination) +bool Wizard::UnshieldWorker::findInCab(const QString &fileName, Unshield *unshield) { - bool success = false; - - QByteArray array(cabFile.toUtf8()); - - Unshield *unshield; - unshield = unshield_open(array.constData()); - - if (!unshield) { - emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); + if (!unshield) return false; - } - - int counter = 0; for (int i=0; ifirst_file; j<=group->last_file; ++j) { + if (unshield_file_is_valid(unshield, j)) { - success = extractFile(unshield, destination, group->name, j, counter); - ++counter; + QString current(QString::fromUtf8(unshield_file_name(unshield, j))); + if (current.toLower() == fileName.toLower()) + return true; // File is found! } } } - unshield_close(unshield); - return success; + return false; } diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index f23dc1aa7..36215bf5d 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -62,7 +62,9 @@ namespace Wizard bool extractCab(const QString &cabFile, const QString &destination); bool extractFile(Unshield *unshield, const QString &destination, const QString &prefix, int index, int counter); - bool findInCab(const QString &cabFile, const QString &fileName); + + Unshield* openCab(const QString &cabFile); + bool findInCab(const QString &fileName, Unshield *unshield); QString findFile(const QString &fileName, const QString &path); From ef617fdf3df484563ef3fd3e1a515042766b57e9 Mon Sep 17 00:00:00 2001 From: pvdk Date: Tue, 18 Mar 2014 00:33:31 +0100 Subject: [PATCH 0044/1983] Fail on the first error whilst extracting an archive --- apps/wizard/unshield/unshieldworker.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 679730715..70c780b3b 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -752,7 +752,7 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &dest success = unshield_file_save(unshield, index, array.constData()); if (!success) { - emit error(tr("Failed to extract %1.").arg(QString::fromUtf8(unshield_file_name(unshield, index))), tr("Complete path: %1.").arg(fileName)); + qDebug() << "error"; dir.remove(fileName); } @@ -783,6 +783,15 @@ bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &d { if (unshield_file_is_valid(unshield, j)) { success = extractFile(unshield, destination, group->name, j, counter); + + if (!success) { + QString name(QString::fromUtf8(unshield_file_name(unshield, j))); + + emit error(tr("Failed to extract %1.").arg(name), + tr("Complete path: %1").arg(destination + QDir::separator() + name)); + return false; + } + ++counter; } } From eb04fa85b7076d83e59fe28f1a9b97bb0109d0b7 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sat, 29 Mar 2014 11:57:25 +0100 Subject: [PATCH 0045/1983] Added background image to the last page too --- apps/wizard/conclusionpage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 932a9715c..4407ac547 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -9,6 +9,7 @@ Wizard::ConclusionPage::ConclusionPage(MainWizard *wizard) : mWizard(wizard) { setupUi(this); + setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/images/intropage-background.png"))); } void Wizard::ConclusionPage::initializePage() From 10d2ca82f7a90a28b944057a32805a28c010b13e Mon Sep 17 00:00:00 2001 From: pvdk Date: Sun, 30 Mar 2014 22:58:50 +0200 Subject: [PATCH 0046/1983] Implemented a simple logger --- apps/wizard/existinginstallationpage.cpp | 1 - apps/wizard/installationpage.cpp | 11 +++++- apps/wizard/mainwizard.cpp | 44 ++++++++++++++++++++++++ apps/wizard/mainwizard.hpp | 6 ++++ apps/wizard/unshield/unshieldworker.cpp | 24 ++++++++----- apps/wizard/unshield/unshieldworker.hpp | 5 ++- 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 56743eb44..d37dd949d 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -19,7 +19,6 @@ Wizard::ExistingInstallationPage::ExistingInstallationPage(MainWizard *wizard) : installationsList->addItem(mEmptyItem); mEmptyItem->setHidden(true); - connect(installationsList, SIGNAL(currentTextChanged(QString)), this, SLOT(textChanged(QString))); diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 74666c394..a0d3dfd21 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -80,6 +80,9 @@ void Wizard::InstallationPage::startInstallation() connect(mUnshield, SIGNAL(textChanged(QString)), logTextEdit, SLOT(appendPlainText(QString)), Qt::QueuedConnection); + connect(mUnshield, SIGNAL(textChanged(QString)), + mWizard, SLOT(addLogText(QString)), Qt::QueuedConnection); + connect(mUnshield, SIGNAL(progressChanged(int)), installProgressBar, SLOT(setValue(int)), Qt::QueuedConnection); @@ -153,6 +156,8 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) if (path.isEmpty()) { logTextEdit->appendHtml(tr("


\ Error: The installation was aborted by the user

")); + + mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); mWizard->mError = true; emit completeChanged(); @@ -185,6 +190,10 @@ void Wizard::InstallationPage::installationError(const QString &text, const QStr logTextEdit->appendHtml(tr("

\ %1

").arg(details)); + mWizard->addLogText(QLatin1String("Error: ") + text); + mWizard->addLogText(details); + + mWizard->mError = true; QMessageBox msgBox; msgBox.setWindowTitle(tr("An error occurred")); msgBox.setIcon(QMessageBox::Critical); @@ -196,7 +205,7 @@ void Wizard::InstallationPage::installationError(const QString &text, const QStr msgBox.setDetailedText(details); msgBox.exec(); - mWizard->mError = true; + emit completeChanged(); } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 6a72d71fb..11e44c6b0 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -4,6 +4,8 @@ #include +#include +#include #include #include #include @@ -40,11 +42,53 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setDefaultProperty("ComponentListWidget", "mCheckedItems", "checkedItemsChanged"); + setupLog(); setupGameSettings(); setupInstallations(); setupPages(); } +void Wizard::MainWizard::setupLog() +{ + QString logPath(QString::fromUtf8(mCfgMgr.getLogPath().string().c_str())); + logPath.append(QLatin1String("wizard.log")); + + QString message(tr("

Could not open %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

")); + + QFile *file = new QFile(logPath); + + if (!file->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening Wizard log file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(message.arg(file->fileName())); + msgBox.exec(); + return qApp->quit(); + } + + qDebug() << logPath; + + mLog = new QTextStream(file); + mLog->setCodec(QTextCodec::codecForName("UTF-8")); + + //addLogText(QLatin1String("test test 123 test")); +} + +void Wizard::MainWizard::addLogText(const QString &text) +{ + + qDebug() << "AddLogText called! " << text; + if (!text.isEmpty()) { + qDebug() << "logging " << text; + *mLog << text << endl; + } + +// file.close(); +} + void Wizard::MainWizard::setupGameSettings() { QString userPath(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index f55709bad..dcb07a8c2 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -46,8 +46,12 @@ namespace Wizard bool mError; + public slots: + void addLogText(const QString &text); + private: + void setupLog(); void setupGameSettings(); void setupInstallations(); void setupPages(); @@ -56,6 +60,8 @@ namespace Wizard Config::GameSettings mGameSettings; + QTextStream *mLog; + private slots: void accept(); void reject(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 70c780b3b..f0b9a8674 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -153,37 +153,39 @@ void Wizard::UnshieldWorker::setIniCodec(QTextCodec *codec) mIniCodec = codec; } -void Wizard::UnshieldWorker::setupSettings() +bool Wizard::UnshieldWorker::setupSettings() { // Create Morrowind.ini settings map if (getIniPath().isEmpty()) - return; + return false; QFile file(getIniPath()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { emit error(tr("Failed to open Morrowind configuration file!"), tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); - return; + return false; } QTextStream stream(&file); stream.setCodec(mIniCodec); mIniSettings.readFile(stream); + + return true; } -void Wizard::UnshieldWorker::writeSettings() +bool Wizard::UnshieldWorker::writeSettings() { if (getIniPath().isEmpty()) - return; + return false; QFile file(getIniPath()); if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { emit error(tr("Failed to open Morrowind configuration file!"), tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); - return; + return false; } QTextStream stream(&file); @@ -192,7 +194,10 @@ void Wizard::UnshieldWorker::writeSettings() if (!mIniSettings.writeFile(getIniPath(), stream)) { emit error(tr("Failed to write Morrowind configuration file!"), tr("Writing to %1 failed: %2.").arg(getIniPath(), file.errorString())); + return false; } + + return true; } bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) @@ -383,7 +388,8 @@ void Wizard::UnshieldWorker::extract() } // Write the settings to the Morrowind config file - writeSettings(); + if (!writeSettings()) + return false; // Remove the temporary directory removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); @@ -626,7 +632,9 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString // Setup Morrowind configuration setIniPath(getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); - setupSettings(); + + if (!setupSettings()) + return false; } if (component == Wizard::Component_Tribunal) diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 36215bf5d..e5a712977 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -42,11 +42,11 @@ namespace Wizard void setIniCodec(QTextCodec *codec); - void setupSettings(); + bool setupSettings(); private: - void writeSettings(); + bool writeSettings(); bool getInstallComponent(Component component); @@ -114,7 +114,6 @@ namespace Wizard void requestFileDialog(Wizard::Component component); void textChanged(const QString &text); - void logTextChanged(const QString &text); void error(const QString &text, const QString &details); void progressChanged(int progress); From 14b164a03d60615f67a0e0cb97a7fe93c36f2fcd Mon Sep 17 00:00:00 2001 From: pvdk Date: Sun, 30 Mar 2014 23:11:38 +0200 Subject: [PATCH 0047/1983] Reverted an unneeded fix --- apps/wizard/unshield/unshieldworker.cpp | 32 +++++++------------------ apps/wizard/unshield/unshieldworker.hpp | 3 +-- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index f0b9a8674..81282f547 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -389,7 +389,7 @@ void Wizard::UnshieldWorker::extract() // Write the settings to the Morrowind config file if (!writeSettings()) - return false; + return; // Remove the temporary directory removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); @@ -453,18 +453,11 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) qDebug() << "current archive: " << file; - // Try to open the archive - Unshield *unshield = NULL; - unshield = openCab(file); - - if (!unshield) - return false; - if (component == Wizard::Component_Morrowind) { - bool morrowindFound = findInCab(QLatin1String("Morrowind.bsa"), unshield); - bool tribunalFound = findInCab(QLatin1String("Tribunal.bsa"), unshield); - bool bloodmoonFound = findInCab(QLatin1String("Bloodmoon.bsa"), unshield); + bool morrowindFound = findInCab(QLatin1String("Morrowind.bsa"), file); + bool tribunalFound = findInCab(QLatin1String("Tribunal.bsa"), file); + bool bloodmoonFound = findInCab(QLatin1String("Bloodmoon.bsa"), file); if (morrowindFound) { // Check if we have correct archive, other archives have Morrowind.bsa too @@ -476,14 +469,12 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) } } else { - if (findInCab(name + QLatin1String(".bsa"), unshield)) { + if (findInCab(name + QLatin1String(".bsa"), file)) { cabFile = file; found = true; } } - // Close the current archive - unshield_close(unshield); } if (!found) { @@ -778,6 +769,7 @@ bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &d if (!unshield) { emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); + unshield_close(unshield); return false; } @@ -878,7 +870,7 @@ QStringList Wizard::UnshieldWorker::findDirectories(const QString &dirName, cons return findFiles(dirName, path, 0, true, true); } -Unshield* Wizard::UnshieldWorker::openCab(const QString &cabFile) +bool Wizard::UnshieldWorker::findInCab(const QString &fileName, const QString &cabFile) { QByteArray array(cabFile.toUtf8()); @@ -888,16 +880,8 @@ Unshield* Wizard::UnshieldWorker::openCab(const QString &cabFile) if (!unshield) { emit error(tr("Failed to open InstallShield Cabinet File."), tr("Opening %1 failed.").arg(cabFile)); unshield_close(unshield); - return NULL; - } - - return unshield; -} - -bool Wizard::UnshieldWorker::findInCab(const QString &fileName, Unshield *unshield) -{ - if (!unshield) return false; + } for (int i=0; i Date: Sun, 30 Mar 2014 23:57:30 +0200 Subject: [PATCH 0048/1983] Started working on the settings tab --- apps/launcher/settingspage.cpp | 17 +++++++++++++++++ apps/launcher/settingspage.hpp | 7 +++++++ files/ui/settingspage.ui | 11 +++++++---- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 96dd6e529..142af5f79 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -1,5 +1,11 @@ #include "settingspage.hpp" +#include + +#include + +using namespace Process; + Launcher::SettingsPage::SettingsPage(QWidget *parent) : QWidget(parent) { setupUi(this); @@ -16,3 +22,14 @@ Launcher::SettingsPage::SettingsPage(QWidget *parent) : QWidget(parent) languageComboBox->addItems(languages); } +void Launcher::SettingsPage::on_wizardButton_clicked() +{ + if (!ProcessInvoker::startProcess(QLatin1String("openmw-wizard"), true)) + qDebug() << "an error occurred"; + +} + +void Launcher::SettingsPage::on_importerButton_clicked() +{ + +} diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index c87f6e46f..4b0773244 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -11,9 +11,16 @@ namespace Launcher class SettingsPage : public QWidget, private Ui::SettingsPage { Q_OBJECT + public: SettingsPage(QWidget *parent = 0); + void saveSettings(); + bool loadSettings(); + + private slots: + void on_wizardButton_clicked(); + void on_importerButton_clicked(); }; } diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index 95b62761e..ce5aa57ec 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -6,8 +6,8 @@ 0 0 - 518 - 401 + 516 + 399
@@ -22,8 +22,11 @@ + + <html><head/><body><p>The language of the original Morrowind installation files (used for the character encoding)</p></body></html> + - Morrowind installation language: + Morrowind content language: @@ -105,7 +108,7 @@ - Import previously selected add-ons (creates a new Content List) + Import add-on and plugin selection (creates a new Content List) true From 21c406316f21cde0922efdde7f9e7a54184d2fa5 Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 16 Apr 2014 16:54:55 +0200 Subject: [PATCH 0049/1983] Working on the Settings tab: start the importer/wizard --- apps/launcher/datafilespage.cpp | 4 +- apps/launcher/maindialog.cpp | 32 +++++- apps/launcher/maindialog.hpp | 4 +- apps/launcher/settingspage.cpp | 116 +++++++++++++++++++- apps/launcher/settingspage.hpp | 32 +++++- apps/launcher/utils/textinputdialog.cpp | 46 +++----- apps/launcher/utils/textinputdialog.hpp | 20 ++-- apps/wizard/mainwizard.cpp | 4 +- components/config/launchersettings.cpp | 10 +- components/process/processinvoker.cpp | 139 ++++++++++++++++++------ components/process/processinvoker.hpp | 28 ++++- files/ui/settingspage.ui | 12 +- 12 files changed, 340 insertions(+), 107 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index db5975102..efd9765ac 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1,5 +1,7 @@ #include "datafilespage.hpp" +#include + #include #include #include @@ -221,7 +223,7 @@ void Launcher::DataFilesPage::on_newProfileAction_triggered() if (newDialog.exec() != QDialog::Accepted) return; - QString profile = newDialog.getText(); + QString profile = newDialog.lineEdit()->text(); if (profile.isEmpty()) return; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 7f129f4e9..b8f9efd1a 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -138,7 +137,7 @@ void Launcher::MainDialog::createPages() mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); - mSettingsPage = new SettingsPage(this); + mSettingsPage = new SettingsPage(mGameSettings, mLauncherSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); @@ -285,7 +284,9 @@ bool Launcher::MainDialog::showFirstRunDialog() arguments.append(QString("--cfg")); arguments.append(path); - if (!ProcessInvoker::startProcess(QLatin1String("mwiniimport"), arguments, false)) + ProcessInvoker invoker(this); + + if (!invoker.startProcess(QLatin1String("mwiniimport"), arguments, false)) return false; // Re-read the game settings @@ -337,6 +338,26 @@ bool Launcher::MainDialog::setup() return true; } +bool Launcher::MainDialog::reloadSettings() +{ + if (!setupLauncherSettings()) + return false; + + if (!setupGameSettings()) + return false; + + if (!setupGraphicsSettings()) + return false; + +// if (!mSettingsPage->loadSettings()) +// return false; + + if (!mGraphicsPage->loadSettings()) + return false; + + return true; +} + void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) @@ -799,6 +820,7 @@ bool Launcher::MainDialog::writeSettings() void Launcher::MainDialog::closeEvent(QCloseEvent *event) { + qDebug() << "close event!"; writeSettings(); event->accept(); } @@ -822,6 +844,8 @@ void Launcher::MainDialog::play() } // Launch the game detached - if (ProcessInvoker::startProcess(QLatin1String("openmw"), true)) + ProcessInvoker invoker(this); + + if (invoker.startProcess(QLatin1String("openmw"), true)) qApp->quit(); } diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 718acb5a9..7f468de9c 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -39,6 +39,9 @@ namespace Launcher bool setup(); bool showFirstRunDialog(); + bool reloadSettings(); + bool writeSettings(); + public slots: void changePage(QListWidgetItem *current, QListWidgetItem *previous); void play(); @@ -53,7 +56,6 @@ namespace Launcher void loadSettings(); void saveSettings(); - bool writeSettings(); inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); } bool startProgram(const QString &name, const QStringList &arguments, bool detached = false); diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 142af5f79..b2d03ba95 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -1,12 +1,21 @@ #include "settingspage.hpp" -#include - +#include #include +#include +#include + +#include "utils/textinputdialog.hpp" + using namespace Process; -Launcher::SettingsPage::SettingsPage(QWidget *parent) : QWidget(parent) +Launcher::SettingsPage::SettingsPage(Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, MainDialog *parent) : + mGameSettings(gameSettings), + mLauncherSettings(launcherSettings), + QWidget(parent), + mMain(parent) { setupUi(this); @@ -20,16 +29,111 @@ Launcher::SettingsPage::SettingsPage(QWidget *parent) : QWidget(parent) << "Spanish"; languageComboBox->addItems(languages); + + mWizardInvoker = new ProcessInvoker(this); + mImporterInvoker = new ProcessInvoker(this); + + connect(mWizardInvoker->getProcess(), SIGNAL(started()), + this, SLOT(wizardStarted())); + + connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(wizardFinished(int,QProcess::ExitStatus))); + + connect(mImporterInvoker->getProcess(), SIGNAL(started()), + this, SLOT(importerStarted())); + + connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(importerFinished(int,QProcess::ExitStatus))); + + mProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + + connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), + this, SLOT(updateOkButton(QString))); + +// // Detect Morrowind configuration files +// foreach (const QString &path, mGameSettings.getDataDirs()) { +// QDir dir(path); +// dir.setPath(dir.canonicalPath()); // Resolve symlinks + +// if (dir.exists(QString("Morrowind.ini"))) +// iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); +// else +// { +// if (!dir.cdUp()) +// continue; // Cannot move from Data Files + +// if (dir.exists(QString("Morrowind.ini"))) +// iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); +// } +// } } void Launcher::SettingsPage::on_wizardButton_clicked() { - if (!ProcessInvoker::startProcess(QLatin1String("openmw-wizard"), true)) - qDebug() << "an error occurred"; - + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) + return; } void Launcher::SettingsPage::on_importerButton_clicked() { + if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), false)) + return; +} + +void Launcher::SettingsPage::wizardStarted() +{ + qDebug() << "wizard started!"; + wizardButton->setEnabled(false); +} + +void Launcher::SettingsPage::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + qDebug() << "wizard finished!"; + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return; + + mMain->writeSettings(); + mMain->reloadSettings(); + wizardButton->setEnabled(true); +} + +void Launcher::SettingsPage::importerStarted() +{ + qDebug() << "importer started!"; + importerButton->setEnabled(false); +} + +void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + qDebug() << "importer finished!"; + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return; + + mMain->writeSettings(); + mMain->reloadSettings(); + + + if (addonsCheckBox->isChecked()) { + + if (mProfileDialog->exec() == QDialog::Accepted) { + QString profile = mProfileDialog->lineEdit()->text(); + qDebug() << profile; + } + } + + importerButton->setEnabled(true); +} + +void Launcher::SettingsPage::updateOkButton(const QString &text) +{ + // We do this here because we need the profiles combobox text + if (text.isEmpty()) { + mProfileDialog->setOkButtonEnabled(false); + return; + } + +// (profilesComboBox->findText(text) == -1) +// ? mNewProfileDialog->setOkButtonEnabled(true) +// : mNewProfileDialog->setOkButtonEnabled(false); } diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index 4b0773244..ed5c068fb 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -2,18 +2,28 @@ #define SETTINGSPAGE_HPP #include +#include + +#include #include "ui_settingspage.h" +#include "maindialog.hpp" + +namespace Config { class GameSettings; + class LauncherSettings; } + namespace Launcher { + class TextInputDialog; class SettingsPage : public QWidget, private Ui::SettingsPage { Q_OBJECT public: - SettingsPage(QWidget *parent = 0); + SettingsPage( Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, MainDialog *parent = 0); void saveSettings(); bool loadSettings(); @@ -21,6 +31,26 @@ namespace Launcher private slots: void on_wizardButton_clicked(); void on_importerButton_clicked(); + + void wizardStarted(); + void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); + + void importerStarted(); + void importerFinished(int exitCode, QProcess::ExitStatus exitStatus); + + void updateOkButton(const QString &text); + + private: + Process::ProcessInvoker *mWizardInvoker; + Process::ProcessInvoker *mImporterInvoker; + + Config::GameSettings &mGameSettings; + Config::LauncherSettings &mLauncherSettings; + + MainDialog *mMain; + TextInputDialog *mProfileDialog; + + }; } diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 76cbe32d0..74e40a81c 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -14,17 +14,17 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString & mButtonBox = new QDialogButtonBox(this); mButtonBox->addButton(QDialogButtonBox::Ok); mButtonBox->addButton(QDialogButtonBox::Cancel); - mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); +// mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); + + QLabel *label = new QLabel(this); + label->setText(text); // Line edit QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - mLineEdit = new DialogLineEdit(this); + mLineEdit = new LineEdit(this); mLineEdit->setValidator(validator); mLineEdit->setCompleter(0); - QLabel *label = new QLabel(this); - label->setText(text); - QVBoxLayout *dialogLayout = new QVBoxLayout(this); dialogLayout->addWidget(label); dialogLayout->addWidget(mLineEdit); @@ -41,8 +41,10 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString & connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); - connect(mLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateOkButton(QString))); +} +Launcher::TextInputDialog::~TextInputDialog() +{ } int Launcher::TextInputDialog::exec() @@ -52,36 +54,18 @@ int Launcher::TextInputDialog::exec() return QDialog::exec(); } -QString Launcher::TextInputDialog::getText() const +void Launcher::TextInputDialog::setOkButtonEnabled(bool enabled) { - return mLineEdit->text(); -} + QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); + okButton->setEnabled(enabled); -void Launcher::TextInputDialog::slotUpdateOkButton(QString text) -{ - bool enabled = !(text.isEmpty()); - mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled); + QPalette *palette = new QPalette(); + palette->setColor(QPalette::Text, Qt::red); - if (enabled) + if (enabled) { mLineEdit->setPalette(QApplication::palette()); - else - { + } else { // Existing profile name, make the text red - QPalette *palette = new QPalette(); - palette->setColor(QPalette::Text,Qt::red); mLineEdit->setPalette(*palette); } } - -Launcher::TextInputDialog::DialogLineEdit::DialogLineEdit (QWidget *parent) : - LineEdit (parent) -{ - int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - - setObjectName(QString("LineEdit")); - setStyleSheet(QString("LineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); - QSize msz = minimumSizeHint(); - setMinimumSize(qMax(msz.width(), mClearButton->sizeHint().height() + frameWidth * 2 + 2), - qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2)); - -} diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp index bb01778be..9eb9c717d 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -13,26 +13,20 @@ namespace Launcher { Q_OBJECT - class DialogLineEdit : public LineEdit - { - public: - explicit DialogLineEdit (QWidget *parent = 0); - }; - - DialogLineEdit *mLineEdit; - QDialogButtonBox *mButtonBox; - public: explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); - ~TextInputDialog () {} + ~TextInputDialog (); - QString getText() const; + inline LineEdit *lineEdit() { return mLineEdit; } + void setOkButtonEnabled(bool enabled); int exec(); - private slots: - void slotUpdateOkButton(QString text); + private: + + QDialogButtonBox *mButtonBox; + LineEdit *mLineEdit; }; } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 11e44c6b0..0a53e10c5 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -211,8 +211,8 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--cfg")); arguments.append(userPath + QLatin1String("openmw.cfg")); - if (!ProcessInvoker::startProcess(QLatin1String("mwiniimport"), arguments, false)) - return qApp->quit();; +// if (!ProcessInvoker::startProcess(QLatin1String("mwiniimport"), arguments, false)) +// return qApp->quit();; // Re-read the game settings setupGameSettings(); diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 14d66bba7..c014579dc 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -41,14 +41,20 @@ QStringList Config::LauncherSettings::subKeys(const QString &key) QMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); + qDebug() << keys; + QRegExp keyRe("(.+)/"); QStringList result; foreach (const QString ¤tKey, keys) { - if (keyRe.indexIn(currentKey) != -1) { + + if (keyRe.indexIn(currentKey) != -1) + { QString prefixedKey = keyRe.cap(1); - if(prefixedKey.startsWith(key)) { + + if(prefixedKey.startsWith(key)) + { QString subKey = prefixedKey.remove(key); if (!subKey.isEmpty()) result.append(subKey); diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp index e75daae9b..7ac8d3c93 100644 --- a/components/process/processinvoker.cpp +++ b/components/process/processinvoker.cpp @@ -1,23 +1,63 @@ #include "processinvoker.hpp" #include -#include #include #include #include #include #include +#include -Process::ProcessInvoker::ProcessInvoker() +Process::ProcessInvoker::ProcessInvoker(QWidget *parent) { + mProcess = new QProcess(this); + + mName = QString(); + mArguments = QStringList(); } Process::ProcessInvoker::~ProcessInvoker() { } +//void Process::ProcessInvoker::setProcessName(const QString &name) +//{ +// mName = name; +//} + +//void Process::ProcessInvoker::setProcessArguments(const QStringList &arguments) +//{ +// mArguments = arguments; +//} + +QProcess* Process::ProcessInvoker::getProcess() +{ + return mProcess; +} + +//QString Process::ProcessInvoker::getProcessName() +//{ +// return mName; +//} + +//QStringList Process::ProcessInvoker::getProcessArguments() +//{ +// return mArguments; +//} + bool Process::ProcessInvoker::startProcess(const QString &name, const QStringList &arguments, bool detached) { +// mProcess = new QProcess(this); + + connect(mProcess, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(processError(QProcess::ProcessError))); + + connect(mProcess, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(processFinished(int,QProcess::ExitStatus))); + + mName = name; + mArguments = arguments; + QString path(name); #ifdef Q_OS_WIN path.append(QLatin1String(".exe")); @@ -28,7 +68,6 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis path.prepend(QLatin1String("./")); #endif - QProcess process; QFileInfo info(path); if (!info.exists()) { @@ -57,7 +96,7 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis // Start the executable if (detached) { - if (!process.startDetached(path, arguments)) { + if (!mProcess->startDetached(path, arguments)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error starting executable")); msgBox.setIcon(QMessageBox::Critical); @@ -65,45 +104,79 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis msgBox.setText(tr("

Could not start %1

\

An error occurred while starting %1.

\

Press \"Show Details...\" for more information.

").arg(info.fileName())); - msgBox.setDetailedText(process.errorString()); + msgBox.setDetailedText(mProcess->errorString()); msgBox.exec(); return false; } } else { - process.start(path, arguments); - if (!process.waitForFinished()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error starting executable")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("

Could not start %1

\ -

An error occurred while starting %1.

\ -

Press \"Show Details...\" for more information.

").arg(info.fileName())); - msgBox.setDetailedText(process.errorString()); - msgBox.exec(); + mProcess->start(path, arguments); - return false; - } +// if (!mProcess->waitForFinished()) { +// QMessageBox msgBox; +// msgBox.setWindowTitle(tr("Error starting executable")); +// msgBox.setIcon(QMessageBox::Critical); +// msgBox.setStandardButtons(QMessageBox::Ok); +// msgBox.setText(tr("

Could not start %1

\ +//

An error occurred while starting %1.

\ +//

Press \"Show Details...\" for more information.

").arg(info.fileName())); +// msgBox.setDetailedText(mProcess->errorString()); +// msgBox.exec(); - if (process.exitCode() != 0 || process.exitStatus() == QProcess::CrashExit) { - QString error(process.readAllStandardError()); - error.append(tr("\nArguments:\n")); - error.append(arguments.join(" ")); +// return false; +// } - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error running executable")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("

Executable %1 returned an error

\ -

An error occurred while running %1.

\ -

Press \"Show Details...\" for more information.

").arg(info.fileName())); - msgBox.setDetailedText(error); - msgBox.exec(); +// if (mProcess->exitCode() != 0 || mProcess->exitStatus() == QProcess::CrashExit) { +// QString error(mProcess->readAllStandardError()); +// error.append(tr("\nArguments:\n")); +// error.append(arguments.join(" ")); - return false; - } +// QMessageBox msgBox; +// msgBox.setWindowTitle(tr("Error running executable")); +// msgBox.setIcon(QMessageBox::Critical); +// msgBox.setStandardButtons(QMessageBox::Ok); +// msgBox.setText(tr("

Executable %1 returned an error

\ +//

An error occurred while running %1.

\ +//

Press \"Show Details...\" for more information.

").arg(info.fileName())); +// msgBox.setDetailedText(error); +// msgBox.exec(); + +// return false; +// } } return true; } + +void Process::ProcessInvoker::processError(QProcess::ProcessError error) +{ + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error running executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Executable %1 returned an error

\ +

An error occurred while running %1.

\ +

Press \"Show Details...\" for more information.

").arg(mName)); + msgBox.setDetailedText(mProcess->errorString()); + msgBox.exec(); + +} + +void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) { + QString error(mProcess->readAllStandardError()); + error.append(tr("\nArguments:\n")); + error.append(mArguments.join(" ")); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error running executable")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Executable %1 returned an error

\ +

An error occurred while running %1.

\ +

Press \"Show Details...\" for more information.

").arg(mName)); + msgBox.setDetailedText(error); + msgBox.exec(); + } +} diff --git a/components/process/processinvoker.hpp b/components/process/processinvoker.hpp index d59d2f012..07907e3b4 100644 --- a/components/process/processinvoker.hpp +++ b/components/process/processinvoker.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Process { @@ -10,13 +11,32 @@ namespace Process { Q_OBJECT - ProcessInvoker(); + public: + + ProcessInvoker(QWidget *parent = 0); ~ProcessInvoker(); - public: +// void setProcessName(const QString &name); +// void setProcessArguments(const QStringList &arguments); + + QProcess* getProcess(); +// QString getProcessName(); +// QStringList getProcessArguments(); + +// inline bool startProcess(bool detached = false) { return startProcess(mName, mArguments, detached); } + inline bool startProcess(const QString &name, bool detached = false) { return startProcess(name, QStringList(), detached); } + bool startProcess(const QString &name, const QStringList &arguments, bool detached = false); + + private: + QProcess *mProcess; + + QString mName; + QStringList mArguments; + + private slots: + void processError(QProcess::ProcessError error); + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); - inline static bool startProcess(const QString &name, bool detached = false) { return startProcess(name, QStringList(), detached); } - bool static startProcess(const QString &name, const QStringList &arguments, bool detached = false); }; } diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index ce5aa57ec..6c873ea92 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -6,8 +6,8 @@ 0 0 - 516 - 399 + 514 + 397
@@ -88,13 +88,7 @@
- - - - /home/user/.local/share/openmw/data/Morrowind.ini - - - + From f8bb797b8a384f33e44436a58184382872b83cbf Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 16 Apr 2014 18:34:24 +0200 Subject: [PATCH 0050/1983] Settings tab is fully functional now --- apps/launcher/datafilespage.cpp | 30 +++--- apps/launcher/settingspage.cpp | 111 ++++++++++++++++------- apps/launcher/settingspage.hpp | 1 + apps/wizard/existinginstallationpage.cpp | 5 +- apps/wizard/languageselectionpage.cpp | 14 +-- 5 files changed, 106 insertions(+), 55 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index efd9765ac..72afe1c1d 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -42,7 +42,7 @@ void Launcher::DataFilesPage::loadSettings() QString profileName = ui.profilesComboBox->currentText(); - QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName, Qt::MatchExactly); + QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/content"), Qt::MatchExactly); QStringList filepaths; @@ -75,7 +75,7 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText()); foreach(const ContentSelectorModel::EsmFile *item, items) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/content"), item->fileName()); mGameSettings.setMultiValue(QString("content"), item->fileName()); } @@ -192,21 +192,21 @@ void Launcher::DataFilesPage::setupDataFiles() if (!mDataLocal.isEmpty()) mSelector->addFiles(mDataLocal); - QStringList profiles; + QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile"); - foreach (QString key, mLauncherSettings.getSettings().keys()) - { - if (key.contains("Profiles/")) - { - QString profile = key.mid (9); - if (profile != "currentprofile") - { - if (!profiles.contains(profile)) - profiles << profile; - } - } - } +// foreach (QString key, mLauncherSettings.getSettings().keys()) +// { +// if (key.contains("Profiles/")) +// { +// QString profile = key.mid (9); +// if (profile != "currentprofile") +// { +// if (!profiles.contains(profile)) +// profiles << profile; +// } +// } +// } foreach (const QString &item, profiles) addProfile (item, false); diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index b2d03ba95..3c8c119ec 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -1,7 +1,9 @@ #include "settingspage.hpp" +#include #include #include +#include #include #include @@ -20,13 +22,13 @@ Launcher::SettingsPage::SettingsPage(Config::GameSettings &gameSettings, setupUi(this); QStringList languages; - languages << "English" - << "French" - << "German" - << "Italian" - << "Polish" - << "Russian" - << "Spanish"; + languages << QLatin1String("English") + << QLatin1String("French") + << QLatin1String("German") + << QLatin1String("Italian") + << QLatin1String("Polish") + << QLatin1String("Russian") + << QLatin1String("Spanish"); languageComboBox->addItems(languages); @@ -50,22 +52,31 @@ Launcher::SettingsPage::SettingsPage(Config::GameSettings &gameSettings, connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); -// // Detect Morrowind configuration files -// foreach (const QString &path, mGameSettings.getDataDirs()) { -// QDir dir(path); -// dir.setPath(dir.canonicalPath()); // Resolve symlinks - -// if (dir.exists(QString("Morrowind.ini"))) -// iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); -// else -// { -// if (!dir.cdUp()) -// continue; // Cannot move from Data Files - -// if (dir.exists(QString("Morrowind.ini"))) -// iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); -// } -// } + // Detect Morrowind configuration files + QStringList iniPaths; + + foreach (const QString &path, mGameSettings.getDataDirs()) { + QDir dir(path); + dir.setPath(dir.canonicalPath()); // Resolve symlinks + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + else + { + if (!dir.cdUp()) + continue; // Cannot move from Data Files + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + } + } + + if (!iniPaths.isEmpty()) { + settingsComboBox->addItems(iniPaths); + importerButton->setEnabled(true); + } else { + importerButton->setEnabled(false); + } } void Launcher::SettingsPage::on_wizardButton_clicked() @@ -80,6 +91,32 @@ void Launcher::SettingsPage::on_importerButton_clicked() return; } +void Launcher::SettingsPage::on_browseButton_clicked() +{ + QString iniFile = QFileDialog::getOpenFileName( + this, + QObject::tr("Select configuration file"), + QDir::currentPath(), + QString(tr("Morrowind configuration file (*.ini)"))); + + + if (iniFile.isEmpty()) + return; + + QFileInfo info(iniFile); + + if (!info.exists() || !info.isReadable()) + return; + + const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); + + if (settingsComboBox->findText(path) == -1) { + settingsComboBox->addItem(path); + settingsComboBox->setCurrentIndex(settingsComboBox->findText(path)); + importerButton->setEnabled(true); + } +} + void Launcher::SettingsPage::wizardStarted() { qDebug() << "wizard started!"; @@ -109,18 +146,27 @@ void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus if (exitCode != 0 || exitStatus == QProcess::CrashExit) return; - mMain->writeSettings(); - mMain->reloadSettings(); + // Import selected data files from openmw.cfg + if (addonsCheckBox->isChecked()) + { + if (mProfileDialog->exec() == QDialog::Accepted) + { + const QString profile(mProfileDialog->lineEdit()->text()); + const QStringList files(mGameSettings.values(QLatin1String("content"))); + // Doesn't quite work right now + mLauncherSettings.setValue(QLatin1String("Profiles/currentprofile"), profile); - if (addonsCheckBox->isChecked()) { + foreach (const QString &file, files) { + mLauncherSettings.setMultiValue(QLatin1String("Profiles/") + profile + QLatin1String("/content"), file); + } - if (mProfileDialog->exec() == QDialog::Accepted) { - QString profile = mProfileDialog->lineEdit()->text(); - qDebug() << profile; + mGameSettings.remove(QLatin1String("content")); } } + mMain->writeSettings(); + mMain->reloadSettings(); importerButton->setEnabled(true); } @@ -132,8 +178,9 @@ void Launcher::SettingsPage::updateOkButton(const QString &text) return; } + const QStringList profiles(mLauncherSettings.subKeys(QString("Profiles/"))); -// (profilesComboBox->findText(text) == -1) -// ? mNewProfileDialog->setOkButtonEnabled(true) -// : mNewProfileDialog->setOkButtonEnabled(false); + (profiles.contains(text)) + ? mProfileDialog->setOkButtonEnabled(false) + : mProfileDialog->setOkButtonEnabled(true); } diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index ed5c068fb..d43141fc4 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -31,6 +31,7 @@ namespace Launcher private slots: void on_wizardButton_clicked(); void on_importerButton_clicked(); + void on_browseButton_clicked(); void wizardStarted(); void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index d37dd949d..13dcf9941 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -72,7 +72,7 @@ bool Wizard::ExistingInstallationPage::validatePage() QString iniFile; if (msgBox.clickedButton() == browseButton) { iniFile = QFileDialog::getOpenFileName( - NULL, + this, QObject::tr("Select configuration file"), QDir::currentPath(), QString(tr("Morrowind configuration file (*.ini)"))); @@ -100,6 +100,9 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() NULL, QFileDialog::DontResolveSymlinks); + if (selectedFile.isEmpty()) + return; + QFileInfo info(selectedFile); if (!info.exists()) diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index b921d5ddb..8beb106a2 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -14,13 +14,13 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(MainWizard *wizard) : void Wizard::LanguageSelectionPage::initializePage() { QStringList languages; - languages << "English" - << "French" - << "German" - << "Italian" - << "Polish" - << "Russian" - << "Spanish"; + languages << QLatin1String("English") + << QLatin1String("French") + << QLatin1String("German") + << QLatin1String("Italian") + << QLatin1String("Polish") + << QLatin1String("Russian") + << QLatin1String("Spanish"); languageComboBox->addItems(languages); } From 6ed76858d9ce85f9ead853bab070ed48cb48574d Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 16 Apr 2014 23:59:25 +0200 Subject: [PATCH 0051/1983] Fixed some minor problems with the wizard --- apps/wizard/existinginstallationpage.cpp | 48 +++++---------- apps/wizard/existinginstallationpage.hpp | 5 -- apps/wizard/languageselectionpage.cpp | 15 ++++- apps/wizard/mainwizard.cpp | 76 +++++++++++++++++++++++- apps/wizard/mainwizard.hpp | 3 + 5 files changed, 106 insertions(+), 41 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 13dcf9941..66edc1e8a 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -14,36 +14,30 @@ Wizard::ExistingInstallationPage::ExistingInstallationPage(MainWizard *wizard) : { setupUi(this); - mEmptyItem = new QListWidgetItem(tr("No existing installations detected")); - mEmptyItem->setFlags(Qt::NoItemFlags); - installationsList->addItem(mEmptyItem); - mEmptyItem->setHidden(true); + QListWidgetItem *emptyItem = new QListWidgetItem(tr("No existing installations detected")); + emptyItem->setFlags(Qt::NoItemFlags); + installationsList->addItem(emptyItem); - connect(installationsList, SIGNAL(currentTextChanged(QString)), - this, SLOT(textChanged(QString))); - - connect(installationsList,SIGNAL(itemSelectionChanged()), - this, SIGNAL(completeChanged())); -} - -void Wizard::ExistingInstallationPage::initializePage() -{ + // Add the available installation paths QStringList paths(mWizard->mInstallations.keys()); + // Hide the default item if there are installations to choose from if (paths.isEmpty()) { - mEmptyItem->setHidden(false); - return; + installationsList->item(0)->setHidden(false); + } else { + installationsList->item(0)->setHidden(true); } - // Make to clear list before adding items - // to prevent duplicates when going back and forth between pages - installationsList->clear(); - foreach (const QString &path, paths) { QListWidgetItem *item = new QListWidgetItem(path); installationsList->addItem(item); } + connect(installationsList, SIGNAL(currentTextChanged(QString)), + this, SLOT(textChanged(QString))); + + connect(installationsList,SIGNAL(itemSelectionChanged()), + this, SIGNAL(completeChanged())); } bool Wizard::ExistingInstallationPage::validatePage() @@ -119,7 +113,7 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() mWizard->addInstallation(path); // Hide the default item - mEmptyItem->setHidden(true); + installationsList->item(0)->setHidden(true); QListWidgetItem *item = new QListWidgetItem(path); installationsList->addItem(item); @@ -149,17 +143,5 @@ bool Wizard::ExistingInstallationPage::isComplete() const int Wizard::ExistingInstallationPage::nextId() const { - QString path(field(QLatin1String("installation.path")).toString()); - - if (path.isEmpty()) - return MainWizard::Page_LanguageSelection; - - if (mWizard->mInstallations[path]->hasMorrowind == true && - mWizard->mInstallations[path]->hasTribunal == true && - mWizard->mInstallations[path]->hasBloodmoon == true) - { - return MainWizard::Page_Import; - } else { - return MainWizard::Page_LanguageSelection; - } + return MainWizard::Page_LanguageSelection; } diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp index 04385893a..54ee8d2e2 100644 --- a/apps/wizard/existinginstallationpage.hpp +++ b/apps/wizard/existinginstallationpage.hpp @@ -2,7 +2,6 @@ #define EXISTINGINSTALLATIONPAGE_HPP #include -#include #include "ui_existinginstallationpage.h" @@ -27,11 +26,7 @@ namespace Wizard private: MainWizard *mWizard; - QListWidgetItem *mEmptyItem; - - protected: - void initializePage(); }; } diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 8beb106a2..7f97e6918 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -27,5 +27,18 @@ void Wizard::LanguageSelectionPage::initializePage() int Wizard::LanguageSelectionPage::nextId() const { - return MainWizard::Page_ComponentSelection; + // Check if we have to install something + QString path(field(QLatin1String("installation.path")).toString()); + + if (path.isEmpty()) + return MainWizard::Page_ComponentSelection; + + if (mWizard->mInstallations[path]->hasMorrowind == true && + mWizard->mInstallations[path]->hasTribunal == true && + mWizard->mInstallations[path]->hasBloodmoon == true) + { + return MainWizard::Page_Import; + } else { + return MainWizard::Page_ComponentSelection; + } } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 0a53e10c5..fa27b548c 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -44,6 +44,7 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setupLog(); setupGameSettings(); + setupLauncherSettings(); setupInstallations(); setupPages(); } @@ -149,6 +150,40 @@ void Wizard::MainWizard::setupGameSettings() } } +void Wizard::MainWizard::setupLauncherSettings() +{ + QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + path.append(QLatin1String("launcher.cfg")); + + QString message(tr("

Could not open %1 for reading

\ +

Please make sure you have the right permissions \ + and try again.

")); + + + QFile file(path); + + qDebug() << "Loading config file:" << qPrintable(path); + + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(message.arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mLauncherSettings.readFile(stream); + } + + file.close(); + +} + void Wizard::MainWizard::setupInstallations() { // Check if the paths actually contain a Morrowind installation @@ -211,8 +246,10 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--cfg")); arguments.append(userPath + QLatin1String("openmw.cfg")); -// if (!ProcessInvoker::startProcess(QLatin1String("mwiniimport"), arguments, false)) -// return qApp->quit();; + ProcessInvoker invoker(this); + + if (!invoker.startProcess(QLatin1String("mwiniimport"), arguments, false)) + return qApp->quit();; // Re-read the game settings setupGameSettings(); @@ -285,6 +322,19 @@ void Wizard::MainWizard::reject() void Wizard::MainWizard::writeSettings() { + // Write the encoding and language settings + QString language(field(QLatin1String("installation.language")).toString()); + mLauncherSettings.setValue(QLatin1String("Settings/language"), language); + + if (language == QLatin1String("Polish")) { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + } else if (language == QLatin1String("Russian")) { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + } else { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + } + + // Write the installation path so that openmw can find them QString path(field(QLatin1String("installation.path")).toString()); // Make sure the installation path is the last data= entry @@ -329,6 +379,28 @@ void Wizard::MainWizard::writeSettings() mGameSettings.writeFile(stream); file.close(); + + // Launcher settings + file.setFileName(userPath + QLatin1String("launcher.cfg")); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not open %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

").arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); + } + + stream.setDevice(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mLauncherSettings.writeFile(stream); + file.close(); } bool Wizard::MainWizard::findFiles(const QString &name, const QString &path) diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index dcb07a8c2..63b7e62e8 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace Wizard { @@ -53,12 +54,14 @@ namespace Wizard void setupLog(); void setupGameSettings(); + void setupLauncherSettings(); void setupInstallations(); void setupPages(); void writeSettings(); Config::GameSettings mGameSettings; + Config::LauncherSettings mLauncherSettings; QTextStream *mLog; From 30c3c3e245c8b19a18ce08df208d24e96594f480 Mon Sep 17 00:00:00 2001 From: pvdk Date: Thu, 17 Apr 2014 00:01:19 +0200 Subject: [PATCH 0052/1983] Working on importing content lists in the launcher --- apps/launcher/datafilespage.cpp | 126 ++++++----- apps/launcher/datafilespage.hpp | 5 +- apps/launcher/maindialog.cpp | 269 ++++++++++-------------- apps/launcher/settingspage.cpp | 95 ++++++++- apps/launcher/settingspage.hpp | 7 +- apps/launcher/utils/textinputdialog.cpp | 2 +- 6 files changed, 269 insertions(+), 235 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 72afe1c1d..21652a8f4 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -34,16 +34,68 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: setupDataFiles(); } -void Launcher::DataFilesPage::loadSettings() +void Launcher::DataFilesPage::buildView() +{ + ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); + + //tool buttons + ui.newProfileButton->setToolTip ("Create a new profile"); + ui.deleteProfileButton->setToolTip ("Delete an existing profile"); + + //combo box + ui.profilesComboBox->addItem ("Default"); + ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); + ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String("Default"))); + + // Add the actions to the toolbuttons + ui.newProfileButton->setDefaultAction (ui.newProfileAction); + ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); + + //establish connections + connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), + this, SLOT (slotProfileChanged(int))); + + connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)), + this, SLOT (slotProfileRenamed(QString, QString))); + + connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), + this, SLOT (slotProfileChangedByUser(QString, QString))); +} + +void Launcher::DataFilesPage::setupDataFiles() +{ + QStringList paths = mGameSettings.getDataDirs(); + + foreach (const QString &path, paths) + mSelector->addFiles(path); + + mDataLocal = mGameSettings.getDataLocal(); + + if (!mDataLocal.isEmpty()) + mSelector->addFiles(mDataLocal); + + loadSettings(); +} + +bool Launcher::DataFilesPage::loadSettings() { QStringList paths = mGameSettings.getDataDirs(); paths.insert (0, mDataLocal); PathIterator pathIterator (paths); - QString profileName = ui.profilesComboBox->currentText(); + QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); + QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile"); + + qDebug() << "current profile is: " << currentProfile; - QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/content"), Qt::MatchExactly); + foreach (const QString &item, profiles) + addProfile (item, false); + + // Hack: also add the current profile + if (!currentProfile.isEmpty()) + addProfile(currentProfile, true); + QStringList files = mLauncherSettings.values(QString("Profiles/") + currentProfile + QString("/content"), Qt::MatchExactly); QStringList filepaths; foreach (const QString &file, files) @@ -55,6 +107,8 @@ void Launcher::DataFilesPage::loadSettings() } mSelector->setProfileContent (filepaths); + + return true; } void Launcher::DataFilesPage::saveSettings(const QString &profile) @@ -81,33 +135,6 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) } -void Launcher::DataFilesPage::buildView() -{ - ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); - - //tool buttons - ui.newProfileButton->setToolTip ("Create a new profile"); - ui.deleteProfileButton->setToolTip ("Delete an existing profile"); - - //combo box - ui.profilesComboBox->addItem ("Default"); - ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); - - // Add the actions to the toolbuttons - ui.newProfileButton->setDefaultAction (ui.newProfileAction); - ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); - - //establish connections - connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), - this, SLOT (slotProfileChanged(int))); - - connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)), - this, SLOT (slotProfileRenamed(QString, QString))); - - connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), - this, SLOT (slotProfileChangedByUser(QString, QString))); -} - void Launcher::DataFilesPage::removeProfile(const QString &profile) { mLauncherSettings.remove(QString("Profiles/") + profile); @@ -140,6 +167,9 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString if (previous == current) return; + if (previous.isEmpty()) + return; + if (!previous.isEmpty() && savePrevious) saveSettings (previous); @@ -180,42 +210,6 @@ void Launcher::DataFilesPage::slotProfileChanged(int index) setProfile (index, true); } -void Launcher::DataFilesPage::setupDataFiles() -{ - QStringList paths = mGameSettings.getDataDirs(); - - foreach (const QString &path, paths) - mSelector->addFiles(path); - - mDataLocal = mGameSettings.getDataLocal(); - - if (!mDataLocal.isEmpty()) - mSelector->addFiles(mDataLocal); - - QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); - QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile"); - -// foreach (QString key, mLauncherSettings.getSettings().keys()) -// { -// if (key.contains("Profiles/")) -// { -// QString profile = key.mid (9); -// if (profile != "currentprofile") -// { -// if (!profiles.contains(profile)) -// profiles << profile; -// } -// } -// } - - foreach (const QString &item, profiles) - addProfile (item, false); - - setProfile (ui.profilesComboBox->findText(currentProfile), false); - - loadSettings(); -} - void Launcher::DataFilesPage::on_newProfileAction_triggered() { TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 642668cb7..06efdfca3 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -39,7 +39,9 @@ namespace Launcher //void writeConfig(QString profile = QString()); void saveSettings(const QString &profile = ""); - void loadSettings(); + bool loadSettings(); + + void setupDataFiles(); signals: void signalProfileChanged (int index); @@ -70,7 +72,6 @@ namespace Launcher void setPluginsCheckstates(Qt::CheckState state); void buildView(); - void setupDataFiles(); void setupConfig(); void readConfig(); void setProfile (int index, bool savePrevious); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index b8f9efd1a..9c6e33e1b 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -137,7 +137,7 @@ void Launcher::MainDialog::createPages() mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); - mSettingsPage = new SettingsPage(mGameSettings, mLauncherSettings, this); + mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); @@ -161,150 +161,103 @@ void Launcher::MainDialog::createPages() bool Launcher::MainDialog::showFirstRunDialog() { - QStringList iniPaths; - foreach (const QString &path, mGameSettings.getDataDirs()) { - QDir dir(path); - dir.setPath(dir.canonicalPath()); // Resolve symlinks - - if (dir.exists(QString("Morrowind.ini"))) - iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); - else - { - if (!dir.cdUp()) - continue; // Cannot move from Data Files - - if (dir.exists(QString("Morrowind.ini"))) - iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); - } - } - - // Ask the user where the Morrowind.ini is - if (iniPaths.empty()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error detecting Morrowind configuration")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(QObject::tr("
Could not find Morrowind.ini

\ - OpenMW needs to import settings from this file.

\ - Press \"Browse...\" to specify the location manually.
")); - - QAbstractButton *dirSelectButton = - msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); - - msgBox.exec(); - - QString iniFile; - if (msgBox.clickedButton() == dirSelectButton) { - iniFile = QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select configuration file"), - QDir::currentPath(), - QString(tr("Morrowind configuration file (*.ini)"))); - } - - if (iniFile.isEmpty()) - return false; // Cancel was clicked; - - QFileInfo info(iniFile); - iniPaths.clear(); - iniPaths.append(info.absoluteFilePath()); - } - - CheckableMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Morrowind installation detected")); - - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); - int size = QApplication::style()->pixelMetric(QStyle::PM_MessageBoxIconSize); - msgBox.setIconPixmap(icon.pixmap(size, size)); - - QAbstractButton *importerButton = - msgBox.addButton(tr("Import"), QDialogButtonBox::AcceptRole); // ActionRole doesn't work?! - QAbstractButton *skipButton = - msgBox.addButton(tr("Skip"), QDialogButtonBox::RejectRole); - - Q_UNUSED(skipButton); // Surpress compiler unused warning - - msgBox.setStandardButtons(QDialogButtonBox::NoButton); - msgBox.setText(tr("
An existing Morrowind configuration was detected
\ -
Would you like to import settings from Morrowind.ini?
\ -
Warning: In most cases OpenMW needs these settings to run properly
")); - msgBox.setCheckBoxText(tr("Include selected masters and plugins (creates a new profile)")); - msgBox.exec(); - - - if (msgBox.clickedButton() == importerButton) { - - if (iniPaths.count() > 1) { - // Multiple Morrowind.ini files found - bool ok; - QString path = QInputDialog::getItem(this, tr("Multiple configurations found"), - tr("
There are multiple Morrowind.ini files found.

\ - Please select the one you wish to import from:"), iniPaths, 0, false, &ok); - if (ok && !path.isEmpty()) { - iniPaths.clear(); - iniPaths.append(path); - } else { - // Cancel was clicked - return false; - } - } - - // Create the file if it doesn't already exist, else the importer will fail - QString path = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + QString("openmw.cfg"); - QFile file(path); - - if (!file.exists()) { - if (!file.open(QIODevice::ReadWrite)) { - // File cannot be created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; - } - file.close(); - } - - // Construct the arguments to run the importer - QStringList arguments; - - if (msgBox.isChecked()) - arguments.append(QString("--game-files")); - - arguments.append(QString("--encoding")); - arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); - arguments.append(QString("--ini")); - arguments.append(iniPaths.first()); - arguments.append(QString("--cfg")); - arguments.append(path); - ProcessInvoker invoker(this); - - if (!invoker.startProcess(QLatin1String("mwiniimport"), arguments, false)) - return false; - - // Re-read the game settings - if (!setupGameSettings()) - return false; - - // Add a new profile - if (msgBox.isChecked()) { - mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported")); - mLauncherSettings.remove(QString("Profiles/Imported/content")); - - QStringList contents = mGameSettings.values(QString("content")); - foreach (const QString &content, contents) { - mLauncherSettings.setMultiValue(QString("Profiles/Imported/content"), content); - } - } - - } +// CheckableMessageBox msgBox(this); +// msgBox.setWindowTitle(tr("Morrowind installation detected")); + +// QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); +// int size = QApplication::style()->pixelMetric(QStyle::PM_MessageBoxIconSize); +// msgBox.setIconPixmap(icon.pixmap(size, size)); + +// QAbstractButton *importerButton = +// msgBox.addButton(tr("Import"), QDialogButtonBox::AcceptRole); // ActionRole doesn't work?! +// QAbstractButton *skipButton = +// msgBox.addButton(tr("Skip"), QDialogButtonBox::RejectRole); + +// Q_UNUSED(skipButton); // Surpress compiler unused warning + +// msgBox.setStandardButtons(QDialogButtonBox::NoButton); +// msgBox.setText(tr("
An existing Morrowind configuration was detected
\ +//
Would you like to import settings from Morrowind.ini?
\ +//
Warning: In most cases OpenMW needs these settings to run properly
")); +// msgBox.setCheckBoxText(tr("Include selected masters and plugins (creates a new profile)")); +// msgBox.exec(); + + +// if (msgBox.clickedButton() == importerButton) { + +// if (iniPaths.count() > 1) { +// // Multiple Morrowind.ini files found +// bool ok; +// QString path = QInputDialog::getItem(this, tr("Multiple configurations found"), +// tr("
There are multiple Morrowind.ini files found.

\ +// Please select the one you wish to import from:"), iniPaths, 0, false, &ok); +// if (ok && !path.isEmpty()) { +// iniPaths.clear(); +// iniPaths.append(path); +// } else { +// // Cancel was clicked +// return false; +// } +// } + +// // Create the file if it doesn't already exist, else the importer will fail +// QString path = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + QString("openmw.cfg"); +// QFile file(path); + +// if (!file.exists()) { +// if (!file.open(QIODevice::ReadWrite)) { +// // File cannot be created +// QMessageBox msgBox; +// msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); +// msgBox.setIcon(QMessageBox::Critical); +// msgBox.setStandardButtons(QMessageBox::Ok); +// msgBox.setText(tr("
Could not open or create %0 for writing

\ +// Please make sure you have the right permissions \ +// and try again.
").arg(file.fileName())); +// msgBox.exec(); +// return false; +// } + +// file.close(); +// } + +// // Construct the arguments to run the importer +// QStringList arguments; + +// if (msgBox.isChecked()) +// arguments.append(QString("--game-files")); + +// arguments.append(QString("--encoding")); +// arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); +// arguments.append(QString("--ini")); +// arguments.append(iniPaths.first()); +// arguments.append(QString("--cfg")); +// arguments.append(path); + +// ProcessInvoker invoker(this); + +// if (!invoker.startProcess(QLatin1String("mwiniimport"), arguments, false)) +// return false; + +// // Re-read the game settings +// if (!setupGameSettings()) +// return false; + +// // Add a new profile +// if (msgBox.isChecked()) { +// mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported")); +// mLauncherSettings.remove(QString("Profiles/Imported/content")); + +// QStringList contents = mGameSettings.values(QString("content")); +// foreach (const QString &content, contents) { +// mLauncherSettings.setMultiValue(QString("Profiles/Imported/content"), content); +// } +// } + +// } return true; } @@ -349,8 +302,11 @@ bool Launcher::MainDialog::reloadSettings() if (!setupGraphicsSettings()) return false; -// if (!mSettingsPage->loadSettings()) -// return false; + if (!mSettingsPage->loadSettings()) + return false; + + if (!mDataFilesPage->loadSettings()) + return false; if (!mGraphicsPage->loadSettings()) return false; @@ -371,17 +327,17 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem DataFilesPage *previousPage = dynamic_cast(pagesWidget->widget(previousIndex)); DataFilesPage *currentPage = dynamic_cast(pagesWidget->widget(currentIndex)); - //special call to update/save data files page list view when it's displayed/hidden. - if (previousPage) - { - if (previousPage->objectName() == "DataFilesPage") - previousPage->saveSettings(); - } - else if (currentPage) - { - if (currentPage->objectName() == "DataFilesPage") - currentPage->loadSettings(); - } +// //special call to update/save data files page list view when it's displayed/hidden. +// if (previousPage) +// { +// if (previousPage->objectName() == "DataFilesPage") +// previousPage->saveSettings(); +// } +// else if (currentPage) +// { +// if (currentPage->objectName() == "DataFilesPage") +// currentPage->loadSettings(); +// } } bool Launcher::MainDialog::setupLauncherSettings() @@ -729,8 +685,9 @@ bool Launcher::MainDialog::writeSettings() { // Now write all config files saveSettings(); - mGraphicsPage->saveSettings(); mDataFilesPage->saveSettings(); + mGraphicsPage->saveSettings(); + mSettingsPage->saveSettings(); QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QDir dir(userPath); diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 3c8c119ec..f673917fd 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include @@ -12,12 +14,14 @@ using namespace Process; -Launcher::SettingsPage::SettingsPage(Config::GameSettings &gameSettings, - Config::LauncherSettings &launcherSettings, MainDialog *parent) : - mGameSettings(gameSettings), - mLauncherSettings(launcherSettings), - QWidget(parent), - mMain(parent) +Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, + Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, MainDialog *parent) + : mCfgMgr(cfg) + , mGameSettings(gameSettings) + , mLauncherSettings(launcherSettings) + , QWidget(parent) + , mMain(parent) { setupUi(this); @@ -77,17 +81,60 @@ Launcher::SettingsPage::SettingsPage(Config::GameSettings &gameSettings, } else { importerButton->setEnabled(false); } + + loadSettings(); } void Launcher::SettingsPage::on_wizardButton_clicked() { + saveSettings(); + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) return; } void Launcher::SettingsPage::on_importerButton_clicked() { - if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), false)) + saveSettings(); + + // Create the file if it doesn't already exist, else the importer will fail + QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); + path.append(QLatin1String("openmw.cfg")); + QFile file(path); + + if (!file.exists()) { + if (!file.open(QIODevice::ReadWrite)) { + // File cannot be created + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("

Could not open or create %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

").arg(file.fileName())); + msgBox.exec(); + return; + } + + file.close(); + } + + // Construct the arguments to run the importer + QStringList arguments; + + if (addonsCheckBox->isChecked()) + arguments.append(QString("--game-files")); + + arguments.append(QString("--encoding")); + arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); + arguments.append(QString("--ini")); + arguments.append(settingsComboBox->currentText()); + arguments.append(QString("--cfg")); + arguments.append(path); + + qDebug() << "arguments " << arguments; + + if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) return; } @@ -129,7 +176,6 @@ void Launcher::SettingsPage::wizardFinished(int exitCode, QProcess::ExitStatus e if (exitCode != 0 || exitStatus == QProcess::CrashExit) return; - mMain->writeSettings(); mMain->reloadSettings(); wizardButton->setEnabled(true); } @@ -146,6 +192,9 @@ void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus if (exitCode != 0 || exitStatus == QProcess::CrashExit) return; + // Re-read the settings in their current state + mMain->reloadSettings(); + // Import selected data files from openmw.cfg if (addonsCheckBox->isChecked()) { @@ -154,6 +203,8 @@ void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus const QString profile(mProfileDialog->lineEdit()->text()); const QStringList files(mGameSettings.values(QLatin1String("content"))); + qDebug() << "Profile " << profile << files; + // Doesn't quite work right now mLauncherSettings.setValue(QLatin1String("Profiles/currentprofile"), profile); @@ -165,7 +216,6 @@ void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus } } - mMain->writeSettings(); mMain->reloadSettings(); importerButton->setEnabled(true); } @@ -184,3 +234,30 @@ void Launcher::SettingsPage::updateOkButton(const QString &text) ? mProfileDialog->setOkButtonEnabled(false) : mProfileDialog->setOkButtonEnabled(true); } + +void Launcher::SettingsPage::saveSettings() +{ + QString language(languageComboBox->currentText()); + + mLauncherSettings.setValue(QLatin1String("Settings/language"), language); + + if (language == QLatin1String("Polish")) { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + } else if (language == QLatin1String("Russian")) { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + } else { + mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + } +} + +bool Launcher::SettingsPage::loadSettings() +{ + QString language(mLauncherSettings.value(QLatin1String("Settings/language"))); + + int index = languageComboBox->findText(language); + + if (index != -1) + languageComboBox->setCurrentIndex(index); + + return true; +} diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index d43141fc4..846f5681c 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -10,6 +10,7 @@ #include "maindialog.hpp" +namespace Files { struct ConfigurationManager; } namespace Config { class GameSettings; class LauncherSettings; } @@ -22,13 +23,14 @@ namespace Launcher Q_OBJECT public: - SettingsPage( Config::GameSettings &gameSettings, + SettingsPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, MainDialog *parent = 0); void saveSettings(); bool loadSettings(); private slots: + void on_wizardButton_clicked(); void on_importerButton_clicked(); void on_browseButton_clicked(); @@ -42,9 +44,12 @@ namespace Launcher void updateOkButton(const QString &text); private: + Process::ProcessInvoker *mWizardInvoker; Process::ProcessInvoker *mImporterInvoker; + Files::ConfigurationManager &mCfgMgr; + Config::GameSettings &mGameSettings; Config::LauncherSettings &mLauncherSettings; diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 74e40a81c..b47fdb6e6 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -14,7 +14,7 @@ Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString & mButtonBox = new QDialogButtonBox(this); mButtonBox->addButton(QDialogButtonBox::Ok); mButtonBox->addButton(QDialogButtonBox::Cancel); -// mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); + mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); QLabel *label = new QLabel(this); label->setText(text); From 5d94cb112f0574a95f79f2773dadc693f1c5fc7f Mon Sep 17 00:00:00 2001 From: pvdk Date: Thu, 17 Apr 2014 02:15:06 +0200 Subject: [PATCH 0053/1983] Fixed segfault bug due to accessing members of unitialized struct --- apps/wizard/componentselectionpage.cpp | 12 ++++---- apps/wizard/existinginstallationpage.cpp | 4 +-- apps/wizard/installationpage.cpp | 10 +++---- apps/wizard/languageselectionpage.cpp | 28 +++++++++++------- apps/wizard/mainwizard.cpp | 37 +++++++++++++++++++----- apps/wizard/mainwizard.hpp | 7 ++++- 6 files changed, 65 insertions(+), 33 deletions(-) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 19a07438d..ac084616c 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -74,7 +74,7 @@ void Wizard::ComponentSelectionPage::initializePage() componentsList->addItem(bloodmoonItem); } else { - if (mWizard->mInstallations[path]->hasMorrowind) { + if (mWizard->mInstallations[path].hasMorrowind) { morrowindItem->setText(tr("Morrowind\t\t(installed)")); morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Unchecked); @@ -85,7 +85,7 @@ void Wizard::ComponentSelectionPage::initializePage() componentsList->addItem(morrowindItem); - if (mWizard->mInstallations[path]->hasTribunal) { + if (mWizard->mInstallations[path].hasTribunal) { tribunalItem->setText(tr("Tribunal\t\t(installed)")); tribunalItem->setFlags(tribunalItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); tribunalItem->setData(Qt::CheckStateRole, Qt::Unchecked); @@ -96,7 +96,7 @@ void Wizard::ComponentSelectionPage::initializePage() componentsList->addItem(tribunalItem); - if (mWizard->mInstallations[path]->hasBloodmoon) { + if (mWizard->mInstallations[path].hasBloodmoon) { bloodmoonItem->setText(tr("Bloodmoon\t\t(installed)")); bloodmoonItem->setFlags(bloodmoonItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); bloodmoonItem->setData(Qt::CheckStateRole, Qt::Unchecked); @@ -114,12 +114,12 @@ bool Wizard::ComponentSelectionPage::validatePage() QStringList components(field(QLatin1String("installation.components")).toStringList()); QString path(field(QLatin1String("installation.path")).toString()); - qDebug() << components << path << mWizard->mInstallations[path]; +// qDebug() << components << path << mWizard->mInstallations[path]; if (field(QLatin1String("installation.new")).toBool() == false) { if (components.contains(QLatin1String("Tribunal")) && !components.contains(QLatin1String("Bloodmoon"))) { - if (mWizard->mInstallations[path]->hasBloodmoon) + if (mWizard->mInstallations[path].hasBloodmoon) { QMessageBox msgBox; msgBox.setWindowTitle(tr("About to install Tribunal after Bloodmoon")); @@ -136,7 +136,7 @@ bool Wizard::ComponentSelectionPage::validatePage() if (msgBox.clickedButton() == reinstallButton) { // Force reinstallation - mWizard->mInstallations[path]->hasBloodmoon = false; + mWizard->mInstallations[path].hasBloodmoon = false; QList items = componentsList->findItems(QLatin1String("Bloodmoon"), Qt::MatchStartsWith); foreach (QListWidgetItem *item, items) { diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 66edc1e8a..0cbdce673 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -47,7 +47,7 @@ bool Wizard::ExistingInstallationPage::validatePage() // Or failed to be detected due to the target being a symlink QString path(field(QLatin1String("installation.path")).toString()); - QFile file(mWizard->mInstallations[path]->iniPath); + QFile file(mWizard->mInstallations[path].iniPath); if (!file.exists()) { QMessageBox msgBox; @@ -78,7 +78,7 @@ bool Wizard::ExistingInstallationPage::validatePage() // A proper Morrowind.ini was selected, set it QFileInfo info(iniFile); - mWizard->mInstallations[path]->iniPath = info.absoluteFilePath(); + mWizard->mInstallations[path].iniPath = info.absoluteFilePath(); } return true; diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index a0d3dfd21..f59b6d640 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -36,11 +36,11 @@ void Wizard::InstallationPage::initializePage() installProgressBar->setMaximum((components.count() * 100)); } else { if (components.contains(QLatin1String("Tribunal")) - && !mWizard->mInstallations[path]->hasTribunal) + && !mWizard->mInstallations[path].hasTribunal) installProgressBar->setMaximum(100); if (components.contains(QLatin1String("Bloodmoon")) - && !mWizard->mInstallations[path]->hasBloodmoon) + && !mWizard->mInstallations[path].hasBloodmoon) installProgressBar->setMaximum(installProgressBar->maximum() + 100); } @@ -104,15 +104,15 @@ void Wizard::InstallationPage::startInstallation() mUnshield->setInstallComponent(Wizard::Component_Morrowind, false); if (components.contains(QLatin1String("Tribunal")) - && !mWizard->mInstallations[path]->hasTribunal) + && !mWizard->mInstallations[path].hasTribunal) mUnshield->setInstallComponent(Wizard::Component_Tribunal, true); if (components.contains(QLatin1String("Bloodmoon")) - && !mWizard->mInstallations[path]->hasBloodmoon) + && !mWizard->mInstallations[path].hasBloodmoon) mUnshield->setInstallComponent(Wizard::Component_Bloodmoon, true); // Set the location of the Morrowind.ini to update - mUnshield->setIniPath(mWizard->mInstallations[path]->iniPath); + mUnshield->setIniPath(mWizard->mInstallations[path].iniPath); mUnshield->setupSettings(); } diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 7f97e6918..fbd08ffcd 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -2,6 +2,8 @@ #include "mainwizard.hpp" +#include + Wizard::LanguageSelectionPage::LanguageSelectionPage(MainWizard *wizard) : QWizardPage(wizard), mWizard(wizard) @@ -27,18 +29,22 @@ void Wizard::LanguageSelectionPage::initializePage() int Wizard::LanguageSelectionPage::nextId() const { - // Check if we have to install something - QString path(field(QLatin1String("installation.path")).toString()); - - if (path.isEmpty()) + if (field(QLatin1String("installation.new")).toBool() == true) { return MainWizard::Page_ComponentSelection; - - if (mWizard->mInstallations[path]->hasMorrowind == true && - mWizard->mInstallations[path]->hasTribunal == true && - mWizard->mInstallations[path]->hasBloodmoon == true) - { - return MainWizard::Page_Import; } else { - return MainWizard::Page_ComponentSelection; + QString path(field(QLatin1String("installation.path")).toString()); + + if (path.isEmpty()) + return MainWizard::Page_ComponentSelection; + + // Check if we have to install something + if (mWizard->mInstallations[path].hasMorrowind == true && + mWizard->mInstallations[path].hasTribunal == true && + mWizard->mInstallations[path].hasBloodmoon == true) + { + return MainWizard::Page_Import; + } else { + return MainWizard::Page_ComponentSelection; + } } } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index fa27b548c..2a4d9f70c 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -246,23 +246,29 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--cfg")); arguments.append(userPath + QLatin1String("openmw.cfg")); - ProcessInvoker invoker(this); + ProcessInvoker *invoker = new ProcessInvoker(this); - if (!invoker.startProcess(QLatin1String("mwiniimport"), arguments, false)) + if (!invoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) return qApp->quit();; + connect(invoker->getProcess(), SIGNAL(started()), + this, SLOT(importerStarted())); + + connect(invoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(importerFinished(int,QProcess::ExitStatus))); + // Re-read the game settings - setupGameSettings(); + // setupGameSettings(); } void Wizard::MainWizard::addInstallation(const QString &path) { qDebug() << "add installation in: " << path; - Installation* install = new Installation(); + Installation install;// = new Installation(); - install->hasMorrowind = findFiles(QLatin1String("Morrowind"), path); - install->hasTribunal = findFiles(QLatin1String("Tribunal"), path); - install->hasBloodmoon = findFiles(QLatin1String("Bloodmoon"), path); + install.hasMorrowind = findFiles(QLatin1String("Morrowind"), path); + install.hasTribunal = findFiles(QLatin1String("Tribunal"), path); + install.hasBloodmoon = findFiles(QLatin1String("Bloodmoon"), path); // Try to autodetect the Morrowind.ini location QDir dir(path); @@ -276,7 +282,7 @@ void Wizard::MainWizard::addInstallation(const QString &path) } if (file.exists()) - install->iniPath = file.fileName(); + install.iniPath = file.fileName(); mInstallations.insert(QDir::toNativeSeparators(path), install); @@ -301,6 +307,21 @@ void Wizard::MainWizard::setupPages() setStartId(Page_Intro); } +void Wizard::MainWizard::importerStarted() +{ + qDebug() << "importer started!"; +} + +void Wizard::MainWizard::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + qDebug() << "importer finished!"; + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return; + + // Re-read the settings + setupGameSettings(); +} + void Wizard::MainWizard::accept() { writeSettings(); diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 63b7e62e8..ed1a38879 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -1,6 +1,7 @@ #ifndef MAINWIZARD_HPP #define MAINWIZARD_HPP +#include #include #include @@ -41,7 +42,7 @@ namespace Wizard void addInstallation(const QString &path); void runSettingsImporter(); - QMap mInstallations; + QMap mInstallations; Files::ConfigurationManager mCfgMgr; @@ -66,6 +67,10 @@ namespace Wizard QTextStream *mLog; private slots: + + void importerStarted(); + void importerFinished(int exitCode, QProcess::ExitStatus exitStatus); + void accept(); void reject(); From af883991e2d3410ec67be345c0065f3ee332dcb1 Mon Sep 17 00:00:00 2001 From: pvdk Date: Thu, 17 Apr 2014 02:17:18 +0200 Subject: [PATCH 0054/1983] Reimplemented the old new profile dialog behaviour --- apps/launcher/datafilespage.cpp | 26 +++++++++++++++++++++----- apps/launcher/datafilespage.hpp | 4 +++- apps/launcher/settingspage.cpp | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 21652a8f4..6b53d89a4 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -30,6 +30,11 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: setObjectName ("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); + mProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + + connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), + this, SLOT(updateOkButton(QString))); + buildView(); setupDataFiles(); } @@ -212,15 +217,13 @@ void Launcher::DataFilesPage::slotProfileChanged(int index) void Launcher::DataFilesPage::on_newProfileAction_triggered() { - TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); - - if (newDialog.exec() != QDialog::Accepted) + if (!mProfileDialog->exec() == QDialog::Accepted) return; - QString profile = newDialog.lineEdit()->text(); + QString profile = mProfileDialog->lineEdit()->text(); if (profile.isEmpty()) - return; + return; saveSettings(); @@ -271,6 +274,19 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() checkForDefaultProfile(); } +void Launcher::DataFilesPage::updateOkButton(const QString &text) +{ + // We do this here because we need the profiles combobox text + if (text.isEmpty()) { + mProfileDialog->setOkButtonEnabled(false); + return; + } + + (ui.profilesComboBox->findText(text) == -1) + ? mProfileDialog->setOkButtonEnabled(true) + : mProfileDialog->setOkButtonEnabled(false); +} + void Launcher::DataFilesPage::checkForDefaultProfile() { //don't allow deleting "Default" profile diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 06efdfca3..908394ae1 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -55,12 +55,14 @@ namespace Launcher void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); + void updateOkButton(const QString &text); + void on_newProfileAction_triggered(); void on_deleteProfileAction_triggered(); private: - QMenu *mContextMenu; + TextInputDialog *mProfileDialog; Files::ConfigurationManager &mCfgMgr; diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index f673917fd..8eaec95db 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -222,7 +222,7 @@ void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus void Launcher::SettingsPage::updateOkButton(const QString &text) { - // We do this here because we need the profiles combobox text + // We do this here because we need to access the profiles if (text.isEmpty()) { mProfileDialog->setOkButtonEnabled(false); return; From aa07a33906bf4b75c27dba1394b7c3d6b3870173 Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 18 Apr 2014 13:17:37 +0200 Subject: [PATCH 0055/1983] Fixed most of the memory leaks and started working on correctly exiting the thread --- apps/launcher/maindialog.cpp | 49 +++++++------ apps/launcher/maindialog.hpp | 6 ++ apps/launcher/settingspage.cpp | 10 ++- apps/launcher/settingspage.hpp | 1 + apps/wizard/componentselectionpage.cpp | 7 +- apps/wizard/componentselectionpage.hpp | 2 +- apps/wizard/conclusionpage.cpp | 7 +- apps/wizard/conclusionpage.hpp | 2 +- apps/wizard/existinginstallationpage.cpp | 17 ++++- apps/wizard/existinginstallationpage.hpp | 5 +- apps/wizard/importpage.cpp | 7 +- apps/wizard/importpage.hpp | 2 +- apps/wizard/installationpage.cpp | 90 ++++++++++++++---------- apps/wizard/installationpage.hpp | 3 +- apps/wizard/installationtargetpage.cpp | 7 +- apps/wizard/installationtargetpage.hpp | 2 +- apps/wizard/intropage.cpp | 7 +- apps/wizard/intropage.hpp | 2 +- apps/wizard/languageselectionpage.cpp | 7 +- apps/wizard/languageselectionpage.hpp | 2 +- apps/wizard/mainwizard.cpp | 80 +++++++++++++-------- apps/wizard/mainwizard.hpp | 7 +- apps/wizard/methodselectionpage.cpp | 7 +- apps/wizard/methodselectionpage.hpp | 2 +- apps/wizard/unshield/unshieldworker.cpp | 23 +++++- apps/wizard/unshield/unshieldworker.hpp | 4 ++ components/process/processinvoker.cpp | 16 ++--- components/process/processinvoker.hpp | 2 +- files/ui/wizard/importpage.ui | 7 +- 29 files changed, 246 insertions(+), 137 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 9c6e33e1b..918cd3d41 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,7 +1,6 @@ #include "maindialog.hpp" #include -#include #include #include @@ -35,27 +34,10 @@ using namespace Process; Launcher::MainDialog::MainDialog(QWidget *parent) : mGameSettings(mCfgMgr), QMainWindow (parent) { - // Install the stylesheet font - QFile file; - QFontDatabase fontDatabase; - - const QStringList fonts = fontDatabase.families(); - - // Check if the font is installed - if (!fonts.contains("EB Garamond")) { - - QString font = QString::fromStdString(mCfgMgr.getGlobalDataPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf"); - file.setFileName(font); - - if (!file.exists()) { - font = QString::fromStdString(mCfgMgr.getLocalPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf"); - } - - fontDatabase.addApplicationFont(font); - } - setupUi(this); + mGameInvoker = new ProcessInvoker(); + iconWidget->setViewMode(QListView::IconMode); iconWidget->setWrapping(false); iconWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Just to be sure @@ -94,9 +76,33 @@ Launcher::MainDialog::MainDialog(QWidget *parent) QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); } + // Install the stylesheet font + QFile file; + QFontDatabase fontDatabase; + + const QStringList fonts = fontDatabase.families(); + + // Check if the font is installed + if (!fonts.contains("EB Garamond")) { + + QString font = QString::fromStdString(mCfgMgr.getGlobalDataPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf"); + file.setFileName(font); + + if (!file.exists()) { + font = QString::fromStdString(mCfgMgr.getLocalPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf"); + } + + fontDatabase.addApplicationFont(font); + } + createIcons(); } +Launcher::MainDialog::~MainDialog() +{ + delete mGameInvoker; +} + void Launcher::MainDialog::createIcons() { if (!QIcon::hasThemeIcon("document-new")) @@ -801,8 +807,7 @@ void Launcher::MainDialog::play() } // Launch the game detached - ProcessInvoker invoker(this); - if (invoker.startProcess(QLatin1String("openmw"), true)) + if (mGameInvoker->startProcess(QLatin1String("openmw"), true)) qApp->quit(); } diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 7f468de9c..fefbaecaf 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -5,6 +5,9 @@ #ifndef Q_MOC_RUN #include #endif + +#include + #include #include @@ -36,6 +39,8 @@ namespace Launcher public: explicit MainDialog(QWidget *parent = 0); + ~MainDialog(); + bool setup(); bool showFirstRunDialog(); @@ -67,6 +72,7 @@ namespace Launcher DataFilesPage *mDataFilesPage; SettingsPage *mSettingsPage; + Process::ProcessInvoker *mGameInvoker; Files::ConfigurationManager mCfgMgr; diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 8eaec95db..fe9369769 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -36,8 +36,8 @@ Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, languageComboBox->addItems(languages); - mWizardInvoker = new ProcessInvoker(this); - mImporterInvoker = new ProcessInvoker(this); + mWizardInvoker = new ProcessInvoker(); + mImporterInvoker = new ProcessInvoker(); connect(mWizardInvoker->getProcess(), SIGNAL(started()), this, SLOT(wizardStarted())); @@ -85,6 +85,12 @@ Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, loadSettings(); } +Launcher::SettingsPage::~SettingsPage() +{ + delete mWizardInvoker; + delete mImporterInvoker; +} + void Launcher::SettingsPage::on_wizardButton_clicked() { saveSettings(); diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index 846f5681c..124c80600 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -25,6 +25,7 @@ namespace Launcher public: SettingsPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, MainDialog *parent = 0); + ~SettingsPage(); void saveSettings(); bool loadSettings(); diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index ac084616c..d372f677d 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -7,10 +7,11 @@ #include "mainwizard.hpp" -Wizard::ComponentSelectionPage::ComponentSelectionPage(MainWizard *wizard) : - QWizardPage(wizard), - mWizard(wizard) +Wizard::ComponentSelectionPage::ComponentSelectionPage(QWidget *parent) : + QWizardPage(parent) { + mWizard = qobject_cast(parent); + setupUi(this); setCommitPage(true); diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp index ed007fd08..ca4347108 100644 --- a/apps/wizard/componentselectionpage.hpp +++ b/apps/wizard/componentselectionpage.hpp @@ -13,7 +13,7 @@ namespace Wizard { Q_OBJECT public: - ComponentSelectionPage(MainWizard *wizard); + ComponentSelectionPage(QWidget *parent); int nextId() const; virtual bool validatePage(); diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 4407ac547..87154732a 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -4,10 +4,11 @@ #include "mainwizard.hpp" -Wizard::ConclusionPage::ConclusionPage(MainWizard *wizard) : - QWizardPage(wizard), - mWizard(wizard) +Wizard::ConclusionPage::ConclusionPage(QWidget *parent) : + QWizardPage(parent) { + mWizard = qobject_cast(parent); + setupUi(this); setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/images/intropage-background.png"))); } diff --git a/apps/wizard/conclusionpage.hpp b/apps/wizard/conclusionpage.hpp index e050684e1..0e9abed72 100644 --- a/apps/wizard/conclusionpage.hpp +++ b/apps/wizard/conclusionpage.hpp @@ -13,7 +13,7 @@ namespace Wizard { Q_OBJECT public: - ConclusionPage(MainWizard *wizard); + ConclusionPage(QWidget *parent); int nextId() const; diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 0cbdce673..7c5d10a80 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -8,16 +8,27 @@ #include "mainwizard.hpp" -Wizard::ExistingInstallationPage::ExistingInstallationPage(MainWizard *wizard) : - QWizardPage(wizard), - mWizard(wizard) +Wizard::ExistingInstallationPage::ExistingInstallationPage(QWidget *parent) : + QWizardPage(parent) { + mWizard = qobject_cast(parent); + setupUi(this); +} + +void Wizard::ExistingInstallationPage::initializePage() +{ QListWidgetItem *emptyItem = new QListWidgetItem(tr("No existing installations detected")); emptyItem->setFlags(Qt::NoItemFlags); installationsList->addItem(emptyItem); + // Test + if (mWizard->mInstallations.isEmpty()) { + qDebug() << "crashy crash"; + return; + } + // Add the available installation paths QStringList paths(mWizard->mInstallations.keys()); diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp index 54ee8d2e2..601295464 100644 --- a/apps/wizard/existinginstallationpage.hpp +++ b/apps/wizard/existinginstallationpage.hpp @@ -13,7 +13,7 @@ namespace Wizard { Q_OBJECT public: - ExistingInstallationPage(MainWizard *wizard); + ExistingInstallationPage(QWidget *parent); int nextId() const; virtual bool isComplete() const; @@ -27,6 +27,9 @@ namespace Wizard private: MainWizard *mWizard; + protected: + void initializePage(); + }; } diff --git a/apps/wizard/importpage.cpp b/apps/wizard/importpage.cpp index b49105faa..71c5544e6 100644 --- a/apps/wizard/importpage.cpp +++ b/apps/wizard/importpage.cpp @@ -2,10 +2,11 @@ #include "mainwizard.hpp" -Wizard::ImportPage::ImportPage(MainWizard *wizard) : - QWizardPage(wizard), - mWizard(wizard) +Wizard::ImportPage::ImportPage(QWidget *parent) : + QWizardPage(parent) { + mWizard = qobject_cast(parent); + setupUi(this); registerField(QLatin1String("installation.import-settings"), importCheckBox); diff --git a/apps/wizard/importpage.hpp b/apps/wizard/importpage.hpp index 2eae08531..386cd59af 100644 --- a/apps/wizard/importpage.hpp +++ b/apps/wizard/importpage.hpp @@ -13,7 +13,7 @@ namespace Wizard { Q_OBJECT public: - ImportPage(MainWizard *wizard); + ImportPage(QWidget *parent); int nextId() const; diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index f59b6d640..b76aeea22 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -9,48 +9,14 @@ #include "mainwizard.hpp" #include "inisettings.hpp" -Wizard::InstallationPage::InstallationPage(MainWizard *wizard) : - QWizardPage(wizard), - mWizard(wizard) +Wizard::InstallationPage::InstallationPage(QWidget *parent) : + QWizardPage(parent) { + mWizard = qobject_cast(parent); + setupUi(this); mFinished = false; -} - -void Wizard::InstallationPage::initializePage() -{ - QString path(field(QLatin1String("installation.path")).toString()); - QStringList components(field(QLatin1String("installation.components")).toStringList()); - - logTextEdit->appendPlainText(QString("Installing to %1").arg(path)); - logTextEdit->appendPlainText(QString("Installing %1.").arg(components.join(", "))); - - installProgressBar->setMinimum(0); - - // Set the progressbar maximum to a multiple of 100 - // That way installing all three components would yield 300% - // When one component is done the bar will be filled by 33% - - if (field(QLatin1String("installation.new")).toBool() == true) { - installProgressBar->setMaximum((components.count() * 100)); - } else { - if (components.contains(QLatin1String("Tribunal")) - && !mWizard->mInstallations[path].hasTribunal) - installProgressBar->setMaximum(100); - - if (components.contains(QLatin1String("Bloodmoon")) - && !mWizard->mInstallations[path].hasBloodmoon) - installProgressBar->setMaximum(installProgressBar->maximum() + 100); - } - - startInstallation(); -} - -void Wizard::InstallationPage::startInstallation() -{ - QStringList components(field(QLatin1String("installation.components")).toStringList()); - QString path(field(QLatin1String("installation.path")).toString()); mThread = new QThread(); mUnshield = new UnshieldWorker(); @@ -88,6 +54,54 @@ void Wizard::InstallationPage::startInstallation() connect(mUnshield, SIGNAL(requestFileDialog(Wizard::Component)), this, SLOT(showFileDialog(Wizard::Component)), Qt::QueuedConnection); +} + +Wizard::InstallationPage::~InstallationPage() +{ + qDebug() << "stop!"; + + if (mThread->isRunning()) { + mUnshield->stopWorker(); + mThread->wait(); + } + + delete mUnshield; + delete mThread; +} + +void Wizard::InstallationPage::initializePage() +{ + QString path(field(QLatin1String("installation.path")).toString()); + QStringList components(field(QLatin1String("installation.components")).toStringList()); + + logTextEdit->appendPlainText(QString("Installing to %1").arg(path)); + logTextEdit->appendPlainText(QString("Installing %1.").arg(components.join(", "))); + + installProgressBar->setMinimum(0); + + // Set the progressbar maximum to a multiple of 100 + // That way installing all three components would yield 300% + // When one component is done the bar will be filled by 33% + + if (field(QLatin1String("installation.new")).toBool() == true) { + installProgressBar->setMaximum((components.count() * 100)); + } else { + if (components.contains(QLatin1String("Tribunal")) + && !mWizard->mInstallations[path].hasTribunal) + installProgressBar->setMaximum(100); + + if (components.contains(QLatin1String("Bloodmoon")) + && !mWizard->mInstallations[path].hasBloodmoon) + installProgressBar->setMaximum(installProgressBar->maximum() + 100); + } + + startInstallation(); +} + +void Wizard::InstallationPage::startInstallation() +{ + QStringList components(field(QLatin1String("installation.components")).toStringList()); + QString path(field(QLatin1String("installation.path")).toString()); if (field(QLatin1String("installation.new")).toBool() == true) { diff --git a/apps/wizard/installationpage.hpp b/apps/wizard/installationpage.hpp index 5e58477be..822cd21cd 100644 --- a/apps/wizard/installationpage.hpp +++ b/apps/wizard/installationpage.hpp @@ -19,7 +19,8 @@ namespace Wizard { Q_OBJECT public: - InstallationPage(MainWizard *wizard); + InstallationPage(QWidget *parent); + ~InstallationPage(); int nextId() const; virtual bool isComplete() const; diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index d89d54a00..3a179a2cb 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -6,11 +6,12 @@ #include "mainwizard.hpp" -Wizard::InstallationTargetPage::InstallationTargetPage(MainWizard *wizard, const Files::ConfigurationManager &cfg) : - QWizardPage(wizard), - mWizard(wizard), +Wizard::InstallationTargetPage::InstallationTargetPage(QWidget *parent, const Files::ConfigurationManager &cfg) : + QWizardPage(parent), mCfgMgr(cfg) { + mWizard = qobject_cast(parent); + setupUi(this); registerField(QLatin1String("installation.path*"), targetLineEdit); diff --git a/apps/wizard/installationtargetpage.hpp b/apps/wizard/installationtargetpage.hpp index 0711c1c05..ca3b505b7 100644 --- a/apps/wizard/installationtargetpage.hpp +++ b/apps/wizard/installationtargetpage.hpp @@ -18,7 +18,7 @@ namespace Wizard { Q_OBJECT public: - InstallationTargetPage(MainWizard *wizard, const Files::ConfigurationManager &cfg); + InstallationTargetPage(QWidget *parent, const Files::ConfigurationManager &cfg); int nextId() const; virtual bool validatePage(); diff --git a/apps/wizard/intropage.cpp b/apps/wizard/intropage.cpp index 91e7d5dc0..0a98ae5f3 100644 --- a/apps/wizard/intropage.cpp +++ b/apps/wizard/intropage.cpp @@ -2,10 +2,11 @@ #include "mainwizard.hpp" -Wizard::IntroPage::IntroPage(MainWizard *wizard) : - QWizardPage(wizard), - mWizard(wizard) +Wizard::IntroPage::IntroPage(QWidget *parent) : + QWizardPage(parent) { + mWizard = qobject_cast(parent); + setupUi(this); setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/images/intropage-background.png"))); } diff --git a/apps/wizard/intropage.hpp b/apps/wizard/intropage.hpp index 78bac3155..4ad9b4111 100644 --- a/apps/wizard/intropage.hpp +++ b/apps/wizard/intropage.hpp @@ -13,7 +13,7 @@ namespace Wizard { Q_OBJECT public: - IntroPage(MainWizard *wizard); + IntroPage(QWidget *parent); int nextId() const; diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index fbd08ffcd..0d5132f5b 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -4,10 +4,11 @@ #include -Wizard::LanguageSelectionPage::LanguageSelectionPage(MainWizard *wizard) : - QWizardPage(wizard), - mWizard(wizard) +Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget *parent) : + QWizardPage(parent) { + mWizard = qobject_cast(parent); + setupUi(this); registerField(QLatin1String("installation.language"), languageComboBox); diff --git a/apps/wizard/languageselectionpage.hpp b/apps/wizard/languageselectionpage.hpp index 3c17514f9..abc3edb98 100644 --- a/apps/wizard/languageselectionpage.hpp +++ b/apps/wizard/languageselectionpage.hpp @@ -13,7 +13,7 @@ namespace Wizard { Q_OBJECT public: - LanguageSelectionPage(MainWizard *wizard); + LanguageSelectionPage(QWidget *parent); int nextId() const; diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 2a4d9f70c..92743417e 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -1,7 +1,5 @@ #include "mainwizard.hpp" -#include - #include #include @@ -25,7 +23,9 @@ using namespace Process; Wizard::MainWizard::MainWizard(QWidget *parent) : mGameSettings(mCfgMgr), - QWizard(parent) + QWizard(parent), + mError(false), + mInstallations() { #ifndef Q_OS_MAC setWizardStyle(QWizard::ModernStyle); @@ -37,11 +37,26 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setWindowIcon(QIcon(QLatin1String(":/images/openmw-wizard.png"))); setMinimumWidth(550); + // This prevents initializePage() being called multiple times + setOption(QWizard::IndependentPages); + // Set the property for comboboxes to the text instead of index setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); setDefaultProperty("ComponentListWidget", "mCheckedItems", "checkedItemsChanged"); + mImporterInvoker = new ProcessInvoker(); + + connect(mImporterInvoker->getProcess(), SIGNAL(started()), + this, SLOT(importerStarted())); + + connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(importerFinished(int,QProcess::ExitStatus))); + + mLogError = tr("

Could not open %1 for writing

\ +

Please make sure you have the right permissions \ + and try again.

"); + setupLog(); setupGameSettings(); setupLauncherSettings(); @@ -49,44 +64,58 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setupPages(); } +Wizard::MainWizard::~MainWizard() +{ + delete mImporterInvoker; +} + void Wizard::MainWizard::setupLog() { QString logPath(QString::fromUtf8(mCfgMgr.getLogPath().string().c_str())); logPath.append(QLatin1String("wizard.log")); - QString message(tr("

Could not open %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

")); - - QFile *file = new QFile(logPath); + QFile file(logPath); - if (!file->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error opening Wizard log file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(message.arg(file->fileName())); + msgBox.setText(mLogError.arg(file.fileName())); msgBox.exec(); return qApp->quit(); } - qDebug() << logPath; - - mLog = new QTextStream(file); - mLog->setCodec(QTextCodec::codecForName("UTF-8")); + addLogText(QString("Started OpenMW Wizard on %1").arg(QDateTime::currentDateTime().toString())); - //addLogText(QLatin1String("test test 123 test")); + qDebug() << logPath; } void Wizard::MainWizard::addLogText(const QString &text) { + QString logPath(QString::fromUtf8(mCfgMgr.getLogPath().string().c_str())); + logPath.append(QLatin1String("wizard.log")); + + QFile file(logPath); - qDebug() << "AddLogText called! " << text; - if (!text.isEmpty()) { - qDebug() << "logging " << text; - *mLog << text << endl; + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening Wizard log file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(mLogError.arg(file.fileName())); + msgBox.exec(); + return qApp->quit(); } + if (!file.isSequential()) + file.seek(file.size()); + + QTextStream out(&file); + + if (!text.isEmpty()) + out << text << endl; + // file.close(); } @@ -246,16 +275,8 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--cfg")); arguments.append(userPath + QLatin1String("openmw.cfg")); - ProcessInvoker *invoker = new ProcessInvoker(this); - - if (!invoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) - return qApp->quit();; - - connect(invoker->getProcess(), SIGNAL(started()), - this, SLOT(importerStarted())); - - connect(invoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(importerFinished(int,QProcess::ExitStatus))); + if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) + return qApp->quit(); // Re-read the game settings // setupGameSettings(); @@ -305,6 +326,7 @@ void Wizard::MainWizard::setupPages() setPage(Page_Import, new ImportPage(this)); setPage(Page_Conclusion, new ConclusionPage(this)); setStartId(Page_Intro); + } void Wizard::MainWizard::importerStarted() diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index ed1a38879..6b8c4931f 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include #include @@ -37,6 +39,7 @@ namespace Wizard }; MainWizard(QWidget *parent = 0); + ~MainWizard(); bool findFiles(const QString &name, const QString &path); void addInstallation(const QString &path); @@ -46,6 +49,8 @@ namespace Wizard Files::ConfigurationManager mCfgMgr; + Process::ProcessInvoker *mImporterInvoker; + bool mError; public slots: @@ -64,7 +69,7 @@ namespace Wizard Config::GameSettings mGameSettings; Config::LauncherSettings mLauncherSettings; - QTextStream *mLog; + QString mLogError; private slots: diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index b1483ac3a..d7c64f3b0 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -2,10 +2,11 @@ #include #include "mainwizard.hpp" -Wizard::MethodSelectionPage::MethodSelectionPage(MainWizard *wizard) : - QWizardPage(wizard), - mWizard(wizard) +Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : + QWizardPage(parent) { + mWizard = qobject_cast(parent); + setupUi(this); registerField(QLatin1String("installation.new"), newLocationRadioButton); diff --git a/apps/wizard/methodselectionpage.hpp b/apps/wizard/methodselectionpage.hpp index de34ff555..60941c651 100644 --- a/apps/wizard/methodselectionpage.hpp +++ b/apps/wizard/methodselectionpage.hpp @@ -13,7 +13,7 @@ namespace Wizard { Q_OBJECT public: - MethodSelectionPage(MainWizard *wizard); + MethodSelectionPage(QWidget *parent); int nextId() const; diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 81282f547..5fa043347 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -35,6 +35,8 @@ Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : mTribunalDone = false; mBloodmoonDone = false; + mStopped = false; + qRegisterMetaType("Wizard::Component"); } @@ -42,6 +44,13 @@ Wizard::UnshieldWorker::~UnshieldWorker() { } +void Wizard::UnshieldWorker::stopWorker() +{ + mMutex.lock(); + mStopped = true; + mMutex.unlock(); +} + void Wizard::UnshieldWorker::setInstallComponent(Wizard::Component component, bool install) { QWriteLocker writeLock(&mLock); @@ -781,6 +790,13 @@ bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &d for (size_t j=group->first_file; j<=group->last_file; ++j) { + if (mStopped) { + qDebug() << "We're asked to stop!"; + + unshield_close(unshield); + return true; + } + if (unshield_file_is_valid(unshield, j)) { success = extractFile(unshield, destination, group->name, j, counter); @@ -789,6 +805,8 @@ bool Wizard::UnshieldWorker::extractCab(const QString &cabFile, const QString &d emit error(tr("Failed to extract %1.").arg(name), tr("Complete path: %1").arg(destination + QDir::separator() + name)); + + unshield_close(unshield); return false; } @@ -892,11 +910,14 @@ bool Wizard::UnshieldWorker::findInCab(const QString &fileName, const QString &c if (unshield_file_is_valid(unshield, j)) { QString current(QString::fromUtf8(unshield_file_name(unshield, j))); - if (current.toLower() == fileName.toLower()) + if (current.toLower() == fileName.toLower()) { + unshield_close(unshield); return true; // File is found! + } } } } + unshield_close(unshield); return false; } diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index 2e509d116..5ea7b04ae 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -30,6 +30,8 @@ namespace Wizard UnshieldWorker(QObject *parent = 0); ~UnshieldWorker(); + void stopWorker(); + void setInstallComponent(Wizard::Component component, bool install); void setDiskPath(const QString &path); @@ -92,6 +94,8 @@ namespace Wizard bool mTribunalDone; bool mBloodmoonDone; + bool mStopped; + QString mPath; QString mIniPath; QString mDiskPath; diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp index 7ac8d3c93..44a11cc4e 100644 --- a/components/process/processinvoker.cpp +++ b/components/process/processinvoker.cpp @@ -8,10 +8,17 @@ #include #include -Process::ProcessInvoker::ProcessInvoker(QWidget *parent) +Process::ProcessInvoker::ProcessInvoker() { mProcess = new QProcess(this); + connect(mProcess, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(processError(QProcess::ProcessError))); + + connect(mProcess, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(processFinished(int,QProcess::ExitStatus))); + + mName = QString(); mArguments = QStringList(); } @@ -48,13 +55,6 @@ QProcess* Process::ProcessInvoker::getProcess() bool Process::ProcessInvoker::startProcess(const QString &name, const QStringList &arguments, bool detached) { // mProcess = new QProcess(this); - - connect(mProcess, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(processError(QProcess::ProcessError))); - - connect(mProcess, SIGNAL(finished(int,QProcess::ExitStatus)), - this, SLOT(processFinished(int,QProcess::ExitStatus))); - mName = name; mArguments = arguments; diff --git a/components/process/processinvoker.hpp b/components/process/processinvoker.hpp index 07907e3b4..8fff6658c 100644 --- a/components/process/processinvoker.hpp +++ b/components/process/processinvoker.hpp @@ -13,7 +13,7 @@ namespace Process public: - ProcessInvoker(QWidget *parent = 0); + ProcessInvoker(); ~ProcessInvoker(); // void setProcessName(const QString &name); diff --git a/files/ui/wizard/importpage.ui b/files/ui/wizard/importpage.ui index 52f9ac4f2..9fd4ea500 100644 --- a/files/ui/wizard/importpage.ui +++ b/files/ui/wizard/importpage.ui @@ -6,8 +6,8 @@ 0 0 - 510 - 324 + 508 + 322
@@ -45,6 +45,9 @@ Import add-on and plugin selection + + true + From 5af12d193acfe5f0470ed947cffff1072b56daf3 Mon Sep 17 00:00:00 2001 From: pvdk Date: Thu, 29 May 2014 17:57:41 +0200 Subject: [PATCH 0056/1983] Cleaned up old wizard stuff from launcher --- CMakeLists.txt | 16 +- apps/launcher/CMakeLists.txt | 24 +- apps/launcher/main.cpp | 9 +- apps/launcher/maindialog.cpp | 393 +++++---------- apps/launcher/maindialog.hpp | 7 + apps/launcher/settingspage.cpp | 6 +- apps/launcher/unshieldthread.cpp | 521 -------------------- apps/launcher/unshieldthread.hpp | 58 --- apps/launcher/utils/checkablemessagebox.cpp | 258 ---------- apps/launcher/utils/checkablemessagebox.hpp | 116 ----- 10 files changed, 144 insertions(+), 1264 deletions(-) delete mode 100644 apps/launcher/unshieldthread.cpp delete mode 100644 apps/launcher/unshieldthread.hpp delete mode 100644 apps/launcher/utils/checkablemessagebox.cpp delete mode 100644 apps/launcher/utils/checkablemessagebox.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a9f618d1..66746b22c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -406,7 +406,7 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/wizard" DESTINATION "${BINDIR}" ) ENDIF(BUILD_WIZARD) - + # Install licenses INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) @@ -460,7 +460,7 @@ if(WIN32) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-wizard.exe" DESTINATION ".") ENDIF(BUILD_WIZARD) - + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") @@ -543,13 +543,6 @@ if (BUILD_ESMTOOL) endif() if (BUILD_LAUNCHER) - if(NOT WIN32) - find_package(LIBUNSHIELD REQUIRED) - if(NOT LIBUNSHIELD_FOUND) - message(SEND_ERROR "Failed to find libunshield") - endif(NOT LIBUNSHIELD_FOUND) - endif(NOT WIN32) - add_subdirectory( apps/launcher ) endif() @@ -562,6 +555,11 @@ if (BUILD_OPENCS) endif() if (BUILD_WIZARD) + find_package(LIBUNSHIELD REQUIRED) + if(NOT LIBUNSHIELD_FOUND) + message(FATAL_ERROR "Failed to find Unshield library") + endif(NOT LIBUNSHIELD_FOUND) + add_subdirectory(apps/wizard) endif() diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index b777856e8..eb11cab49 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -9,16 +9,12 @@ set(LAUNCHER settings/graphicssettings.cpp - utils/checkablemessagebox.cpp utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc ) -if(NOT WIN32) - LIST(APPEND LAUNCHER unshieldthread.cpp) -endif(NOT WIN32) set(LAUNCHER_HEADER datafilespage.hpp @@ -30,15 +26,10 @@ set(LAUNCHER_HEADER settings/graphicssettings.hpp - utils/checkablemessagebox.hpp utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp ) -if(NOT WIN32) - LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp) -endif(NOT WIN32) - # Headers that must be pre-processed set(LAUNCHER_HEADER_MOC @@ -50,17 +41,11 @@ set(LAUNCHER_HEADER_MOC settingspage.hpp utils/textinputdialog.hpp - utils/checkablemessagebox.hpp utils/profilescombobox.hpp utils/lineedit.hpp ) -if(NOT WIN32) - LIST(APPEND LAUNCHER_HEADER_MOC unshieldthread.hpp) -endif(NOT WIN32) - - set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui @@ -119,19 +104,12 @@ target_link_libraries(omwlauncher ${QT_LIBRARIES} components ) -if(NOT WIN32) - target_link_libraries(omwlauncher - ${LIBUNSHIELD_LIBRARY} - ) -endif(NOT WIN32) - - if(DPKG_PROGRAM) INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher) endif() -if (BUILD_WITH_CODE_COVERAGE) +if(BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(omwlauncher gcov) endif() diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index fabf77d90..67320bff0 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -52,11 +52,12 @@ int main(int argc, char *argv[]) Launcher::MainDialog mainWin; - if (mainWin.setup()) { - mainWin.show(); - } else { + if (!mainWin.setup()) { return 0; - } + //mainWin.show(); + }/* else { + return 0; + }*/ int returnValue = app.exec(); SDL_Quit(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 3f9f27554..76b9ad3c5 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -16,14 +17,6 @@ #include -#ifndef WIN32 - #include "unshieldthread.hpp" -#endif - -#include "textslotmsgbox.hpp" - -#include "utils/checkablemessagebox.hpp" - #include "playpage.hpp" #include "graphicspage.hpp" #include "datafilespage.hpp" @@ -56,6 +49,10 @@ Launcher::MainDialog::MainDialog(QWidget *parent) setupUi(this); mGameInvoker = new ProcessInvoker(); + mWizardInvoker = new ProcessInvoker(); + + connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(wizardFinished(int,QProcess::ExitStatus))); iconWidget->setViewMode(QListView::IconMode); iconWidget->setWrapping(false); @@ -83,13 +80,13 @@ Launcher::MainDialog::MainDialog(QWidget *parent) if (!revision.isEmpty() && !tag.isEmpty()) { if (revision == tag) { - versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION)); + versionLabel->setText(tr("OpenMW %1 release").arg(OPENMW_VERSION)); } else { - versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10))); + versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10))); } // Add the compile date and time - versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), + versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); @@ -101,6 +98,7 @@ Launcher::MainDialog::MainDialog(QWidget *parent) Launcher::MainDialog::~MainDialog() { delete mGameInvoker; + delete mWizardInvoker; } void Launcher::MainDialog::createIcons() @@ -167,102 +165,32 @@ void Launcher::MainDialog::createPages() bool Launcher::MainDialog::showFirstRunDialog() { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("First run")); + msgBox.setIcon(QMessageBox::Question); + msgBox.setStandardButtons(QMessageBox::NoButton); + msgBox.setText(tr("

Welcome to OpenMW!

\ +

It is recommended to run the Installation Wizard.

\ +

The Wizard will let you select an existing Morrowind installation, \ + or install Morrowind for OpenMW to use.

")); + QAbstractButton *wizardButton = + msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! + QAbstractButton *skipButton = + msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); + Q_UNUSED(skipButton); // Surpress compiler unused warning + + msgBox.exec(); + + if (msgBox.clickedButton() == wizardButton) + { + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) { + return false; + } else { + return true; + } + } -// CheckableMessageBox msgBox(this); -// msgBox.setWindowTitle(tr("Morrowind installation detected")); - -// QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); -// int size = QApplication::style()->pixelMetric(QStyle::PM_MessageBoxIconSize); -// msgBox.setIconPixmap(icon.pixmap(size, size)); - -// QAbstractButton *importerButton = -// msgBox.addButton(tr("Import"), QDialogButtonBox::AcceptRole); // ActionRole doesn't work?! -// QAbstractButton *skipButton = -// msgBox.addButton(tr("Skip"), QDialogButtonBox::RejectRole); - -// Q_UNUSED(skipButton); // Surpress compiler unused warning - -// msgBox.setStandardButtons(QDialogButtonBox::NoButton); -// msgBox.setText(tr("
An existing Morrowind configuration was detected
\ -//
Would you like to import settings from Morrowind.ini?
\ -//
Warning: In most cases OpenMW needs these settings to run properly
")); -// msgBox.setCheckBoxText(tr("Include selected masters and plugins (creates a new profile)")); -// msgBox.exec(); - - -// if (msgBox.clickedButton() == importerButton) { - -// if (iniPaths.count() > 1) { -// // Multiple Morrowind.ini files found -// bool ok; -// QString path = QInputDialog::getItem(this, tr("Multiple configurations found"), -// tr("
There are multiple Morrowind.ini files found.

\ -// Please select the one you wish to import from:"), iniPaths, 0, false, &ok); -// if (ok && !path.isEmpty()) { -// iniPaths.clear(); -// iniPaths.append(path); -// } else { -// // Cancel was clicked -// return false; -// } -// } - -// // Create the file if it doesn't already exist, else the importer will fail -// QString path = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + QString("openmw.cfg"); -// QFile file(path); - -// if (!file.exists()) { -// if (!file.open(QIODevice::ReadWrite)) { -// // File cannot be created -// QMessageBox msgBox; -// msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); -// msgBox.setIcon(QMessageBox::Critical); -// msgBox.setStandardButtons(QMessageBox::Ok); -// msgBox.setText(tr("
Could not open or create %0 for writing

\ -// Please make sure you have the right permissions \ -// and try again.
").arg(file.fileName())); -// msgBox.exec(); -// return false; -// } - -// file.close(); -// } - -// // Construct the arguments to run the importer -// QStringList arguments; - -// if (msgBox.isChecked()) -// arguments.append(QString("--game-files")); - -// arguments.append(QString("--encoding")); -// arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); -// arguments.append(QString("--ini")); -// arguments.append(iniPaths.first()); -// arguments.append(QString("--cfg")); -// arguments.append(path); - -// ProcessInvoker invoker(this); - -// if (!invoker.startProcess(QLatin1String("mwiniimport"), arguments, false)) -// return false; - -// // Re-read the game settings -// if (!setupGameSettings()) -// return false; - -// // Add a new profile -// if (msgBox.isChecked()) { -// mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported")); -// mLauncherSettings.remove(QString("Profiles/Imported/content")); - -// QStringList contents = mGameSettings.values(QString("content")); -// foreach (const QString &content, contents) { -// mLauncherSettings.setMultiValue(QString("Profiles/Imported/content"), content); -// } -// } - -// } - + show(); return true; } @@ -277,13 +205,6 @@ bool Launcher::MainDialog::setup() if (!setupGraphicsSettings()) return false; - // Check if we need to show the importer - if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) - { - if (!showFirstRunDialog()) - return false; - } - // Now create the pages as they need the settings createPages(); @@ -292,6 +213,18 @@ bool Launcher::MainDialog::setup() return false; loadSettings(); + + // Check if we need to run the wizard + if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) + { + if (!showFirstRunDialog()) { + return false; + } else { + return true; + } + } + + show(); // Show ourselves if the wizard is not being run return true; } @@ -328,20 +261,20 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem pagesWidget->setCurrentIndex(currentIndex); - DataFilesPage *previousPage = dynamic_cast(pagesWidget->widget(previousIndex)); - DataFilesPage *currentPage = dynamic_cast(pagesWidget->widget(currentIndex)); - -// //special call to update/save data files page list view when it's displayed/hidden. -// if (previousPage) -// { -// if (previousPage->objectName() == "DataFilesPage") -// previousPage->saveSettings(); -// } -// else if (currentPage) -// { -// if (currentPage->objectName() == "DataFilesPage") -// currentPage->loadSettings(); -// } + // DataFilesPage *previousPage = dynamic_cast(pagesWidget->widget(previousIndex)); + // DataFilesPage *currentPage = dynamic_cast(pagesWidget->widget(currentIndex)); + + // //special call to update/save data files page list view when it's displayed/hidden. + // if (previousPage) + // { + // if (previousPage->objectName() == "DataFilesPage") + // previousPage->saveSettings(); + // } + // else if (currentPage) + // { + // if (currentPage->objectName() == "DataFilesPage") + // currentPage->loadSettings(); + // } } bool Launcher::MainDialog::setupLauncherSettings() @@ -363,10 +296,10 @@ bool Launcher::MainDialog::setupLauncherSettings() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + msgBox.setText(tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); return false; } QTextStream stream(&file); @@ -380,78 +313,6 @@ bool Launcher::MainDialog::setupLauncherSettings() return true; } -#ifndef WIN32 -bool Launcher::expansions(Launcher::UnshieldThread& cd) -{ - if(cd.BloodmoonDone()) - { - cd.Done(); - return false; - } - - QMessageBox expansionsBox; - expansionsBox.setText(QObject::tr("
Would you like to install expansions now ? (make sure you have the disc)
\ - If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.
")); - - QAbstractButton* tribunalButton = NULL; - if(!cd.TribunalDone()) - tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole); - - QAbstractButton* bloodmoonButton = expansionsBox.addButton(QObject::tr("&Bloodmoon"), QMessageBox::ActionRole); - QAbstractButton* noneButton = expansionsBox.addButton(QObject::tr("&None"), QMessageBox::ActionRole); - - expansionsBox.exec(); - - if(expansionsBox.clickedButton() == noneButton) - { - cd.Done(); - return false; - } - else if(expansionsBox.clickedButton() == tribunalButton) - { - - TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); - - QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); - QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); - - cd.SetTribunalPath( - QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select data1.hdr from Tribunal Installation CD (Tribunal/data1.hdr on GOTY CDs)"), - QDir::currentPath(), - QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); - - cd.start(); - cdbox.exec(); - } - else if(expansionsBox.clickedButton() == bloodmoonButton) - { - - TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); - - QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); - QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); - - cd.SetBloodmoonPath( - QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select data1.hdr from Bloodmoon Installation CD (Bloodmoon/data1.hdr on GOTY CDs)"), - QDir::currentPath(), - QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); - - cd.start(); - cdbox.exec(); - } - - - - return true; -} -#endif // WIN32 - bool Launcher::MainDialog::setupGameSettings() { QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); @@ -470,7 +331,7 @@ bool Launcher::MainDialog::setupGameSettings() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + msgBox.setText(tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); msgBox.exec(); @@ -498,7 +359,7 @@ bool Launcher::MainDialog::setupGameSettings() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + msgBox.setText(tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); msgBox.exec(); @@ -530,63 +391,35 @@ bool Launcher::MainDialog::setupGameSettings() msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(QObject::tr("
Could not find the Data Files location

\ + msgBox.setText(tr("
Could not find the Data Files location

\ The directory containing the data files was not found.

\ Press \"Browse...\" to specify the location manually.
")); - QAbstractButton *dirSelectButton = - msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole); - - #ifndef WIN32 - QAbstractButton *cdSelectButton = - msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); - #endif + QAbstractButton *browseButton = + msgBox.addButton(tr("Browse..."), QMessageBox::ActionRole); + QAbstractButton *wizardButton = + msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole); - msgBox.exec(); + msgBox.exec(); QString selectedFile; - if (msgBox.clickedButton() == dirSelectButton) { + if (msgBox.clickedButton() == browseButton) + { selectedFile = QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select master file"), + this, + tr("Select master file"), QDir::currentPath(), - QString(tr("Morrowind master file (*.esm)"))); + tr("Morrowind master file (*.esm)")); } - #ifndef WIN32 - else if(msgBox.clickedButton() == cdSelectButton) { - UnshieldThread cd; - - { - TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); - - QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); - QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); - - cd.SetMorrowindPath( - QFileDialog::getOpenFileName( - NULL, - QObject::tr("Select data1.hdr from Morrowind Installation CD"), - QDir::currentPath(), - QString(tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); - - cd.SetOutputPath( - QFileDialog::getExistingDirectory( - NULL, - QObject::tr("Select where to extract files to"), - QDir::currentPath(), - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData()); - - cd.start(); - cdbox.exec(); + else if (msgBox.clickedButton() == wizardButton) + { + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) { + return false; + } else { + return true; } - - while(expansions(cd)); - - selectedFile = QString::fromUtf8(cd.GetMWEsmPath().c_str()); } - #endif // WIN32 if (selectedFile.isEmpty()) return false; // Cancel was clicked; @@ -616,7 +449,7 @@ bool Launcher::MainDialog::setupGraphicsSettings() msgBox.setWindowTitle(tr("Error reading OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not find settings-default.cfg

\ + msgBox.setText(tr("
Could not find settings-default.cfg

\ The problem may be due to an incomplete installation of OpenMW.
\ Reinstalling OpenMW may resolve the problem.")); msgBox.exec(); @@ -638,7 +471,7 @@ bool Launcher::MainDialog::setupGraphicsSettings() msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + msgBox.setText(tr("
Could not open %0 for reading

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); msgBox.exec(); @@ -705,8 +538,8 @@ bool Launcher::MainDialog::writeSettings() msgBox.setText(tr("
Could not create %0

\ Please make sure you have the right permissions \ and try again.
").arg(userPath)); - msgBox.exec(); - return false; + msgBox.exec(); + return false; } } @@ -722,8 +555,8 @@ bool Launcher::MainDialog::writeSettings() msgBox.setText(tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + msgBox.exec(); + return false; } QTextStream stream(&file); @@ -744,8 +577,8 @@ bool Launcher::MainDialog::writeSettings() msgBox.setText(tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + msgBox.exec(); + return false; } stream.setDevice(&file); @@ -766,8 +599,8 @@ bool Launcher::MainDialog::writeSettings() msgBox.setText(tr("
Could not open or create %0 for writing

\ Please make sure you have the right permissions \ and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + msgBox.exec(); + return false; } stream.setDevice(&file); @@ -786,26 +619,38 @@ void Launcher::MainDialog::closeEvent(QCloseEvent *event) event->accept(); } +void Launcher::MainDialog::wizardStarted() +{ + qDebug() << "wizard started!"; +} + +void Launcher::MainDialog::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0 || exitStatus == QProcess::CrashExit) + return qApp->quit(); + + reloadSettings(); + show(); +} + void Launcher::MainDialog::play() { - if (!writeSettings()) { - qApp->quit(); - return; - } + if (!writeSettings()) + return qApp->quit(); - if(!mGameSettings.hasMaster()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("No game file selected")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
You do not have a game file selected.

\ - OpenMW will not start without a game file selected.
")); - msgBox.exec(); - return; + if (!mGameSettings.hasMaster()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("No game file selected")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(tr("
You do not have a game file selected.

\ + OpenMW will not start without a game file selected.
")); + msgBox.exec(); + return; } // Launch the game detached if (mGameInvoker->startProcess(QLatin1String("openmw"), true)) - qApp->quit(); + return qApp->quit(); } diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index fefbaecaf..0708f7002 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -2,6 +2,8 @@ #define MAINDIALOG_H #include +#include + #ifndef Q_MOC_RUN #include #endif @@ -51,6 +53,10 @@ namespace Launcher void changePage(QListWidgetItem *current, QListWidgetItem *previous); void play(); + private slots: + void wizardStarted(); + void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus); + private: void createIcons(); void createPages(); @@ -73,6 +79,7 @@ namespace Launcher SettingsPage *mSettingsPage; Process::ProcessInvoker *mGameInvoker; + Process::ProcessInvoker *mWizardInvoker; Files::ConfigurationManager mCfgMgr; diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index fe9369769..45e0f72a4 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -172,6 +172,8 @@ void Launcher::SettingsPage::on_browseButton_clicked() void Launcher::SettingsPage::wizardStarted() { + mMain->hide(); // Hide the launcher + qDebug() << "wizard started!"; wizardButton->setEnabled(false); } @@ -180,10 +182,12 @@ void Launcher::SettingsPage::wizardFinished(int exitCode, QProcess::ExitStatus e { qDebug() << "wizard finished!"; if (exitCode != 0 || exitStatus == QProcess::CrashExit) - return; + return qApp->quit(); mMain->reloadSettings(); wizardButton->setEnabled(true); + + mMain->show(); // Show the launcher again } void Launcher::SettingsPage::importerStarted() diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp deleted file mode 100644 index 3d0f2e5da..000000000 --- a/apps/launcher/unshieldthread.cpp +++ /dev/null @@ -1,521 +0,0 @@ -#include "unshieldthread.hpp" - -#include -#include - -namespace bfs = boost::filesystem; - -namespace -{ - static bool make_sure_directory_exists(bfs::path directory) - { - - if(!bfs::exists(directory)) - { - bfs::create_directories(directory); - } - - return bfs::exists(directory); - } - - void fill_path(bfs::path& path, const std::string& name) - { - size_t start = 0; - - size_t i; - for(i = 0; i < name.length(); i++) - { - switch(name[i]) - { - case '\\': - path /= name.substr(start, i-start); - start = i+1; - break; - } - } - - path /= name.substr(start, i-start); - } - - std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx) - { - size_t start = inx.find(category); - start = inx.find(setting, start) + setting.length() + 3; - - size_t end = inx.find("!", start); - - return inx.substr(start, end-start); - } - - std::string read_to_string(const bfs::path& path) - { - bfs::ifstream strstream(path, std::ios::in | std::ios::binary); - std::string str; - - strstream.seekg(0, std::ios::end); - str.resize(strstream.tellg()); - strstream.seekg(0, std::ios::beg); - strstream.read(&str[0], str.size()); - strstream.close(); - - return str; - } - - void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini) - { - size_t loc; - loc = ini.find("[" + category + "]"); - - // If category is not found, create it - if(loc == std::string::npos) - { - loc = ini.size() + 2; - ini += ("\r\n[" + category + "]\r\n"); - } - - loc += category.length() +2 +2; - ini.insert(loc, setting + "=" + val + "\r\n"); - } - - #define FIX(setting) add_setting(category, setting, get_setting(category, setting, inx), ini) - - void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath) - { - std::string inx = read_to_string(inxPath); - - // Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones) - size_t start = ini.find("[Weather Blight]"); - start = ini.find("Ambient Loop Sound ID", start); - size_t end = ini.find("\r\n", start) +2; - ini.erase(start, end-start); - - std::string category; - - category = "General"; - { - FIX("Werewolf FOV"); - } - category = "Moons"; - { - FIX("Script Color"); - } - category = "Weather"; - { - FIX("Snow Ripples"); - FIX("Snow Ripple Radius"); - FIX("Snow Ripples Per Flake"); - FIX("Snow Ripple Scale"); - FIX("Snow Ripple Speed"); - FIX("Snow Gravity Scale"); - FIX("Snow High Kill"); - FIX("Snow Low Kill"); - } - category = "Weather Blight"; - { - FIX("Ambient Loop Sound ID"); - } - category = "Weather Snow"; - { - FIX("Sky Sunrise Color"); - FIX("Sky Day Color"); - FIX("Sky Sunset Color"); - FIX("Sky Night Color"); - FIX("Fog Sunrise Color"); - FIX("Fog Day Color"); - FIX("Fog Sunset Color"); - FIX("Fog Night Color"); - FIX("Ambient Sunrise Color"); - FIX("Ambient Day Color"); - FIX("Ambient Sunset Color"); - FIX("Ambient Night Color"); - FIX("Sun Sunrise Color"); - FIX("Sun Day Color"); - FIX("Sun Sunset Color"); - FIX("Sun Night Color"); - FIX("Sun Disc Sunset Color"); - FIX("Transition Delta"); - FIX("Land Fog Day Depth"); - FIX("Land Fog Night Depth"); - FIX("Clouds Maximum Percent"); - FIX("Wind Speed"); - FIX("Cloud Speed"); - FIX("Glare View"); - FIX("Cloud Texture"); - FIX("Ambient Loop Sound ID"); - FIX("Snow Threshold"); - FIX("Snow Diameter"); - FIX("Snow Height Min"); - FIX("Snow Height Max"); - FIX("Snow Entrance Speed"); - FIX("Max Snowflakes"); - } - category = "Weather Blizzard"; - { - FIX("Sky Sunrise Color"); - FIX("Sky Day Color"); - FIX("Sky Sunset Color"); - FIX("Sky Night Color"); - FIX("Fog Sunrise Color"); - FIX("Fog Day Color"); - FIX("Fog Sunset Color"); - FIX("Fog Night Color"); - FIX("Ambient Sunrise Color"); - FIX("Ambient Day Color"); - FIX("Ambient Sunset Color"); - FIX("Ambient Night Color"); - FIX("Sun Sunrise Color"); - FIX("Sun Day Color"); - FIX("Sun Sunset Color"); - FIX("Sun Night Color"); - FIX("Sun Disc Sunset Color"); - FIX("Transition Delta"); - FIX("Land Fog Day Depth"); - FIX("Land Fog Night Depth"); - FIX("Clouds Maximum Percent"); - FIX("Wind Speed"); - FIX("Cloud Speed"); - FIX("Glare View"); - FIX("Cloud Texture"); - FIX("Ambient Loop Sound ID"); - FIX("Storm Threshold"); - } - } - - - void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon) - { - bfs::path ini_path = output_dir; - ini_path /= "Morrowind.ini"; - - std::string ini = read_to_string(ini_path.string()); - - if(tribunal) - { - add_setting("Game Files", "GameFile1", "Tribunal.esm", ini); - add_setting("Archives", "Archive 0", "Tribunal.bsa", ini); - } - if(bloodmoon) - { - bloodmoon_fix_ini(ini, cdPath / "setup.inx"); - add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini); - add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini); - } - - bfs::ofstream inistream((ini_path)); - inistream << ini; - inistream.close(); - } - - void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false) - { - make_sure_directory_exists(to); - - for ( bfs::directory_iterator end, dir(from); dir != end; ++dir ) - { - if(bfs::is_directory(dir->path())) - installToPath(dir->path(), to / dir->path().filename(), copy); - else - { - if(copy) - { - bfs::path dest = to / dir->path().filename(); - if(bfs::exists(dest)) - bfs::remove_all(dest); - bfs::copy_file(dir->path(), dest); - } - else - bfs::rename(dir->path(), to / dir->path().filename()); - } - } - } - - bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true) - { - if(recursive) - { - for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) - { - if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) - return dir->path(); - } - } - else - { - for ( bfs::directory_iterator end, dir(in); dir != end; ++dir ) - { - if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) - return dir->path(); - } - } - - return ""; - } - - bool contains(const bfs::path& in, std::string filename) - { - for(bfs::directory_iterator end, dir(in); dir != end; ++dir) - { - if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename)) - return true; - } - - return false; - } - - time_t getTime(const char* time) - { - struct tm tms; - memset(&tms, 0, sizeof(struct tm)); - strptime(time, "%d %B %Y", &tms); - return mktime(&tms); - } - - // Some cds have cab files which have the Data Files subfolders outside the Data Files folder - void install_dfiles_outside(const bfs::path& from, const bfs::path& dFiles) - { - bfs::path fonts = findFile(from, "fonts", false); - if(fonts.string() != "") - installToPath(fonts, dFiles / "Fonts"); - - bfs::path music = findFile(from, "music", false); - if(music.string() != "") - installToPath(music, dFiles / "Music"); - - bfs::path sound = findFile(from, "sound", false); - if(sound.string() != "") - installToPath(sound, dFiles / "Sound"); - - bfs::path splash = findFile(from, "splash", false); - if(splash.string() != "") - installToPath(splash, dFiles / "Splash"); - } - -} - -bool Launcher::UnshieldThread::SetMorrowindPath(const std::string& path) -{ - mMorrowindPath = path; - return true; -} - -bool Launcher::UnshieldThread::SetTribunalPath(const std::string& path) -{ - mTribunalPath = path; - return true; -} - -bool Launcher::UnshieldThread::SetBloodmoonPath(const std::string& path) -{ - mBloodmoonPath = path; - return true; -} - -void Launcher::UnshieldThread::SetOutputPath(const std::string& path) -{ - mOutputPath = path; -} - -bool Launcher::UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) -{ - bool success; - bfs::path dirname; - bfs::path filename; - int directory = unshield_file_directory(unshield, index); - - dirname = output_dir; - - if (prefix && prefix[0]) - dirname /= prefix; - - if (directory >= 0) - { - const char* tmp = unshield_directory_name(unshield, directory); - if (tmp && tmp[0]) - fill_path(dirname, tmp); - } - - make_sure_directory_exists(dirname); - - filename = dirname; - filename /= unshield_file_name(unshield, index); - - emit signalGUI(QString("Extracting: ") + QString(filename.c_str())); - - success = unshield_file_save(unshield, index, filename.c_str()); - - if (!success) - bfs::remove(filename); - - return success; -} - -void Launcher::UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) -{ - Unshield * unshield; - unshield = unshield_open(cab.c_str()); - - int i; - for (i = 0; i < unshield_file_group_count(unshield); i++) - { - UnshieldFileGroup* file_group = unshield_file_group_get(unshield, i); - - for (size_t j = file_group->first_file; j <= file_group->last_file; j++) - { - if (unshield_file_is_valid(unshield, j)) - extract_file(unshield, output_dir, file_group->name, j); - } - } - unshield_close(unshield); -} - - -bool Launcher::UnshieldThread::extract() -{ - bfs::path outputDataFilesDir = mOutputPath; - outputDataFilesDir /= "Data Files"; - bfs::path extractPath = mOutputPath; - extractPath /= "extract-temp"; - - if(!mMorrowindDone && mMorrowindPath.string().length() > 0) - { - mMorrowindDone = true; - - bfs::path mwExtractPath = extractPath / "morrowind"; - extract_cab(mMorrowindPath, mwExtractPath, true); - - bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path(); - - installToPath(dFilesDir, outputDataFilesDir); - - install_dfiles_outside(mwExtractPath, outputDataFilesDir); - - // Videos are often kept uncompressed on the cd - bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false); - if(videosPath.string() != "") - { - emit signalGUI(QString("Installing Videos...")); - installToPath(videosPath, outputDataFilesDir / "Video", true); - } - - bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false); - if(cdDFiles.string() != "") - { - emit signalGUI(QString("Installing Uncompressed Data files from CD...")); - installToPath(cdDFiles, outputDataFilesDir, true); - } - - - bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini"); - - mTribunalDone = contains(outputDataFilesDir, "tribunal.esm"); - mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); - - } - - else if(!mTribunalDone && mTribunalPath.string().length() > 0) - { - mTribunalDone = true; - - bfs::path tbExtractPath = extractPath / "tribunal"; - extract_cab(mTribunalPath, tbExtractPath, true); - - bfs::path dFilesDir = findFile(tbExtractPath, "tribunal.esm").parent_path(); - - installToPath(dFilesDir, outputDataFilesDir); - - install_dfiles_outside(tbExtractPath, outputDataFilesDir); - - // Mt GOTY CD has Sounds in a seperate folder from the rest of the data files - bfs::path soundsPath = findFile(tbExtractPath, "sounds", false); - if(soundsPath.string() != "") - installToPath(soundsPath, outputDataFilesDir / "Sounds"); - - bfs::path cdDFiles = findFile(mTribunalPath.parent_path(), "data files", false); - if(cdDFiles.string() != "") - { - emit signalGUI(QString("Installing Uncompressed Data files from CD...")); - installToPath(cdDFiles, outputDataFilesDir, true); - } - - mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); - - fix_ini(outputDataFilesDir, bfs::path(mTribunalPath).parent_path(), mTribunalDone, mBloodmoonDone); - } - - else if(!mBloodmoonDone && mBloodmoonPath.string().length() > 0) - { - mBloodmoonDone = true; - - bfs::path bmExtractPath = extractPath / "bloodmoon"; - extract_cab(mBloodmoonPath, bmExtractPath, true); - - bfs::path dFilesDir = findFile(bmExtractPath, "bloodmoon.esm").parent_path(); - - installToPath(dFilesDir, outputDataFilesDir); - - install_dfiles_outside(bmExtractPath, outputDataFilesDir); - - // My GOTY CD contains a folder within cab files called Tribunal patch, - // which contains Tribunal.esm - bfs::path tbPatchPath = findFile(bmExtractPath, "tribunal.esm"); - if(tbPatchPath.string() != "") - bfs::rename(tbPatchPath, outputDataFilesDir / "Tribunal.esm"); - - bfs::path cdDFiles = findFile(mBloodmoonPath.parent_path(), "data files", false); - if(cdDFiles.string() != "") - { - emit signalGUI(QString("Installing Uncompressed Data files from CD...")); - installToPath(cdDFiles, outputDataFilesDir, true); - } - - fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone); - } - - - return true; -} - -void Launcher::UnshieldThread::Done() -{ - // Get rid of unnecessary files - bfs::remove_all(mOutputPath / "extract-temp"); - - // Set modified time to release dates, to preserve load order - if(mMorrowindDone) - bfs::last_write_time(findFile(mOutputPath, "morrowind.esm"), getTime("1 May 2002")); - - if(mTribunalDone) - bfs::last_write_time(findFile(mOutputPath, "tribunal.esm"), getTime("6 November 2002")); - - if(mBloodmoonDone) - bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003")); -} - -std::string Launcher::UnshieldThread::GetMWEsmPath() -{ - return findFile(mOutputPath / "Data Files", "morrowind.esm").string(); -} - -bool Launcher::UnshieldThread::TribunalDone() -{ - return mTribunalDone; -} - -bool Launcher::UnshieldThread::BloodmoonDone() -{ - return mBloodmoonDone; -} - -void Launcher::UnshieldThread::run() -{ - extract(); - emit close(); -} - -Launcher::UnshieldThread::UnshieldThread() -{ - unshield_set_log_level(0); - mMorrowindDone = false; - mTribunalDone = false; - mBloodmoonDone = false; -} diff --git a/apps/launcher/unshieldthread.hpp b/apps/launcher/unshieldthread.hpp deleted file mode 100644 index de6a32b44..000000000 --- a/apps/launcher/unshieldthread.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef UNSHIELD_THREAD_H -#define UNSHIELD_THREAD_H - -#include - -#include - -#include - -namespace Launcher -{ - class UnshieldThread : public QThread - { - Q_OBJECT - - public: - bool SetMorrowindPath(const std::string& path); - bool SetTribunalPath(const std::string& path); - bool SetBloodmoonPath(const std::string& path); - - void SetOutputPath(const std::string& path); - - bool extract(); - - bool TribunalDone(); - bool BloodmoonDone(); - - void Done(); - - std::string GetMWEsmPath(); - - UnshieldThread(); - - private: - - void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); - bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index); - - boost::filesystem::path mMorrowindPath; - boost::filesystem::path mTribunalPath; - boost::filesystem::path mBloodmoonPath; - - bool mMorrowindDone; - bool mTribunalDone; - bool mBloodmoonDone; - - boost::filesystem::path mOutputPath; - - - protected: - virtual void run(); - - signals: - void signalGUI(QString); - void close(); - }; -} -#endif diff --git a/apps/launcher/utils/checkablemessagebox.cpp b/apps/launcher/utils/checkablemessagebox.cpp deleted file mode 100644 index 2f775af57..000000000 --- a/apps/launcher/utils/checkablemessagebox.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "checkablemessagebox.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/*! - \class Utils::CheckableMessageBox - - \brief A messagebox suitable for questions with a - "Do not ask me again" checkbox. - - Emulates the QMessageBox API with - static conveniences. The message label can open external URLs. -*/ -Launcher::CheckableMessageBoxPrivate::CheckableMessageBoxPrivate(QDialog *q) - : clickedButton(0) -{ - QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - - pixmapLabel = new QLabel(q); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth()); - pixmapLabel->setSizePolicy(sizePolicy); - pixmapLabel->setVisible(false); - - QSpacerItem *pixmapSpacer = - new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); - - messageLabel = new QLabel(q); - messageLabel->setMinimumSize(QSize(300, 0)); - messageLabel->setWordWrap(true); - messageLabel->setOpenExternalLinks(true); - messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse); - - QSpacerItem *checkBoxRightSpacer = - new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); - QSpacerItem *buttonSpacer = - new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::Minimum); - - checkBox = new QCheckBox(q); - checkBox->setText(Launcher::CheckableMessageBox::tr("Do not ask again")); - - buttonBox = new QDialogButtonBox(q); - buttonBox->setOrientation(Qt::Horizontal); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - - QVBoxLayout *verticalLayout = new QVBoxLayout(); - verticalLayout->addWidget(pixmapLabel); - verticalLayout->addItem(pixmapSpacer); - - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->addLayout(verticalLayout); - horizontalLayout_2->addWidget(messageLabel); - - QHBoxLayout *horizontalLayout = new QHBoxLayout(); - horizontalLayout->addWidget(checkBox); - horizontalLayout->addItem(checkBoxRightSpacer); - - QVBoxLayout *verticalLayout_2 = new QVBoxLayout(q); - verticalLayout_2->addLayout(horizontalLayout_2); - verticalLayout_2->addLayout(horizontalLayout); - verticalLayout_2->addItem(buttonSpacer); - verticalLayout_2->addWidget(buttonBox); -} - -Launcher::CheckableMessageBox::CheckableMessageBox(QWidget *parent) : - QDialog(parent), - d(new Launcher::CheckableMessageBoxPrivate(this)) -{ - setModal(true); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - connect(d->buttonBox, SIGNAL(accepted()), SLOT(accept())); - connect(d->buttonBox, SIGNAL(rejected()), SLOT(reject())); - connect(d->buttonBox, SIGNAL(clicked(QAbstractButton*)), - SLOT(slotClicked(QAbstractButton*))); -} - -Launcher::CheckableMessageBox::~CheckableMessageBox() -{ - delete d; -} - -void Launcher::CheckableMessageBox::slotClicked(QAbstractButton *b) -{ - d->clickedButton = b; -} - -QAbstractButton *Launcher::CheckableMessageBox::clickedButton() const -{ - return d->clickedButton; -} - -QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::clickedStandardButton() const -{ - if (d->clickedButton) - return d->buttonBox->standardButton(d->clickedButton); - return QDialogButtonBox::NoButton; -} - -QString Launcher::CheckableMessageBox::text() const -{ - return d->messageLabel->text(); -} - -void Launcher::CheckableMessageBox::setText(const QString &t) -{ - d->messageLabel->setText(t); -} - -QPixmap Launcher::CheckableMessageBox::iconPixmap() const -{ - if (const QPixmap *p = d->pixmapLabel->pixmap()) - return QPixmap(*p); - return QPixmap(); -} - -void Launcher::CheckableMessageBox::setIconPixmap(const QPixmap &p) -{ - d->pixmapLabel->setPixmap(p); - d->pixmapLabel->setVisible(!p.isNull()); -} - -bool Launcher::CheckableMessageBox::isChecked() const -{ - return d->checkBox->isChecked(); -} - -void Launcher::CheckableMessageBox::setChecked(bool s) -{ - d->checkBox->setChecked(s); -} - -QString Launcher::CheckableMessageBox::checkBoxText() const -{ - return d->checkBox->text(); -} - -void Launcher::CheckableMessageBox::setCheckBoxText(const QString &t) -{ - d->checkBox->setText(t); -} - -bool Launcher::CheckableMessageBox::isCheckBoxVisible() const -{ - return d->checkBox->isVisible(); -} - -void Launcher::CheckableMessageBox::setCheckBoxVisible(bool v) -{ - d->checkBox->setVisible(v); -} - -QDialogButtonBox::StandardButtons Launcher::CheckableMessageBox::standardButtons() const -{ - return d->buttonBox->standardButtons(); -} - -void Launcher::CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) -{ - d->buttonBox->setStandardButtons(s); -} - -QPushButton *Launcher::CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const -{ - return d->buttonBox->button(b); -} - -QPushButton *Launcher::CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) -{ - return d->buttonBox->addButton(text, role); -} - -QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::defaultButton() const -{ - foreach (QAbstractButton *b, d->buttonBox->buttons()) - if (QPushButton *pb = qobject_cast(b)) - if (pb->isDefault()) - return d->buttonBox->standardButton(pb); - return QDialogButtonBox::NoButton; -} - -void Launcher::CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) -{ - if (QPushButton *b = d->buttonBox->button(s)) { - b->setDefault(true); - b->setFocus(); - } -} - -QDialogButtonBox::StandardButton -Launcher::CheckableMessageBox::question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton) -{ - CheckableMessageBox mb(parent); - mb.setWindowTitle(title); - mb.setIconPixmap(QMessageBox::standardIcon(QMessageBox::Question)); - mb.setText(question); - mb.setCheckBoxText(checkBoxText); - mb.setChecked(*checkBoxSetting); - mb.setStandardButtons(buttons); - mb.setDefaultButton(defaultButton); - mb.exec(); - *checkBoxSetting = mb.isChecked(); - return mb.clickedStandardButton(); -} - -QMessageBox::StandardButton Launcher::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) -{ - return static_cast(int(db)); -} diff --git a/apps/launcher/utils/checkablemessagebox.hpp b/apps/launcher/utils/checkablemessagebox.hpp deleted file mode 100644 index 09a501b9c..000000000 --- a/apps/launcher/utils/checkablemessagebox.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#ifndef CHECKABLEMESSAGEBOX_HPP -#define CHECKABLEMESSAGEBOX_HPP - -#include -#include -#include - -class QCheckBox; - -namespace Launcher -{ - class CheckableMessageBoxPrivate - { - public: - - QLabel *pixmapLabel; - QLabel *messageLabel; - QCheckBox *checkBox; - QDialogButtonBox *buttonBox; - QAbstractButton *clickedButton; - - public: - CheckableMessageBoxPrivate(QDialog *q); - }; - - class CheckableMessageBox : public QDialog - { - Q_OBJECT - Q_PROPERTY(QString text READ text WRITE setText) - Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) - Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) - Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) - Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) - Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) - - public: - explicit CheckableMessageBox(QWidget *parent); - virtual ~CheckableMessageBox(); - - static QDialogButtonBox::StandardButton - question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No); - - QString text() const; - void setText(const QString &); - - bool isChecked() const; - void setChecked(bool s); - - QString checkBoxText() const; - void setCheckBoxText(const QString &); - - bool isCheckBoxVisible() const; - void setCheckBoxVisible(bool); - - QDialogButtonBox::StandardButtons standardButtons() const; - void setStandardButtons(QDialogButtonBox::StandardButtons s); - QPushButton *button(QDialogButtonBox::StandardButton b) const; - QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); - - QDialogButtonBox::StandardButton defaultButton() const; - void setDefaultButton(QDialogButtonBox::StandardButton s); - - // See static QMessageBox::standardPixmap() - QPixmap iconPixmap() const; - void setIconPixmap (const QPixmap &p); - - // Query the result - QAbstractButton *clickedButton() const; - QDialogButtonBox::StandardButton clickedStandardButton() const; - - // Conversion convenience - static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); - - private slots: - void slotClicked(QAbstractButton *b); - - private: - CheckableMessageBoxPrivate *d; - }; -} -#endif // CHECKABLEMESSAGEBOX_HPP From 88064f49469f06b2c5b5a2a534ed175c05c560b6 Mon Sep 17 00:00:00 2001 From: pvdk Date: Thu, 29 May 2014 20:35:07 +0200 Subject: [PATCH 0057/1983] Fix for MOC error due to Boost includes --- apps/wizard/mainwizard.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 6b8c4931f..c22f20c25 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -7,7 +7,9 @@ #include +#ifndef Q_MOC_RUN #include +#endif #include #include From 2a6a26c0d050daddc31f2883d82c35e3af3d93fd Mon Sep 17 00:00:00 2001 From: pvdk Date: Thu, 29 May 2014 21:02:06 +0200 Subject: [PATCH 0058/1983] Minor fixes --- apps/wizard/existinginstallationpage.cpp | 8 ++------ apps/wizard/mainwizard.cpp | 2 +- files/ui/wizard/intropage.ui | 10 ++++------ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 7c5d10a80..7171cb26b 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -23,12 +23,6 @@ void Wizard::ExistingInstallationPage::initializePage() emptyItem->setFlags(Qt::NoItemFlags); installationsList->addItem(emptyItem); - // Test - if (mWizard->mInstallations.isEmpty()) { - qDebug() << "crashy crash"; - return; - } - // Add the available installation paths QStringList paths(mWizard->mInstallations.keys()); @@ -133,6 +127,8 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() installationsList->setCurrentItem(items.first()); } + // Update the button + emit completeChanged(); } void Wizard::ExistingInstallationPage::textChanged(const QString &text) diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 92743417e..33d08b50a 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -279,7 +279,7 @@ void Wizard::MainWizard::runSettingsImporter() return qApp->quit(); // Re-read the game settings - // setupGameSettings(); + setupGameSettings(); } void Wizard::MainWizard::addInstallation(const QString &path) diff --git a/files/ui/wizard/intropage.ui b/files/ui/wizard/intropage.ui index b06f7f170..117d2098e 100644 --- a/files/ui/wizard/intropage.ui +++ b/files/ui/wizard/intropage.ui @@ -6,8 +6,8 @@ 0 0 - 474 - 370 + 472 + 368
@@ -20,7 +20,7 @@ - This Wizard will help you install Morrowindand its add-ons for OpenMW to use. + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. true @@ -29,8 +29,6 @@ - - - + From a390dde818180fe846dda7c5abcb72f38b649666 Mon Sep 17 00:00:00 2001 From: pvdk Date: Thu, 29 May 2014 21:17:49 +0200 Subject: [PATCH 0059/1983] Fixed importing configurations of existing installations --- apps/launcher/maindialog.cpp | 2 +- apps/wizard/mainwizard.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 76b9ad3c5..708969516 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -257,7 +257,7 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem current = previous; int currentIndex = iconWidget->row(current); - int previousIndex = iconWidget->row(previous); +// int previousIndex = iconWidget->row(previous); pagesWidget->setCurrentIndex(currentIndex); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 33d08b50a..2469cfa8b 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -271,7 +271,13 @@ void Wizard::MainWizard::runSettingsImporter() // Now the paths arguments.append(QLatin1String("--ini")); - arguments.append(path + QDir::separator() + QLatin1String("Morrowind.ini")); + + if (field(QLatin1String("installation.new")).toBool() == true) { + arguments.append(path + QDir::separator() + QLatin1String("Morrowind.ini")); + } else { + arguments.append(mInstallations[path].iniPath); + } + arguments.append(QLatin1String("--cfg")); arguments.append(userPath + QLatin1String("openmw.cfg")); From 6348af586e80545cae112850ab369b1a93ba45c9 Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 30 May 2014 02:12:48 +0200 Subject: [PATCH 0060/1983] Fixed some problems with the launcher and the wizard --- apps/launcher/datafilespage.cpp | 10 +--- apps/launcher/datafilespage.hpp | 2 - apps/launcher/main.cpp | 10 ++-- apps/launcher/maindialog.cpp | 71 ++++++++++++------------ apps/wizard/existinginstallationpage.cpp | 14 +++-- apps/wizard/mainwizard.cpp | 6 -- 6 files changed, 50 insertions(+), 63 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 6b53d89a4..17951f2e4 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -36,7 +36,7 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: this, SLOT(updateOkButton(QString))); buildView(); - setupDataFiles(); + loadSettings(); } void Launcher::DataFilesPage::buildView() @@ -67,7 +67,7 @@ void Launcher::DataFilesPage::buildView() this, SLOT (slotProfileChangedByUser(QString, QString))); } -void Launcher::DataFilesPage::setupDataFiles() +bool Launcher::DataFilesPage::loadSettings() { QStringList paths = mGameSettings.getDataDirs(); @@ -79,12 +79,6 @@ void Launcher::DataFilesPage::setupDataFiles() if (!mDataLocal.isEmpty()) mSelector->addFiles(mDataLocal); - loadSettings(); -} - -bool Launcher::DataFilesPage::loadSettings() -{ - QStringList paths = mGameSettings.getDataDirs(); paths.insert (0, mDataLocal); PathIterator pathIterator (paths); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 908394ae1..5beeb0e03 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -41,8 +41,6 @@ namespace Launcher void saveSettings(const QString &profile = ""); bool loadSettings(); - void setupDataFiles(); - signals: void signalProfileChanged (int index); diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 67320bff0..da319426d 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -52,12 +52,12 @@ int main(int argc, char *argv[]) Launcher::MainDialog mainWin; - if (!mainWin.setup()) { + if (!mainWin.showFirstRunDialog()) return 0; - //mainWin.show(); - }/* else { - return 0; - }*/ + +// if (!mainWin.setup()) +// return 0; + int returnValue = app.exec(); SDL_Quit(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 708969516..c66de0b03 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -165,40 +165,44 @@ void Launcher::MainDialog::createPages() bool Launcher::MainDialog::showFirstRunDialog() { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("First run")); - msgBox.setIcon(QMessageBox::Question); - msgBox.setStandardButtons(QMessageBox::NoButton); - msgBox.setText(tr("

Welcome to OpenMW!

\ -

It is recommended to run the Installation Wizard.

\ -

The Wizard will let you select an existing Morrowind installation, \ - or install Morrowind for OpenMW to use.

")); - QAbstractButton *wizardButton = - msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! - QAbstractButton *skipButton = - msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); - Q_UNUSED(skipButton); // Surpress compiler unused warning - - msgBox.exec(); - - if (msgBox.clickedButton() == wizardButton) + if (!setupLauncherSettings()) + return false; + + if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) { - if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) { - return false; - } else { - return true; + QMessageBox msgBox; + msgBox.setWindowTitle(tr("First run")); + msgBox.setIcon(QMessageBox::Question); + msgBox.setStandardButtons(QMessageBox::NoButton); + msgBox.setText(tr("

Welcome to OpenMW!

\ +

It is recommended to run the Installation Wizard.

\ +

The Wizard will let you select an existing Morrowind installation, \ + or install Morrowind for OpenMW to use.

")); + + QAbstractButton *wizardButton = + msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! + QAbstractButton *skipButton = + msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); + + Q_UNUSED(skipButton); // Surpress compiler unused warning + + msgBox.exec(); + + if (msgBox.clickedButton() == wizardButton) + { + if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) { + return false; + } else { + return true; + } } } - show(); - return true; + return setup(); } bool Launcher::MainDialog::setup() { - if (!setupLauncherSettings()) - return false; - if (!setupGameSettings()) return false; @@ -214,17 +218,7 @@ bool Launcher::MainDialog::setup() loadSettings(); - // Check if we need to run the wizard - if (mLauncherSettings.value(QString("General/firstrun"), QString("true")) == QLatin1String("true")) - { - if (!showFirstRunDialog()) { - return false; - } else { - return true; - } - } - - show(); // Show ourselves if the wizard is not being run + show(); return true; } @@ -629,6 +623,9 @@ void Launcher::MainDialog::wizardFinished(int exitCode, QProcess::ExitStatus exi if (exitCode != 0 || exitStatus == QProcess::CrashExit) return qApp->quit(); + if (!setup()) + return; + reloadSettings(); show(); } diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 7171cb26b..83ea20f5a 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -15,14 +15,16 @@ Wizard::ExistingInstallationPage::ExistingInstallationPage(QWidget *parent) : setupUi(this); + // Add a placeholder item to the list of installations + QListWidgetItem *emptyItem = new QListWidgetItem(tr("No existing installations detected")); + emptyItem->setFlags(Qt::NoItemFlags); + + installationsList->insertItem(0, emptyItem); + } void Wizard::ExistingInstallationPage::initializePage() { - QListWidgetItem *emptyItem = new QListWidgetItem(tr("No existing installations detected")); - emptyItem->setFlags(Qt::NoItemFlags); - installationsList->addItem(emptyItem); - // Add the available installation paths QStringList paths(mWizard->mInstallations.keys()); @@ -35,7 +37,9 @@ void Wizard::ExistingInstallationPage::initializePage() foreach (const QString &path, paths) { QListWidgetItem *item = new QListWidgetItem(path); - installationsList->addItem(item); + + if (installationsList->findItems(path, Qt::MatchExactly).isEmpty()) + installationsList->addItem(item); } connect(installationsList, SIGNAL(currentTextChanged(QString)), diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 2469cfa8b..e587d77e5 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -37,9 +37,6 @@ Wizard::MainWizard::MainWizard(QWidget *parent) : setWindowIcon(QIcon(QLatin1String(":/images/openmw-wizard.png"))); setMinimumWidth(550); - // This prevents initializePage() being called multiple times - setOption(QWizard::IndependentPages); - // Set the property for comboboxes to the text instead of index setDefaultProperty("QComboBox", "currentText", "currentIndexChanged"); @@ -283,9 +280,6 @@ void Wizard::MainWizard::runSettingsImporter() if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) return qApp->quit(); - - // Re-read the game settings - setupGameSettings(); } void Wizard::MainWizard::addInstallation(const QString &path) From 0cc7c062624c7ea9a6ad8adf44ca11eacf803ab0 Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 30 May 2014 02:41:09 +0200 Subject: [PATCH 0061/1983] Ensure the launcher is not shown when the wizard is run --- apps/launcher/main.cpp | 4 +++- apps/launcher/maindialog.cpp | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index da319426d..562f5c779 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -55,9 +55,11 @@ int main(int argc, char *argv[]) if (!mainWin.showFirstRunDialog()) return 0; -// if (!mainWin.setup()) +// if (!mainWin.setup()) { // return 0; +// } + mainWin.show(); int returnValue = app.exec(); SDL_Quit(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index c66de0b03..59e023827 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -51,6 +51,9 @@ Launcher::MainDialog::MainDialog(QWidget *parent) mGameInvoker = new ProcessInvoker(); mWizardInvoker = new ProcessInvoker(); + connect(mWizardInvoker->getProcess(), SIGNAL(started()), + this, SLOT(wizardStarted())); + connect(mWizardInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(wizardFinished(int,QProcess::ExitStatus))); @@ -218,7 +221,6 @@ bool Launcher::MainDialog::setup() loadSettings(); - show(); return true; } @@ -416,7 +418,7 @@ bool Launcher::MainDialog::setupGameSettings() } if (selectedFile.isEmpty()) - return false; // Cancel was clicked; + return false; // Cancel was clicked QFileInfo info(selectedFile); @@ -616,6 +618,7 @@ void Launcher::MainDialog::closeEvent(QCloseEvent *event) void Launcher::MainDialog::wizardStarted() { qDebug() << "wizard started!"; + hide(); } void Launcher::MainDialog::wizardFinished(int exitCode, QProcess::ExitStatus exitStatus) @@ -623,11 +626,8 @@ void Launcher::MainDialog::wizardFinished(int exitCode, QProcess::ExitStatus exi if (exitCode != 0 || exitStatus == QProcess::CrashExit) return qApp->quit(); - if (!setup()) - return; - - reloadSettings(); - show(); + if (reloadSettings()) + show(); } void Launcher::MainDialog::play() From fda25f385aa85fc05fe9e9637b709fab7efec849 Mon Sep 17 00:00:00 2001 From: pvdk Date: Fri, 30 May 2014 02:50:17 +0200 Subject: [PATCH 0062/1983] Fixed segfault when launcher tries to reload settings on pages not yet there --- apps/launcher/maindialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 59e023827..41a23e246 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -626,6 +626,9 @@ void Launcher::MainDialog::wizardFinished(int exitCode, QProcess::ExitStatus exi if (exitCode != 0 || exitStatus == QProcess::CrashExit) return qApp->quit(); + // HACK: Ensure the pages are created, else segfault + setup(); + if (reloadSettings()) show(); } From 673b10207948598535444ae1afbb153f824a4298 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Jul 2014 12:17:24 +0200 Subject: [PATCH 0063/1983] increased version number --- CMakeLists.txt | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9de8efeb5..cb9a54a6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 30) +set(OPENMW_VERSION_MINOR 31) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") diff --git a/readme.txt b/readme.txt index 92cb35f31..90f223a64 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.30.0 +Version: 0.31.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org From 6ba4a5335cdbdcbd8683989fb4fcf86c53fd0f2b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 4 Jul 2014 08:58:54 +0200 Subject: [PATCH 0064/1983] updated changelog --- readme.txt | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/readme.txt b/readme.txt index 90f223a64..bafca48a4 100644 --- a/readme.txt +++ b/readme.txt @@ -96,6 +96,188 @@ Allowed options: CHANGELOG +0.31.0 + +Bug #245: Cloud direction and weather systems differ from Morrowind +Bug #275: Local Map does not always show objects that span multiple cells +Bug #538: Update CenterOnCell (COC) function behavior +Bug #618: Local and World Map Textures are sometimes Black +Bug #640: Water behaviour at night +Bug #668: OpenMW doesn't support non-latin paths on Windows +Bug #746: OpenMW doesn't check if the background music was already played +Bug #747: Door is stuck if cell is left before animation finishes +Bug #772: Disabled statics are visible on map +Bug #829: OpenMW uses up all available vram, when playing for extended time +Bug #869: Dead bodies don't collide with anything +Bug #894: Various character creation issues +Bug #897/#1369: opencs Segmentation Fault after "new" or "load" +Bug #899: Various jumping issues +Bug #952: Reflection effects are one frame delayed +Bug #993: Able to interact with world during Wait/Rest dialog +Bug #995: Dropped items can be placed inside the wall +Bug #1008: Corpses always face up upon reentering the cell +Bug #1035: Random colour patterns appearing in automap +Bug #1037: Footstep volume issues +Bug #1047: Creation of wrong links in dialogue window +Bug #1129: Summoned creature time life duration seems infinite +Bug #1134: Crimes can be committed against hostile NPCs +Bug #1136: Creature run speed formula is incorrect +Bug #1150: Weakness to Fire doesn't apply to Fire Damage in the same spell +Bug #1155: NPCs killing each other +Bug #1166: Bittercup script still does not work +Bug #1178: .bsa file names are case sensitive. +Bug #1179: Crash after trying to load game after being killed +Bug #1180: Changing footstep sound location +Bug #1196: Jumping not disabled when showing messageboxes +Bug #1202: "strange" keys are not shown in binding menu, and are not saved either, but works +Bug #1217: Container content changes based on the current position of the mouse +Bug #1234: Loading/saving issues with dynamic records +Bug #1277: Text pasted into the console appears twice +Bug #1284: Crash on New Game +Bug #1303: It's possible to skip the chargen +Bug #1304: Slaughterfish should not detect the player unless the player is in the water +Bug #1311: Editor: deleting Record Filter line does not reset the filter +Bug #1324: ERROR: ESM Error: String table overflow when loading Animated Morrowind.esp +Bug #1328: Editor: Bogus Filter created when dragging multiple records to filter bar of non-applicable table +Bug #1331: Walking/running sound persist after killing NPC`s that are walking/running. +Bug #1334: Previously equipped items not shown as unequipped after attempting to sell them. +Bug #1335: Actors ignore vertical axis when deciding to attack +Bug #1338: Unknown toggle option for shadows +Bug #1339: "Ashlands Region" is visible when beginning new game during "Loading Area" process +Bug #1340: Guards prompt Player with punishment options after resisting arrest with another guard. +Bug #1348: Regression: Bug #1098 has returned with a vengeance +Bug #1349: [TR] TR_Data mesh tr_ex_imp_gatejamb01 cannot be activated +Bug #1352: Disabling an ESX file does not disable dependent ESX files +Bug #1355: CppCat Checks OpenMW +Bug #1356: Incorrect voice type filtering for sleep interrupts +Bug #1357: Restarting the game clears saves +Bug #1360: Seyda Neen silk rider dialog problem +Bug #1361: Some lights don't work +Bug #1364: It is difficult to bind "Mouse 1" to an action in the options menu +Bug #1370: Animation compilation mod does not work properly +Bug #1371: SL_Pick01.nif from third party fails to load in openmw, but works in Vanilla +Bug #1373: When stealing in front of Sellus Gravius cannot exit the dialog +Bug #1378: Installs to /usr/local are not working +Bug #1380: Loading a save file fail if one of the content files is disabled +Bug #1382: "getHExact() size mismatch" crash on loading official plugin "Siege at Firemoth.esp" +Bug #1386: Arkngthand door will not open +Bug #1388: Segfault when modifying View Distance in Menu options +Bug #1389: Crash when loading a save after dying +Bug #1390: Apostrophe characters not displayed [French version] +Bug #1391: Custom made icon background texture for magical weapons and stuff isn't scaled properly on GUI. +Bug #1393: Coin icon during the level up dialogue are off of the background +Bug #1394: Alt+F4 doesn't work on Win version +Bug #1395: Changing rings switches only the last one put on +Bug #1396: Pauldron parts aren't showing when the robe is equipped +Bug #1402: Dialogue of some shrines have wrong button orientation +Bug #1403: Items are floating in the air when they're dropped onto dead bodies. +Bug #1404: Forearms are not rendered on Argonian females +Bug #1407: Alchemy allows making potions from two of the same item +Bug #1408: "Max sale" button gives you all the items AND all the trader's gold +Bug #1409: Rest "Until Healed" broken for characters with stunted magicka. +Bug #1412: Empty travel window opens while playing through start game +Bug #1413: Save game ignores missing writing permission +Bug #1414: The Underground 2 ESM Error +Bug #1416: Not all splash screens in the Splash directory are used +Bug #1417: Loading saved game does not terminate +Bug #1419: Skyrim: Home of the Nords error +Bug #1422: ClearInfoActor +Bug #1423: ForceGreeting closes existing dialogue windows +Bug #1425: Cannot load save game +Bug #1426: Read skill books aren't stored in savegame +Bug #1427: Useless items can be set under hotkeys +Bug #1429: Text variables in journal +Bug #1432: When attacking friendly NPC, the crime is reported and bounty is raised after each swing +Bug #1435: Stealing priceless items is without punishment +Bug #1437: Door marker at Jobasha's Rare Books is spawning PC in the air +Bug #1440: Topic selection menu should be wider +Bug #1441: Dropping items on the rug makes them inaccessible +Bug #1442: When dropping and taking some looted items, bystanders consider that as a crime +Bug #1444: Arrows and bolts are not dropped where the cursor points +Bug #1445: Security trainers offering acrobatics instead +Bug #1447: Character dash not displayed, French edition +Bug #1448: When the player is killed by the guard while having a bounty on his head, the guard dialogue opens over and over instead of loading dialogue +Bug #1454: Script error in SkipTutorial +Bug #1456: Bad lighting when using certain Morrowind.ini generated by MGE +Bug #1457: Heart of Lorkan comes after you when attacking it +Bug #1458: Modified Keybindings are not remembered +Bug #1459: Dura Gra-Bol doesn't respond to PC attack +Bug #1462: Interior cells not loaded with Morrowind Patch active +Bug #1469: Item tooltip should show the base value, not real value +Bug #1477: Death count is not stored in savegame +Bug #1478: AiActivate does not trigger activate scripts +Bug #1481: Weapon not rendered when partially submerged in water +Bug #1483: Enemies are attacking even while dying +Bug #1486: ESM Error: Don't know what to do with INFO +Bug #1490: Arrows shot at PC can end up in inventory +Bug #1492: Monsters respawn on top of one another +Bug #1493: Dialogue box opens with follower NPC even if NPC is dead +Bug #1494: Paralysed cliffracers remain airbourne +Bug #1495: Dialogue box opens with follower NPC even the game is paused +Bug #1496: GUI messages are not cleared when loading another saved game +Bug #1499: Underwater sound sometimes plays when transitioning from interior. +Bug #1500: Targetted spells and water. +Bug #1502: Console error message on info refusal +Bug #1507: Bloodmoon MQ The Ritual of Beasts: Can't remove the arrow +Bug #1508: Bloodmoon: Fort Frostmoth, cant talk with Carnius Magius +Bug #1516: PositionCell doesn't move actors to current cell +Bug #1518: ForceGreeting broken for explicit references +Bug #1522: Crash after attempting to play non-music file +Bug #1523: World map empty after loading interior save +Bug #1524: Arrows in waiting/resting dialog act like minimum and maximum buttons +Bug #1525: Werewolf: Killed NPC's don't fill werewolfs hunger for blood +Bug #1527: Werewolf: Detect life detects wrong type of actor +Bug #1529: OpenMW crash during "the shrine of the dead" mission (tribunal) +Bug #1530: Selected text in the console has the same color as the background +Bug #1539: Barilzar's Mazed Band: Tribunal +Bug #1542: Looping taunts from NPC`s after death: Tribunal +Bug #1543: OpenCS crash when using drag&drop in script editor +Bug #1547: Bamz-Amschend: Centurion Archers combat problem +Bug #1548: The Missing Hand: Tribunal +Bug #1549: The Mad God: Tribunal, Dome of Serlyn +Bug #1557: A bounty is calculated from actual item cost +Bug #1562: Invisible terrain on top of Red Mountain +Bug #1564: Cave of the hidden music: Bloodmoon +Bug #1567: Editor: Deleting of referenceables does not work +Bug #1568: Picking up a stack of items and holding the enter key and moving your mouse around paints a bunch of garbage on screen. +Bug #1574: Solstheim: Drauger cant inflict damage on player +Bug #1578: Solstheim: Bonewolf running animation not working +Bug #1585: Particle effects on PC are stopped when paralyzed +Bug #1589: Tribunal: Crimson Plague quest does not update when Gedna Relvel is killed +Bug #1590: Failed to save game: compile error +Bug #1598: Segfault when making Drain/Fortify Skill spells +Bug #1599: Unable to switch to fullscreen +Bug #1613: Morrowind Rebirth duplicate objects / vanilla objects not removed +Feature #32: Periodic Cleanup/Refill +Feature #41: Precipitation and weather particles +Feature #568: Editor: Configuration setup +Feature #649: Editor: Threaded loading +Feature #930: Editor: Cell record saving +Feature #934: Editor: Body part table +Feature #935: Editor: Enchantment effect table +Feature #1162: Dialogue merging +Feature #1174: Saved Game: add missing creature state +Feature #1177: Saved Game: fog of war state +Feature #1312: Editor: Combat/Magic/Stealth values for creatures are not displayed +Feature #1314: Make NPCs and creatures fight each other +Feature #1315: Crime: Murder +Feature #1321: Sneak skill enhancements +Feature #1323: Handle restocking items +Feature #1332: Saved Game: levelled creatures +Feature #1347: modFactionReaction script instruction +Feature #1362: Animated main menu support +Feature #1433: Store walk/run toggle +Feature #1449: Use names instead of numbers for saved game files and folders +Feature #1453: Adding Delete button to the load menu +Feature #1460: Enable Journal screen while in dialogue +Feature #1480: Play Battle music when in combat +Feature #1501: Followers unable to fast travel with you +Feature #1520: Disposition and distance-based aggression/ShouldAttack +Feature #1595: Editor: Object rendering in cells +Task #940: Move license to locations where applicable +Task #1333: Remove cmake git tag reading +Task #1566: Editor: Object rendering refactoring + 0.30.0 Bug #416: Extreme shaking can occur during cell transitions while moving From f27e3067a39a2ba005ae621fdb6a77ff9a045ea1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 4 Jul 2014 23:19:40 +0200 Subject: [PATCH 0065/1983] Use explicit C locale in Misc::StringUtils (Fixes #1216) --- components/misc/stringops.cpp | 55 +---------------------------------- components/misc/stringops.hpp | 32 ++++++-------------- 2 files changed, 10 insertions(+), 77 deletions(-) diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp index 0bc8e290a..0f801e554 100644 --- a/components/misc/stringops.cpp +++ b/components/misc/stringops.cpp @@ -12,59 +12,6 @@ namespace Misc { -bool begins(const char* str1, const char* str2) -{ - while(*str2) - { - if(*str1 == 0 || *str1 != *str2) return false; - - str1++; - str2++; - } - return true; -} - -bool ends(const char* str1, const char* str2) -{ - int len1 = strlen(str1); - int len2 = strlen(str2); - - if(len1 < len2) return false; - - return strcmp(str2, str1+len1-len2) == 0; -} - -// True if the given chars match, case insensitive -static bool icmp(char a, char b) -{ - if(a >= 'A' && a <= 'Z') - a += 'a' - 'A'; - if(b >= 'A' && b <= 'Z') - b += 'a' - 'A'; - - return a == b; -} - -bool ibegins(const char* str1, const char* str2) -{ - while(*str2) - { - if(*str1 == 0 || !icmp(*str1,*str2)) return false; - - str1++; - str2++; - } - return true; -} - -bool iends(const char* str1, const char* str2) -{ - int len1 = strlen(str1); - int len2 = strlen(str2); - - if(len1 < len2) return false; - - return strcasecmp(str2, str1+len1-len2) == 0; -} +std::locale StringUtils::mLocale = std::locale::classic(); } diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index d41463cfc..04dedb072 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,15 +4,18 @@ #include #include #include +#include namespace Misc { class StringUtils { + + static std::locale mLocale; struct ci { - bool operator()(int x, int y) const { - return std::tolower(x) < std::tolower(y); + bool operator()(char x, char y) const { + return std::tolower(x, StringUtils::mLocale) < std::tolower(y, StringUtils::mLocale); } }; @@ -28,7 +31,7 @@ public: std::string::const_iterator xit = x.begin(); std::string::const_iterator yit = y.begin(); for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit) != std::tolower(*yit)) { + if (std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) { return false; } } @@ -42,7 +45,7 @@ public: for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { int res = *xit - *yit; - if(res != 0 && std::tolower(*xit) != std::tolower(*yit)) + if(res != 0 && std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) return (res > 0) ? 1 : -1; } if(len > 0) @@ -57,12 +60,8 @@ public: /// Transforms input string to lower case w/o copy static std::string &toLower(std::string &inout) { - std::transform( - inout.begin(), - inout.end(), - inout.begin(), - (int (*)(int)) std::tolower - ); + for (unsigned int i=0; i Date: Sat, 5 Jul 2014 07:24:30 +0200 Subject: [PATCH 0066/1983] Fix travis --- .../components/misc/test_stringops.cpp | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp index 44587c445..55fe0e0c2 100644 --- a/apps/openmw_test_suite/components/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/components/misc/test_stringops.cpp @@ -12,68 +12,3 @@ struct StringOpsTest : public ::testing::Test { } }; - -TEST_F(StringOpsTest, begins_matching) -{ - ASSERT_TRUE(Misc::begins("abc", "a")); - ASSERT_TRUE(Misc::begins("abc", "ab")); - ASSERT_TRUE(Misc::begins("abc", "abc")); - ASSERT_TRUE(Misc::begins("abcd", "abc")); -} - -TEST_F(StringOpsTest, begins_not_matching) -{ - ASSERT_FALSE(Misc::begins("abc", "b")); - ASSERT_FALSE(Misc::begins("abc", "bc")); - ASSERT_FALSE(Misc::begins("abc", "bcd")); - ASSERT_FALSE(Misc::begins("abc", "abcd")); -} - -TEST_F(StringOpsTest, ibegins_matching) -{ - ASSERT_TRUE(Misc::ibegins("Abc", "a")); - ASSERT_TRUE(Misc::ibegins("aBc", "ab")); - ASSERT_TRUE(Misc::ibegins("abC", "abc")); - ASSERT_TRUE(Misc::ibegins("abcD", "abc")); -} - -TEST_F(StringOpsTest, ibegins_not_matching) -{ - ASSERT_FALSE(Misc::ibegins("abc", "b")); - ASSERT_FALSE(Misc::ibegins("abc", "bc")); - ASSERT_FALSE(Misc::ibegins("abc", "bcd")); - ASSERT_FALSE(Misc::ibegins("abc", "abcd")); -} - -TEST_F(StringOpsTest, ends_matching) -{ - ASSERT_TRUE(Misc::ends("abc", "c")); - ASSERT_TRUE(Misc::ends("abc", "bc")); - ASSERT_TRUE(Misc::ends("abc", "abc")); - ASSERT_TRUE(Misc::ends("abcd", "abcd")); -} - -TEST_F(StringOpsTest, ends_not_matching) -{ - ASSERT_FALSE(Misc::ends("abc", "b")); - ASSERT_FALSE(Misc::ends("abc", "ab")); - ASSERT_FALSE(Misc::ends("abc", "bcd")); - ASSERT_FALSE(Misc::ends("abc", "abcd")); -} - -TEST_F(StringOpsTest, iends_matching) -{ - ASSERT_TRUE(Misc::iends("Abc", "c")); - ASSERT_TRUE(Misc::iends("aBc", "bc")); - ASSERT_TRUE(Misc::iends("abC", "abc")); - ASSERT_TRUE(Misc::iends("abcD", "abcd")); -} - -TEST_F(StringOpsTest, iends_not_matching) -{ - ASSERT_FALSE(Misc::iends("abc", "b")); - ASSERT_FALSE(Misc::iends("abc", "ab")); - ASSERT_FALSE(Misc::iends("abc", "bcd")); - ASSERT_FALSE(Misc::iends("abc", "abcd")); -} - From 05cb22f5b79acabf181ef540d57a2de8d8d7ce6c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Jul 2014 10:36:29 +0200 Subject: [PATCH 0067/1983] updated changelog --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index bafca48a4..23fe99f71 100644 --- a/readme.txt +++ b/readme.txt @@ -130,6 +130,7 @@ Bug #1179: Crash after trying to load game after being killed Bug #1180: Changing footstep sound location Bug #1196: Jumping not disabled when showing messageboxes Bug #1202: "strange" keys are not shown in binding menu, and are not saved either, but works +Bug #1216: Broken dialog topics in russian Morrowind Bug #1217: Container content changes based on the current position of the mouse Bug #1234: Loading/saving issues with dynamic records Bug #1277: Text pasted into the console appears twice From 0295673869888ee3075b12e7e9681c5e1f167f71 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 7 Jul 2014 23:37:59 +0200 Subject: [PATCH 0068/1983] Reset item model when reference is reset (Fixes #1628) This caused crashes when the window was resized after the reference no longer exists (e.g. when a savegame is loaded) --- apps/openmw/mwgui/companionwindow.cpp | 5 +++++ apps/openmw/mwgui/companionwindow.hpp | 2 ++ apps/openmw/mwgui/container.cpp | 6 ++++++ apps/openmw/mwgui/container.hpp | 2 ++ apps/openmw/mwgui/tradewindow.cpp | 6 ++++++ apps/openmw/mwgui/tradewindow.hpp | 1 + 6 files changed, 22 insertions(+) diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index d0ac3e7c3..8d199e727 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -153,6 +153,11 @@ void CompanionWindow::onReferenceUnavailable() MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); } +void CompanionWindow::resetReference() +{ + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); +} } diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index 006d0a3c3..dc460e2fc 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -20,6 +20,8 @@ namespace MWGui virtual void exit(); + virtual void resetReference(); + void open(const MWWorld::Ptr& npc); void onFrame (); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 011feb4d3..8da3def5f 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -258,6 +258,12 @@ namespace MWGui onTakeAllButtonClicked(mTakeButton); } + void ContainerWindow::resetReference() + { + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); + } + void ContainerWindow::close() { WindowBase::close(); diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 5446a4ab7..79951f70e 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -54,6 +54,8 @@ namespace MWGui void open(const MWWorld::Ptr& container, bool loot=false); virtual void close(); + virtual void resetReference(); + virtual void exit(); private: diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c0a51311f..19187cde1 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -531,4 +531,10 @@ namespace MWGui sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); } } + + void TradeWindow::resetReference() + { + ReferenceInterface::resetReference(); + mItemView->setModel(NULL); + } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index cc70f1ae9..b487a8870 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -37,6 +37,7 @@ namespace MWGui virtual void exit(); + virtual void resetReference(); private: ItemView* mItemView; From 4a844ef79bbbfe23d9a9c2220aa0675a187bf7d1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 5 Jul 2014 18:24:55 +0200 Subject: [PATCH 0069/1983] Fix compile error for OPENMW_USE_FFMPEG=0 --- apps/openmw/mwrender/videoplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 03e74697c..409e27388 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -1088,7 +1088,7 @@ public: void close() { } - bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) + bool update() { return false; } }; From ef9eb8351498118c32888345c68688cec30df5db Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 3 Jul 2014 20:28:52 +0200 Subject: [PATCH 0070/1983] Fix initializing CharacterController with fists or spell equipped --- apps/openmw/mwmechanics/character.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2c5d68ceb..6549442ed 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -570,10 +570,14 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if (cls.hasInventoryStore(mPtr)) { getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); - if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + if (mWeaponType != WeapType_None) { - getWeaponGroup(mWeaponType, mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; + getWeaponGroup(mWeaponType, mCurrentWeapon); + } + + if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + { mAnimation->showWeapons(true); mAnimation->setWeaponGroup(mCurrentWeapon); } From 3042a9da7331e7908faaea50112c6e59ffbcd19c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 3 Jul 2014 19:37:12 +0200 Subject: [PATCH 0071/1983] Cancel queued view mode switch when switching view mode (Fixes #1618) --- apps/openmw/mwrender/camera.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 9e683cc15..4580bae70 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -149,6 +149,8 @@ namespace MWRender mViewModeToggleQueued = true; return; } + else + mViewModeToggleQueued = false; mFirstPersonView = !mFirstPersonView; processViewChange(); From 05d92f0a7b3df74c48bfc4d89870a8c3f4f2e4d8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 8 Jul 2014 08:07:13 +0200 Subject: [PATCH 0072/1983] updated changelog --- readme.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.txt b/readme.txt index 23fe99f71..a02cc4ebc 100644 --- a/readme.txt +++ b/readme.txt @@ -249,6 +249,8 @@ Bug #1590: Failed to save game: compile error Bug #1598: Segfault when making Drain/Fortify Skill spells Bug #1599: Unable to switch to fullscreen Bug #1613: Morrowind Rebirth duplicate objects / vanilla objects not removed +Bug #1618: Death notice fails to show up +Bug #1628: Alt+Tab Segfault Feature #32: Periodic Cleanup/Refill Feature #41: Precipitation and weather particles Feature #568: Editor: Configuration setup From 563c2e57309d33d2ef6156045b4202344b0540c9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 15 Jul 2014 10:39:11 +0200 Subject: [PATCH 0073/1983] be a bit more relaxed about allowing - in names (Fixes #1593) --- components/compiler/scanner.cpp | 19 +++++++++++++------ components/compiler/scanner.hpp | 2 ++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 46e50a2e9..dd8fb431b 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -343,17 +343,13 @@ namespace Compiler } else if (!(c=='"' && name.empty())) { - if (!(std::isalpha (c) || std::isdigit (c) || c=='_' || c=='`' || - /// \todo add an option to disable the following hack. Also, find out who is - /// responsible for allowing it in the first place and meet up with that person in - /// a dark alley. - (c=='-' && !name.empty() && std::isalpha (mStream.peek())))) + if (!isStringCharacter (c)) { putback (c); break; } - if (first && std::isdigit (c)) + if (first && (std::isdigit (c) || c=='`' || c=='-')) error = true; } @@ -499,6 +495,17 @@ namespace Compiler return true; } + bool Scanner::isStringCharacter (char c, bool lookAhead) + { + return std::isalpha (c) || std::isdigit (c) || c=='_' || + /// \todo disable this when doing more stricter compiling + c=='`' || + /// \todo disable this when doing more stricter compiling. Also, find out who is + /// responsible for allowing it in the first place and meet up with that person in + /// a dark alley. + (c=='-' && (!lookAhead || isStringCharacter (mStream.peek(), false))); + } + bool Scanner::isWhitespace (char c) { return c==' ' || c=='\t'; diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 344ae0582..7f6609f76 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -92,6 +92,8 @@ namespace Compiler bool scanSpecial (char c, Parser& parser, bool& cont); + bool isStringCharacter (char c, bool lookAhead = true); + static bool isWhitespace (char c); public: From 8241ee59c3e0a9519200f9af64acabc18291bf85 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 15 Jul 2014 12:59:02 +0200 Subject: [PATCH 0074/1983] modified GlobalScripts data structures to accommodate targeted script data --- apps/openmw/mwscript/globalscripts.cpp | 60 +++++++++++++------------- apps/openmw/mwscript/globalscripts.hpp | 11 ++++- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 8e118e2f8..411a0ec13 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -15,6 +15,9 @@ namespace MWScript { + GlobalScriptDesc::GlobalScriptDesc() : mRunning (false) {} + + GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store) : mStore (store) { @@ -23,53 +26,53 @@ namespace MWScript void GlobalScripts::addScript (const std::string& name) { - std::map >::iterator iter = + std::map::iterator iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) { if (const ESM::Script *script = mStore.get().find (name)) { - Locals locals; - - locals.configure (*script); + GlobalScriptDesc desc; + desc.mRunning = true; + desc.mLocals.configure (*script); - mScripts.insert (std::make_pair (name, std::make_pair (true, locals))); + mScripts.insert (std::make_pair (name, desc)); } } else - iter->second.first = true; + iter->second.mRunning = true; } void GlobalScripts::removeScript (const std::string& name) { - std::map >::iterator iter = + std::map::iterator iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter!=mScripts.end()) - iter->second.first = false; + iter->second.mRunning = false; } bool GlobalScripts::isRunning (const std::string& name) const { - std::map >::const_iterator iter = + std::map::const_iterator iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) return false; - return iter->second.first; + return iter->second.mRunning; } void GlobalScripts::run() { - for (std::map >::iterator iter (mScripts.begin()); + for (std::map::iterator iter (mScripts.begin()); iter!=mScripts.end(); ++iter) { - if (iter->second.first) + if (iter->second.mRunning) { MWScript::InterpreterContext interpreterContext ( - &iter->second.second, MWWorld::Ptr()); + &iter->second.mLocals, MWWorld::Ptr()); MWBase::Environment::get().getScriptManager()->run (iter->first, interpreterContext); } } @@ -99,16 +102,16 @@ namespace MWScript void GlobalScripts::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { - for (std::map >::const_iterator iter (mScripts.begin()); + for (std::map::const_iterator iter (mScripts.begin()); iter!=mScripts.end(); ++iter) { ESM::GlobalScript script; script.mId = iter->first; - iter->second.second.write (script.mLocals, iter->first); + iter->second.mLocals.write (script.mLocals, iter->first); - script.mRunning = iter->second.first ? 1 : 0; + script.mRunning = iter->second.mRunning ? 1 : 0; writer.startRecord (ESM::REC_GSCR); script.save (writer); @@ -124,25 +127,24 @@ namespace MWScript ESM::GlobalScript script; script.load (reader); - std::map >::iterator iter = + std::map::iterator iter = mScripts.find (script.mId); if (iter==mScripts.end()) { if (const ESM::Script *scriptRecord = mStore.get().search (script.mId)) { - std::pair data (false, Locals()); + GlobalScriptDesc desc; + desc.mLocals.configure (*scriptRecord); - data.second.configure (*scriptRecord); - - iter = mScripts.insert (std::make_pair (script.mId, data)).first; + iter = mScripts.insert (std::make_pair (script.mId, desc)).first; } else // script does not exist anymore return true; } - iter->second.first = script.mRunning!=0; - iter->second.second.read (script.mLocals, script.mId); + iter->second.mRunning = script.mRunning!=0; + iter->second.mLocals.read (script.mLocals, script.mId); return true; } @@ -153,21 +155,19 @@ namespace MWScript Locals& GlobalScripts::getLocals (const std::string& name) { std::string name2 = ::Misc::StringUtils::lowerCase (name); - std::map >::iterator iter = - mScripts.find (name2); + std::map::iterator iter = mScripts.find (name2); if (iter==mScripts.end()) { if (const ESM::Script *script = mStore.get().find (name)) { - Locals locals; - - locals.configure (*script); + GlobalScriptDesc desc; + desc.mLocals.configure (*script); - iter = mScripts.insert (std::make_pair (name, std::make_pair (false, locals))).first; + iter = mScripts.insert (std::make_pair (name, desc)).first; } } - return iter->second.second; + return iter->second.mLocals; } } diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 97584a5b8..9ad9361b6 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -26,10 +26,19 @@ namespace MWWorld namespace MWScript { + struct GlobalScriptDesc + { + bool mRunning; + Locals mLocals; + std::string mId; // ID used to start targeted script (empty if not a targeted script) + + GlobalScriptDesc(); + }; + class GlobalScripts { const MWWorld::ESMStore& mStore; - std::map > mScripts; // running, local variables + std::map mScripts; public: From e9377ad5c41abdfaecb6e12c79d91750cd0cfd12 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 15 Jul 2014 13:05:38 +0200 Subject: [PATCH 0075/1983] include targeted script data in saved games --- apps/openmw/mwscript/globalscripts.cpp | 3 +++ components/esm/globalscript.cpp | 4 ++++ components/esm/globalscript.hpp | 1 + 3 files changed, 8 insertions(+) diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 411a0ec13..3335f4415 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -113,6 +113,8 @@ namespace MWScript script.mRunning = iter->second.mRunning ? 1 : 0; + script.mTargetId = iter->second.mId; + writer.startRecord (ESM::REC_GSCR); script.save (writer); writer.endRecord (ESM::REC_GSCR); @@ -145,6 +147,7 @@ namespace MWScript iter->second.mRunning = script.mRunning!=0; iter->second.mLocals.read (script.mLocals, script.mId); + iter->second.mId = script.mTargetId; return true; } diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp index dcbd91140..467fe54a1 100644 --- a/components/esm/globalscript.cpp +++ b/components/esm/globalscript.cpp @@ -12,6 +12,8 @@ void ESM::GlobalScript::load (ESMReader &esm) mRunning = 0; esm.getHNOT (mRunning, "RUN_"); + + mTargetId = esm.getHNOString ("TARG"); } void ESM::GlobalScript::save (ESMWriter &esm) const @@ -22,4 +24,6 @@ void ESM::GlobalScript::save (ESMWriter &esm) const if (mRunning) esm.writeHNT ("RUN_", mRunning); + + esm.writeHNOString ("TARG", mTargetId); } \ No newline at end of file diff --git a/components/esm/globalscript.hpp b/components/esm/globalscript.hpp index 4fb8b7c48..43c859e09 100644 --- a/components/esm/globalscript.hpp +++ b/components/esm/globalscript.hpp @@ -15,6 +15,7 @@ namespace ESM std::string mId; Locals mLocals; int mRunning; + std::string mTargetId; // for targeted scripts void load (ESMReader &esm); void save (ESMWriter &esm) const; From 75ab8de3d200e24b934a86727f65f37cd787fe25 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 15 Jul 2014 13:26:04 +0200 Subject: [PATCH 0076/1983] added opcode for running scripts with explicit references (targeted scripts) --- apps/openmw/mwscript/globalscripts.cpp | 8 ++++-- apps/openmw/mwscript/globalscripts.hpp | 2 +- apps/openmw/mwscript/interpretercontext.cpp | 4 +-- apps/openmw/mwscript/interpretercontext.hpp | 2 +- components/interpreter/context.hpp | 2 +- components/interpreter/docs/vmformat.txt | 3 ++- components/interpreter/installopcodes.cpp | 1 + components/interpreter/scriptopcodes.hpp | 28 ++++++++++++++++----- 8 files changed, 36 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 3335f4415..83c6560f0 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -24,7 +24,7 @@ namespace MWScript addStartup(); } - void GlobalScripts::addScript (const std::string& name) + void GlobalScripts::addScript (const std::string& name, const std::string& targetId) { std::map::iterator iter = mScripts.find (::Misc::StringUtils::lowerCase (name)); @@ -36,12 +36,16 @@ namespace MWScript GlobalScriptDesc desc; desc.mRunning = true; desc.mLocals.configure (*script); + desc.mId = targetId; mScripts.insert (std::make_pair (name, desc)); } } - else + else if (!iter->second.mRunning) + { iter->second.mRunning = true; + iter->second.mId = targetId; + } } void GlobalScripts::removeScript (const std::string& name) diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 9ad9361b6..55c2e9321 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -44,7 +44,7 @@ namespace MWScript GlobalScripts (const MWWorld::ESMStore& store); - void addScript (const std::string& name); + void addScript (const std::string& name, const std::string& targetId = ""); void removeScript (const std::string& name); diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 028f83a61..0b474510d 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -366,9 +366,9 @@ namespace MWScript return MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning (name); } - void InterpreterContext::startScript (const std::string& name) + void InterpreterContext::startScript (const std::string& name, const std::string& targetId) { - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript (name, targetId); } void InterpreterContext::stopScript (const std::string& name) diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 99026392e..0f4eefeeb 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -113,7 +113,7 @@ namespace MWScript virtual bool isScriptRunning (const std::string& name) const; - virtual void startScript (const std::string& name); + virtual void startScript (const std::string& name, const std::string& targetId = ""); virtual void stopScript (const std::string& name); diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp index 97e4fad4f..25a3ab30d 100644 --- a/components/interpreter/context.hpp +++ b/components/interpreter/context.hpp @@ -81,7 +81,7 @@ namespace Interpreter virtual bool isScriptRunning (const std::string& name) const = 0; - virtual void startScript (const std::string& name) = 0; + virtual void startScript (const std::string& name, const std::string& targetId = "") = 0; virtual void stopScript (const std::string& name) = 0; diff --git a/components/interpreter/docs/vmformat.txt b/components/interpreter/docs/vmformat.txt index 990762268..5d1eba088 100644 --- a/components/interpreter/docs/vmformat.txt +++ b/components/interpreter/docs/vmformat.txt @@ -133,5 +133,6 @@ op 67: store stack[0] in member float stack[2] of global script with ID stack[1] op 68: replace stack[0] with member short stack[1] of global script with ID stack[0] op 69: replace stack[0] with member short stack[1] of global script with ID stack[0] op 70: replace stack[0] with member short stack[1] of global script with ID stack[0] -opcodes 71-33554431 unused +op 71: explicit reference (target) = stack[0]; pop; start script stack[0] and pop +opcodes 72-33554431 unused opcodes 33554432-67108863 reserved for extensions diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index 721cde3d8..d705a109c 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -113,6 +113,7 @@ namespace Interpreter interpreter.installSegment5 (46, new OpScriptRunning); interpreter.installSegment5 (47, new OpStartScript); interpreter.installSegment5 (48, new OpStopScript); + interpreter.installSegment5 (71, new OpStartScriptExplicit); // spacial interpreter.installSegment5 (49, new OpGetDistance); diff --git a/components/interpreter/scriptopcodes.hpp b/components/interpreter/scriptopcodes.hpp index 56502d510..c98bcd23e 100644 --- a/components/interpreter/scriptopcodes.hpp +++ b/components/interpreter/scriptopcodes.hpp @@ -10,36 +10,52 @@ namespace Interpreter class OpScriptRunning : public Opcode0 { public: - + virtual void execute (Runtime& runtime) { std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime[0].mInteger = runtime.getContext().isScriptRunning (name); - } + } }; class OpStartScript : public Opcode0 { public: - + virtual void execute (Runtime& runtime) { std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); runtime.getContext().startScript (name); - } + } + }; + + class OpStartScriptExplicit : public Opcode0 + { + public: + + virtual void execute (Runtime& runtime) + { + std::string targetId = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string name = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + runtime.getContext().startScript (name, targetId); + } }; class OpStopScript : public Opcode0 { public: - + virtual void execute (Runtime& runtime) { std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); runtime.getContext().stopScript (name); - } + } }; } From e8322da6631ecf49a6761210df1e9f89ac26725a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 17 Jul 2014 08:35:28 +0200 Subject: [PATCH 0077/1983] added support for targeted scripts to script compiler --- components/compiler/generator.cpp | 15 +++++++++++---- components/compiler/generator.hpp | 2 +- components/compiler/lineparser.cpp | 14 +++++++------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index c67e51e57..235b163ad 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -300,9 +300,9 @@ namespace code.push_back (Compiler::Generator::segment5 (46)); } - void opStartScript (Compiler::Generator::CodeContainer& code) + void opStartScript (Compiler::Generator::CodeContainer& code, bool targeted) { - code.push_back (Compiler::Generator::segment5 (47)); + code.push_back (Compiler::Generator::segment5 (targeted ? 71 : 47)); } void opStopScript (Compiler::Generator::CodeContainer& code) @@ -830,9 +830,16 @@ namespace Compiler opScriptRunning (code); } - void startScript (CodeContainer& code) + void startScript (CodeContainer& code, Literals& literals, const std::string& id) { - opStartScript (code); + if (id.empty()) + opStartScript (code, false); + else + { + int index = literals.addString (id); + opPushInt (code, index); + opStartScript (code, true); + } } void stopScript (CodeContainer& code) diff --git a/components/compiler/generator.hpp b/components/compiler/generator.hpp index b51116122..2619033c8 100644 --- a/components/compiler/generator.hpp +++ b/components/compiler/generator.hpp @@ -113,7 +113,7 @@ namespace Compiler void scriptRunning (CodeContainer& code); - void startScript (CodeContainer& code); + void startScript (CodeContainer& code, Literals& literals, const std::string& id); void stopScript (CodeContainer& code); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index f7d2726e3..b1b831bc2 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -262,6 +262,13 @@ namespace Compiler Generator::disable (mCode, mLiterals, mExplicit); mState = PotentialEndState; return true; + + case Scanner::K_startscript: + + mExprParser.parseArguments ("c", scanner, mCode); + Generator::startScript (mCode, mLiterals, mExplicit); + mState = EndState; + return true; } // check for custom extensions @@ -361,13 +368,6 @@ namespace Compiler mState = EndState; return true; - case Scanner::K_startscript: - - mExprParser.parseArguments ("c", scanner, mCode); - Generator::startScript (mCode); - mState = EndState; - return true; - case Scanner::K_stopscript: mExprParser.parseArguments ("c", scanner, mCode); From dba6a9ebff4ba8c3ad8b4d2cee94a5b3bf3d7212 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 17 Jul 2014 09:15:41 +0200 Subject: [PATCH 0078/1983] run targeted scripts with an implicit reference based on the ID given --- apps/openmw/mwscript/globalscripts.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 83c6560f0..332431b16 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -75,8 +75,15 @@ namespace MWScript { if (iter->second.mRunning) { + MWWorld::Ptr ptr; + + if (!iter->second.mId.empty()) + ptr = MWBase::Environment::get().getWorld()->getPtr ( + iter->second.mId, false); + MWScript::InterpreterContext interpreterContext ( - &iter->second.mLocals, MWWorld::Ptr()); + &iter->second.mLocals, ptr); + MWBase::Environment::get().getScriptManager()->run (iter->first, interpreterContext); } } From 35b27ea8cb480e07ddffb24e91e7255f79d4de87 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 17 Jul 2014 11:29:04 +0200 Subject: [PATCH 0079/1983] ignore stray string argument after GetDisabled --- components/compiler/exprparser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index e54b2e2a8..d0cda0ec2 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -386,6 +386,9 @@ namespace Compiler mExplicit.clear(); mRefOp = false; + std::vector ignore; + parseArguments ("x", scanner, ignore); + mNextOperand = false; return true; } @@ -527,6 +530,9 @@ namespace Compiler Generator::getDisabled (mCode, mLiterals, ""); mOperands.push_back ('l'); + std::vector ignore; + parseArguments ("x", scanner, ignore); + mNextOperand = false; return true; } From 27c84d6cb7130204334af3575edabfe589337762 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 17 Jul 2014 13:36:55 +0200 Subject: [PATCH 0080/1983] the reference for a targeted script is now determined when needed instead of at the start of the script execution --- apps/openmw/mwscript/globalscripts.cpp | 6 +-- apps/openmw/mwscript/interpretercontext.cpp | 49 +++++++++++++-------- apps/openmw/mwscript/interpretercontext.hpp | 19 ++++++-- components/interpreter/context.hpp | 2 + 4 files changed, 49 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 332431b16..0c94f503b 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -77,12 +77,8 @@ namespace MWScript { MWWorld::Ptr ptr; - if (!iter->second.mId.empty()) - ptr = MWBase::Environment::get().getWorld()->getPtr ( - iter->second.mId, false); - MWScript::InterpreterContext interpreterContext ( - &iter->second.mLocals, ptr); + &iter->second.mLocals, MWWorld::Ptr(), iter->second.mId); MWBase::Environment::get().getScriptManager()->run (iter->first, interpreterContext); } diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 0b474510d..c64b4c0d3 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -22,7 +22,7 @@ namespace MWScript { - MWWorld::Ptr InterpreterContext::getReference ( + MWWorld::Ptr InterpreterContext::getReferenceImp ( const std::string& id, bool activeOnly, bool doThrow) { if (!id.empty()) @@ -31,6 +31,10 @@ namespace MWScript } else { + if (mReference.isEmpty() && !mTargetId.empty()) + mReference = + MWBase::Environment::get().getWorld()->searchPtr (mTargetId, false); + if (mReference.isEmpty() && doThrow) throw std::runtime_error ("no implicit reference"); @@ -38,7 +42,7 @@ namespace MWScript } } - const MWWorld::Ptr InterpreterContext::getReference ( + const MWWorld::Ptr InterpreterContext::getReferenceImp ( const std::string& id, bool activeOnly, bool doThrow) const { if (!id.empty()) @@ -47,6 +51,10 @@ namespace MWScript } else { + if (mReference.isEmpty() && !mTargetId.empty()) + mReference = + MWBase::Environment::get().getWorld()->searchPtr (mTargetId, false); + if (mReference.isEmpty() && doThrow) throw std::runtime_error ("no implicit reference"); @@ -64,7 +72,7 @@ namespace MWScript } else { - const MWWorld::Ptr ptr = getReference (id, false); + const MWWorld::Ptr ptr = getReferenceImp (id, false); id = ptr.getClass().getScript (ptr); @@ -84,7 +92,7 @@ namespace MWScript } else { - const MWWorld::Ptr ptr = getReference (id, false); + const MWWorld::Ptr ptr = getReferenceImp (id, false); id = ptr.getClass().getScript (ptr); @@ -96,9 +104,9 @@ namespace MWScript } InterpreterContext::InterpreterContext ( - MWScript::Locals *locals, MWWorld::Ptr reference) + MWScript::Locals *locals, MWWorld::Ptr reference, const std::string& targetId) : mLocals (locals), mReference (reference), - mActivationHandled (false) + mActivationHandled (false), mTargetId (targetId) {} int InterpreterContext::getLocalShort (int index) const @@ -236,34 +244,34 @@ namespace MWScript std::string InterpreterContext::getNPCName() const { - ESM::NPC npc = *mReference.get()->mBase; + ESM::NPC npc = *getReferenceImp().get()->mBase; return npc.mName; } std::string InterpreterContext::getNPCRace() const { - ESM::NPC npc = *mReference.get()->mBase; + ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mRace); return race->mName; } std::string InterpreterContext::getNPCClass() const { - ESM::NPC npc = *mReference.get()->mBase; + ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mClass); return class_->mName; } std::string InterpreterContext::getNPCFaction() const { - ESM::NPC npc = *mReference.get()->mBase; + ESM::NPC npc = *getReferenceImp().get()->mBase; const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(npc.mFaction); return faction->mName; } std::string InterpreterContext::getNPCRank() const { - std::map ranks = mReference.getClass().getNpcStats (mReference).getFactionRanks(); + std::map ranks = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks(); std::map::const_iterator it = ranks.begin(); MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -299,7 +307,7 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - std::string factionId = mReference.getClass().getNpcStats (mReference).getFactionRanks().begin()->first; + std::string factionId = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks().begin()->first; std::map ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map::const_iterator it = ranks.find(factionId); @@ -326,7 +334,7 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - std::string factionId = mReference.getClass().getNpcStats (mReference).getFactionRanks().begin()->first; + std::string factionId = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks().begin()->first; std::map ranks = player.getClass().getNpcStats (player).getFactionRanks(); std::map::const_iterator it = ranks.find(factionId); @@ -383,7 +391,7 @@ namespace MWScript MWWorld::Ptr ref2; if (id.empty()) - ref2 = getReference("", true, true); + ref2 = getReferenceImp(); else ref2 = MWBase::Environment::get().getWorld()->searchPtr(id, true); @@ -448,19 +456,19 @@ namespace MWScript bool InterpreterContext::isDisabled (const std::string& id) const { - const MWWorld::Ptr ref = getReference (id, false); + const MWWorld::Ptr ref = getReferenceImp (id, false); return !ref.getRefData().isEnabled(); } void InterpreterContext::enable (const std::string& id) { - MWWorld::Ptr ref = getReference (id, false); + MWWorld::Ptr ref = getReferenceImp (id, false); MWBase::Environment::get().getWorld()->enable (ref); } void InterpreterContext::disable (const std::string& id) { - MWWorld::Ptr ref = getReference (id, false); + MWWorld::Ptr ref = getReferenceImp (id, false); MWBase::Environment::get().getWorld()->disable (ref); } @@ -542,6 +550,11 @@ namespace MWScript MWWorld::Ptr InterpreterContext::getReference(bool required) { - return getReference ("", true, required); + return getReferenceImp ("", true, required); + } + + std::string InterpreterContext::getTargetId() const + { + return mTargetId; } } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 0f4eefeeb..f897282e2 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -27,14 +27,22 @@ namespace MWScript class InterpreterContext : public Interpreter::Context { Locals *mLocals; - MWWorld::Ptr mReference; + mutable MWWorld::Ptr mReference; MWWorld::Ptr mActivated; bool mActivationHandled; - MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true); + std::string mTargetId; - const MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true) const; + /// If \a id is empty, a reference the script is run from is returned or in case + /// of a non-local script the reference derived from the target ID. + MWWorld::Ptr getReferenceImp (const std::string& id = "", bool activeOnly = false, + bool doThrow=true); + + /// If \a id is empty, a reference the script is run from is returned or in case + /// of a non-local script the reference derived from the target ID. + const MWWorld::Ptr getReferenceImp (const std::string& id = "", + bool activeOnly = false, bool doThrow=true) const; const Locals& getMemberLocals (std::string& id, bool global) const; ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before @@ -44,7 +52,8 @@ namespace MWScript public: - InterpreterContext (MWScript::Locals *locals, MWWorld::Ptr reference); + InterpreterContext (MWScript::Locals *locals, MWWorld::Ptr reference, + const std::string& targetId = ""); ///< The ownership of \a locals is not transferred. 0-pointer allowed. virtual int getLocalShort (int index) const; @@ -158,6 +167,8 @@ namespace MWScript MWWorld::Ptr getReference(bool required=true); ///< Reference, that the script is running from (can be empty) + + virtual std::string getTargetId() const; }; } diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp index 25a3ab30d..881687366 100644 --- a/components/interpreter/context.hpp +++ b/components/interpreter/context.hpp @@ -108,6 +108,8 @@ namespace Interpreter virtual void setMemberFloat (const std::string& id, const std::string& name, float value, bool global) = 0; + + virtual std::string getTargetId() const = 0; }; } From 8952154488789989e7721678c1bfc5672590a1ff Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 17 Jul 2014 13:37:57 +0200 Subject: [PATCH 0081/1983] inherit target ID when starting a script from another script --- components/interpreter/scriptopcodes.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/interpreter/scriptopcodes.hpp b/components/interpreter/scriptopcodes.hpp index c98bcd23e..976390eb5 100644 --- a/components/interpreter/scriptopcodes.hpp +++ b/components/interpreter/scriptopcodes.hpp @@ -26,7 +26,7 @@ namespace Interpreter { std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - runtime.getContext().startScript (name); + runtime.getContext().startScript (name, runtime.getContext().getTargetId()); } }; From 4fb897f2f8ea61f3f40a133c9a908d0db8092863 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 18 Jul 2014 09:56:58 +0200 Subject: [PATCH 0082/1983] added missing getId functions to classes derived from MWWorld::Class --- apps/openmw/mwclass/activator.cpp | 5 +++++ apps/openmw/mwclass/activator.hpp | 3 +++ apps/openmw/mwclass/apparatus.cpp | 5 +++++ apps/openmw/mwclass/apparatus.hpp | 3 +++ apps/openmw/mwclass/armor.cpp | 5 +++++ apps/openmw/mwclass/armor.hpp | 3 +++ apps/openmw/mwclass/book.cpp | 5 +++++ apps/openmw/mwclass/book.hpp | 3 +++ apps/openmw/mwclass/clothing.cpp | 5 +++++ apps/openmw/mwclass/clothing.hpp | 3 +++ apps/openmw/mwclass/container.cpp | 5 +++++ apps/openmw/mwclass/container.hpp | 3 +++ apps/openmw/mwclass/creaturelevlist.cpp | 5 +++++ apps/openmw/mwclass/creaturelevlist.hpp | 3 +++ apps/openmw/mwclass/door.cpp | 5 +++++ apps/openmw/mwclass/door.hpp | 3 +++ apps/openmw/mwclass/itemlevlist.cpp | 5 +++++ apps/openmw/mwclass/itemlevlist.hpp | 3 +++ apps/openmw/mwclass/light.cpp | 5 +++++ apps/openmw/mwclass/light.hpp | 3 +++ apps/openmw/mwclass/lockpick.cpp | 5 +++++ apps/openmw/mwclass/lockpick.hpp | 3 +++ apps/openmw/mwclass/misc.cpp | 5 +++++ apps/openmw/mwclass/misc.hpp | 3 +++ apps/openmw/mwclass/potion.cpp | 5 +++++ apps/openmw/mwclass/potion.hpp | 3 +++ apps/openmw/mwclass/probe.cpp | 5 +++++ apps/openmw/mwclass/probe.hpp | 3 +++ apps/openmw/mwclass/repair.cpp | 5 +++++ apps/openmw/mwclass/repair.hpp | 3 +++ apps/openmw/mwclass/static.cpp | 5 +++++ apps/openmw/mwclass/static.hpp | 3 +++ 32 files changed, 128 insertions(+) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 043aadd35..0585ced51 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -25,6 +25,11 @@ namespace MWClass { + std::string Activator::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 1e772ef4f..3e4bc3de4 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -13,6 +13,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index d61ba038a..32f2e0bb7 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -21,6 +21,11 @@ namespace MWClass { + std::string Apparatus::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 17b8b9254..5cdda8f26 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -13,6 +13,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index b29bf36b2..79766e9ec 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -25,6 +25,11 @@ namespace MWClass { + std::string Armor::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index e9164f920..8b7804c63 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 0adee57e3..91b570783 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -22,6 +22,11 @@ namespace MWClass { + std::string Book::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Book::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index b60ef41d6..49d21e8bf 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index dc98e323e..1a9a497d7 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -22,6 +22,11 @@ namespace MWClass { + std::string Clothing::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 052928238..99ce61ece 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 53add4274..044d67536 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -43,6 +43,11 @@ namespace namespace MWClass { + std::string Container::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Container::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 9fc013e45..e926a71fe 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -15,6 +15,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 784304804..754780b1d 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -27,6 +27,11 @@ namespace namespace MWClass { + std::string CreatureLevList::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const { return ""; diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 6c51a3189..7016524eb 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -11,6 +11,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 677ad462e..889268a9a 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -42,6 +42,11 @@ namespace namespace MWClass { + std::string Door::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Door::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 12b360aa8..23e11d336 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -16,6 +16,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index 6ed9ab2e5..d31080bb2 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -5,6 +5,11 @@ namespace MWClass { + std::string ItemLevList::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + std::string ItemLevList::getName (const MWWorld::Ptr& ptr) const { return ""; diff --git a/apps/openmw/mwclass/itemlevlist.hpp b/apps/openmw/mwclass/itemlevlist.hpp index 0b71b072c..2b507135f 100644 --- a/apps/openmw/mwclass/itemlevlist.hpp +++ b/apps/openmw/mwclass/itemlevlist.hpp @@ -9,6 +9,9 @@ namespace MWClass { public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 8a2c20f69..6178984cf 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -47,6 +47,11 @@ namespace namespace MWClass { + std::string Light::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Light::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 5568e1727..584039336 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,6 +14,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index bc6855129..9cce5694d 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -22,6 +22,11 @@ namespace MWClass { + std::string Lockpick::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 8f5a699d9..d4bdf3fa6 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 1044fb01d..7bd2f9b67 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -38,6 +38,11 @@ bool isGold (const MWWorld::Ptr& ptr) namespace MWClass { + std::string Miscellaneous::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 16e9ca10b..53a8e050b 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 440121d35..b0b15193d 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -24,6 +24,11 @@ namespace MWClass { + std::string Potion::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 0f0578ca0..4c407d161 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index ed8625eec..030426dee 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -22,6 +22,11 @@ namespace MWClass { + std::string Probe::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index a959e6c42..047cb8ed4 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index d7a080534..f62d5f6cf 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -21,6 +21,11 @@ namespace MWClass { + std::string Repair::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 28ca5ad4c..17730d6ec 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 8768bde06..c241935ab 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -12,6 +12,11 @@ namespace MWClass { + std::string Static::getId (const MWWorld::Ptr& ptr) const + { + return ptr.get()->mBase->mId; + } + void Static::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { MWWorld::LiveCellRef *ref = diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index e36b3d142..2ac2e8682 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + /// Return ID of \a ptr + virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering From 6a3ff211b18cbf5505dfb6f06a554aac4cf8355d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 18 Jul 2014 09:57:47 +0200 Subject: [PATCH 0083/1983] automatically get target ID at InterpreterContext construction, if a reference is available --- apps/openmw/mwscript/interpretercontext.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index c64b4c0d3..e1dc6273c 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -107,7 +107,13 @@ namespace MWScript MWScript::Locals *locals, MWWorld::Ptr reference, const std::string& targetId) : mLocals (locals), mReference (reference), mActivationHandled (false), mTargetId (targetId) - {} + { + // If we run on a reference (local script, dialogue script or console with object + // selected), store the ID of that reference store it so it can be inherited by + // targeted scripts started from this one. + if (targetId.empty() && !reference.isEmpty()) + mTargetId = reference.getClass().getId (reference); + } int InterpreterContext::getLocalShort (int index) const { From e33ee52b917c2e9a295b89b20c6cd117db32a439 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 18 Jul 2014 12:29:20 +0200 Subject: [PATCH 0084/1983] make stray names in the begin line a warning instead of an error --- components/compiler/fileparser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 185af4a51..e90e9a8f6 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -51,6 +51,12 @@ namespace Compiler /// \todo allow this workaround to be disabled for newer scripts } + if (mState==BeginCompleteState) + { + reportWarning ("Stray string (" + name + ") after begin statement", loc); + return true; + } + return Parser::parseName (name, loc, scanner); } From d87630b41a0c5dc5deada97c2b3f363361f9554f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Jul 2014 09:34:10 +0200 Subject: [PATCH 0085/1983] blacklisting for scripts in OpenMW --- apps/openmw/engine.cpp | 14 ++++++++++++- apps/openmw/engine.hpp | 6 ++++++ apps/openmw/main.cpp | 20 ++++++++++++++----- apps/openmw/mwscript/scriptmanagerimp.cpp | 24 ++++++++++++++++++----- apps/openmw/mwscript/scriptmanagerimp.hpp | 4 +++- files/openmw.cfg | 1 + files/openmw.cfg.local | 1 + 7 files changed, 58 insertions(+), 12 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f8b4c9856..66eae5f5d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -180,6 +180,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mEncoder(NULL) , mActivationDistanceOverride(-1) , mGrab(true) + , mScriptBlacklistUse (true) { std::srand ( std::time(NULL) ); @@ -406,7 +407,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mScriptContext->setExtensions (&mExtensions); mEnvironment.setScriptManager (new MWScript::ScriptManager (MWBase::Environment::get().getWorld()->getStore(), - mVerboseScripts, *mScriptContext, mWarningsMode)); + mVerboseScripts, *mScriptContext, mWarningsMode, + mScriptBlacklistUse ? mScriptBlacklist : std::vector())); // Create game mechanics system MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager; @@ -565,3 +567,13 @@ void OMW::Engine::setWarningsMode (int mode) { mWarningsMode = mode; } + +void OMW::Engine::setScriptBlacklist (const std::vector& list) +{ + mScriptBlacklist = list; +} + +void OMW::Engine::setScriptBlacklistUse (bool use) +{ + mScriptBlacklistUse = use; +} \ No newline at end of file diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index e0f51d0dc..203379a93 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -89,6 +89,8 @@ namespace OMW Files::Collections mFileCollections; bool mFSStrict; Translation::Storage mTranslationDataStorage; + std::vector mScriptBlacklist; + bool mScriptBlacklistUse; // not implemented Engine (const Engine&); @@ -181,6 +183,10 @@ namespace OMW void setWarningsMode (int mode); + void setScriptBlacklist (const std::vector& list); + + void setScriptBlacklistUse (bool use); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index adde408b9..8cdab74d8 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -144,6 +144,12 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat "\t1 - show warning but consider script as correctly compiled anyway\n" "\t2 - treat warnings as errors") + ("script-blacklist", bpo::value()->default_value(StringsVector(), "") + ->multitoken(), "ignore the specified script (if the use of the blacklist is enabled)") + + ("script-blacklist-use", bpo::value()->implicit_value(true) + ->default_value(true), "enable script blacklisting") + ("skip-menu", bpo::value()->implicit_value(true) ->default_value(false), "skip main menu on game startup") @@ -241,15 +247,19 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setCell(variables["start"].as()); engine.setSkipMenu (variables["skip-menu"].as()); - // other settings - engine.setSoundUsage(!variables["no-sound"].as()); - engine.setScriptsVerbosity(variables["script-verbose"].as()); + // scripts engine.setCompileAll(variables["script-all"].as()); - engine.setFallbackValues(variables["fallback"].as().mMap); + engine.setScriptsVerbosity(variables["script-verbose"].as()); engine.setScriptConsoleMode (variables["script-console"].as()); engine.setStartupScript (variables["script-run"].as()); - engine.setActivationDistanceOverride (variables["activate-dist"].as()); engine.setWarningsMode (variables["script-warn"].as()); + engine.setScriptBlacklist (variables["script-blacklist"].as()); + engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); + + // other settings + engine.setSoundUsage(!variables["no-sound"].as()); + engine.setFallbackValues(variables["fallback"].as().mMap); + engine.setActivationDistanceOverride (variables["activate-dist"].as()); return true; } diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 7b858dacf..781c16299 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -22,12 +23,19 @@ namespace MWScript { ScriptManager::ScriptManager (const MWWorld::ESMStore& store, bool verbose, - Compiler::Context& compilerContext, int warningsMode) + Compiler::Context& compilerContext, int warningsMode, + const std::vector& scriptBlacklist) : mErrorHandler (std::cerr), mStore (store), mVerbose (verbose), mCompilerContext (compilerContext), mParser (mErrorHandler, mCompilerContext), mOpcodesInstalled (false), mGlobalScripts (store) { mErrorHandler.setWarningsMode (warningsMode); + + mScriptBlacklist.resize (scriptBlacklist.size()); + + std::transform (scriptBlacklist.begin(), scriptBlacklist.end(), + mScriptBlacklist.begin(), Misc::StringUtils::lowerCase); + std::sort (mScriptBlacklist.begin(), mScriptBlacklist.end()); } bool ScriptManager::compile (const std::string& name) @@ -133,11 +141,17 @@ namespace MWScript int success = 0; const MWWorld::Store& scripts = mStore.get(); - MWWorld::Store::iterator it = scripts.begin(); - for (; it != scripts.end(); ++it, ++count) - if (compile (it->mId)) - ++success; + for (MWWorld::Store::iterator iter = scripts.begin(); + iter != scripts.end(); ++iter) + if (!std::binary_search (mScriptBlacklist.begin(), mScriptBlacklist.end(), + Misc::StringUtils::lowerCase (iter->mId))) + { + ++count; + + if (compile (iter->mId)) + ++success; + } return std::make_pair (count, success); } diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp index da3abc60b..4edc09eca 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.hpp +++ b/apps/openmw/mwscript/scriptmanagerimp.hpp @@ -48,11 +48,13 @@ namespace MWScript ScriptCollection mScripts; GlobalScripts mGlobalScripts; std::map mOtherLocals; + std::vector mScriptBlacklist; public: ScriptManager (const MWWorld::ESMStore& store, bool verbose, - Compiler::Context& compilerContext, int warningsMode); + Compiler::Context& compilerContext, int warningsMode, + const std::vector& scriptBlacklist); virtual void run (const std::string& name, Interpreter::Context& interpreterContext); ///< Run the script with the given name (compile first, if not compiled yet) diff --git a/files/openmw.cfg b/files/openmw.cfg index b67b79a96..4ebe287d5 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -2,3 +2,4 @@ data="?global?data" data="?mw?Data Files" data-local="?userdata?data" resources=${OPENMW_RESOURCE_FILES} +script-blacklist=Museum diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index 6a578542d..4ae51382e 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -3,3 +3,4 @@ data="?mw?Data Files" data=./data data-local="?userdata?data" resources=./resources +script-blacklist=Museum From a9f9dec923927934c3adc9772b963cedd28e3874 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Jul 2014 12:15:21 +0200 Subject: [PATCH 0086/1983] consider script blacklist in OpenCS verifier --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/editor.cpp | 10 +++++++- apps/opencs/model/doc/blacklist.cpp | 31 +++++++++++++++++++++++ apps/opencs/model/doc/blacklist.hpp | 25 ++++++++++++++++++ apps/opencs/model/doc/document.cpp | 14 ++++++++-- apps/opencs/model/doc/document.hpp | 7 ++++- apps/opencs/model/doc/documentmanager.cpp | 7 ++++- apps/opencs/model/doc/documentmanager.hpp | 3 +++ apps/opencs/model/tools/scriptcheck.cpp | 21 ++++++++++----- apps/opencs/model/tools/scriptcheck.hpp | 9 +++++-- apps/opencs/model/tools/tools.cpp | 6 +++-- apps/opencs/model/tools/tools.hpp | 4 ++- 12 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 apps/opencs/model/doc/blacklist.cpp create mode 100644 apps/opencs/model/doc/blacklist.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index c03cc3138..e1e467889 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -9,7 +9,7 @@ opencs_units (model/doc ) opencs_units_noqt (model/doc - stage savingstate savingstages + stage savingstate savingstages blacklist ) opencs_hdrs_noqt (model/doc diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index b3513a7f1..44d66b236 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -86,7 +86,11 @@ std::pair > CS::Editor::readConfi ("encoding", boost::program_options::value()->default_value("win1252")) ("resources", boost::program_options::value()->default_value("resources")) ("fallback-archive", boost::program_options::value >()-> - default_value(std::vector(), "fallback-archive")->multitoken()); + default_value(std::vector(), "fallback-archive")->multitoken()) + ("script-blacklist", boost::program_options::value >()->default_value(std::vector(), "") + ->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)") + ("script-blacklist-use", boost::program_options::value()->implicit_value(true) + ->default_value(true), "enable script blacklisting"); boost::program_options::notify(variables); @@ -97,6 +101,10 @@ std::pair > CS::Editor::readConfi mDocumentManager.setResourceDir (mResources = variables["resources"].as()); + if (variables["script-blacklist-use"].as()) + mDocumentManager.setBlacklistedScripts ( + variables["script-blacklist"].as >()); + mFsStrict = variables["fs-strict"].as(); Files::PathContainer dataDirs, dataLocal; diff --git a/apps/opencs/model/doc/blacklist.cpp b/apps/opencs/model/doc/blacklist.cpp new file mode 100644 index 000000000..9b37a4302 --- /dev/null +++ b/apps/opencs/model/doc/blacklist.cpp @@ -0,0 +1,31 @@ + +#include "blacklist.hpp" + +#include + +#include + +bool CSMDoc::Blacklist::isBlacklisted (const CSMWorld::UniversalId& id) const +{ + std::map >::const_iterator iter = + mIds.find (id.getType()); + + if (iter==mIds.end()) + return false; + + return std::binary_search (iter->second.begin(), iter->second.end(), + Misc::StringUtils::lowerCase (id.getId())); +} + +void CSMDoc::Blacklist::add (CSMWorld::UniversalId::Type type, + const std::vector& ids) +{ + std::vector& list = mIds[type]; + + int size = list.size(); + + list.resize (size+ids.size()); + + std::transform (ids.begin(), ids.end(), list.begin()+size, Misc::StringUtils::lowerCase); + std::sort (list.begin(), list.end()); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/blacklist.hpp b/apps/opencs/model/doc/blacklist.hpp new file mode 100644 index 000000000..9bf7f1d86 --- /dev/null +++ b/apps/opencs/model/doc/blacklist.hpp @@ -0,0 +1,25 @@ +#ifndef CSM_DOC_BLACKLIST_H +#define CSM_DOC_BLACKLIST_H + +#include +#include +#include + +#include "../world/universalid.hpp" + +namespace CSMDoc +{ + /// \brief ID blacklist sorted by UniversalId type + class Blacklist + { + std::map > mIds; + + public: + + bool isBlacklisted (const CSMWorld::UniversalId& id) const; + + void add (CSMWorld::UniversalId::Type type, const std::vector& ids); + }; +} + +#endif diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 23a47b313..c608757e0 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2205,9 +2205,10 @@ void CSMDoc::Document::createBase() CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, - ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager) + ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, + const std::vector& blacklistedScripts) : mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), - mTools (mData), mResDir(resDir), + mTools (*this), mResDir(resDir), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSaving (*this, mProjectPath, encoding) @@ -2239,6 +2240,8 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, createBase(); } + mBlacklist.add (CSMWorld::UniversalId::Type_Script, blacklistedScripts); + addOptionalGmsts(); addOptionalGlobals(); @@ -2358,6 +2361,13 @@ CSMTools::ReportModel *CSMDoc::Document::getReport (const CSMWorld::UniversalId& return mTools.getReport (id); } +bool CSMDoc::Document::isBlacklisted (const CSMWorld::UniversalId& id) + const +{ + return mBlacklist.isBlacklisted (id); +} + + void CSMDoc::Document::progress (int current, int max, int type) { emit progress (current, max, type, 1, this); diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index d092b47c8..d0e94d5e0 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -17,6 +17,7 @@ #include "state.hpp" #include "saving.hpp" +#include "blacklist.hpp" class QAbstractItemModel; @@ -52,6 +53,7 @@ namespace CSMDoc boost::filesystem::path mProjectPath; Saving mSaving; boost::filesystem::path mResDir; + Blacklist mBlacklist; // 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. @@ -78,7 +80,8 @@ namespace CSMDoc Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, - ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager); + ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, + const std::vector& blacklistedScripts); ~Document(); @@ -110,6 +113,8 @@ namespace CSMDoc CSMTools::ReportModel *getReport (const CSMWorld::UniversalId& id); ///< The ownership of the returned report is not transferred. + bool isBlacklisted (const CSMWorld::UniversalId& id) const; + signals: void stateChanged (int state, CSMDoc::Document *document); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 6953db0ed..9b807225c 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -52,7 +52,7 @@ CSMDoc::DocumentManager::~DocumentManager() 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); + Document *document = new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); mDocuments.push_back (document); @@ -85,6 +85,11 @@ void CSMDoc::DocumentManager::setEncoding (ToUTF8::FromType encoding) mEncoding = encoding; } +void CSMDoc::DocumentManager::setBlacklistedScripts (const std::vector& scriptIds) +{ + mBlacklistedScripts = scriptIds; +} + void CSMDoc::DocumentManager::listResources() { mResourcesManager.listResources(); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index cebae6f6d..c545b9a9f 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -34,6 +34,7 @@ namespace CSMDoc Loader mLoader; ToUTF8::FromType mEncoding; CSMWorld::ResourcesManager mResourcesManager; + std::vector mBlacklistedScripts; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); @@ -53,6 +54,8 @@ namespace CSMDoc void setEncoding (ToUTF8::FromType encoding); + void setBlacklistedScripts (const std::vector& scriptIds); + /// Ask OGRE for a list of available resources. void listResources(); diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index b989e22a2..d2c647bda 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -7,6 +7,8 @@ #include #include +#include "../doc/document.hpp" + #include "../world/data.hpp" void CSMTools::ScriptCheckStage::report (const std::string& message, const Compiler::TokenLoc& loc, @@ -37,8 +39,8 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) (type==ErrorMessage ? "error: " : "warning: ") + message)); } -CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMWorld::Data& data) -: mData (data), mContext (data), mMessages (0) +CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document) +: mDocument (document), mContext (document.getData()), mMessages (0) { /// \todo add an option to configure warning mode setWarningsMode (0); @@ -53,18 +55,25 @@ int CSMTools::ScriptCheckStage::setup() mMessages = 0; mId.clear(); - return mData.getScripts().getSize(); + return mDocument.getData().getScripts().getSize(); } void CSMTools::ScriptCheckStage::perform (int stage, Messages& messages) { + mId = mDocument.getData().getScripts().getId (stage); + + if (mDocument.isBlacklisted ( + CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, mId))) + return; + mMessages = &messages; - mId = mData.getScripts().getId (stage); try { - mFile = mData.getScripts().getRecord (stage).get().mId; - std::istringstream input (mData.getScripts().getRecord (stage).get().mScriptText); + const CSMWorld::Data& data = mDocument.getData(); + + mFile = data.getScripts().getRecord (stage).get().mId; + std::istringstream input (data.getScripts().getRecord (stage).get().mScriptText); Compiler::Scanner scanner (*this, input, mContext.getExtensions()); diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp index ecf8d61b7..75f11b9d4 100644 --- a/apps/opencs/model/tools/scriptcheck.hpp +++ b/apps/opencs/model/tools/scriptcheck.hpp @@ -8,12 +8,17 @@ #include "../world/scriptcontext.hpp" +namespace CSMDoc +{ + class Document; +} + namespace CSMTools { /// \brief VerifyStage: make sure that scripts compile class ScriptCheckStage : public CSMDoc::Stage, private Compiler::ErrorHandler { - const CSMWorld::Data& mData; + const CSMDoc::Document& mDocument; Compiler::Extensions mExtensions; CSMWorld::ScriptContext mContext; std::string mId; @@ -28,7 +33,7 @@ namespace CSMTools public: - ScriptCheckStage (const CSMWorld::Data& data); + ScriptCheckStage (const CSMDoc::Document& document); virtual int setup(); ///< \return number of steps diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 2f93db48e..fcf98868a 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -5,6 +5,7 @@ #include "../doc/state.hpp" #include "../doc/operation.hpp" +#include "../doc/document.hpp" #include "../world/data.hpp" #include "../world/universalid.hpp" @@ -80,13 +81,14 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() mVerifier->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions())); - mVerifier->appendStage (new ScriptCheckStage (mData)); + mVerifier->appendStage (new ScriptCheckStage (mDocument)); } return mVerifier; } -CSMTools::Tools::Tools (CSMWorld::Data& data) : mData (data), mVerifier (0), mNextReportNumber (0) +CSMTools::Tools::Tools (CSMDoc::Document& document) +: mDocument (document), mData (document.getData()), mVerifier (0), mNextReportNumber (0) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 3394d3f62..4d677142d 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -14,6 +14,7 @@ namespace CSMWorld namespace CSMDoc { class Operation; + class Document; } namespace CSMTools @@ -24,6 +25,7 @@ namespace CSMTools { Q_OBJECT + CSMDoc::Document& mDocument; CSMWorld::Data& mData; CSMDoc::Operation *mVerifier; std::map mReports; @@ -44,7 +46,7 @@ namespace CSMTools public: - Tools (CSMWorld::Data& data); + Tools (CSMDoc::Document& document); virtual ~Tools(); From 11a2c767cc474a3a8c1f3b831d5a67b7e7449c72 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Jul 2014 12:50:29 +0200 Subject: [PATCH 0087/1983] some argument parsing cleanup --- components/compiler/exprparser.cpp | 30 +++++++++++++++++++----------- components/compiler/extensions.hpp | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index d0cda0ec2..6e29626b1 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -777,11 +777,22 @@ namespace Compiler ++optionalCount; } } + else if (*iter=='X') + { + parser.reset(); + + parser.setOptional (true); + + scanner.scan (parser); + + if (optional && parser.isEmpty()) + break; + } else { parser.reset(); - if (optional || *iter == 'X') + if (optional) parser.setOptional (true); scanner.scan (parser); @@ -789,20 +800,17 @@ namespace Compiler if (optional && parser.isEmpty()) break; - if (*iter != 'X') - { - std::vector tmp; + std::vector tmp; - char type = parser.append (tmp); + char type = parser.append (tmp); - if (type!=*iter) - Generator::convert (tmp, type, *iter); + if (type!=*iter) + Generator::convert (tmp, type, *iter); - stack.push (tmp); + stack.push (tmp); - if (optional) - ++optionalCount; - } + if (optional) + ++optionalCount; } } diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index d229751de..aa62fd0eb 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -21,7 +21,7 @@ namespace Compiler s - Short
S - String, case preserved
x - Optional, ignored string argument - X - Optional, ignored integer argument + X - Optional, ignored float argument **/ typedef std::string ScriptArgs; From f8019b4a97baf5ee15fb88bc8047a4d4e6b76e9c Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 21 Jul 2014 18:35:51 +0200 Subject: [PATCH 0088/1983] added a bilboard with the Cell coord. Can't be toggled off yet, and no clean up too. --- .../view/render/pagedworldspacewidget.cpp | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 1ee32fa97..e8a38321f 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -4,12 +4,192 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" +void WriteToTexture(const Ogre::String &str, Ogre::TexturePtr destTexture, Ogre::Image::Box destRectangle, Ogre::Font* font, const Ogre::ColourValue &color, char justify = 'l', bool wordwrap = true) +{ + using namespace Ogre; + + if (destTexture->getHeight() < destRectangle.bottom) + destRectangle.bottom = destTexture->getHeight(); + if (destTexture->getWidth() < destRectangle.right) + destRectangle.right = destTexture->getWidth(); + + if (!font->isLoaded()) + font->load(); + + TexturePtr fontTexture = (TexturePtr)TextureManager::getSingleton().getByName(font->getMaterial()->getTechnique(0)->getPass(0)->getTextureUnitState(0)->getTextureName()); + + HardwarePixelBufferSharedPtr fontBuffer = fontTexture->getBuffer(); + HardwarePixelBufferSharedPtr destBuffer = destTexture->getBuffer(); + + PixelBox destPb = destBuffer->lock(destRectangle, HardwareBuffer::HBL_NORMAL); + + // The font texture buffer was created write only...so we cannot read it back :o). One solution is to copy the buffer instead of locking it. (Maybe there is a way to create a font texture which is not write_only ?) + + // create a buffer + size_t nBuffSize = fontBuffer->getSizeInBytes(); + uint8* buffer = (uint8*)calloc(nBuffSize, sizeof(uint8)); + + // create pixel box using the copy of the buffer + PixelBox fontPb(fontBuffer->getWidth(), fontBuffer->getHeight(), fontBuffer->getDepth(), fontBuffer->getFormat(), buffer); + fontBuffer->blitToMemory(fontPb); + + uint8* fontData = static_cast(fontPb.data); + uint8* destData = static_cast(destPb.data); + + const size_t fontPixelSize = PixelUtil::getNumElemBytes(fontPb.format); + const size_t destPixelSize = PixelUtil::getNumElemBytes(destPb.format); + + const size_t fontRowPitchBytes = fontPb.rowPitch * fontPixelSize; + const size_t destRowPitchBytes = destPb.rowPitch * destPixelSize; + + Box *GlyphTexCoords; + GlyphTexCoords = new Box[str.size()]; + + Font::UVRect glypheTexRect; + size_t charheight = 0; + size_t charwidth = 0; + + for (unsigned int i = 0; i < str.size(); i++) + { + if ((str[i] != '\t') && (str[i] != '\n') && (str[i] != ' ')) + { + glypheTexRect = font->getGlyphTexCoords(str[i]); + GlyphTexCoords[i].left = glypheTexRect.left * fontTexture->getSrcWidth(); + GlyphTexCoords[i].top = glypheTexRect.top * fontTexture->getSrcHeight(); + GlyphTexCoords[i].right = glypheTexRect.right * fontTexture->getSrcWidth(); + GlyphTexCoords[i].bottom = glypheTexRect.bottom * fontTexture->getSrcHeight(); + + if (GlyphTexCoords[i].getHeight() > charheight) + charheight = GlyphTexCoords[i].getHeight(); + if (GlyphTexCoords[i].getWidth() > charwidth) + charwidth = GlyphTexCoords[i].getWidth(); + } + + } + + size_t cursorX = 0; + size_t cursorY = 0; + size_t lineend = destRectangle.getWidth(); + bool carriagreturn = true; + for (unsigned int strindex = 0; strindex < str.size(); strindex++) + { + switch (str[strindex]) + { + case ' ': cursorX += charwidth; break; + case '\t':cursorX += charwidth * 3; break; + case '\n':cursorY += charheight; carriagreturn = true; break; + default: + { + //wrapping + if ((cursorX + GlyphTexCoords[strindex].getWidth()> lineend) && !carriagreturn) + { + cursorY += charheight; + carriagreturn = true; + } + + //justify + if (carriagreturn) + { + size_t l = strindex; + size_t textwidth = 0; + size_t wordwidth = 0; + + while ((l < str.size()) && (str[l] != '\n)')) + { + wordwidth = 0; + + switch (str[l]) + { + case ' ': wordwidth = charwidth; ++l; break; + case '\t': wordwidth = charwidth * 3; ++l; break; + case '\n': l = str.size(); + } + + if (wordwrap) + while ((l < str.size()) && (str[l] != ' ') && (str[l] != '\t') && (str[l] != '\n')) + { + wordwidth += GlyphTexCoords[l].getWidth(); + ++l; + } + else + { + wordwidth += GlyphTexCoords[l].getWidth(); + l++; + } + + if ((textwidth + wordwidth) <= destRectangle.getWidth()) + textwidth += (wordwidth); + else + break; + } + + if ((textwidth == 0) && (wordwidth > destRectangle.getWidth())) + textwidth = destRectangle.getWidth(); + + switch (justify) + { + case 'c': cursorX = (destRectangle.getWidth() - textwidth) / 2; + lineend = destRectangle.getWidth() - cursorX; + break; + + case 'r': cursorX = (destRectangle.getWidth() - textwidth); + lineend = destRectangle.getWidth(); + break; + + default: cursorX = 0; + lineend = textwidth; + break; + } + + carriagreturn = false; + } + + //abort - net enough space to draw + if ((cursorY + charheight) > destRectangle.getHeight()) + goto stop; + + //draw pixel by pixel + for (size_t i = 0; i < GlyphTexCoords[strindex].getHeight(); i++) + for (size_t j = 0; j < GlyphTexCoords[strindex].getWidth(); j++) + { + float alpha = color.a * (fontData[(i + GlyphTexCoords[strindex].top) * fontRowPitchBytes + (j + GlyphTexCoords[strindex].left) * fontPixelSize + 1] / 255.0); + float invalpha = 1.0 - alpha; + size_t offset = (i + cursorY) * destRowPitchBytes + (j + cursorX) * destPixelSize; + ColourValue pix; + PixelUtil::unpackColour(&pix, destPb.format, &destData[offset]); + pix = (pix * invalpha) + (color * alpha); + PixelUtil::packColour(pix, destPb.format, &destData[offset]); + } + + cursorX += GlyphTexCoords[strindex].getWidth(); + }//default + }//switch + }//for + +stop: + delete[] GlyphTexCoords; + + destBuffer->unlock(); + + // Free the memory allocated for the buffer + free(buffer); buffer = 0; +} + bool CSVRender::PagedWorldspaceWidget::adjustCells() { bool modified = false; @@ -59,6 +239,61 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() new Cell (mDocument.getData(), getSceneManager(), iter->getId (mWorldspace)))); + //billboard which indicate the Cell coord + Ogre::SceneNode* billboardNode = getSceneManager()->getRootSceneNode()->createChildSceneNode("CellBillboardNode" + iter->getId(mWorldspace)); + billboardNode->setPosition(8192 * iter->getX() + 4096, 8192 * iter->getY() + 4096, 0); + + Ogre::Font* font; + if (Ogre::FontManager::getSingletonPtr()->getByName("CellBillboardFont" + iter->getId(mWorldspace)).isNull()) + { + font = Ogre::FontManager::getSingletonPtr()->create("CellBillboardFont" + iter->getId(mWorldspace), "Data00000001").getPointer(); + font->setType(Ogre::FT_TRUETYPE); + font->setSource("Comic.ttf"); + font->setTrueTypeSize(256); + font->load(); + } + else + { + font = Ogre::FontManager::getSingletonPtr()->getByName("CellBillboardFont" + iter->getId(mWorldspace)).getPointer(); + } + + //std:: + + Ogre::TexturePtr texture; + if (Ogre::TextureManager::getSingleton().resourceExists("CellBillboardTexture" + iter->getId(mWorldspace))) + { + texture = Ogre::TextureManager::getSingleton().getByName("CellBillboardTexture" + iter->getId(mWorldspace)); + } + else + { + texture = Ogre::TextureManager::getSingleton().createManual("CellBillboardTexture" + iter->getId(mWorldspace), "Data00000001", Ogre::TEX_TYPE_2D, 1024, 512, Ogre::MIP_UNLIMITED, Ogre::PF_X8R8G8B8, Ogre::TU_STATIC | Ogre::TU_AUTOMIPMAP); + WriteToTexture(std::to_string(iter->getX()) + ";" + std::to_string(iter->getY()), texture, Ogre::Image::Box(0, 100, 1024, 512), font, Ogre::ColourValue(1.0, 1.0, 1.0, 1.0), 'c'); + } + + Ogre::MaterialPtr material; + if (Ogre::MaterialManager::getSingleton().resourceExists("CellBillboardMaterial" + iter->getId(mWorldspace))) + { + material = Ogre::MaterialManager::getSingleton().getByName("CellBillboardMaterial" + iter->getId(mWorldspace)); + } + else + { + material = Ogre::MaterialManager::getSingleton().create( + "CellBillboardMaterial" + iter->getId(mWorldspace), // name + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + + material->getTechnique(0)->getPass(0)->createTextureUnitState("CellBillboardTexture" + iter->getId(mWorldspace)); + material->getTechnique(0)->getPass(0)->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + material->setDepthCheckEnabled(false); + material->setDepthWriteEnabled(false); + } + + Ogre::BillboardSet* mySet = getSceneManager()->createBillboardSet("CellBillboardSet" + iter->getId(mWorldspace)); + Ogre::Billboard* myBillboard = mySet->createBillboard(Ogre::Vector3(0, 0, 0)); + mySet->setMaterial(material); + myBillboard->setDimensions(4000, 2000); + mySet->setRenderQueueGroup(mySet->getRenderQueueGroup() + 1); // render the bilboard on top + billboardNode->attachObject(mySet); + modified = true; } } From a5058625b3b606f513647be735f68d81e5026054 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 21 Jul 2014 18:57:35 +0200 Subject: [PATCH 0089/1983] clean up + toggle function for the billboard --- .../view/render/pagedworldspacewidget.cpp | 24 ++++++++++++++++--- .../view/render/pagedworldspacewidget.hpp | 3 +++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index e8a38321f..9602d9f0c 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -190,6 +190,18 @@ stop: free(buffer); buffer = 0; } +void CSVRender::PagedWorldspaceWidget::displayCellCoord(bool display) +{ + mDisplayCellCoord = display; + std::map::iterator iter(mCells.begin()); + + while (iter != mCells.end()) + { + getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->setVisible(display); + iter++; + } +} + bool CSVRender::PagedWorldspaceWidget::adjustCells() { bool modified = false; @@ -210,6 +222,12 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() { delete iter->second; mCells.erase (iter++); + + getSceneManager()->getSceneNode("CellBillboardNode" + iter->first.getId(mWorldspace))->detachAllObjects(); + getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->removeBillboard( + getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->getBillboard(0)); + getSceneManager()->destroyBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace)); + modified = true; } else @@ -256,8 +274,6 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() { font = Ogre::FontManager::getSingletonPtr()->getByName("CellBillboardFont" + iter->getId(mWorldspace)).getPointer(); } - - //std:: Ogre::TexturePtr texture; if (Ogre::TextureManager::getSingleton().resourceExists("CellBillboardTexture" + iter->getId(mWorldspace))) @@ -294,6 +310,8 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() mySet->setRenderQueueGroup(mySet->getRenderQueueGroup() + 1); // render the bilboard on top billboardNode->attachObject(mySet); + mySet->setVisible(mDisplayCellCoord); + modified = true; } } @@ -365,7 +383,7 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent } CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) -: WorldspaceWidget (document, parent), mDocument (document), mWorldspace ("std::default") +: WorldspaceWidget(document, parent), mDocument(document), mWorldspace("std::default"), mDisplayCellCoord(true) { QAbstractItemModel *cells = document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index c4fb789ee..e0c1c6f54 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -18,6 +18,7 @@ namespace CSVRender CSMWorld::CellSelection mSelection; std::map mCells; std::string mWorldspace; + bool mDisplayCellCoord; private: @@ -54,6 +55,8 @@ namespace CSVRender void setCellSelection (const CSMWorld::CellSelection& selection); + void displayCellCoord(bool display); + virtual void handleDrop(const std::vector& data); virtual dropRequirments getDropRequirements(dropType type) const; From 2092e5fe228ca917685a36fe35668d94b1be6437 Mon Sep 17 00:00:00 2001 From: gus Date: Tue, 22 Jul 2014 11:49:45 +0200 Subject: [PATCH 0090/1983] compile fixes --- apps/opencs/model/doc/document.cpp | 1 + apps/openmw/mwmechanics/levelledlist.hpp | 2 ++ apps/openmw/mwrender/animation.cpp | 2 ++ apps/openmw/mwrender/occlusionquery.cpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 2 ++ apps/openmw/mwworld/containerstore.cpp | 1 + components/bsa/bsa_archive.cpp | 2 +- components/nif/niffile.hpp | 1 + extern/sdl4ogre/sdlinputwrapper.cpp | 1 + extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp | 2 ++ libs/openengine/bullet/physic.hpp | 1 + 12 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 23a47b313..6dc361c1e 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -1,6 +1,7 @@ #include "document.hpp" #include +#include #include diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index 5d9e29118..ad0677067 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -8,6 +8,8 @@ #include "../mwbase/environment.hpp" #include "../mwmechanics/creaturestats.hpp" +#include + namespace MWMechanics { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 872740d74..f1fcce5f7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1,5 +1,7 @@ #include "animation.hpp" +#include + #include #include #include diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 92a49acc0..c228a0160 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -1,5 +1,7 @@ #include "occlusionquery.hpp" +#include + #include #include #include diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 23edb3a7f..157265c96 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1,6 +1,7 @@ #include "renderingmanager.hpp" #include +#include #include #include diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index a3604cc66..cf4f05a4c 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -1,6 +1,8 @@ #include "statemanagerimp.hpp" +#include + #include #include #include diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index e330ddaee..7c7470bd1 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -1,6 +1,7 @@ #include "containerstore.hpp" +#include #include #include #include diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 6574f096b..4cd754f2b 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -45,7 +45,7 @@ static char strict_normalize_char(char ch) static char nonstrict_normalize_char(char ch) { - return ch == '\\' ? '/' : std::tolower(ch); + return ch == '\\' ? '/' : std::tolower(ch,std::locale()); } template diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index d70124263..87e46b234 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index f65dfb376..0f2008e99 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace SFO { diff --git a/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp b/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp index e71854019..8b819f6ef 100644 --- a/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp +++ b/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp @@ -8,6 +8,8 @@ #include #include +#include + namespace sh { OgreGpuProgram::OgreGpuProgram( diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index e37caee38..a806c2f3b 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -8,6 +8,7 @@ #include #include "BulletShapeLoader.h" #include "BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.h" +#include From 1e7c4f26df4d2b61c29181dc8b1b066b60431ecc Mon Sep 17 00:00:00 2001 From: gus Date: Tue, 22 Jul 2014 12:01:21 +0200 Subject: [PATCH 0091/1983] more compile fixes (damn you Visual studio) --- apps/openmw/mwrender/terraingrid.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwrender/terraingrid.cpp b/apps/openmw/mwrender/terraingrid.cpp index f2bd92061..02899e0c5 100644 --- a/apps/openmw/mwrender/terraingrid.cpp +++ b/apps/openmw/mwrender/terraingrid.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" From 546b44a3be33a81174d77342d08fe07b04511970 Mon Sep 17 00:00:00 2001 From: gus Date: Tue, 22 Jul 2014 12:10:58 +0200 Subject: [PATCH 0092/1983] bugfixes --- apps/opencs/editor.cpp | 3 +++ apps/opencs/view/render/pagedworldspacewidget.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index b3513a7f1..f36c28e44 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -13,6 +13,7 @@ #include #include +#include #include @@ -297,6 +298,8 @@ std::auto_ptr CS::Editor::setupGraphics() sh::Factory::getInstance ().setGlobalSetting ("num_lights", "8"); + Ogre::FontManager* fontManager = new Ogre::FontManager(); + /// \todo add more configurable shiny settings return factory; diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 9602d9f0c..76be80c02 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -262,7 +262,7 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() billboardNode->setPosition(8192 * iter->getX() + 4096, 8192 * iter->getY() + 4096, 0); Ogre::Font* font; - if (Ogre::FontManager::getSingletonPtr()->getByName("CellBillboardFont" + iter->getId(mWorldspace)).isNull()) + if (!Ogre::FontManager::getSingletonPtr()->resourceExists("CellBillboardFont" + iter->getId(mWorldspace))) { font = Ogre::FontManager::getSingletonPtr()->create("CellBillboardFont" + iter->getId(mWorldspace), "Data00000001").getPointer(); font->setType(Ogre::FT_TRUETYPE); From 195767db7f4baa027ef4514bc6ed1d6ba3165499 Mon Sep 17 00:00:00 2001 From: gus Date: Tue, 22 Jul 2014 12:25:31 +0200 Subject: [PATCH 0093/1983] link back to OGre Wiki for some copied past code --- apps/opencs/view/render/pagedworldspacewidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 76be80c02..cce7a02c2 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -19,6 +19,7 @@ #include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" +//all credits to http://www.ogre3d.org/tikiwiki/tiki-index.php?page=HowTo:+Write+text+on+texture void WriteToTexture(const Ogre::String &str, Ogre::TexturePtr destTexture, Ogre::Image::Box destRectangle, Ogre::Font* font, const Ogre::ColourValue &color, char justify = 'l', bool wordwrap = true) { using namespace Ogre; From baf6cca0519f2ad17464f4887c568c5fff937cf8 Mon Sep 17 00:00:00 2001 From: gus Date: Tue, 22 Jul 2014 13:32:15 +0200 Subject: [PATCH 0094/1983] modified tab to space --- .../view/render/pagedworldspacewidget.cpp | 458 +++++++++--------- .../view/render/pagedworldspacewidget.hpp | 6 +- 2 files changed, 232 insertions(+), 232 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index cce7a02c2..ac10cc908 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -22,185 +22,185 @@ //all credits to http://www.ogre3d.org/tikiwiki/tiki-index.php?page=HowTo:+Write+text+on+texture void WriteToTexture(const Ogre::String &str, Ogre::TexturePtr destTexture, Ogre::Image::Box destRectangle, Ogre::Font* font, const Ogre::ColourValue &color, char justify = 'l', bool wordwrap = true) { - using namespace Ogre; - - if (destTexture->getHeight() < destRectangle.bottom) - destRectangle.bottom = destTexture->getHeight(); - if (destTexture->getWidth() < destRectangle.right) - destRectangle.right = destTexture->getWidth(); - - if (!font->isLoaded()) - font->load(); - - TexturePtr fontTexture = (TexturePtr)TextureManager::getSingleton().getByName(font->getMaterial()->getTechnique(0)->getPass(0)->getTextureUnitState(0)->getTextureName()); - - HardwarePixelBufferSharedPtr fontBuffer = fontTexture->getBuffer(); - HardwarePixelBufferSharedPtr destBuffer = destTexture->getBuffer(); - - PixelBox destPb = destBuffer->lock(destRectangle, HardwareBuffer::HBL_NORMAL); - - // The font texture buffer was created write only...so we cannot read it back :o). One solution is to copy the buffer instead of locking it. (Maybe there is a way to create a font texture which is not write_only ?) - - // create a buffer - size_t nBuffSize = fontBuffer->getSizeInBytes(); - uint8* buffer = (uint8*)calloc(nBuffSize, sizeof(uint8)); - - // create pixel box using the copy of the buffer - PixelBox fontPb(fontBuffer->getWidth(), fontBuffer->getHeight(), fontBuffer->getDepth(), fontBuffer->getFormat(), buffer); - fontBuffer->blitToMemory(fontPb); - - uint8* fontData = static_cast(fontPb.data); - uint8* destData = static_cast(destPb.data); - - const size_t fontPixelSize = PixelUtil::getNumElemBytes(fontPb.format); - const size_t destPixelSize = PixelUtil::getNumElemBytes(destPb.format); - - const size_t fontRowPitchBytes = fontPb.rowPitch * fontPixelSize; - const size_t destRowPitchBytes = destPb.rowPitch * destPixelSize; - - Box *GlyphTexCoords; - GlyphTexCoords = new Box[str.size()]; - - Font::UVRect glypheTexRect; - size_t charheight = 0; - size_t charwidth = 0; - - for (unsigned int i = 0; i < str.size(); i++) - { - if ((str[i] != '\t') && (str[i] != '\n') && (str[i] != ' ')) - { - glypheTexRect = font->getGlyphTexCoords(str[i]); - GlyphTexCoords[i].left = glypheTexRect.left * fontTexture->getSrcWidth(); - GlyphTexCoords[i].top = glypheTexRect.top * fontTexture->getSrcHeight(); - GlyphTexCoords[i].right = glypheTexRect.right * fontTexture->getSrcWidth(); - GlyphTexCoords[i].bottom = glypheTexRect.bottom * fontTexture->getSrcHeight(); - - if (GlyphTexCoords[i].getHeight() > charheight) - charheight = GlyphTexCoords[i].getHeight(); - if (GlyphTexCoords[i].getWidth() > charwidth) - charwidth = GlyphTexCoords[i].getWidth(); - } - - } - - size_t cursorX = 0; - size_t cursorY = 0; - size_t lineend = destRectangle.getWidth(); - bool carriagreturn = true; - for (unsigned int strindex = 0; strindex < str.size(); strindex++) - { - switch (str[strindex]) - { - case ' ': cursorX += charwidth; break; - case '\t':cursorX += charwidth * 3; break; - case '\n':cursorY += charheight; carriagreturn = true; break; - default: - { - //wrapping - if ((cursorX + GlyphTexCoords[strindex].getWidth()> lineend) && !carriagreturn) - { - cursorY += charheight; - carriagreturn = true; - } - - //justify - if (carriagreturn) - { - size_t l = strindex; - size_t textwidth = 0; - size_t wordwidth = 0; - - while ((l < str.size()) && (str[l] != '\n)')) - { - wordwidth = 0; - - switch (str[l]) - { - case ' ': wordwidth = charwidth; ++l; break; - case '\t': wordwidth = charwidth * 3; ++l; break; - case '\n': l = str.size(); - } - - if (wordwrap) - while ((l < str.size()) && (str[l] != ' ') && (str[l] != '\t') && (str[l] != '\n')) - { - wordwidth += GlyphTexCoords[l].getWidth(); - ++l; - } - else - { - wordwidth += GlyphTexCoords[l].getWidth(); - l++; - } - - if ((textwidth + wordwidth) <= destRectangle.getWidth()) - textwidth += (wordwidth); - else - break; - } - - if ((textwidth == 0) && (wordwidth > destRectangle.getWidth())) - textwidth = destRectangle.getWidth(); - - switch (justify) - { - case 'c': cursorX = (destRectangle.getWidth() - textwidth) / 2; - lineend = destRectangle.getWidth() - cursorX; - break; - - case 'r': cursorX = (destRectangle.getWidth() - textwidth); - lineend = destRectangle.getWidth(); - break; - - default: cursorX = 0; - lineend = textwidth; - break; - } - - carriagreturn = false; - } - - //abort - net enough space to draw - if ((cursorY + charheight) > destRectangle.getHeight()) - goto stop; - - //draw pixel by pixel - for (size_t i = 0; i < GlyphTexCoords[strindex].getHeight(); i++) - for (size_t j = 0; j < GlyphTexCoords[strindex].getWidth(); j++) - { - float alpha = color.a * (fontData[(i + GlyphTexCoords[strindex].top) * fontRowPitchBytes + (j + GlyphTexCoords[strindex].left) * fontPixelSize + 1] / 255.0); - float invalpha = 1.0 - alpha; - size_t offset = (i + cursorY) * destRowPitchBytes + (j + cursorX) * destPixelSize; - ColourValue pix; - PixelUtil::unpackColour(&pix, destPb.format, &destData[offset]); - pix = (pix * invalpha) + (color * alpha); - PixelUtil::packColour(pix, destPb.format, &destData[offset]); - } - - cursorX += GlyphTexCoords[strindex].getWidth(); - }//default - }//switch - }//for + using namespace Ogre; + + if (destTexture->getHeight() < destRectangle.bottom) + destRectangle.bottom = destTexture->getHeight(); + if (destTexture->getWidth() < destRectangle.right) + destRectangle.right = destTexture->getWidth(); + + if (!font->isLoaded()) + font->load(); + + TexturePtr fontTexture = (TexturePtr)TextureManager::getSingleton().getByName(font->getMaterial()->getTechnique(0)->getPass(0)->getTextureUnitState(0)->getTextureName()); + + HardwarePixelBufferSharedPtr fontBuffer = fontTexture->getBuffer(); + HardwarePixelBufferSharedPtr destBuffer = destTexture->getBuffer(); + + PixelBox destPb = destBuffer->lock(destRectangle, HardwareBuffer::HBL_NORMAL); + + // The font texture buffer was created write only...so we cannot read it back :o). One solution is to copy the buffer instead of locking it. (Maybe there is a way to create a font texture which is not write_only ?) + + // create a buffer + size_t nBuffSize = fontBuffer->getSizeInBytes(); + uint8* buffer = (uint8*)calloc(nBuffSize, sizeof(uint8)); + + // create pixel box using the copy of the buffer + PixelBox fontPb(fontBuffer->getWidth(), fontBuffer->getHeight(), fontBuffer->getDepth(), fontBuffer->getFormat(), buffer); + fontBuffer->blitToMemory(fontPb); + + uint8* fontData = static_cast(fontPb.data); + uint8* destData = static_cast(destPb.data); + + const size_t fontPixelSize = PixelUtil::getNumElemBytes(fontPb.format); + const size_t destPixelSize = PixelUtil::getNumElemBytes(destPb.format); + + const size_t fontRowPitchBytes = fontPb.rowPitch * fontPixelSize; + const size_t destRowPitchBytes = destPb.rowPitch * destPixelSize; + + Box *GlyphTexCoords; + GlyphTexCoords = new Box[str.size()]; + + Font::UVRect glypheTexRect; + size_t charheight = 0; + size_t charwidth = 0; + + for (unsigned int i = 0; i < str.size(); i++) + { + if ((str[i] != '\t') && (str[i] != '\n') && (str[i] != ' ')) + { + glypheTexRect = font->getGlyphTexCoords(str[i]); + GlyphTexCoords[i].left = glypheTexRect.left * fontTexture->getSrcWidth(); + GlyphTexCoords[i].top = glypheTexRect.top * fontTexture->getSrcHeight(); + GlyphTexCoords[i].right = glypheTexRect.right * fontTexture->getSrcWidth(); + GlyphTexCoords[i].bottom = glypheTexRect.bottom * fontTexture->getSrcHeight(); + + if (GlyphTexCoords[i].getHeight() > charheight) + charheight = GlyphTexCoords[i].getHeight(); + if (GlyphTexCoords[i].getWidth() > charwidth) + charwidth = GlyphTexCoords[i].getWidth(); + } + + } + + size_t cursorX = 0; + size_t cursorY = 0; + size_t lineend = destRectangle.getWidth(); + bool carriagreturn = true; + for (unsigned int strindex = 0; strindex < str.size(); strindex++) + { + switch (str[strindex]) + { + case ' ': cursorX += charwidth; break; + case '\t':cursorX += charwidth * 3; break; + case '\n':cursorY += charheight; carriagreturn = true; break; + default: + { + //wrapping + if ((cursorX + GlyphTexCoords[strindex].getWidth()> lineend) && !carriagreturn) + { + cursorY += charheight; + carriagreturn = true; + } + + //justify + if (carriagreturn) + { + size_t l = strindex; + size_t textwidth = 0; + size_t wordwidth = 0; + + while ((l < str.size()) && (str[l] != '\n)')) + { + wordwidth = 0; + + switch (str[l]) + { + case ' ': wordwidth = charwidth; ++l; break; + case '\t': wordwidth = charwidth * 3; ++l; break; + case '\n': l = str.size(); + } + + if (wordwrap) + while ((l < str.size()) && (str[l] != ' ') && (str[l] != '\t') && (str[l] != '\n')) + { + wordwidth += GlyphTexCoords[l].getWidth(); + ++l; + } + else + { + wordwidth += GlyphTexCoords[l].getWidth(); + l++; + } + + if ((textwidth + wordwidth) <= destRectangle.getWidth()) + textwidth += (wordwidth); + else + break; + } + + if ((textwidth == 0) && (wordwidth > destRectangle.getWidth())) + textwidth = destRectangle.getWidth(); + + switch (justify) + { + case 'c': cursorX = (destRectangle.getWidth() - textwidth) / 2; + lineend = destRectangle.getWidth() - cursorX; + break; + + case 'r': cursorX = (destRectangle.getWidth() - textwidth); + lineend = destRectangle.getWidth(); + break; + + default: cursorX = 0; + lineend = textwidth; + break; + } + + carriagreturn = false; + } + + //abort - net enough space to draw + if ((cursorY + charheight) > destRectangle.getHeight()) + goto stop; + + //draw pixel by pixel + for (size_t i = 0; i < GlyphTexCoords[strindex].getHeight(); i++) + for (size_t j = 0; j < GlyphTexCoords[strindex].getWidth(); j++) + { + float alpha = color.a * (fontData[(i + GlyphTexCoords[strindex].top) * fontRowPitchBytes + (j + GlyphTexCoords[strindex].left) * fontPixelSize + 1] / 255.0); + float invalpha = 1.0 - alpha; + size_t offset = (i + cursorY) * destRowPitchBytes + (j + cursorX) * destPixelSize; + ColourValue pix; + PixelUtil::unpackColour(&pix, destPb.format, &destData[offset]); + pix = (pix * invalpha) + (color * alpha); + PixelUtil::packColour(pix, destPb.format, &destData[offset]); + } + + cursorX += GlyphTexCoords[strindex].getWidth(); + }//default + }//switch + }//for stop: - delete[] GlyphTexCoords; + delete[] GlyphTexCoords; - destBuffer->unlock(); + destBuffer->unlock(); - // Free the memory allocated for the buffer - free(buffer); buffer = 0; + // Free the memory allocated for the buffer + free(buffer); buffer = 0; } void CSVRender::PagedWorldspaceWidget::displayCellCoord(bool display) { - mDisplayCellCoord = display; - std::map::iterator iter(mCells.begin()); - - while (iter != mCells.end()) - { - getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->setVisible(display); - iter++; - } + mDisplayCellCoord = display; + std::map::iterator iter(mCells.begin()); + + while (iter != mCells.end()) + { + getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->setVisible(display); + iter++; + } } bool CSVRender::PagedWorldspaceWidget::adjustCells() @@ -224,10 +224,10 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() delete iter->second; mCells.erase (iter++); - getSceneManager()->getSceneNode("CellBillboardNode" + iter->first.getId(mWorldspace))->detachAllObjects(); - getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->removeBillboard( - getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->getBillboard(0)); - getSceneManager()->destroyBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace)); + getSceneManager()->getSceneNode("CellBillboardNode" + iter->first.getId(mWorldspace))->detachAllObjects(); + getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->removeBillboard( + getSceneManager()->getBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace))->getBillboard(0)); + getSceneManager()->destroyBillboardSet("CellBillboardSet" + iter->first.getId(mWorldspace)); modified = true; } @@ -258,60 +258,60 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() new Cell (mDocument.getData(), getSceneManager(), iter->getId (mWorldspace)))); - //billboard which indicate the Cell coord - Ogre::SceneNode* billboardNode = getSceneManager()->getRootSceneNode()->createChildSceneNode("CellBillboardNode" + iter->getId(mWorldspace)); - billboardNode->setPosition(8192 * iter->getX() + 4096, 8192 * iter->getY() + 4096, 0); - - Ogre::Font* font; - if (!Ogre::FontManager::getSingletonPtr()->resourceExists("CellBillboardFont" + iter->getId(mWorldspace))) - { - font = Ogre::FontManager::getSingletonPtr()->create("CellBillboardFont" + iter->getId(mWorldspace), "Data00000001").getPointer(); - font->setType(Ogre::FT_TRUETYPE); - font->setSource("Comic.ttf"); - font->setTrueTypeSize(256); - font->load(); - } - else - { - font = Ogre::FontManager::getSingletonPtr()->getByName("CellBillboardFont" + iter->getId(mWorldspace)).getPointer(); - } - - Ogre::TexturePtr texture; - if (Ogre::TextureManager::getSingleton().resourceExists("CellBillboardTexture" + iter->getId(mWorldspace))) - { - texture = Ogre::TextureManager::getSingleton().getByName("CellBillboardTexture" + iter->getId(mWorldspace)); - } - else - { - texture = Ogre::TextureManager::getSingleton().createManual("CellBillboardTexture" + iter->getId(mWorldspace), "Data00000001", Ogre::TEX_TYPE_2D, 1024, 512, Ogre::MIP_UNLIMITED, Ogre::PF_X8R8G8B8, Ogre::TU_STATIC | Ogre::TU_AUTOMIPMAP); - WriteToTexture(std::to_string(iter->getX()) + ";" + std::to_string(iter->getY()), texture, Ogre::Image::Box(0, 100, 1024, 512), font, Ogre::ColourValue(1.0, 1.0, 1.0, 1.0), 'c'); - } - - Ogre::MaterialPtr material; - if (Ogre::MaterialManager::getSingleton().resourceExists("CellBillboardMaterial" + iter->getId(mWorldspace))) - { - material = Ogre::MaterialManager::getSingleton().getByName("CellBillboardMaterial" + iter->getId(mWorldspace)); - } - else - { - material = Ogre::MaterialManager::getSingleton().create( - "CellBillboardMaterial" + iter->getId(mWorldspace), // name - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - - material->getTechnique(0)->getPass(0)->createTextureUnitState("CellBillboardTexture" + iter->getId(mWorldspace)); - material->getTechnique(0)->getPass(0)->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); - material->setDepthCheckEnabled(false); - material->setDepthWriteEnabled(false); - } - - Ogre::BillboardSet* mySet = getSceneManager()->createBillboardSet("CellBillboardSet" + iter->getId(mWorldspace)); - Ogre::Billboard* myBillboard = mySet->createBillboard(Ogre::Vector3(0, 0, 0)); - mySet->setMaterial(material); - myBillboard->setDimensions(4000, 2000); - mySet->setRenderQueueGroup(mySet->getRenderQueueGroup() + 1); // render the bilboard on top - billboardNode->attachObject(mySet); - - mySet->setVisible(mDisplayCellCoord); + //billboard which indicate the Cell coord + Ogre::SceneNode* billboardNode = getSceneManager()->getRootSceneNode()->createChildSceneNode("CellBillboardNode" + iter->getId(mWorldspace)); + billboardNode->setPosition(8192 * iter->getX() + 4096, 8192 * iter->getY() + 4096, 0); + + Ogre::Font* font; + if (!Ogre::FontManager::getSingletonPtr()->resourceExists("CellBillboardFont" + iter->getId(mWorldspace))) + { + font = Ogre::FontManager::getSingletonPtr()->create("CellBillboardFont" + iter->getId(mWorldspace), "Data00000001").getPointer(); + font->setType(Ogre::FT_TRUETYPE); + font->setSource("Comic.ttf"); + font->setTrueTypeSize(256); + font->load(); + } + else + { + font = Ogre::FontManager::getSingletonPtr()->getByName("CellBillboardFont" + iter->getId(mWorldspace)).getPointer(); + } + + Ogre::TexturePtr texture; + if (Ogre::TextureManager::getSingleton().resourceExists("CellBillboardTexture" + iter->getId(mWorldspace))) + { + texture = Ogre::TextureManager::getSingleton().getByName("CellBillboardTexture" + iter->getId(mWorldspace)); + } + else + { + texture = Ogre::TextureManager::getSingleton().createManual("CellBillboardTexture" + iter->getId(mWorldspace), "Data00000001", Ogre::TEX_TYPE_2D, 1024, 512, Ogre::MIP_UNLIMITED, Ogre::PF_X8R8G8B8, Ogre::TU_STATIC | Ogre::TU_AUTOMIPMAP); + WriteToTexture(std::to_string(iter->getX()) + ";" + std::to_string(iter->getY()), texture, Ogre::Image::Box(0, 100, 1024, 512), font, Ogre::ColourValue(1.0, 1.0, 1.0, 1.0), 'c'); + } + + Ogre::MaterialPtr material; + if (Ogre::MaterialManager::getSingleton().resourceExists("CellBillboardMaterial" + iter->getId(mWorldspace))) + { + material = Ogre::MaterialManager::getSingleton().getByName("CellBillboardMaterial" + iter->getId(mWorldspace)); + } + else + { + material = Ogre::MaterialManager::getSingleton().create( + "CellBillboardMaterial" + iter->getId(mWorldspace), // name + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + + material->getTechnique(0)->getPass(0)->createTextureUnitState("CellBillboardTexture" + iter->getId(mWorldspace)); + material->getTechnique(0)->getPass(0)->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + material->setDepthCheckEnabled(false); + material->setDepthWriteEnabled(false); + } + + Ogre::BillboardSet* mySet = getSceneManager()->createBillboardSet("CellBillboardSet" + iter->getId(mWorldspace)); + Ogre::Billboard* myBillboard = mySet->createBillboard(Ogre::Vector3(0, 0, 0)); + mySet->setMaterial(material); + myBillboard->setDimensions(4000, 2000); + mySet->setRenderQueueGroup(mySet->getRenderQueueGroup() + 1); // render the bilboard on top + billboardNode->attachObject(mySet); + + mySet->setVisible(mDisplayCellCoord); modified = true; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index e0c1c6f54..8350ded1e 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -18,7 +18,7 @@ namespace CSVRender CSMWorld::CellSelection mSelection; std::map mCells; std::string mWorldspace; - bool mDisplayCellCoord; + bool mDisplayCellCoord; private: @@ -53,9 +53,9 @@ namespace CSVRender void useViewHint (const std::string& hint); - void setCellSelection (const CSMWorld::CellSelection& selection); + void setCellSelection(const CSMWorld::CellSelection& selection); - void displayCellCoord(bool display); + void displayCellCoord(bool display); virtual void handleDrop(const std::vector& data); From aa8c0bccb4b8c304bcdef0cbdda0f808aa31d437 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 23 Jul 2014 09:44:29 +0200 Subject: [PATCH 0095/1983] added new argument type: z (optional, any) --- components/CMakeLists.txt | 2 +- components/compiler/discardparser.cpp | 70 +++++++++++++++++++++++++++ components/compiler/discardparser.hpp | 45 +++++++++++++++++ components/compiler/exprparser.cpp | 14 +++++- components/compiler/extensions.hpp | 3 +- 5 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 components/compiler/discardparser.cpp create mode 100644 components/compiler/discardparser.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b8ebb84b1..a9bd376a9 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -58,7 +58,7 @@ add_component_dir (compiler context controlparser errorhandler exception exprparser extensions fileparser generator lineparser literals locals output parser scanner scriptparser skipparser streamerrorhandler stringparser tokenloc nullerrorhandler opcodes extensions0 declarationparser - quickfileparser + quickfileparser discardparser ) add_component_dir (interpreter diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp new file mode 100644 index 000000000..6028968bb --- /dev/null +++ b/components/compiler/discardparser.cpp @@ -0,0 +1,70 @@ + +#include "discardparser.hpp" + +#include "scanner.hpp" + +namespace Compiler +{ + DiscardParser::DiscardParser (ErrorHandler& errorHandler, const Context& context) + : Parser (errorHandler, context), mState (StartState) + { + + } + + bool DiscardParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) + { + if (mState==StartState || mState==CommaState || mState==MinusState) + { + start(); + return false; + } + + return Parser::parseInt (value, loc, scanner); + } + + bool DiscardParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) + { + if (mState==StartState || mState==CommaState || mState==MinusState) + { + start(); + return false; + } + + return Parser::parseFloat (value, loc, scanner); + } + + bool DiscardParser::parseName (const std::string& name, const TokenLoc& loc, + Scanner& scanner) + { + if (mState==StartState || mState==CommaState) + { + start(); + return false; + } + + return Parser::parseName (name, loc, scanner); + } + + bool DiscardParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) + { + if (code==Scanner::S_comma && mState==StartState) + { + mState = CommaState; + return true; + } + + if (code==Scanner::S_minus && (mState==StartState || mState==CommaState)) + { + mState = MinusState; + return true; + } + + return Parser::parseSpecial (code, loc, scanner); + } + + void DiscardParser::reset() + { + mState = StartState; + Parser::reset(); + } +} diff --git a/components/compiler/discardparser.hpp b/components/compiler/discardparser.hpp new file mode 100644 index 000000000..bee8a87bb --- /dev/null +++ b/components/compiler/discardparser.hpp @@ -0,0 +1,45 @@ +#ifndef COMPILER_DISCARDPARSER_H_INCLUDED +#define COMPILER_DISCARDPARSER_H_INCLUDED + +#include "parser.hpp" + +namespace Compiler +{ + /// \brief Parse a single optional numeric value or string and discard it + class DiscardParser : public Parser + { + enum State + { + StartState, CommaState, MinusState + }; + + State mState; + + public: + + DiscardParser (ErrorHandler& errorHandler, const Context& context); + + virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner); + ///< Handle an int token. + /// \return fetch another token? + + virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner); + ///< Handle a float token. + /// \return fetch another token? + + virtual bool parseName (const std::string& name, const TokenLoc& loc, + Scanner& scanner); + ///< Handle a name token. + /// \return fetch another token? + + virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); + ///< Handle a special character token. + /// \return fetch another token? + + virtual void reset(); + ///< Reset parser to clean state. + }; +} + +#endif + diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 6e29626b1..d94f6c436 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -16,6 +16,7 @@ #include "stringparser.hpp" #include "extensions.hpp" #include "context.hpp" +#include "discardparser.hpp" namespace Compiler { @@ -743,6 +744,7 @@ namespace Compiler ExprParser parser (getErrorHandler(), getContext(), mLocals, mLiterals, true); StringParser stringParser (getErrorHandler(), getContext(), mLiterals); + DiscardParser discardParser (getErrorHandler(), getContext()); std::stack > stack; @@ -785,7 +787,17 @@ namespace Compiler scanner.scan (parser); - if (optional && parser.isEmpty()) + if (parser.isEmpty()) + break; + } + else if (*iter=='z') + { + discardParser.reset(); + discardParser.setOptional (true); + + scanner.scan (discardParser); + + if (discardParser.isEmpty()) break; } else diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index aa62fd0eb..a15218d99 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -21,7 +21,8 @@ namespace Compiler s - Short
S - String, case preserved
x - Optional, ignored string argument - X - Optional, ignored float argument + X - Optional, ignored numeric expression + z - Optional, ignored string or numeric argument **/ typedef std::string ScriptArgs; From 19f4c46fe4eac193e0423dca48803f7b48af301e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 23 Jul 2014 12:33:35 +0200 Subject: [PATCH 0096/1983] alllow (and ignore) explicit references for StopScript and ScriptRunning --- components/compiler/exprparser.cpp | 15 +++++++++++++++ components/compiler/lineparser.cpp | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index d94f6c436..6dcca08df 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -408,6 +408,21 @@ namespace Compiler mNextOperand = false; return true; } + else if (keyword==Scanner::K_scriptrunning) + { + start(); + + mTokenLoc = loc; + parseArguments ("c", scanner); + + Generator::scriptRunning (mCode); + mOperands.push_back ('l'); + + mExplicit.clear(); + mRefOp = false; + mNextOperand = false; + return true; + } // check for custom extensions if (const Extensions *extensions = getContext().getExtensions()) diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index b1b831bc2..cdbfaa04a 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -269,6 +269,13 @@ namespace Compiler Generator::startScript (mCode, mLiterals, mExplicit); mState = EndState; return true; + + case Scanner::K_stopscript: + + mExprParser.parseArguments ("c", scanner, mCode); + Generator::stopScript (mCode); + mState = EndState; + return true; } // check for custom extensions From f55084463b47cb3bee1d420f7badee83de0f710f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 23 Jul 2014 12:35:15 +0200 Subject: [PATCH 0097/1983] added MockChangeScript to script blacklist --- files/openmw.cfg | 1 + files/openmw.cfg.local | 1 + 2 files changed, 2 insertions(+) diff --git a/files/openmw.cfg b/files/openmw.cfg index 4ebe287d5..de1f22e50 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -3,3 +3,4 @@ data="?mw?Data Files" data-local="?userdata?data" resources=${OPENMW_RESOURCE_FILES} script-blacklist=Museum +script-blacklist=MockChangeScript \ No newline at end of file diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index 4ae51382e..b2970fb19 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -4,3 +4,4 @@ data=./data data-local="?userdata?data" resources=./resources script-blacklist=Museum +script-blacklist=MockChangeScript From 2e355df8b31a60a1c72b0b837c895caf0b0c8714 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 07:59:50 +0200 Subject: [PATCH 0098/1983] removed function ScriptManager::getLocalIndex (was redundant and was also depending on precompiled scripts) --- apps/openmw/mwbase/scriptmanager.hpp | 8 +-- apps/openmw/mwscript/interpretercontext.cpp | 60 ++++++++++++--------- apps/openmw/mwscript/interpretercontext.hpp | 4 ++ apps/openmw/mwscript/scriptmanagerimp.cpp | 42 --------------- apps/openmw/mwscript/scriptmanagerimp.hpp | 5 -- components/compiler/locals.hpp | 6 ++- 6 files changed, 45 insertions(+), 80 deletions(-) diff --git a/apps/openmw/mwbase/scriptmanager.hpp b/apps/openmw/mwbase/scriptmanager.hpp index ae146e064..1a9eee456 100644 --- a/apps/openmw/mwbase/scriptmanager.hpp +++ b/apps/openmw/mwbase/scriptmanager.hpp @@ -50,13 +50,7 @@ namespace MWBase ///< Return locals for script \a name. virtual MWScript::GlobalScripts& getGlobalScripts() = 0; - - virtual int getLocalIndex (const std::string& scriptId, const std::string& variable, - char type) = 0; - ///< Return index of the variable of the given name and type in the given script. Will - /// throw an exception, if there is no such script or variable or the type does not match. - - }; + }; } #endif diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index e1dc6273c..121b07e34 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -3,8 +3,12 @@ #include #include +#include #include + +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -103,6 +107,32 @@ namespace MWScript } } + int InterpreterContext::findLocalVariableIndex (const std::string& scriptId, + const std::string& name, char type) const + { + int index = MWBase::Environment::get().getScriptManager()->getLocals (scriptId). + search (type, name); + + if (index!=-1) + return index; + + std::ostringstream stream; + + stream << "Failed to access "; + + switch (type) + { + case 's': stream << "short"; break; + case 'l': stream << "long"; break; + case 'f': stream << "float"; break; + } + + stream << " member variable " << name << " in script " << scriptId; + + throw std::runtime_error (stream.str().c_str()); + } + + InterpreterContext::InterpreterContext ( MWScript::Locals *locals, MWWorld::Ptr reference, const std::string& targetId) : mLocals (locals), mReference (reference), @@ -485,10 +515,7 @@ namespace MWScript const Locals& locals = getMemberLocals (scriptId, global); - int index = MWBase::Environment::get().getScriptManager()->getLocalIndex ( - scriptId, name, 's'); - - return locals.mShorts[index]; + return locals.mShorts[findLocalVariableIndex (scriptId, name, 's')]; } int InterpreterContext::getMemberLong (const std::string& id, const std::string& name, @@ -498,10 +525,7 @@ namespace MWScript const Locals& locals = getMemberLocals (scriptId, global); - int index = MWBase::Environment::get().getScriptManager()->getLocalIndex ( - scriptId, name, 'l'); - - return locals.mLongs[index]; + return locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')]; } float InterpreterContext::getMemberFloat (const std::string& id, const std::string& name, @@ -511,10 +535,7 @@ namespace MWScript const Locals& locals = getMemberLocals (scriptId, global); - int index = MWBase::Environment::get().getScriptManager()->getLocalIndex ( - scriptId, name, 'f'); - - return locals.mFloats[index]; + return locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')]; } void InterpreterContext::setMemberShort (const std::string& id, const std::string& name, @@ -524,10 +545,7 @@ namespace MWScript Locals& locals = getMemberLocals (scriptId, global); - int index = - MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 's'); - - locals.mShorts[index] = value; + locals.mShorts[findLocalVariableIndex (scriptId, name, 's')] = value; } void InterpreterContext::setMemberLong (const std::string& id, const std::string& name, int value, bool global) @@ -536,10 +554,7 @@ namespace MWScript Locals& locals = getMemberLocals (scriptId, global); - int index = - MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'l'); - - locals.mLongs[index] = value; + locals.mLongs[findLocalVariableIndex (scriptId, name, 'l')] = value; } void InterpreterContext::setMemberFloat (const std::string& id, const std::string& name, float value, bool global) @@ -548,10 +563,7 @@ namespace MWScript Locals& locals = getMemberLocals (scriptId, global); - int index = - MWBase::Environment::get().getScriptManager()->getLocalIndex (scriptId, name, 'f'); - - locals.mFloats[index] = value; + locals.mFloats[findLocalVariableIndex (scriptId, name, 'f')] = value; } MWWorld::Ptr InterpreterContext::getReference(bool required) diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index f897282e2..bc43f3e44 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -50,6 +50,10 @@ namespace MWScript Locals& getMemberLocals (std::string& id, bool global); ///< \a id is changed to the respective script ID, if \a id wasn't a script ID before + /// Throws an exception if local variable can't be found. + int findLocalVariableIndex (const std::string& scriptId, const std::string& name, + char type) const; + public: InterpreterContext (MWScript::Locals *locals, MWWorld::Ptr reference, diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 781c16299..289fd9a70 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -196,46 +196,4 @@ namespace MWScript { return mGlobalScripts; } - - int ScriptManager::getLocalIndex (const std::string& scriptId, const std::string& variable, - char type) - { - const ESM::Script *script = mStore.get().find (scriptId); - - int offset = 0; - int size = 0; - - switch (type) - { - case 's': - - offset = 0; - size = script->mData.mNumShorts; - break; - - case 'l': - - offset = script->mData.mNumShorts; - size = script->mData.mNumLongs; - break; - - case 'f': - - offset = script->mData.mNumShorts+script->mData.mNumLongs; - size = script->mData.mNumFloats; - break; - - default: - - throw std::runtime_error ("invalid variable type"); - } - - std::string variable2 = Misc::StringUtils::lowerCase (variable); - - for (int i=0; imVarNames.at (i+offset))==variable2) - return i; - - throw std::runtime_error ("unable to access local variable " + variable + " of " + scriptId); - } } diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp index 4edc09eca..7b1121aca 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.hpp +++ b/apps/openmw/mwscript/scriptmanagerimp.hpp @@ -71,11 +71,6 @@ namespace MWScript ///< Return locals for script \a name. virtual GlobalScripts& getGlobalScripts(); - - virtual int getLocalIndex (const std::string& scriptId, const std::string& variable, - char type); - ///< Return index of the variable of the given name and type in the given script. Will - /// throw an exception, if there is no such script or variable or the type does not match. }; } diff --git a/components/compiler/locals.hpp b/components/compiler/locals.hpp index d5bf05253..cf7899b5c 100644 --- a/components/compiler/locals.hpp +++ b/components/compiler/locals.hpp @@ -17,8 +17,6 @@ namespace Compiler int searchIndex (char type, const std::string& name) const; - bool search (char type, const std::string& name) const; - std::vector& get (char type); public: @@ -29,6 +27,10 @@ namespace Compiler int getIndex (const std::string& name) const; ///< return index for local variable \a name (-1: does not exist). + /// Return index for local variable \a name of type \a type (-1: variable does not + /// exit). + bool search (char type, const std::string& name) const; + const std::vector& get (char type) const; void write (std::ostream& localFile) const; From 9f69db0d69538add19a714391e825c01a7254da2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 08:12:53 +0200 Subject: [PATCH 0099/1983] added missing const to ScriptManager::getLocals --- apps/openmw/mwbase/scriptmanager.hpp | 2 +- apps/openmw/mwscript/scriptmanagerimp.cpp | 2 +- apps/openmw/mwscript/scriptmanagerimp.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/scriptmanager.hpp b/apps/openmw/mwbase/scriptmanager.hpp index 1a9eee456..7bdeba132 100644 --- a/apps/openmw/mwbase/scriptmanager.hpp +++ b/apps/openmw/mwbase/scriptmanager.hpp @@ -46,7 +46,7 @@ namespace MWBase ///< Compile all scripts /// \return count, success - virtual Compiler::Locals& getLocals (const std::string& name) = 0; + virtual const Compiler::Locals& getLocals (const std::string& name) = 0; ///< Return locals for script \a name. virtual MWScript::GlobalScripts& getGlobalScripts() = 0; diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 289fd9a70..5af214268 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -156,7 +156,7 @@ namespace MWScript return std::make_pair (count, success); } - Compiler::Locals& ScriptManager::getLocals (const std::string& name) + const Compiler::Locals& ScriptManager::getLocals (const std::string& name) { std::string name2 = Misc::StringUtils::lowerCase (name); diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp index 7b1121aca..6026f6aba 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.hpp +++ b/apps/openmw/mwscript/scriptmanagerimp.hpp @@ -67,7 +67,7 @@ namespace MWScript ///< Compile all scripts /// \return count, success - virtual Compiler::Locals& getLocals (const std::string& name); + virtual const Compiler::Locals& getLocals (const std::string& name); ///< Return locals for script \a name. virtual GlobalScripts& getGlobalScripts(); From f6b502b195f508e12db8491a0c29b0db72820081 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 09:05:17 +0200 Subject: [PATCH 0100/1983] rewrote dialgoue filter access to local variables --- apps/openmw/mwdialogue/filter.cpp | 52 ++++++++++++------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 08cdb1d00..b7f91613a 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -1,11 +1,14 @@ #include "filter.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/scriptmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -187,33 +190,28 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c if (scriptName.empty()) return false; // no script - const ESM::Script *script = - MWBase::Environment::get().getWorld()->getStore().get().find (scriptName); + std::string name = Misc::StringUtils::lowerCase (select.getName()); - std::string name = select.getName(); + const Compiler::Locals& localDefs = + MWBase::Environment::get().getScriptManager()->getLocals (scriptName); - int i = 0; + char type = localDefs.getType (name); - for (; i (script->mVarNames.size()); ++i) - if (Misc::StringUtils::ciEqual(script->mVarNames[i], name)) - break; + if (type==' ') + return false; // script does not have a variable of this name. - if (i>=static_cast (script->mVarNames.size())) - return false; // script does not have a variable of this name + int index = localDefs.getIndex (name); const MWScript::Locals& locals = mActor.getRefData().getLocals(); - if (imData.mNumShorts) - return select.selectCompare (static_cast (locals.mShorts[i])); - - i -= script->mData.mNumShorts; - - if (imData.mNumLongs) - return select.selectCompare (locals.mLongs[i]); - - i -= script->mData.mNumLongs; + switch (type) + { + case 's': return select.selectCompare (static_cast (locals.mShorts[index])); + case 'l': return select.selectCompare (locals.mLongs[index]); + case 'f': return select.selectCompare (locals.mFloats[index]); + } - return select.selectCompare (locals.mFloats.at (i)); + throw std::logic_error ("unknown local variable type in dialogue filter"); } case SelectWrapper::Function_PcHealthPercent: @@ -453,20 +451,10 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co // This actor has no attached script, so there is no local variable return true; - const ESM::Script *script = - MWBase::Environment::get().getWorld()->getStore().get().find (scriptName); - - std::string name = select.getName(); - - int i = 0; - for (; i < static_cast (script->mVarNames.size()); ++i) - if (Misc::StringUtils::ciEqual(script->mVarNames[i], name)) - break; - - if (i >= static_cast (script->mVarNames.size())) - return true; // script does not have a variable of this name + const Compiler::Locals& localDefs = + MWBase::Environment::get().getScriptManager()->getLocals (scriptName); - return false; + return localDefs.getIndex (Misc::StringUtils::lowerCase (select.getName()))==-1; } case SelectWrapper::Function_SameGender: From 87c54adb2460fa1028aae89c3a7cca52a154ebbe Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 09:26:30 +0200 Subject: [PATCH 0101/1983] some cleanup --- apps/openmw/mwscript/locals.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 57584ac30..6a8a857d8 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -29,7 +29,7 @@ namespace MWScript int Locals::getIntVar(const std::string &script, const std::string &var) { - Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); if(index != -1) @@ -53,7 +53,7 @@ namespace MWScript bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) { - Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); if(index != -1) From a3c4000198b1b970d52275ff8cdb67515158fc6a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 09:36:02 +0200 Subject: [PATCH 0102/1983] moved call to Globalscripts::addStartup out of the constructor because at the time of construction the environment may not be set up yet to perform this operation --- apps/openmw/engine.cpp | 2 ++ apps/openmw/mwscript/globalscripts.cpp | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 66eae5f5d..6a48789a5 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -421,6 +421,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mOgre->getRoot()->addFrameListener (this); // scripts + mEnvironment.getScriptManager()->getGlobalScripts().addStartup(); + if (mCompileAll) { std::pair result = MWBase::Environment::get().getScriptManager()->compileAll(); diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 0c94f503b..8270c4e6b 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -20,9 +20,7 @@ namespace MWScript GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store) : mStore (store) - { - addStartup(); - } + {} void GlobalScripts::addScript (const std::string& name, const std::string& targetId) { From 1ca0cc4988caccd1ccc848dce3a2b2c8a200b606 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 09:37:21 +0200 Subject: [PATCH 0103/1983] rewrote MWScript::Locals::configure to be independent of precompiled script data --- apps/openmw/mwscript/locals.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 6a8a857d8..a1ee48ae6 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -14,12 +14,15 @@ namespace MWScript { void Locals::configure (const ESM::Script& script) { + const Compiler::Locals& locals = + MWBase::Environment::get().getScriptManager()->getLocals (script.mId); + mShorts.clear(); - mShorts.resize (script.mData.mNumShorts, 0); + mShorts.resize (locals.get ('s').size(), 0); mLongs.clear(); - mLongs.resize (script.mData.mNumLongs, 0); + mLongs.resize (locals.get ('l').size(), 0); mFloats.clear(); - mFloats.resize (script.mData.mNumFloats, 0); + mFloats.resize (locals.get ('f').size(), 0); } bool Locals::isEmpty() const From acb728195f84f199bfa5bdd704d79819e6439f60 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 10:37:34 +0200 Subject: [PATCH 0104/1983] improved documentation of ESM::Script member variables --- components/esm/loadscpt.hpp | 38 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index d5200d4c1..38160e7f4 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -23,29 +23,8 @@ public: struct SCHDstruct { - /* Script name. - - NOTE: You should handle the name "Main" (case insensitive) with - care. With tribunal, modders got the ability to add 'start - scripts' to their mods, which is a script that is run at - startup and which runs throughout the game (I think.) - - However, before Tribunal, there was only one startup script, - called "Main". If mods wanted to make their own start scripts, - they had to overwrite Main. This is obviously problem if - multiple mods to this at the same time. - - Although most mods have switched to using Trib-style startup - scripts, some legacy mods might still overwrite Main, and this - can cause problems if several mods do it. I think the best - course of action is to NEVER overwrite main, but instead add - each with a separate unique name and add them to the start - script list. But there might be other problems with this - approach though. - */ - - // These describe the sizes we need to allocate for the script - // data. + /// Data from script-precompling in the editor. + /// \warning Do not use them. OpenCS currently does not precompile scripts. int mNumShorts, mNumLongs, mNumFloats, mScriptDataSize, mStringTableSize; }; // 52 bytes @@ -53,9 +32,16 @@ public: SCHDstruct mData; - std::vector mVarNames; // Variable names - std::vector mScriptData; // Compiled bytecode - std::string mScriptText; // Uncompiled script + /// Variable names generated by script-precompiling in the editor. + /// \warning Do not use this field. OpenCS currently does not precompile scripts. + std::vector mVarNames; + + /// Bytecode generated from script-precompiling in the editor. + /// \warning Do not use this field. OpenCS currently does not precompile scripts. + std::vector mScriptData; + + /// Script source code + std::string mScriptText; void load(ESMReader &esm); void save(ESMWriter &esm) const; From 151ef14b67839c63d9937bc74de09ff11a2f906c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 11:10:12 +0200 Subject: [PATCH 0105/1983] some cleanup --- files/opencs/resources.qrc | 134 +++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 2031e54cc..aa6996c9b 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -2,71 +2,73 @@ opencs.png activator.png - added.png - apparatus.png - armor.png - attribute.png - base.png - birthsign.png - body-part.png - book.png - cell.png - class.png - clothing.png - container.png - creature.png - dialogoue-info.png - dialogoue-journal.png - dialogoue-regular.png - dialogue-greeting.png - dialogue-persuasion.png - dialogue-speech.png - door.png - enchantment.png - faction.png - filter.png - globvar.png - GMST.png - Info.png - ingredient.png - landpaint.png - land.png - LandTexture.png - leveled-creature.png - light.png - lockpick.png - magic-effect.png - magicrabbit.png - map.png - miscellaneous.png - modified.png - npc.png - PathGrid.png - potion.png - probe.png - race.png - random-item.png - random.png - removed.png - repair.png - script.png - skill.png - soundgen.png - sound.png - spell.png - static.png - weapon.png - multitype.png - go-next.png - go-previous.png - edit-delete.png - edit-undo.png - edit-preview.png - edit-clone.png - add.png - raster/startup/big/create-addon.png - raster/startup/big/new-game.png - raster/startup/big/edit-content.png - raster/startup/small/configure.png + added.png + apparatus.png + armor.png + attribute.png + base.png + birthsign.png + body-part.png + book.png + cell.png + class.png + clothing.png + container.png + creature.png + dialogoue-info.png + dialogoue-journal.png + dialogoue-regular.png + dialogue-greeting.png + dialogue-persuasion.png + dialogue-speech.png + door.png + enchantment.png + faction.png + filter.png + globvar.png + GMST.png + Info.png + ingredient.png + landpaint.png + land.png + LandTexture.png + leveled-creature.png + light.png + lockpick.png + magic-effect.png + magicrabbit.png + map.png + miscellaneous.png + modified.png + npc.png + PathGrid.png + potion.png + probe.png + race.png + random-item.png + random.png + removed.png + repair.png + script.png + skill.png + soundgen.png + sound.png + spell.png + static.png + weapon.png + multitype.png + go-next.png + go-previous.png + edit-delete.png + edit-undo.png + edit-preview.png + edit-clone.png + add.png + + + raster/startup/big/create-addon.png + raster/startup/big/new-game.png + raster/startup/big/edit-content.png + raster/startup/small/configure.png From 4421e7a5cc88eef8f5a122dea989c35d76812e95 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 11:15:04 +0200 Subject: [PATCH 0106/1983] added lighting mode toolbar icons --- apps/opencs/view/render/scenewidget.cpp | 6 +++--- files/opencs/resources.qrc | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index ebd3eb764..494229600 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -64,19 +64,19 @@ namespace CSVRender CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Lighting Mode"); /// \todo replace icons - tool->addButton (":door.png", "day", + tool->addButton (":scenetoolbar/day", "day", "Day" "
  • Cell specific ambient in interiors
  • " "
  • Low ambient in exteriors
  • " "
  • Strong directional light source/lir>" "
  • This mode closely resembles day time in-game
"); - tool->addButton (":GMST.png", "night", + tool->addButton (":scenetoolbar/night", "night", "Night" "
  • Cell specific ambient in interiors
  • " "
  • Low ambient in exteriors
  • " "
  • Weak directional light source
  • " "
  • This mode closely resembles night time in-game
"); - tool->addButton (":Info.png", "bright", + tool->addButton (":scenetoolbar/bright", "bright", "Bright" "
  • Maximum ambient
  • " "
  • Strong directional light source
"); diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index aa6996c9b..659734826 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -71,4 +71,9 @@ raster/startup/big/edit-content.png raster/startup/small/configure.png + + Moon-48.png + Sun-48.png + Lightbulb-48.png + From 5e543ac8065cbeaa7625ab838176d3e76ea6d2f4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 11:53:30 +0200 Subject: [PATCH 0107/1983] added script doortestwarp to blacklist --- files/openmw.cfg | 3 ++- files/openmw.cfg.local | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/files/openmw.cfg b/files/openmw.cfg index de1f22e50..e105b6304 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -3,4 +3,5 @@ data="?mw?Data Files" data-local="?userdata?data" resources=${OPENMW_RESOURCE_FILES} script-blacklist=Museum -script-blacklist=MockChangeScript \ No newline at end of file +script-blacklist=MockChangeScript +script-blacklist=doortestwarp diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index b2970fb19..9d86174d9 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -5,3 +5,4 @@ data-local="?userdata?data" resources=./resources script-blacklist=Museum script-blacklist=MockChangeScript +script-blacklist=doortestwarp From d0654f3adefc86a82641a7435e4b84682e606600 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 12:17:15 +0200 Subject: [PATCH 0108/1983] move starting of startup scripts from engine startup to new game start; also restart all startup scripts on saved game load --- apps/openmw/engine.cpp | 2 -- apps/openmw/mwstate/statemanagerimp.cpp | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 6a48789a5..66eae5f5d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -421,8 +421,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mOgre->getRoot()->addFrameListener (this); // scripts - mEnvironment.getScriptManager()->getGlobalScripts().addStartup(); - if (mCompileAll) { std::pair result = MWBase::Environment::get().getScriptManager()->compileAll(); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index a3604cc66..b69e6b908 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -139,6 +139,9 @@ void MWState::StateManager::newGame (bool bypass) else MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + mState = State_Running; } @@ -401,6 +404,8 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + // Do not trigger erroneous cellChanged events MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } From af54bb96237e17066ed81ce56712fbada9353372 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Jul 2014 12:23:18 +0200 Subject: [PATCH 0109/1983] removed some redundancies; some more general cleanup --- apps/openmw/mwstate/statemanagerimp.cpp | 7 ++----- apps/openmw/mwworld/worldimp.cpp | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index b69e6b908..96f14d7e9 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -132,16 +132,13 @@ void MWState::StateManager::newGame (bool bypass) { cleanup(); - MWBase::Environment::get().getWorld()->startNewGame (bypass); - if (!bypass) MWBase::Environment::get().getWindowManager()->setNewGame (true); - else - MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + MWBase::Environment::get().getWorld()->startNewGame (bypass); + mState = State_Running; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 386834882..98025b00e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -211,9 +211,9 @@ namespace MWWorld // set new game mark mGlobalVariables["chargenstate"].setInteger (1); mGlobalVariables["pcrace"].setInteger (3); - - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); } + else + mGlobalVariables["chargenstate"].setInteger (-1); if (bypass && !mStartCell.empty()) { From 60499eff83ffd6528e4b285784dae8d8eb232710 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 25 Jul 2014 19:48:37 +0200 Subject: [PATCH 0110/1983] Hotfix for message boxes locking up the game --- apps/openmw/mwgui/windowmanagerimp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 9ebafc90c..5cd8899d8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -785,6 +785,7 @@ namespace MWGui } else { mMessageBoxManager->createInteractiveMessageBox(message, buttons); MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode()); + updateVisible(); } } From 90b2709d6c0fa1997faf64127b3c85990e3610fd Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 25 Jul 2014 19:56:06 +0200 Subject: [PATCH 0111/1983] Allow absorption of non-harmful spells (Fixes #1693) Also fix absorption being calculated for each effect rather than the whole spell. --- apps/openmw/mwmechanics/spellcasting.cpp | 48 ++++++++++++------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 67da99200..2566bf189 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -287,6 +287,27 @@ namespace MWMechanics bool castByPlayer = (!caster.isEmpty() && caster.getRefData().getHandle() == "player"); + // Try absorbing if it's a spell + // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure + // if that is worth replicating. + bool absorbed = false; + if (spell && caster != target && target.getClass().isActor()) + { + int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + absorbed = (roll < absorb); + if (absorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + target.getClass().getCreatureStats(target).setMagicka(magicka); + } + } + for (std::vector::const_iterator effectIt (effects.mList.begin()); effectIt!=effects.mList.end(); ++effectIt) { @@ -326,31 +347,13 @@ namespace MWMechanics { anyHarmfulEffect = true; + if (absorbed) // Absorbed, and we know there was a harmful effect (figuring that out is the only reason we are in this loop) + break; + // If player is attempting to cast a harmful spell, show the target's HP bar if (castByPlayer && target != caster) MWBase::Environment::get().getWindowManager()->setEnemy(target); - // Try absorbing if it's a spell - // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure - // if that is worth replicating. - if (spell && caster != target) - { - int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - bool isAbsorbed = (roll < absorb); - if (isAbsorbed) - { - const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); - MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( - "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, ""); - // Magicka is increased by cost of spell - DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); - target.getClass().getCreatureStats(target).setMagicka(magicka); - magnitudeMult = 0; - } - } - // Try reflecting if (!reflected && magnitudeMult > 0 && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { @@ -382,8 +385,7 @@ namespace MWMechanics } } - - if (magnitudeMult > 0) + if (magnitudeMult > 0 && !absorbed) { float random = std::rand() / static_cast(RAND_MAX); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; From 6a745c014fec66622bdda9759c3c6e181067d5e7 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 27 Jul 2014 11:51:53 +0200 Subject: [PATCH 0112/1983] workaround for incorrect argument order for PositionCell instruction --- components/compiler/errorhandler.cpp | 30 +++++++++++++++++++++++----- components/compiler/errorhandler.hpp | 21 +++++++++++++++++++ components/compiler/lineparser.cpp | 27 +++++++++++++++++++++++-- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index fe58836cc..bcd30ef2d 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -3,11 +3,8 @@ namespace Compiler { - // constructor - - ErrorHandler::ErrorHandler() : mWarnings (0), mErrors (0), mWarningsMode (1) {} - - // destructor + ErrorHandler::ErrorHandler() + : mWarnings (0), mErrors (0), mWarningsMode (1), mDowngradeErrors (false) {} ErrorHandler::~ErrorHandler() {} @@ -49,6 +46,12 @@ namespace Compiler void ErrorHandler::error (const std::string& message, const TokenLoc& loc) { + if (mDowngradeErrors) + { + warning (message, loc); + return; + } + ++mErrors; report (message, loc, ErrorMessage); } @@ -72,4 +75,21 @@ namespace Compiler { mWarningsMode = mode; } + + void ErrorHandler::downgradeErrors (bool downgrade) + { + mDowngradeErrors = downgrade; + } + + + ErrorDowngrade::ErrorDowngrade (ErrorHandler& handler) : mHandler (handler) + { + mHandler.downgradeErrors (true); + } + + ErrorDowngrade::~ErrorDowngrade() + { + mHandler.downgradeErrors (false); + } + } diff --git a/components/compiler/errorhandler.hpp b/components/compiler/errorhandler.hpp index e5922a6be..c92e7bb8d 100644 --- a/components/compiler/errorhandler.hpp +++ b/components/compiler/errorhandler.hpp @@ -17,6 +17,7 @@ namespace Compiler int mWarnings; int mErrors; int mWarningsMode; + bool mDowngradeErrors; protected: @@ -66,6 +67,26 @@ namespace Compiler void setWarningsMode (int mode); ///< // 0 ignore, 1 rate as warning, 2 rate as error + + /// Treat errors as warnings. + void downgradeErrors (bool downgrade); + }; + + class ErrorDowngrade + { + ErrorHandler& mHandler; + + /// not implemented + ErrorDowngrade (const ErrorDowngrade&); + + /// not implemented + ErrorDowngrade& operator= (const ErrorDowngrade&); + + public: + + ErrorDowngrade (ErrorHandler& handler); + + ~ErrorDowngrade(); }; } diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index cdbfaa04a..2226f5845 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -11,6 +11,7 @@ #include "generator.hpp" #include "extensions.hpp" #include "declarationparser.hpp" +#include "exception.hpp" namespace Compiler { @@ -292,9 +293,31 @@ namespace Compiler mExplicit.clear(); } - int optionals = mExprParser.parseArguments (argumentType, scanner, mCode); + int optionals = 0; + + try + { + ErrorDowngrade errorDowngrade (getErrorHandler()); + std::vector code; + optionals = mExprParser.parseArguments (argumentType, scanner, code); + mCode.insert (mCode.begin(), code.begin(), code.end()); + extensions->generateInstructionCode (keyword, mCode, mLiterals, + mExplicit, optionals); + } + catch (const SourceException& exception) + { + // Ignore argument exceptions for positioncell. + /// \todo add option to disable this + if (Misc::StringUtils::lowerCase (loc.mLiteral)=="positioncell") + { + SkipParser skip (getErrorHandler(), getContext()); + scanner.scan (skip); + return false; + } + + throw; + } - extensions->generateInstructionCode (keyword, mCode, mLiterals, mExplicit, optionals); mState = EndState; return true; } From 4a1e5610153eb1be6f72dcd730084b609b83753c Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 25 Jul 2014 23:03:34 +0200 Subject: [PATCH 0113/1983] Properly assign effect attribute/skill in potion creation (Fixes #1704) --- apps/openmw/mwmechanics/alchemy.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 2e03122d5..a097446a0 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -181,7 +181,13 @@ void MWMechanics::Alchemy::updateEffects() ESM::ENAMstruct effect; effect.mEffectID = iter->mId; - effect.mSkill = effect.mAttribute = iter->mArg; // somewhat hack-ish, but should work + effect.mAttribute = -1; + effect.mSkill = -1; + + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) + effect.mSkill = iter->mArg; + else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + effect.mAttribute = iter->mArg; effect.mRange = 0; effect.mArea = 0; From b370c0f7b5c29a45fca9f923d78649651ba96fbd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 00:57:49 +0200 Subject: [PATCH 0114/1983] Enchanting: Don't check price on self-enchanting (Bug #1701) --- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 92221977b..145602eb0 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -299,7 +299,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - if (mEnchanting.getEnchantPrice() > playerGold) + if (mPtr != player && mEnchanting.getEnchantPrice() > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; From 09607f992ea2f725e886dc193813f88b3bbe7fd9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 00:59:35 +0200 Subject: [PATCH 0115/1983] Enchanting: fix inverted self-enchant success chance (Fixes #1701) --- apps/openmw/mwmechanics/enchanting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index f3f6795db..18732514b 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -62,7 +62,7 @@ namespace MWMechanics if(mSelfEnchanting) { - if(getEnchantChance() (RAND_MAX)*100) + if(std::rand()/static_cast (RAND_MAX)*100 < getEnchantChance()) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); From 9c60e4d8265c0e1dd54499790de491c7e3929e7b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 01:03:27 +0200 Subject: [PATCH 0116/1983] Change button caption from "Buy" to "Create" when self-enchanting --- apps/openmw/mwgui/enchantingdialog.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 145602eb0..1fef34371 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -144,6 +144,8 @@ namespace MWGui mEnchanting.setSelfEnchanting(false); mEnchanting.setEnchanter(actor); + mBuyButton->setCaptionWithReplacing("#{sBuy}"); + mPtr = actor; startEditing (); @@ -156,6 +158,8 @@ namespace MWGui mEnchanting.setSelfEnchanting(true); mEnchanting.setEnchanter(player); + mBuyButton->setCaptionWithReplacing("#{sCreate}"); + mPtr = player; startEditing(); mEnchanting.setSoulGem(soulgem); From 16b089cdc833e332bba1243cfb7ee9328aa75324 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 01:07:32 +0200 Subject: [PATCH 0117/1983] Fix invisible enchanting price when self-enchanting was previously used. --- apps/openmw/mwgui/enchantingdialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 1fef34371..d7e79e7a2 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -149,6 +149,9 @@ namespace MWGui mPtr = actor; startEditing (); + mPrice->setVisible(true); + mPriceText->setVisible(true); + updateLabels(); } void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem) @@ -162,7 +165,6 @@ namespace MWGui mPtr = player; startEditing(); - mEnchanting.setSoulGem(soulgem); setSoulGem(soulgem); From 31d058b98cf7a17a9081768d36b60309afabbd6b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 02:23:42 +0200 Subject: [PATCH 0118/1983] Add workaround for ScrollView messing up canvas size (Fixes #1700) TODO: Create fixed ScrollView widget? --- apps/openmw/mwgui/itemview.cpp | 6 ++++++ apps/openmw/mwgui/journalwindow.cpp | 3 +++ apps/openmw/mwgui/list.cpp | 4 ++++ apps/openmw/mwgui/merchantrepair.cpp | 3 +++ apps/openmw/mwgui/quickkeysmenu.cpp | 5 +++-- apps/openmw/mwgui/recharge.cpp | 4 ++++ apps/openmw/mwgui/repair.cpp | 3 +++ apps/openmw/mwgui/review.cpp | 3 +++ apps/openmw/mwgui/scrollwindow.cpp | 3 +++ apps/openmw/mwgui/settingswindow.cpp | 3 +++ apps/openmw/mwgui/spellbuyingwindow.cpp | 5 +++++ apps/openmw/mwgui/spellcreationdialog.cpp | 3 +++ apps/openmw/mwgui/spellwindow.cpp | 3 +++ apps/openmw/mwgui/statswindow.cpp | 6 ++++++ apps/openmw/mwgui/travelwindow.cpp | 3 +++ 15 files changed, 55 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index b86f61034..c4c6c6545 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -115,7 +115,13 @@ void ItemView::update() } x += 42; MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height); + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mScrollView->setVisibleVScroll(false); + mScrollView->setVisibleHScroll(false); mScrollView->setCanvasSize(size); + mScrollView->setVisibleVScroll(true); + mScrollView->setVisibleHScroll(true); dragArea->setSize(size); } diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index fa27b4ef0..b588ba3e1 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -400,7 +400,10 @@ namespace getPage (pageId)->showPage (book, 0); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + getWidget (listId)->setVisibleVScroll(false); getWidget (listId)->setCanvasSize (size.first, size.second); + getWidget (listId)->setVisibleVScroll(true); } void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId character) diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp index b0c514b9d..e88d43534 100644 --- a/apps/openmw/mwgui/list.cpp +++ b/apps/openmw/mwgui/list.cpp @@ -93,7 +93,11 @@ namespace MWGui } ++i; } + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mScrollView->setVisibleVScroll(false); mScrollView->setCanvasSize(mClient->getSize().width, std::max(mItemHeight, mClient->getSize().height)); + mScrollView->setVisibleVScroll(true); if (!scrollbarShown && mItemHeight > mClient->getSize().height) redraw(true); diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 0d1a57000..013a40017 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -91,7 +91,10 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); } } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mList->setVisibleVScroll(false); mList->setCanvasSize (MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY))); + mList->setVisibleVScroll(true); mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 328ef21fb..84150c322 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -654,9 +654,10 @@ namespace MWGui mHeight += spellHeight; } - + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mMagicList->setVisibleVScroll(false); mMagicList->setCanvasSize (mWidth, std::max(mMagicList->getHeight(), mHeight)); - + mMagicList->setVisibleVScroll(true); } void MagicSelectionDialog::addGroup(const std::string &label, const std::string& label2) diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 9c43b1416..1b994d584 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -119,7 +119,11 @@ void Recharge::updateView() currentY += 32 + 4; } + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mView->setVisibleVScroll(false); mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY))); + mView->setVisibleVScroll(true); } void Recharge::onCancel(MyGUI::Widget *sender) diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 986e27243..019f341b5 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -126,7 +126,10 @@ void Repair::updateRepairView() currentY += 32 + 4; } } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mRepairView->setVisibleVScroll(false); mRepairView->setCanvasSize (MyGUI::IntSize(mRepairView->getWidth(), std::max(mRepairView->getHeight(), currentY))); + mRepairView->setVisibleVScroll(true); } void Repair::onCancel(MyGUI::Widget *sender) diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index e27e40ae6..072411707 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -320,7 +320,10 @@ namespace MWGui if (!mMiscSkills.empty()) addSkills(mMiscSkills, "sSkillClassMisc", "Misc Skills", coord1, coord2); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); + mSkillView->setVisibleVScroll(true); } // widget controls diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 3d0751114..867bef0d7 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -57,10 +57,13 @@ namespace MWGui BookTextParser parser; MyGUI::IntSize size = parser.parseScroll(ref->mBase->mText, mTextView, 390); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mTextView->setVisibleVScroll(false); if (size.height > mTextView->getSize().height) mTextView->setCanvasSize(MyGUI::IntSize(410, size.height)); else mTextView->setCanvasSize(410, mTextView->getSize().height); + mTextView->setVisibleVScroll(true); mTextView->setViewOffset(MyGUI::IntPoint(0,0)); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 09d2fc19c..abeb6b86c 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -473,7 +473,10 @@ namespace MWGui curH += h; } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mControlsBox->setVisibleVScroll(false); mControlsBox->setCanvasSize (mControlsBox->getWidth(), std::max(curH, mControlsBox->getHeight())); + mControlsBox->setVisibleVScroll(true); } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 8d9f35daa..d6c716453 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -50,6 +50,8 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); + // TODO: refactor to use MyGUI::ListBox + MyGUI::Button* toAdd = mSpellsView->createWidget( "SandTextButton", @@ -106,7 +108,10 @@ namespace MWGui updateLabels(); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSpellsView->setVisibleVScroll(false); mSpellsView->setCanvasSize (MyGUI::IntSize(mSpellsView->getWidth(), std::max(mSpellsView->getHeight(), mCurrentY))); + mSpellsView->setVisibleVScroll(true); } bool SpellBuyingWindow::playerHasSpell(const std::string &id) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 030b8bf37..f1ca3c802 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -637,7 +637,10 @@ namespace MWGui ++i; } + // Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mUsedEffectsView->setVisibleHScroll(false); mUsedEffectsView->setCanvasSize(size); + mUsedEffectsView->setVisibleHScroll(true); notifyEffectsChanged(); } diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 77da56fa4..15b5bf280 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -261,7 +261,10 @@ namespace MWGui mHeight += spellHeight; } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSpellView->setVisibleVScroll(false); mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight)); + mSpellView->setVisibleVScroll(true); } void SpellWindow::addGroup(const std::string &label, const std::string& label2) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index b11258f1c..7f3b66d83 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -82,7 +82,10 @@ namespace MWGui { mLeftPane->setCoord( MyGUI::IntCoord(0, 0, 0.44*window->getSize().width, window->getSize().height) ); mRightPane->setCoord( MyGUI::IntCoord(0.44*window->getSize().width, 0, 0.56*window->getSize().width, window->getSize().height) ); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), mSkillView->getCanvasSize().height); + mSkillView->setVisibleVScroll(true); } void StatsWindow::setBar(const std::string& name, const std::string& tname, int val, int max) @@ -578,7 +581,10 @@ namespace MWGui mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}"); } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mSkillView->setVisibleVScroll(false); mSkillView->setCanvasSize (mSkillView->getWidth(), std::max(mSkillView->getHeight(), coord1.top)); + mSkillView->setVisibleVScroll(true); } void StatsWindow::onPinToggled() diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 9aa75173a..d874e8c0c 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -126,7 +126,10 @@ namespace MWGui } updateLabels(); + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mDestinationsView->setVisibleVScroll(false); mDestinationsView->setCanvasSize (MyGUI::IntSize(mDestinationsView->getWidth(), std::max(mDestinationsView->getHeight(), mCurrentY))); + mDestinationsView->setVisibleVScroll(true); } void TravelWindow::onTravelButtonClick(MyGUI::Widget* _sender) From fdc6dd6985959a24b4c4f0b29e16e7717201f79a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 15:34:42 +0200 Subject: [PATCH 0119/1983] Shader compatibility fixes for GLSL ES --- files/materials/atmosphere.shader | 2 +- files/materials/clouds.shader | 2 +- files/materials/mygui.shader | 2 +- files/materials/objects.shader | 16 ++++++++-------- files/materials/terrain.shader | 24 ++++++++++++------------ 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader index f02c5766f..c8a8e0220 100644 --- a/files/materials/atmosphere.shader +++ b/files/materials/atmosphere.shader @@ -33,7 +33,7 @@ SH_START_PROGRAM { - shOutputColour(0) = alphaFade * atmosphereColour + (1.f - alphaFade) * horizonColour; + shOutputColour(0) = alphaFade * atmosphereColour + (1.0 - alphaFade) * horizonColour; } #endif diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index d3e5f083c..9e9b10256 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -25,7 +25,7 @@ shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition)); UV = uv0; - alphaFade = (shInputPosition.z <= 200.f) ? ((shInputPosition.z <= 100.f) ? 0.0 : 0.25) : 1.0; + alphaFade = (shInputPosition.z <= 200.0) ? ((shInputPosition.z <= 100.0) ? 0.0 : 0.25) : 1.0; } #else diff --git a/files/materials/mygui.shader b/files/materials/mygui.shader index 014558d97..4d12eba90 100644 --- a/files/materials/mygui.shader +++ b/files/materials/mygui.shader @@ -14,7 +14,7 @@ SH_START_PROGRAM { - shOutputPosition = float4(shInputPosition.xyz, 1.f); + shOutputPosition = float4(shInputPosition.xyz, 1.0); #if TEXTURE UV.xy = uv0; #endif diff --git a/files/materials/objects.shader b/files/materials/objects.shader index ed75babdd..bacf3f912 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -210,11 +210,11 @@ #if VERTEXCOLOR_MODE == 2 lightResult.xyz += colour.xyz * lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0); + * max(dot(viewNormal.xyz, lightDir), 0.0); #else lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0); + * max(dot(viewNormal.xyz, lightDir), 0.0); #endif #if @shIterator == 0 @@ -432,11 +432,11 @@ #if VERTEXCOLOR_MODE == 2 lightResult.xyz += colourPassthrough.xyz * lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0); + * max(dot(viewNormal.xyz, lightDir), 0.0); #else lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0); + * max(dot(viewNormal.xyz, lightDir), 0.0); #endif #if @shIterator == 0 @@ -504,8 +504,8 @@ #if ENV_MAP // Everything looks better with fresnel - float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); - float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1)); + float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0.0); + float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1.0)); shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color; #endif @@ -513,7 +513,7 @@ #if SPECULAR float3 light0Dir = normalize(lightPosObjSpace0.xyz); - float NdotL = max(dot(normal, light0Dir), 0); + float NdotL = max(dot(normal, light0Dir), 0.0); float3 halfVec = normalize (light0Dir + eyeDir); float shininess = matShininess; @@ -522,7 +522,7 @@ shininess *= (specTex.a); #endif - float3 specular = pow(max(dot(normal, halfVec), 0), shininess) * lightSpec0 * matSpec; + float3 specular = pow(max(dot(normal, halfVec), 0.0), shininess) * lightSpec0 * matSpec; #if SPEC_MAP specular *= specTex.xyz; #else diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 1436de0c3..8384588e4 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -175,7 +175,7 @@ lightResult.xyz += lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(normal.xyz, lightDir), 0); + * max(dot(normal.xyz, lightDir), 0.0); #if @shIterator == 0 directionalResult = lightResult.xyz; @@ -310,7 +310,7 @@ shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) #if !IS_FIRST_PASS // Opacity the previous passes should have, i.e. 1 - (opacity of this pass) -float previousAlpha = 1.f; +float previousAlpha = 1.0; #endif @@ -334,7 +334,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; float4 albedo = float4(0,0,0,1); - float2 layerUV = float2(UV.x, 1.f-UV.y) * 16; // Reverse Y, required to get proper tangents + float2 layerUV = float2(UV.x, 1.0-UV.y) * 16.0; // Reverse Y, required to get proper tangents float2 thisLayerUV; float4 normalTex; float4 diffuseTex; @@ -349,9 +349,9 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; #if @shPropertyBool(use_normal_map_@shIterator) normalTex = shSample(normalMap@shIterator, thisLayerUV); #if @shIterator == 0 && IS_FIRST_PASS - TSnormal = normalize(normalTex.xyz * 2 - 1); + TSnormal = normalize(normalTex.xyz * 2.0 - 1.0); #else - TSnormal = shLerp(TSnormal, normalTex.xyz * 2 - 1, blendValues@shPropertyString(blendmap_component_@shIterator)); + TSnormal = shLerp(TSnormal, normalTex.xyz * 2.0 - 1.0, blendValues@shPropertyString(blendmap_component_@shIterator)); #endif #endif @@ -361,7 +361,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; diffuseTex = shSample(diffuseMap@shIterator, layerUV); #if !@shPropertyBool(use_specular_@shIterator) - diffuseTex.a = 0; + diffuseTex.a = 0.0; #endif #if @shIterator == 0 @@ -371,7 +371,7 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon #endif #if !IS_FIRST_PASS - previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator); + previousAlpha *= 1.0-blendValues@shPropertyString(blendmap_component_@shIterator); #endif @@ -404,7 +404,7 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon lightResult.xyz += lightDiffuse[@shIterator].xyz * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(normal.xyz, lightDir), 0); + * max(dot(normal.xyz, lightDir), 0.0); #if @shIterator == 0 float3 directionalResult = lightResult.xyz; #endif @@ -444,10 +444,10 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon // Specular float3 light0Dir = normalize(lightPos0.xyz); - float NdotL = max(dot(normal, light0Dir), 0); + float NdotL = max(dot(normal, light0Dir), 0.0); float3 halfVec = normalize (light0Dir + eyeDir); - float3 specular = pow(max(dot(normal, halfVec), 0), 32) * lightSpec0; + float3 specular = pow(max(dot(normal, halfVec), 0.0), 32.0) * lightSpec0; shOutputColour(0).xyz += specular * (albedo.a) * shadow; #endif @@ -465,9 +465,9 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); #if IS_FIRST_PASS - shOutputColour(0).a = 1; + shOutputColour(0).a = 1.0; #else - shOutputColour(0).a = 1.f-previousAlpha; + shOutputColour(0).a = 1.0-previousAlpha; #endif } From 8c81e22f3e3c92905e5ec21c30d439ef225822c3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 18:01:53 +0200 Subject: [PATCH 0120/1983] Determine target for On Touch effects for non-player actors --- apps/openmw/mwworld/worldimp.cpp | 86 ++++++++++++++++++++++--------- apps/openmw/mwworld/worldimp.hpp | 2 +- libs/openengine/bullet/physic.cpp | 4 +- libs/openengine/bullet/physic.hpp | 4 +- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9b747582f..10c2e4974 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -148,7 +148,7 @@ namespace MWWorld mSky (true), mCells (mStore, mEsm), mActivationDistanceOverride (activationDistanceOverride), mFallback(fallbackMap), mTeleportEnabled(true), mLevitationEnabled(true), - mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), + mGodMode(false), mContentFiles (contentFiles), mGoToJail(false), mStartCell (startCell), mStartupScript(startupScript) { @@ -290,7 +290,6 @@ namespace MWWorld mGodMode = false; mSky = true; mTeleportEnabled = true; - mFacedDistance = FLT_MAX; mGlobalVariables.fill (mStore); } @@ -1462,30 +1461,22 @@ namespace MWWorld updateFacedHandle (); } - void World::updateFacedHandle () + void World::getFacedHandle(std::string& facedHandle, float maxDistance) { - float telekinesisRangeBonus = - mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() - .get(ESM::MagicEffect::Telekinesis).mMagnitude; - telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); + maxDistance += mRendering->getCameraDistance(); - float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; - activationDistance += mRendering->getCameraDistance(); - - // send new query - // figure out which object we want to test against std::vector < std::pair < float, std::string > > results; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { float x, y; MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - results = mPhysics->getFacedHandles(x, y, activationDistance); + results = mPhysics->getFacedHandles(x, y, maxDistance); if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()*50); } else { - results = mPhysics->getFacedHandles(activationDistance); + results = mPhysics->getFacedHandles(maxDistance); } // ignore the player and other things we're not interested in @@ -1508,15 +1499,21 @@ namespace MWWorld if (results.empty() || results.front().second.find("HeightField") != std::string::npos) // Blocked by terrain - { - mFacedHandle = ""; - mFacedDistance = FLT_MAX; - } + facedHandle = ""; else - { - mFacedHandle = results.front().second; - mFacedDistance = results.front().first; - } + facedHandle = results.front().second; + } + + void World::updateFacedHandle () + { + float telekinesisRangeBonus = + mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() + .get(ESM::MagicEffect::Telekinesis).mMagnitude; + telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); + + float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; + + getFacedHandle(mFacedHandle, activationDistance); } bool World::isCellExterior() const @@ -2344,8 +2341,49 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // TODO: this only works for the player - MWWorld::Ptr target = getFacedObject(); + // Get the target to use for "on touch" effects + MWWorld::Ptr target; + float distance = 192.f; // ?? + + if (actor == getPlayerPtr()) + { + // For the player, use camera to aim + std::string facedHandle; + getFacedHandle(facedHandle, distance); + if (!facedHandle.empty()) + target = getPtrViaHandle(facedHandle); + } + else + { + // For NPCs use facing direction from Head node + Ogre::Vector3 origin(actor.getRefData().getPosition().pos); + MWRender::Animation *anim = mRendering->getAnimation(actor); + if(anim != NULL) + { + Ogre::Node *node = anim->getNode("Head"); + if (node == NULL) + node = anim->getNode("Bip01 Head"); + if(node != NULL) + origin += node->_getDerivedPosition(); + } + Ogre::Quaternion orient; + orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + Ogre::Vector3 direction = orient.yAxis(); + Ogre::Vector3 dest = origin + direction * distance; + + + std::vector > collisions = mPhysEngine->rayTest2(btVector3(origin.x, origin.y, origin.z), btVector3(dest.x, dest.y, dest.z)); + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end(); ++cIt) + { + MWWorld::Ptr collided = getPtrViaHandle(cIt->second); + if (collided != actor) + { + target = collided; + break; + } + } + } std::string selectedSpell = stats.getSpells().getSelectedSpell(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index dda442963..5c44388ca 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -92,7 +92,6 @@ namespace MWWorld int mActivationDistanceOverride; std::string mFacedHandle; - float mFacedDistance; std::string mStartupScript; @@ -114,6 +113,7 @@ namespace MWWorld void updateWindowManager (); void performUpdateSceneQueries (); void updateFacedHandle (); + void getFacedHandle(std::string& facedHandle, float maxDistance); float getMaxActivationDistance (); float getNpcActivationDistance (); diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 6bc544af4..954d7c283 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -737,7 +737,7 @@ namespace Physic { } - std::pair PhysicEngine::rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly,bool ignoreHeightMap, Ogre::Vector3* normal) + std::pair PhysicEngine::rayTest(const btVector3 &from, const btVector3 &to, bool raycastingObjectOnly, bool ignoreHeightMap, Ogre::Vector3* normal) { std::string name = ""; float d = -1; @@ -801,7 +801,7 @@ namespace Physic return std::make_pair(false, 1); } - std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) + std::vector< std::pair > PhysicEngine::rayTest2(const btVector3& from, const btVector3& to) { MyRayResultCallback resultCallback1; resultCallback1.m_collisionFilterGroup = 0xff; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index e37caee38..09bff4b04 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -302,13 +302,13 @@ namespace Physic * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). * If \a normal is non-NULL, the hit normal will be written there (if there is a hit) */ - std::pair rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly = true, + std::pair rayTest(const btVector3& from,const btVector3& to,bool raycastingObjectOnly = true, bool ignoreHeightMap = false, Ogre::Vector3* normal = NULL); /** * Return all objects hit by a ray. */ - std::vector< std::pair > rayTest2(btVector3& from, btVector3& to); + std::vector< std::pair > rayTest2(const btVector3 &from, const btVector3 &to); std::pair sphereCast (float radius, btVector3& from, btVector3& to); ///< @return (hit, relative distance) From d7acb7fc7dfc0f462232732f9d3fcf4d0f7793ee Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 22:24:05 +0200 Subject: [PATCH 0121/1983] Ignore invalid shader cache index (Bug #1664) --- extern/shiny/Main/Factory.cpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/extern/shiny/Main/Factory.cpp b/extern/shiny/Main/Factory.cpp index 72af07d58..eab5c8dfc 100644 --- a/extern/shiny/Main/Factory.cpp +++ b/extern/shiny/Main/Factory.cpp @@ -51,24 +51,32 @@ namespace sh { assert(mCurrentLanguage != Language_None); - if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt")) + try { - std::ifstream file; - file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str()); - - std::string line; - while (getline(file, line)) + if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt")) { - std::string sourceFile = line; + std::ifstream file; + file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str()); + + std::string line; + while (getline(file, line)) + { + std::string sourceFile = line; - if (!getline(file, line)) - assert(0); + if (!getline(file, line)) + assert(0); - int modified = boost::lexical_cast(line); + int modified = boost::lexical_cast(line); - mShadersLastModified[sourceFile] = modified; + mShadersLastModified[sourceFile] = modified; + } } } + catch (std::exception& e) + { + std::cerr << "Failed to load shader modification index: " << e.what() << std::endl; + mShadersLastModified.clear(); + } // load configurations { From 47e42d4fda6bdadb67b6b1eadcd893da172cf3e0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Jul 2014 22:29:04 +0200 Subject: [PATCH 0122/1983] Destroy Engine after exception is logged In cases where OpenMW throws an exception, then crashes in the Engine destructor (ideally should not happen, but keeps happening), we will at least see what the exception was about. --- apps/openmw/main.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index adde408b9..f91d7de7a 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -309,6 +309,8 @@ int main(int argc, char**argv) boost::filesystem::ofstream logfile; + std::auto_ptr engine; + int ret = 0; try { @@ -350,11 +352,11 @@ int main(int argc, char**argv) boost::filesystem::current_path(bundlePath); #endif - OMW::Engine engine(cfgMgr); + engine.reset(new OMW::Engine(cfgMgr)); - if (parseOptions(argc, argv, engine, cfgMgr)) + if (parseOptions(argc, argv, *engine, cfgMgr)) { - engine.go(); + engine->go(); } } catch (std::exception &e) From 1a04501951c6dce2f7b762e619bc3daa24cfcabe Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 27 Jul 2014 18:53:54 +0200 Subject: [PATCH 0123/1983] Handle faction save/load properly when player has faction reputation in a faction he is not a member of (Fixes #1573) --- components/esm/npcstats.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 3fa954182..26d8f9596 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -4,7 +4,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (0), mReputation (0) {} +ESM::NpcStats::Faction::Faction() : mExpelled (false), mRank (-1), mReputation (0) {} void ESM::NpcStats::load (ESMReader &esm) { @@ -98,7 +98,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const esm.writeHNT ("FAEX", expelled); } - if (iter->second.mRank) + if (iter->second.mRank >= 0) esm.writeHNT ("FARA", iter->second.mRank); if (iter->second.mReputation) From d81e9cfefd179c8498325baf3bb231ec9fd10a07 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 27 Jul 2014 20:30:52 +0200 Subject: [PATCH 0124/1983] Implement actors fighting for the actor they are following (Fixes #1141) --- apps/openmw/mwclass/creature.cpp | 13 ++- apps/openmw/mwmechanics/actors.cpp | 84 +++++++++---------- apps/openmw/mwmechanics/aicombat.cpp | 2 + apps/openmw/mwmechanics/aifollow.cpp | 5 ++ apps/openmw/mwmechanics/aifollow.hpp | 2 + apps/openmw/mwmechanics/aisequence.cpp | 10 +++ apps/openmw/mwmechanics/aisequence.hpp | 4 + .../mwmechanics/mechanicsmanagerimp.cpp | 2 + apps/openmw/mwworld/class.hpp | 6 -- 9 files changed, 77 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a74ee6fd6..c175ce3eb 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -366,10 +366,17 @@ namespace MWClass getCreatureStats(ptr).setAttacked(true); // Self defense - if (!attacker.isEmpty() && !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker) - && (canWalk(ptr) || canFly(ptr) || canSwim(ptr))) // No retaliation for totally static creatures - // (they have no movement or attacks anyway) + if ( ((!attacker.isEmpty() && attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr)) + || attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker) + && (canWalk(ptr) || canFly(ptr) || canSwim(ptr)) // No retaliation for totally static creatures + // (they have no movement or attacks anyway) + ) + { + // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. + // Note: accidental or collateral damage attacks are ignored. MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); + } if(!successful) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 8f1209827..36397ac09 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -192,6 +192,15 @@ namespace MWMechanics CreatureStats& creatureStats = actor1.getClass().getCreatureStats(actor1); if (againstPlayer && creatureStats.isHostile()) return; // already fighting against player + if (actor2.getClass().getCreatureStats(actor2).isDead() + || actor1.getClass().getCreatureStats(actor1).isDead()) + return; + + const ESM::Position& actor1Pos = actor2.getRefData().getPosition(); + const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); + float sqrDist = Ogre::Vector3(actor1Pos.pos).squaredDistance(Ogre::Vector3(actor2Pos.pos)); + if (sqrDist > 7168*7168) + return; // pure water creatures won't try to fight with the target on the ground // except that creature is already hostile @@ -208,9 +217,9 @@ namespace MWMechanics else { aggressive = false; - // if one of actors is creature then we should make a decision to start combat or not - // NOTE: function doesn't take into account combat between 2 creatures - if (!actor1.getClass().isNpc()) + + // Make guards fight aggressive creatures + if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard")) { // if creature is hostile then it is necessarily to start combat if (creatureStats.isHostile()) aggressive = true; @@ -218,24 +227,34 @@ namespace MWMechanics } } + // start combat if we are in combat with any followers of this actor + const std::list& followers = getActorsFollowing(actor2); + for (std::list::const_iterator it = followers.begin(); it != followers.end(); ++it) + { + if (creatureStats.getAiSequence().isInCombat(*it)) + aggressive = true; + } + // start combat if we are in combat with someone this actor is following + const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2); + for (std::list::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow && + creatureStats.getAiSequence().isInCombat(dynamic_cast(*it)->getTarget())) + aggressive = true; + } + if(aggressive) { - const ESM::Position& actor1Pos = actor2.getRefData().getPosition(); - const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); - float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos)); - if (againstPlayer || actor2.getClass().getCreatureStats(actor2).getAiSequence().canAddTarget(actor2Pos, d)) - { - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); + bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); - if (againstPlayer) LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); + if (againstPlayer) LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); - if (LOS) + if (LOS) + { + MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + if (!againstPlayer) // start combat between each other { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); - if (!againstPlayer) // start combat between each other - { - MWBase::Environment::get().getMechanicsManager()->startCombat(actor2, actor1); - } + MWBase::Environment::get().getMechanicsManager()->startCombat(actor2, actor1); } } } @@ -964,19 +983,10 @@ namespace MWMechanics } } - static Ogre::Vector3 sBasePoint; - bool comparePtrDist (const MWWorld::Ptr& ptr1, const MWWorld::Ptr& ptr2) - { - return (sBasePoint.squaredDistance(Ogre::Vector3(ptr1.getRefData().getPosition().pos)) - < sBasePoint.squaredDistance(Ogre::Vector3(ptr2.getRefData().getPosition().pos))); - } - void Actors::update (float duration, bool paused) { if(!paused) - { - std::list listGuards; // at the moment only guards certainly will fight with creatures - + { static float timerUpdateAITargets = 0; // target lists get updated once every 1.0 sec @@ -989,16 +999,10 @@ namespace MWMechanics // Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation // (below) iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string()); - - // add guards to list to later make them fight with creatures - if (timerUpdateAITargets == 0 && iter->first.getClass().isClass(iter->first, "Guard")) - listGuards.push_back(iter->first); } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - listGuards.push_back(player); - // AI and magic effects update for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { @@ -1008,20 +1012,16 @@ namespace MWMechanics if (MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - // make guards and creatures fight each other - if (timerUpdateAITargets == 0 && iter->first.getTypeName() == typeid(ESM::Creature).name() && !listGuards.empty()) - { - sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos); - listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest guard - - for (std::list::iterator it = listGuards.begin(); it != listGuards.end(); ++it) + if (timerUpdateAITargets == 0) + { + for(PtrControllerMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { - engageCombat(iter->first, *it, *it == player); + if (it->first == iter->first || iter->first == player) // player is not AI-controlled + continue; + engageCombat(iter->first, it->first, it->first == player); } } - if (iter->first != player) engageCombat(iter->first, player, true); - if (iter->first.getClass().isNpc() && iter->first != player) updateCrimePersuit(iter->first, duration); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index c0a97a1b1..491954495 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -174,6 +174,8 @@ namespace MWMechanics return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + if (target.isEmpty()) + return false; if(target.getClass().getCreatureStats(target).isDead()) return true; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 5ab7e1730..c2bb0f2f4 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -121,3 +121,8 @@ MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) { } + +MWWorld::Ptr MWMechanics::AiFollow::getTarget() const +{ + return MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index e9587b36e..744a9c505 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -31,6 +31,8 @@ namespace MWMechanics AiFollow(const ESM::AiSequence::AiFollow* follow); + MWWorld::Ptr getTarget() const; + virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 02f00dfc6..1d0065679 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -72,6 +72,16 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const return true; } +std::list::const_iterator AiSequence::begin() const +{ + return mPackages.begin(); +} + +std::list::const_iterator AiSequence::end() const +{ + return mPackages.end(); +} + bool AiSequence::isInCombat() const { for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index bc20dc61b..af14cce59 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -50,6 +50,10 @@ namespace MWMechanics virtual ~AiSequence(); + /// Iterator may be invalidated by any function calls other than begin() or end(). + std::list::const_iterator begin() const; + std::list::const_iterator end() const; + /// Returns currently executing AiPackage type /** \see enum AiPackage::TypeId **/ int getTypeId() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 861d5e110..fd4269930 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1171,6 +1171,8 @@ namespace MWMechanics void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { + if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) + return; ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) { diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index c3f94d7f1..e880030f9 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -72,12 +72,6 @@ namespace MWWorld public: - /// NPC-stances. - enum Stance - { - Run, Sneak - }; - virtual ~Class(); const std::string& getTypeName() const { From 2abc4e42c8610d97901db3c863c50cabfa15c0a2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 27 Jul 2014 20:49:57 +0200 Subject: [PATCH 0125/1983] end parsing of line after parsing a declaration --- components/compiler/lineparser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 2226f5845..0a3629912 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -386,7 +386,7 @@ namespace Compiler if (declaration.parseKeyword (keyword, loc, scanner)) scanner.scan (declaration); - return true; + return false; } case Scanner::K_set: mState = SetState; return true; From 17bd094afd9fabc19dc3a86a6a542996805db2c8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 27 Jul 2014 20:55:54 +0200 Subject: [PATCH 0126/1983] allow a few more stray arguments --- components/compiler/extensions0.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index ef4fe4fbd..3945b73cc 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -59,7 +59,7 @@ namespace Compiler extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit); extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); - extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI); + extensions.registerInstruction ("toggleai", "X", opcodeToggleAI, opcodeToggleAI); extensions.registerInstruction ("tai", "", opcodeToggleAI, opcodeToggleAI); extensions.registerInstruction("startcombat", "c", opcodeStartCombat, opcodeStartCombatExplicit); extensions.registerInstruction("stopcombat", "x", opcodeStopCombat, opcodeStopCombatExplicit); @@ -113,12 +113,12 @@ namespace Compiler { void registerExtensions (Extensions& extensions) { - extensions.registerInstruction ("additem", "cl", opcodeAddItem, opcodeAddItemExplicit); + extensions.registerInstruction ("additem", "clxx", opcodeAddItem, opcodeAddItemExplicit); extensions.registerFunction ("getitemcount", 'l', "c", opcodeGetItemCount, opcodeGetItemCountExplicit); extensions.registerInstruction ("removeitem", "cl", opcodeRemoveItem, opcodeRemoveItemExplicit); - extensions.registerInstruction ("equip", "c", opcodeEquip, opcodeEquipExplicit); + extensions.registerInstruction ("equip", "cX", opcodeEquip, opcodeEquipExplicit); extensions.registerFunction ("getarmortype", 'l', "l", opcodeGetArmorType, opcodeGetArmorTypeExplicit); extensions.registerFunction ("hasitemequipped", 'l', "c", opcodeHasItemEquipped, opcodeHasItemEquippedExplicit); extensions.registerFunction ("hassoulgem", 'l', "c", opcodeHasSoulGem, opcodeHasSoulGemExplicit); @@ -163,7 +163,7 @@ namespace Compiler { void registerExtensions (Extensions& extensions) { - extensions.registerInstruction ("journal", "cl", opcodeJournal); + extensions.registerInstruction ("journal", "clxxX", opcodeJournal); extensions.registerInstruction ("setjournalindex", "cl", opcodeSetJournalIndex); extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); @@ -406,7 +406,7 @@ namespace Compiler extensions.registerInstruction ("setpccrimelevel", "f", opcodeSetPCCrimeLevel); extensions.registerInstruction ("modpccrimelevel", "f", opcodeModPCCrimeLevel); - extensions.registerInstruction ("addspell", "cxX", opcodeAddSpell, opcodeAddSpellExplicit); + extensions.registerInstruction ("addspell", "cz", opcodeAddSpell, opcodeAddSpellExplicit); extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, opcodeRemoveSpellExplicit); extensions.registerInstruction ("removespelleffects", "c", opcodeRemoveSpellEffects, From 6262d6c9644b5459078126b389dc218db8eb680a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 27 Jul 2014 23:10:58 +0200 Subject: [PATCH 0127/1983] Don't leave stale player CharacterController in Actors when loading game (Fixes #1713) --- apps/openmw/mwmechanics/actors.cpp | 14 ++++++++------ apps/openmw/mwworld/worldimp.cpp | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 36397ac09..a215a71a3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -924,12 +924,7 @@ namespace MWMechanics Actors::~Actors() { - PtrControllerMap::iterator it(mActors.begin()); - for (; it != mActors.end(); ++it) - { - delete it->second; - it->second = NULL; - } + clear(); } void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) @@ -1359,6 +1354,13 @@ namespace MWMechanics void Actors::clear() { + PtrControllerMap::iterator it(mActors.begin()); + for (; it != mActors.end(); ++it) + { + delete it->second; + it->second = NULL; + } + mActors.clear(); mDeathCount.clear(); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 10c2e4974..14746526b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2111,8 +2111,8 @@ namespace MWWorld void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); - - physicActor->enableCollisionBody(enable); + if (physicActor) + physicActor->enableCollisionBody(enable); } bool World::findInteriorPosition(const std::string &name, ESM::Position &pos) From b4ba18afe7ba0c4a91a24d99d21f6ff9439e896f Mon Sep 17 00:00:00 2001 From: MiroslavR Date: Sun, 27 Jul 2014 23:36:40 +0200 Subject: [PATCH 0128/1983] Include revision number in the "version" command line option (Closes #1711) --- apps/openmw/main.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index adde408b9..0aa763407 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -184,6 +184,14 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (variables.count ("version")) { std::cout << "OpenMW version " << OPENMW_VERSION << std::endl; + + std::string rev = OPENMW_VERSION_COMMITHASH; + std::string tag = OPENMW_VERSION_TAGHASH; + if (!rev.empty() && !tag.empty()) + { + rev = rev.substr(0, 10); + std::cout << "Revision " << rev << std::endl; + } run = false; } From 45206bc3f6c973d899f70e367921206bf1a53715 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 00:32:59 +0200 Subject: [PATCH 0129/1983] Savegame: write and read dynamic Store before Cells --- apps/openmw/mwworld/worldimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 14746526b..ebbc16c23 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -319,8 +319,9 @@ namespace MWWorld MWMechanics::CreatureStats::writeActorIdCounter(writer); progress.increaseProgress(); + mStore.write (writer, progress); // dynamic Store must be written (and read) before Cells, so that + // references to custom made records will be recognized mCells.write (writer, progress); - mStore.write (writer, progress); mGlobalVariables.write (writer, progress); mPlayer->write (writer, progress); mWeatherManager->write (writer, progress); From 315b022d2d01a1887cd5a76f108b14a2d160b103 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 00:55:57 +0200 Subject: [PATCH 0130/1983] Add transfer gold from all services to NPC trade gold pool --- apps/openmw/mwgui/merchantrepair.cpp | 6 ++++++ apps/openmw/mwgui/spellbuyingwindow.cpp | 5 +++++ apps/openmw/mwgui/spellcreationdialog.cpp | 8 +++++++- apps/openmw/mwgui/trainingwindow.cpp | 3 +++ apps/openmw/mwgui/travelwindow.cpp | 6 ++++++ apps/openmw/mwmechanics/enchanting.cpp | 4 ++++ 6 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 013a40017..b6f9936a8 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -10,6 +10,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" @@ -134,6 +136,10 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor); + actorStats.setGoldPool(actorStats.getGoldPool() + price); + startRepair(mActor); } diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index d6c716453..cd378aadf 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -135,6 +135,11 @@ namespace MWGui MWMechanics::Spells& spells = stats.getSpells(); spells.add (mSpellsWidgetMap.find(_sender)->second); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); + startSpellBuying(mPtr); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index f1ca3c802..63cbdb567 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -352,7 +352,13 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, boost::lexical_cast(mPriceLabel->getCaption()), player); + int price = boost::lexical_cast(mPriceLabel->getCaption()); + + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 6463db3d7..babfe099f 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -159,6 +159,9 @@ namespace MWGui // remove gold player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + npcStats.setGoldPool(npcStats.getGoldPool() + price); + // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index d874e8c0c..ae191e764 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -12,6 +12,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionteleport.hpp" @@ -150,6 +152,10 @@ namespace MWGui player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); + // add gold to NPC trading gold pool + MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); + npcStats.setGoldPool(npcStats.getGoldPool() + price); + MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 18732514b..9663a2d51 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -292,5 +292,9 @@ namespace MWMechanics MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); + + // add gold to NPC trading gold pool + CreatureStats& enchanterStats = mEnchanter.getClass().getCreatureStats(mEnchanter); + enchanterStats.setGoldPool(enchanterStats.getGoldPool() + getEnchantPrice()); } } From c6d3b0b70b27d7bf6c590dbaa08ae1e60e1d38fc Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 02:27:48 +0200 Subject: [PATCH 0131/1983] Moved merchant restock from trade start to dialogue start, since other services also interact with it. --- apps/openmw/mwgui/dialogue.cpp | 25 +++++++++++++++++++++++++ apps/openmw/mwgui/dialogue.hpp | 1 + apps/openmw/mwgui/tradewindow.cpp | 25 ------------------------- apps/openmw/mwgui/tradewindow.hpp | 2 -- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 2283e0cbe..23098bf42 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -396,6 +396,31 @@ namespace MWGui mLinks.clear(); updateOptions(); + + restock(); + } + + void DialogueWindow::restock() + { + MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); + float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); + + if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) + { + sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); + + mPtr.getClass().restock(mPtr); + + // Also restock any containers owned by this merchant, which are also available to buy in the trade window + std::vector itemSources; + MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); + for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) + { + it->getClass().restock(*it); + } + + sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); + } } void DialogueWindow::setKeywords(std::list keyWords) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 516c04942..71935dfaf 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -152,6 +152,7 @@ namespace MWGui private: void updateOptions(); + void restock(); int mServices; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c56c2ee94..f94e39b2f 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -99,8 +99,6 @@ namespace MWGui mCurrentBalance = 0; mCurrentMerchantOffer = 0; - restock(); - std::vector itemSources; MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); @@ -509,29 +507,6 @@ namespace MWGui return merchantGold; } - void TradeWindow::restock() - { - MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); - float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); - - if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) - { - sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); - - mPtr.getClass().restock(mPtr); - - // Also restock any containers owned by this merchant, which are also available to buy in the trade window - std::vector itemSources; - MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); - for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) - { - it->getClass().restock(*it); - } - - sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); - } - } - void TradeWindow::resetReference() { ReferenceInterface::resetReference(); diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index b487a8870..7baf0ce8e 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -104,8 +104,6 @@ namespace MWGui virtual void onReferenceUnavailable(); int getMerchantGold(); - - void restock(); }; } From ad50b926f5a667fed85b2ef4a6b44ccad091eeb8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 28 Jul 2014 09:01:44 +0200 Subject: [PATCH 0132/1983] reducing some stray arguments again --- components/compiler/extensions0.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 3945b73cc..0181fae96 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -59,7 +59,7 @@ namespace Compiler extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit); extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); - extensions.registerInstruction ("toggleai", "X", opcodeToggleAI, opcodeToggleAI); + extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI); extensions.registerInstruction ("tai", "", opcodeToggleAI, opcodeToggleAI); extensions.registerInstruction("startcombat", "c", opcodeStartCombat, opcodeStartCombatExplicit); extensions.registerInstruction("stopcombat", "x", opcodeStopCombat, opcodeStopCombatExplicit); @@ -113,7 +113,7 @@ namespace Compiler { void registerExtensions (Extensions& extensions) { - extensions.registerInstruction ("additem", "clxx", opcodeAddItem, opcodeAddItemExplicit); + extensions.registerInstruction ("additem", "cl", opcodeAddItem, opcodeAddItemExplicit); extensions.registerFunction ("getitemcount", 'l', "c", opcodeGetItemCount, opcodeGetItemCountExplicit); extensions.registerInstruction ("removeitem", "cl", opcodeRemoveItem, @@ -163,7 +163,7 @@ namespace Compiler { void registerExtensions (Extensions& extensions) { - extensions.registerInstruction ("journal", "clxxX", opcodeJournal); + extensions.registerInstruction ("journal", "cl", opcodeJournal); extensions.registerInstruction ("setjournalindex", "cl", opcodeSetJournalIndex); extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); From 3067082534c3b2247ebf11b58818ab5cc0b47ad9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 14:53:53 +0200 Subject: [PATCH 0133/1983] Make base_anim.nif take priority for biped creatures Fixes the skeletal minion's WalkForward1h animation. --- apps/openmw/mwrender/creatureanimation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index f2447cb70..e4806072a 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -27,9 +27,9 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr) setObjectRoot(model, false); setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha); + addAnimSource(model); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\base_anim.nif"); - addAnimSource(model); } } @@ -47,9 +47,9 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr) setObjectRoot(model, false); setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha); + addAnimSource(model); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\base_anim.nif"); - addAnimSource(model); mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); From d69ed78ccd995a0bb4f3601216951f6b0aa8d7ea Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 15:40:52 +0200 Subject: [PATCH 0134/1983] Don't add combat AI to player --- apps/openmw/mwclass/npc.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 49e016d4f..13b61d920 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -675,17 +675,20 @@ namespace MWClass // NOTE: 'object' and/or 'attacker' may be empty. - // Attacking peaceful NPCs is a crime - if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).isHostile() && - !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) - MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); - - if (!attacker.isEmpty() && attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr) - && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) + if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) { - // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. - // Note: accidental or collateral damage attacks are ignored. - MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); + // Attacking peaceful NPCs is a crime + if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).isHostile() && + !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) + MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); + + if (!attacker.isEmpty() && attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr) + && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) + { + // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. + // Note: accidental or collateral damage attacks are ignored. + MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); + } } bool wasDead = getCreatureStats(ptr).isDead(); From 0077296c917db940d6d1fc473cd74731d3b2e71b Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 15:57:16 +0200 Subject: [PATCH 0135/1983] Take actor's speed into account in stuck check The Winged Twilight's walking animation was so slow that it incorrectly detects being stuck. --- apps/openmw/mwmechanics/aipackage.cpp | 14 +++++--------- apps/openmw/mwmechanics/aipackage.hpp | 2 -- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 7790942b2..ff0329341 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -16,7 +16,7 @@ MWMechanics::AiPackage::~AiPackage() {} -MWMechanics::AiPackage::AiPackage() : mLastDoorChecked(MWWorld::Ptr()), mTimer(.26), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild +MWMechanics::AiPackage::AiPackage() : mTimer(.26), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild } @@ -92,22 +92,19 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po { /// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason //if(mObstacleCheck.check(actor, duration)) { - if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < 10 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care + if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care // first check if we're walking into a door MWWorld::Ptr door = getNearbyDoor(actor); if(door != MWWorld::Ptr()) // NOTE: checks interior cells only { - if(door.getCellRef().getTrap().empty() && mLastDoorChecked != door) { //Open the door if untrapped - door.getClass().activate(door, actor).get()->execute(actor); - mLastDoorChecked = door; + if(door.getCellRef().getTrap().empty() && door.getClass().getDoorState(door) == 0) { //Open the door if untrapped + MWBase::Environment::get().getWorld()->activateDoor(door, 1); } } else // probably walking into another NPC { - // TODO: diagonal should have same animation as walk forward - // but doesn't seem to do that? actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // change the angle a bit, too zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); } @@ -115,7 +112,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po else { //Not stuck, so reset things mStuckTimer = 0; mStuckPos = pos; - mLastDoorChecked = MWWorld::Ptr(); //Resets it, in case he gets stuck behind the door again actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward } } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 983777c0a..0a5e1c545 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -73,8 +73,6 @@ namespace MWMechanics float mTimer; float mStuckTimer; - MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door - ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; }; From 4773d754c67dba844821d4fd85446744a9b2d155 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 16:41:12 +0200 Subject: [PATCH 0136/1983] Remove redundant isHostile flag (Fixes #1652) --- apps/openmw/mwclass/npc.cpp | 6 +++--- apps/openmw/mwmechanics/actors.cpp | 12 +++++------- apps/openmw/mwmechanics/aicombat.cpp | 2 -- apps/openmw/mwmechanics/creaturestats.cpp | 14 +------------- apps/openmw/mwmechanics/creaturestats.hpp | 3 --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 -- apps/openmw/mwscript/aiextensions.cpp | 2 -- components/esm/creaturestats.cpp | 7 ++----- components/esm/creaturestats.hpp | 1 - 9 files changed, 11 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 13b61d920..d087febd0 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -678,8 +678,8 @@ namespace MWClass if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) { // Attacking peaceful NPCs is a crime - if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).isHostile() && - !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) + if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker) + && !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); if (!attacker.isEmpty() && attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr) @@ -910,7 +910,7 @@ namespace MWClass if(getCreatureStats(ptr).isDead()) return boost::shared_ptr(new MWWorld::ActionOpen(ptr, true)); - if(ptr.getClass().getCreatureStats(ptr).isHostile()) + if(ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat()) return boost::shared_ptr(new MWWorld::FailedAction("#{sActorInCombat}")); if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)) return boost::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a215a71a3..67e80ec67 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -191,7 +191,6 @@ namespace MWMechanics { CreatureStats& creatureStats = actor1.getClass().getCreatureStats(actor1); - if (againstPlayer && creatureStats.isHostile()) return; // already fighting against player if (actor2.getClass().getCreatureStats(actor2).isDead() || actor1.getClass().getCreatureStats(actor1).isDead()) return; @@ -204,7 +203,7 @@ namespace MWMechanics // pure water creatures won't try to fight with the target on the ground // except that creature is already hostile - if ((againstPlayer || !creatureStats.isHostile()) + if ((againstPlayer || !creatureStats.getAiSequence().isInCombat()) && ((actor1.getClass().canSwim(actor1) && !actor1.getClass().canWalk(actor1) // pure water creature && !MWBase::Environment::get().getWorld()->isSwimming(actor2)) || (!actor1.getClass().canSwim(actor1) && MWBase::Environment::get().getWorld()->isSwimming(actor2)))) // creature can't swim to target @@ -222,7 +221,7 @@ namespace MWMechanics if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard")) { // if creature is hostile then it is necessarily to start combat - if (creatureStats.isHostile()) aggressive = true; + if (creatureStats.getAiSequence().isInCombat()) aggressive = true; else aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); } } @@ -796,7 +795,7 @@ namespace MWMechanics { if (torch != inventoryStore.end()) { - if (!ptr.getClass().getCreatureStats (ptr).isHostile()) + if (!ptr.getClass().getCreatureStats (ptr).getAiSequence().isInCombat()) { // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) @@ -876,7 +875,7 @@ namespace MWMechanics CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); - if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile()) + if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat()) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); @@ -909,7 +908,6 @@ namespace MWMechanics creatureStats.getAiSequence().stopCombat(); // Reset factors to attack - creatureStats.setHostile(false); creatureStats.setAttacked(false); creatureStats.setAlarmed(false); @@ -1074,7 +1072,7 @@ namespace MWMechanics if(!stats.isDead()) { - if (stats.isHostile()) hostilesCount++; + if (stats.getAiSequence().isInCombat()) hostilesCount++; } } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 491954495..d59b0a3ec 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -190,8 +190,6 @@ namespace MWMechanics // 2. creature can't swim to target || (!actorClass.canSwim(actor) && world->isSwimming(target)))) { - if (target == world->getPlayerPtr()) - actorClass.getCreatureStats(actor).setHostile(false); actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return true; } diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 71217dbb9..fc7481410 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -17,7 +17,7 @@ namespace MWMechanics CreatureStats::CreatureStats() : mLevel (0), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), - mAttacked (false), mHostile (false), + mAttacked (false), mAttackingOrSpell(false), mIsWerewolf(false), mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mKnockdownOneFrame(false), @@ -331,16 +331,6 @@ namespace MWMechanics mAttacked = attacked; } - bool CreatureStats::isHostile() const - { - return mHostile; - } - - void CreatureStats::setHostile (bool hostile) - { - mHostile = hostile; - } - bool CreatureStats::getCreatureTargetted() const { MWWorld::Ptr targetPtr; @@ -506,7 +496,6 @@ namespace MWMechanics state.mTalkedTo = mTalkedTo; state.mAlarmed = mAlarmed; state.mAttacked = mAttacked; - state.mHostile = mHostile; state.mAttackingOrSpell = mAttackingOrSpell; // TODO: rewrite. does this really need 3 separate bools? state.mKnockdown = mKnockdown; @@ -555,7 +544,6 @@ namespace MWMechanics mTalkedTo = state.mTalkedTo; mAlarmed = state.mAlarmed; mAttacked = state.mAttacked; - mHostile = state.mHostile; mAttackingOrSpell = state.mAttackingOrSpell; // TODO: rewrite. does this really need 3 separate bools? mKnockdown = state.mKnockdown; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index a83da4249..4ee994de5 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -200,9 +200,6 @@ namespace MWMechanics bool getAttacked() const; void setAttacked (bool attacked); - bool isHostile() const; - void setHostile (bool hostile); - bool getCreatureTargetted() const; float getEvasion() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fd4269930..199dc488d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1176,8 +1176,6 @@ namespace MWMechanics ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) { - ptr.getClass().getCreatureStats(ptr).setHostile(true); - // if guard starts combat with player, guards pursuing player should do the same if (ptr.getClass().isClass(ptr, "Guard")) { diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index d844138d6..a3f935487 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -420,7 +420,6 @@ namespace MWScript runtime.pop(); const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); - std::string currentTargetId; bool targetsAreEqual = false; MWWorld::Ptr targetPtr; @@ -457,7 +456,6 @@ namespace MWScript MWWorld::Ptr actor = R()(runtime); MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); creatureStats.getAiSequence().stopCombat(); - creatureStats.setHostile(false); } }; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 37f0cc63c..5326b7f77 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -36,8 +36,8 @@ void ESM::CreatureStats::load (ESMReader &esm) mAttacked = false; esm.getHNOT (mAttacked, "ATKD"); - mHostile = false; - esm.getHNOT (mHostile, "HOST"); + if (esm.isNextSub("HOST")) + esm.skipHSub(); // Hostile, no longer used mAttackingOrSpell = false; esm.getHNOT (mAttackingOrSpell, "ATCK"); @@ -148,9 +148,6 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mAttacked) esm.writeHNT ("ATKD", mAttacked); - if (mHostile) - esm.writeHNT ("HOST", mHostile); - if (mAttackingOrSpell) esm.writeHNT ("ATCK", mAttackingOrSpell); diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 7ae57da16..610be0246 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -43,7 +43,6 @@ namespace ESM bool mTalkedTo; bool mAlarmed; bool mAttacked; - bool mHostile; bool mAttackingOrSpell; bool mKnockdown; bool mKnockdownOneFrame; From 18b3e71be54f2246d4f6de43ba05524a6488f879 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 16:59:46 +0200 Subject: [PATCH 0137/1983] Reset player position when spawning in fallback cell --- apps/openmw/mwworld/player.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 12908ca9d..280761215 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -253,6 +253,10 @@ namespace MWWorld catch (...) { // Cell no longer exists. Place the player in a default cell. + ESM::Position pos = mPlayer.mData.getPosition(); + MWBase::Environment::get().getWorld()->indexToPosition(0, 0, pos.pos[0], pos.pos[1], true); + pos.pos[2] = 0; + mPlayer.mData.setPosition(pos); mCellStore = world.getExterior(0,0); } From d9a6515fe3fea5f43102290171e647dd5dc2444e Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 17:11:46 +0200 Subject: [PATCH 0138/1983] Adjust AiFollow running threshold to more closely match vanilla MW --- apps/openmw/mwmechanics/aifollow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index c2bb0f2f4..13c4a9891 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -74,9 +74,9 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) } //Check if you're far away - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 1000) + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run - else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 800) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold + else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk return false; From d956df83e48d2b7ff472bb6a4341328cbb456f82 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 17:19:20 +0200 Subject: [PATCH 0139/1983] Don't make guards fight non-aggressive creatures that are in combat Ex. summoned creature that is helping in a fight. --- apps/openmw/mwmechanics/actors.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 67e80ec67..bd87f8b4b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -220,9 +220,8 @@ namespace MWMechanics // Make guards fight aggressive creatures if (!actor1.getClass().isNpc() && actor2.getClass().isClass(actor2, "Guard")) { - // if creature is hostile then it is necessarily to start combat - if (creatureStats.getAiSequence().isInCombat()) aggressive = true; - else aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); + if (creatureStats.getAiSequence().isInCombat() && MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2)) + aggressive = true; } } From f67b7dae913352d15cff4dfc75ddcac2f82dfd06 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 17:28:00 +0200 Subject: [PATCH 0140/1983] Sheath weapon in all Ai packages except for AiCombat --- apps/openmw/mwmechanics/aiactivate.cpp | 4 ++++ apps/openmw/mwmechanics/aiescort.cpp | 4 ++++ apps/openmw/mwmechanics/aifollow.cpp | 2 ++ apps/openmw/mwmechanics/aipursue.cpp | 2 ++ apps/openmw/mwmechanics/aitravel.cpp | 2 ++ 5 files changed, 14 insertions(+) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 9e01c3fe7..7310b27ab 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -5,6 +5,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -24,6 +26,8 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + if(target == MWWorld::Ptr()) return true; //Target doesn't exist diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 3f5724077..cc8dead8a 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -9,6 +9,8 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "steering.hpp" #include "movement.hpp" @@ -72,6 +74,8 @@ namespace MWMechanics return true; } + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); const float* const leaderPos = actor.getRefData().getPosition().pos; const float* const followerPos = follower.getRefData().getPosition().pos; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 13c4a9891..27d944657 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -35,6 +35,8 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) if(target == MWWorld::Ptr()) return true; //Target doesn't exist + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor if(!mAlwaysFollow) //Update if you only follow for a bit diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 60f671c12..2995a8c36 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -38,6 +38,8 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, float duration) if(target == MWWorld::Ptr()) return true; //Target doesn't exist + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index db137037d..3fd4704d9 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -44,6 +44,8 @@ namespace MWMechanics 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) { From b56cb7e5eec16a5b59102df953db9ee7e86b2a16 Mon Sep 17 00:00:00 2001 From: bogglez Date: Mon, 28 Jul 2014 17:46:41 +0200 Subject: [PATCH 0141/1983] Remove defunct option for building without FFmpeg - Added REQUIRED to find_package(FFmpeg) - Removed USE_FFMPEG option from CMakeLists.txt - Always use FFmpeg for sound input - Removed SOUND_DEFINE from CMakeLists.txt - Removed #else branch from videoplayer.cpp with dummy VideoState code (FFmpeg is now guaranteed to exist and the code was incomplete) - Remove #ifdef OPENMW_USE_FFMPEG in ffmpeg_decoder.cpp, it is guaranteed to be used - Remove #ifdef OPENMW_USE_FFMPEG from soundmanagerimp.cpp, it is guaranteed to be used --- CMakeLists.txt | 33 +++---------------------- apps/openmw/CMakeLists.txt | 1 - apps/openmw/mwrender/videoplayer.cpp | 23 ----------------- apps/openmw/mwsound/ffmpeg_decoder.cpp | 5 ---- apps/openmw/mwsound/soundmanagerimp.cpp | 5 ---- 5 files changed, 4 insertions(+), 63 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee3850ddb..2f5e21c61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,9 +74,6 @@ option(BUILD_OPENCS "build OpenMW Construction Set" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock frameworks" OFF) -# Sound source selection -option(USE_FFMPEG "use ffmpeg for sound" ON) - # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) @@ -138,32 +135,10 @@ set(OPENMW_LIBS ${OENGINE_ALL}) set(OPENMW_LIBS_HEADER) # Sound setup -set(GOT_SOUND_INPUT 0) -set(SOUND_INPUT_INCLUDES "") -set(SOUND_INPUT_LIBRARY "") -set(SOUND_DEFINE "") -if (USE_FFMPEG) - set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) - find_package(FFmpeg) - if (FFMPEG_FOUND) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIRS}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) - set(GOT_SOUND_INPUT 1) - endif (FFMPEG_FOUND) -endif (USE_FFMPEG) - -if (NOT GOT_SOUND_INPUT) - message(WARNING "--------------------") - message(WARNING "Failed to find any sound input packages") - message(WARNING "--------------------") -endif (NOT GOT_SOUND_INPUT) - -if (NOT FFMPEG_FOUND) - message(WARNING "--------------------") - message(WARNING "FFmpeg not found, video playback will be disabled") - message(WARNING "--------------------") -endif (NOT FFMPEG_FOUND) +set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE) +find_package(FFmpeg REQUIRED) +set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIRS}) +set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARIES}) # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0dda1131b..c5ff2a83c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -99,7 +99,6 @@ add_executable(openmw # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. include_directories(${SOUND_INPUT_INCLUDES} ${BULLET_INCLUDE_DIRS}) -add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 409e27388..2d05f2770 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -26,8 +26,6 @@ typedef SSIZE_T ssize_t; namespace MWRender { -#ifdef OPENMW_USE_FFMPEG - extern "C" { #include @@ -1073,27 +1071,6 @@ void VideoState::deinit() } } -#else // defined OPENMW_USE_FFMPEG - -class VideoState -{ -public: - VideoState() { } - - void init(const std::string& resourceName) - { - throw std::runtime_error("FFmpeg not supported, cannot play \""+resourceName+"\""); - } - void deinit() { } - - void close() { } - - bool update() - { return false; } -}; - -#endif // defined OPENMW_USE_FFMPEG - VideoPlayer::VideoPlayer() : mState(NULL) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 10a782b96..982d0c5ff 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -1,6 +1,3 @@ -#ifdef OPENMW_USE_FFMPEG - - #include "ffmpeg_decoder.hpp" // auto_ptr @@ -375,5 +372,3 @@ FFmpeg_Decoder::~FFmpeg_Decoder() } } - -#endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index ba7b4f3ba..453a19905 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -17,15 +17,10 @@ #include "openal_output.hpp" #define SOUND_OUT "OpenAL" -/* Set up the sound manager to use FFMPEG for input. - * The OPENMW_USE_x macros are set in CMakeLists.txt. -*/ -#ifdef OPENMW_USE_FFMPEG #include "ffmpeg_decoder.hpp" #ifndef SOUND_IN #define SOUND_IN "FFmpeg" #endif -#endif namespace MWSound From 53496991723a0860da90088ad969596f1767cc64 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 17:54:14 +0200 Subject: [PATCH 0142/1983] Only show "target resists magic" message for spells cast by player --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 2566bf189..94b479007 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -379,7 +379,7 @@ namespace MWMechanics // Fully resisted, show message if (target.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else + else if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); } } From 8455ed62799491a1f12332dd453af4b29e2475c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 28 Jul 2014 19:40:56 +0200 Subject: [PATCH 0143/1983] Cancel upper body animations when knocked down --- apps/openmw/mwmechanics/character.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 638feeef3..0809cd640 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -221,6 +221,21 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mCurrentHit = "shield"; mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0); } + + // Cancel upper body animations + if (mHitState == CharState_KnockDown || mHitState == CharState_KnockOut) + { + if (mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_Nothing; + } + } } else if(!mAnimation->isPlaying(mCurrentHit)) { From 0032426817376061cb6d68624e900933c6e734c9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 28 Jul 2014 20:39:37 +0200 Subject: [PATCH 0144/1983] updated credits file --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index 5c757f957..2c8d86f49 100644 --- a/credits.txt +++ b/credits.txt @@ -72,6 +72,7 @@ Sandy Carter (bwrsandman) Sebastian Wick (swick) Sergey Shambir sir_herrbatka +Stefan Galowicz (bogglez) Sylvain Thesnieres (Garvek) Thomas Luppi (Digmaster) Tom Mason (wheybags) From c006393178a359a95a3f089e8e2b734ee6020fff Mon Sep 17 00:00:00 2001 From: bogglez Date: Mon, 28 Jul 2014 21:52:34 +0200 Subject: [PATCH 0145/1983] Fix http://bugs.openmw.org/issues/768 Changes application_name in configuration manager to OpenMW instead of openmw, if on windows. This shouldn't break anything since Windows uses case-insensitive filesystems. Strictly speaking the installation directory must be OpenMW now though (not sure whether this is the case) --- components/files/configurationmanager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 58d75d1fd..942f47d4e 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -16,13 +16,19 @@ namespace Files static const char* const openmwCfgFile = "openmw.cfg"; +#if defined(_WIN32) || defined(__WINDOWS__) +static const char* const applicationName = "OpenMW"; +#else +static const char* const applicationName = "openmw"; +#endif + const char* const mwToken = "?mw?"; const char* const localToken = "?local?"; const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; ConfigurationManager::ConfigurationManager() - : mFixedPath("openmw") + : mFixedPath(applicationName) { setupTokensMapping(); From 7dfb624ee2837ed8d6181bc11bffe682a7ba2a8f Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 00:23:00 +0200 Subject: [PATCH 0146/1983] Support loading text keys for objects without a skeleton This means we can no longer map them to bone IDs, but they are unused anyway. Required to load text keys from the default head models (such as Talk: Start/Stop and Blink: Start/Stop) --- components/nifogre/ogrenifloader.cpp | 6 +----- components/nifogre/ogrenifloader.hpp | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 81b2e55d2..c39c62b67 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -1038,11 +1038,7 @@ class NIFObjectLoader { const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); - if (scene->mSkelBase) - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); - extractTextKeys(tk, scene->mTextKeys[trgtid]); - } + extractTextKeys(tk, scene->mTextKeys); } else if(e->recType == Nif::RC_NiStringExtraData) { diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index badb6ccd3..011245bea 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -69,7 +69,7 @@ struct ObjectScene { // The maximum length on any of the controllers. For animations with controllers, but no text keys, consider this the animation length. float mMaxControllerLength; - std::map mTextKeys; + TextKeyMap mTextKeys; MaterialControllerManager mMaterialControllerMgr; From 20efeea5d9ed9590c3c8a0f703a7dbf37e5dc6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aciubid=C5=82o?= Date: Mon, 28 Jul 2014 20:39:13 +0100 Subject: [PATCH 0147/1983] AI tell their greatings even when player is moving (like morrowind). Greating reset distance was greatly inflated comparing with morrowind. --- apps/openmw/mwmechanics/aiwander.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index b97554e2a..f1a815d30 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -454,9 +454,7 @@ namespace MWMechanics if (!mSaidGreeting) { // TODO: check if actor is aware / has line of sight - if (playerDistSqr <= helloDistance*helloDistance - // Only play a greeting if the player is not moving - && Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0) + if (playerDistSqr <= helloDistance*helloDistance) { mSaidGreeting = true; MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); @@ -467,7 +465,7 @@ namespace MWMechanics static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore() .get().find("fGreetDistanceReset")->getFloat(); - if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset * iGreetDistanceMultiplier*iGreetDistanceMultiplier) + if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset) mSaidGreeting = false; } From d47bfbe69c9c4ac81d4140445c0451a3dd96eec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aciubid=C5=82o?= Date: Mon, 28 Jul 2014 23:16:24 +0100 Subject: [PATCH 0148/1983] Implement AI greeting states. Greeting consist of 3 phases: - none - default one, greeting state can only change to "in progress" when near enough and some time passes - in progress - NPC says his greating and rotates toward player, state can only change to "done" after some time - done - rotation is stoped, after idling can go away from player, state can only change to "none" when player and NPC are faraway --- apps/openmw/mwmechanics/aiwander.cpp | 37 +++++++++++++++------------- apps/openmw/mwmechanics/aiwander.hpp | 7 +++++- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index f1a815d30..519cc8a61 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -43,7 +43,7 @@ namespace MWMechanics mReaction = 0; mRotate = false; mTargetAngle = 0; - mSaidGreeting = false; + mSaidGreeting = Greet_None; mHasReturnPosition = false; mReturnPosition = Ogre::Vector3(0,0,0); @@ -407,7 +407,7 @@ namespace MWMechanics } // Allow interrupting a walking actor to trigger a greeting - if(mIdleNow || (mWalking && !mObstacleCheck.isNormalState() && mDistance)) + if(mIdleNow || mWalking) { // Play a random voice greeting if the player gets too close int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); @@ -421,8 +421,18 @@ namespace MWMechanics Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); float playerDistSqr = playerPos.squaredDistance(actorPos); - - if(playerDistSqr <= helloDistance*helloDistance) + + if (mSaidGreeting == Greet_None) + { + // TODO: check if actor is aware / has line of sight + if (playerDistSqr <= helloDistance*helloDistance) + { + mSaidGreeting = Greet_InProgress; + MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); + } + } + + if(mSaidGreeting == Greet_InProgress) { if(mWalking) { @@ -449,29 +459,22 @@ namespace MWMechanics mRotate = true; } } + + mSaidGreeting = Greet_Done; } - - if (!mSaidGreeting) - { - // TODO: check if actor is aware / has line of sight - if (playerDistSqr <= helloDistance*helloDistance) - { - mSaidGreeting = true; - MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); - } - } - else + + if (mSaidGreeting == MWMechanics::AiWander::Greet_Done) { static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore() .get().find("fGreetDistanceReset")->getFloat(); if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset) - mSaidGreeting = false; + mSaidGreeting = Greet_None; } // Check if idle animation finished // FIXME: don't stay forever - if(!checkIdle(actor, mPlayedIdle) && playerDistSqr > helloDistance*helloDistance) + if(!checkIdle(actor, mPlayedIdle) && (playerDistSqr > helloDistance*helloDistance || mSaidGreeting == MWMechanics::AiWander::Greet_Done)) { mPlayedIdle = 0; mIdleNow = false; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 7abd19e27..fdfd6f42e 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -62,7 +62,12 @@ namespace MWMechanics std::vector mIdle; bool mRepeat; - bool mSaidGreeting; + enum GreetingState { + Greet_None, + Greet_InProgress, + Greet_Done + }; + GreetingState mSaidGreeting; bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... From bd3729a6cbbf2067b6c1f46a37c749ddc8f18daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aciubid=C5=82o?= Date: Mon, 28 Jul 2014 23:41:37 +0100 Subject: [PATCH 0149/1983] Add grace periods for player greeting. Add delay for NPC greating. NPC ignores greeted player after some time. Fixes bug 1503. --- apps/openmw/mwmechanics/aiwander.cpp | 23 ++++++++++++++++++----- apps/openmw/mwmechanics/aiwander.hpp | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 519cc8a61..27dad88cd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -22,6 +22,9 @@ namespace MWMechanics { static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed static const float DOOR_CHECK_INTERVAL = 1.5f; + static const float REACTION_INTERVAL = 0.25f; + static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player + static const int GREETING_SHOULD_END = 10; AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) @@ -44,6 +47,7 @@ namespace MWMechanics mRotate = false; mTargetAngle = 0; mSaidGreeting = Greet_None; + greetingTimer = 0; mHasReturnPosition = false; mReturnPosition = Ogre::Vector3(0,0,0); @@ -221,14 +225,14 @@ namespace MWMechanics } mReaction += duration; - if(mReaction < 0.25f) // FIXME: hard coded constant + if(mReaction < REACTION_INTERVAL) { return false; } else mReaction = 0; - // NOTE: everything below get updated every 0.25 seconds + // NOTE: everything below get updated every REACTION_INTERVAL seconds MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) @@ -424,16 +428,22 @@ namespace MWMechanics if (mSaidGreeting == Greet_None) { - // TODO: check if actor is aware / has line of sight if (playerDistSqr <= helloDistance*helloDistance) + greetingTimer++; + + // TODO: check if actor is aware / has line of sight + if (greetingTimer >= GREETING_SHOULD_START) { mSaidGreeting = Greet_InProgress; MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); + greetingTimer = 0; } } if(mSaidGreeting == Greet_InProgress) { + greetingTimer++; + if(mWalking) { stopWalking(actor); @@ -460,7 +470,11 @@ namespace MWMechanics } } - mSaidGreeting = Greet_Done; + if (greetingTimer >= GREETING_SHOULD_END) + { + mSaidGreeting = Greet_Done; + greetingTimer = 0; + } } if (mSaidGreeting == MWMechanics::AiWander::Greet_Done) @@ -473,7 +487,6 @@ namespace MWMechanics } // Check if idle animation finished - // FIXME: don't stay forever if(!checkIdle(actor, mPlayedIdle) && (playerDistSqr > helloDistance*helloDistance || mSaidGreeting == MWMechanics::AiWander::Greet_Done)) { mPlayedIdle = 0; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index fdfd6f42e..9b579b24a 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -68,6 +68,7 @@ namespace MWMechanics Greet_Done }; GreetingState mSaidGreeting; + int greetingTimer; bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... From 598c0c4ae7137718a65afed676fa715203329aec Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 00:26:26 +0200 Subject: [PATCH 0150/1983] Implement mouth animation for NPCs based on say sound (Fixes #642) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/soundmanager.hpp | 5 ++ apps/openmw/mwrender/npcanimation.cpp | 47 ++++++++++++-- apps/openmw/mwrender/npcanimation.hpp | 11 +++- apps/openmw/mwsound/loudness.cpp | 53 +++++++++++++++ apps/openmw/mwsound/loudness.hpp | 20 ++++++ apps/openmw/mwsound/openal_output.cpp | 86 +++++++++++++++---------- apps/openmw/mwsound/openal_output.hpp | 4 +- apps/openmw/mwsound/sound.cpp | 23 +++++++ apps/openmw/mwsound/sound.hpp | 10 +++ apps/openmw/mwsound/sound_output.hpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 17 ++++- apps/openmw/mwsound/soundmanagerimp.hpp | 5 ++ 13 files changed, 241 insertions(+), 44 deletions(-) create mode 100644 apps/openmw/mwsound/loudness.cpp create mode 100644 apps/openmw/mwsound/loudness.hpp create mode 100644 apps/openmw/mwsound/sound.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0dda1131b..25cf48b09 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -48,7 +48,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanagerimp openal_output ffmpeg_decoder sound + soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 15739730b..a02a463dd 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -101,6 +101,11 @@ namespace MWBase virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()) = 0; ///< Stop an actor speaking + virtual float getSaySoundLoudness(const MWWorld::Ptr& reference) const = 0; + ///< Check the currently playing say sound for this actor + /// and get an average loudness value (scale [0,1]) at the current time position. + /// If the actor is not saying anything, returns 0. + virtual SoundPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; ///< Play a 2D audio track, using a custom decoder diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index be2b262fc..597d0c2df 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -66,15 +66,40 @@ std::string getVampireHead(const std::string& race, bool female) namespace MWRender { +HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference) + : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0) +{ +} + float HeadAnimationTime::getValue() const { - // TODO use time from text keys (Talk Start/Stop, Blink Start/Stop) // TODO: Handle eye blinking if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) - return 0; + return mBlinkStop; else - // TODO: Use the loudness of the currently playing sound - return 1; + return mTalkStart + + (mTalkStop - mTalkStart) * + std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*4); // Rescale a bit (most voices are not very loud) +} + +void HeadAnimationTime::setTalkStart(float value) +{ + mTalkStart = value; +} + +void HeadAnimationTime::setTalkStop(float value) +{ + mTalkStop = value; +} + +void HeadAnimationTime::setBlinkStart(float value) +{ + mBlinkStart = value; +} + +void HeadAnimationTime::setBlinkStop(float value) +{ + mBlinkStop = value; } static NpcAnimation::PartBoneMap createPartListMap() @@ -620,7 +645,21 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g ctrl->setSource(mNullAnimationTimePtr); if (type == ESM::PRT_Head) + { ctrl->setSource(mHeadAnimationTime); + const NifOgre::TextKeyMap& keys = mObjectParts[type]->mTextKeys; + for (NifOgre::TextKeyMap::const_iterator it = keys.begin(); it != keys.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->second, "talk: start")) + mHeadAnimationTime->setTalkStart(it->first); + if (Misc::StringUtils::ciEqual(it->second, "talk: stop")) + mHeadAnimationTime->setTalkStop(it->first); + if (Misc::StringUtils::ciEqual(it->second, "blink: start")) + mHeadAnimationTime->setBlinkStart(it->first); + if (Misc::StringUtils::ciEqual(it->second, "blink: stop")) + mHeadAnimationTime->setBlinkStop(it->first); + } + } else if (type == ESM::PRT_Weapon) ctrl->setSource(mWeaponAnimationTime); } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 8ec46facd..057f67e2f 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -19,8 +19,17 @@ class HeadAnimationTime : public Ogre::ControllerValue { private: MWWorld::Ptr mReference; + float mTalkStart; + float mTalkStop; + float mBlinkStart; + float mBlinkStop; public: - HeadAnimationTime(MWWorld::Ptr reference) : mReference(reference) {} + HeadAnimationTime(MWWorld::Ptr reference); + + void setTalkStart(float value); + void setTalkStop(float value); + void setBlinkStart(float value); + void setBlinkStop(float value); virtual Ogre::Real getValue() const; virtual void setValue(Ogre::Real value) diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp new file mode 100644 index 000000000..88c706a91 --- /dev/null +++ b/apps/openmw/mwsound/loudness.cpp @@ -0,0 +1,53 @@ +#include "loudness.hpp" + +#include "soundmanagerimp.hpp" + +namespace MWSound +{ + + void analyzeLoudness(const std::vector &data, int sampleRate, ChannelConfig chans, + SampleType type, std::vector &out, float valuesPerSecond) + { + int samplesPerSegment = sampleRate / valuesPerSecond; + int numSamples = bytesToFrames(data.size(), chans, type); + int advance = framesToBytes(1, chans, type); + + out.reserve(numSamples/samplesPerSegment); + + int segment=0; + int sample=0; + while (segment < numSamples/samplesPerSegment) + { + float sum=0; + int samplesAdded = 0; + while (sample < numSamples && sample < (segment+1)*samplesPerSegment) + { + // get sample on a scale from -1 to 1 + float value = 0; + if (type == SampleType_UInt8) + value = data[sample*advance]/128.f; + else if (type == SampleType_Int16) + { + value = *reinterpret_cast(&data[sample*advance]); + value /= float(std::numeric_limits().max()); + } + else if (type == SampleType_Float32) + { + value = *reinterpret_cast(&data[sample*advance]); + value /= std::numeric_limits().max(); + } + + sum += value*value; + ++samplesAdded; + ++sample; + } + + float rms = 0; // root mean square + if (samplesAdded > 0) + rms = std::sqrt(sum / samplesAdded); + out.push_back(rms); + ++segment; + } + } + +} diff --git a/apps/openmw/mwsound/loudness.hpp b/apps/openmw/mwsound/loudness.hpp new file mode 100644 index 000000000..df727bd0b --- /dev/null +++ b/apps/openmw/mwsound/loudness.hpp @@ -0,0 +1,20 @@ +#include "sound_decoder.hpp" + +namespace MWSound +{ + +/** + * Analyzes the energy (closely related to loudness) of a sound buffer. + * The buffer will be divided into segments according to \a valuesPerSecond, + * and for each segment a loudness value in the range of [0,1] will be computed. + * @param data the sound buffer to analyze, containing raw samples + * @param sampleRate the sample rate of the sound buffer + * @param chans channel layout of the buffer + * @param type sample type of the buffer + * @param out Will contain the output loudness values. + * @param valuesPerSecond How many loudness values per second of audio to compute. + */ +void analyzeLoudness (const std::vector& data, int sampleRate, ChannelConfig chans, SampleType type, + std::vector& out, float valuesPerSecond); + +} diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index b245b9241..b20bd4f7e 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -11,11 +11,16 @@ #include "sound_decoder.hpp" #include "sound.hpp" #include "soundmanagerimp.hpp" +#include "loudness.hpp" #ifndef ALC_ALL_DEVICES_SPECIFIER #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif +namespace +{ + const int loudnessFPS = 20; // loudness values per second of audio +} namespace MWSound { @@ -750,7 +755,7 @@ void OpenAL_Output::deinit() } -ALuint OpenAL_Output::getBuffer(const std::string &fname) +ALuint OpenAL_Output::getBuffer(const std::string &fname, std::vector* loudnessBuffer) { ALuint buf = 0; @@ -765,11 +770,12 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) if(iter != mUnusedBuffers.end()) mUnusedBuffers.erase(iter); } - - return buf; } throwALerror(); + if (buf != 0 && loudnessBuffer == NULL) + return buf; + std::vector data; ChannelConfig chans; SampleType type; @@ -795,42 +801,50 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) decoder->readAll(data); decoder->close(); - alGenBuffers(1, &buf); - throwALerror(); + if (loudnessBuffer != NULL) + { + analyzeLoudness(data, srate, chans, type, *loudnessBuffer, loudnessFPS); + } + + if (buf == 0) + { + alGenBuffers(1, &buf); + throwALerror(); - alBufferData(buf, format, &data[0], data.size(), srate); - mBufferCache[fname] = buf; - mBufferRefs[buf] = 1; + alBufferData(buf, format, &data[0], data.size(), srate); + mBufferCache[fname] = buf; + mBufferRefs[buf] = 1; - ALint bufsize = 0; - alGetBufferi(buf, AL_SIZE, &bufsize); - mBufferCacheMemSize += bufsize; + ALint bufsize = 0; + alGetBufferi(buf, AL_SIZE, &bufsize); + mBufferCacheMemSize += bufsize; - // NOTE: Max buffer cache: 15MB - while(mBufferCacheMemSize > 15*1024*1024) - { - if(mUnusedBuffers.empty()) + // NOTE: Max buffer cache: 15MB + while(mBufferCacheMemSize > 15*1024*1024) { - std::cout <<"No more unused buffers to clear!"<< std::endl; - break; - } + if(mUnusedBuffers.empty()) + { + std::cout <<"No more unused buffers to clear!"<< std::endl; + break; + } - ALuint oldbuf = mUnusedBuffers.front(); - mUnusedBuffers.pop_front(); + ALuint oldbuf = mUnusedBuffers.front(); + mUnusedBuffers.pop_front(); - NameMap::iterator nameiter = mBufferCache.begin(); - while(nameiter != mBufferCache.end()) - { - if(nameiter->second == oldbuf) - mBufferCache.erase(nameiter++); - else - ++nameiter; - } + NameMap::iterator nameiter = mBufferCache.begin(); + while(nameiter != mBufferCache.end()) + { + if(nameiter->second == oldbuf) + mBufferCache.erase(nameiter++); + else + ++nameiter; + } - bufsize = 0; - alGetBufferi(oldbuf, AL_SIZE, &bufsize); - alDeleteBuffers(1, &oldbuf); - mBufferCacheMemSize -= bufsize; + bufsize = 0; + alGetBufferi(oldbuf, AL_SIZE, &bufsize); + alDeleteBuffers(1, &oldbuf); + mBufferCacheMemSize -= bufsize; + } } return buf; } @@ -883,7 +897,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, f } MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float vol, float basevol, float pitch, - float min, float max, int flags, float offset) + float min, float max, int flags, float offset, bool extractLoudness) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -895,8 +909,12 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre try { - buf = getBuffer(fname); + std::vector loudnessVector; + + buf = getBuffer(fname, extractLoudness ? &loudnessVector : NULL); + sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); + sound->setLoudnessVector(loudnessVector, loudnessFPS); } catch(std::exception&) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 31edf7359..f96c588cf 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -36,7 +36,7 @@ namespace MWSound typedef std::vector SoundVec; SoundVec mActiveSounds; - ALuint getBuffer(const std::string &fname); + ALuint getBuffer(const std::string &fname, std::vector* loudnessBuffer=NULL); void bufferFinished(ALuint buffer); Environment mLastEnvironment; @@ -49,7 +49,7 @@ namespace MWSound virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags, float offset); /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float vol, float basevol, float pitch, float min, float max, int flags, float offset); + float vol, float basevol, float pitch, float min, float max, int flags, float offset, bool extractLoudness=false); virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags); virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); diff --git a/apps/openmw/mwsound/sound.cpp b/apps/openmw/mwsound/sound.cpp new file mode 100644 index 000000000..b3105a82c --- /dev/null +++ b/apps/openmw/mwsound/sound.cpp @@ -0,0 +1,23 @@ +#include "sound.hpp" + +namespace MWSound +{ + + float Sound::getCurrentLoudness() + { + if (mLoudnessVector.empty()) + return 0.f; + int index = getTimeOffset() * mLoudnessFPS; + + index = std::max(0, std::min(index, int(mLoudnessVector.size()-1))); + + return mLoudnessVector[index]; + } + + void Sound::setLoudnessVector(const std::vector &loudnessVector, float loudnessFPS) + { + mLoudnessVector = loudnessVector; + mLoudnessFPS = loudnessFPS; + } + +} diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 670002a30..1b5c00196 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -24,6 +24,9 @@ namespace MWSound int mFlags; float mFadeOutTime; + std::vector mLoudnessVector; + float mLoudnessFPS; + public: virtual void stop() = 0; virtual bool isPlaying() = 0; @@ -31,6 +34,12 @@ namespace MWSound void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } void setFadeout(float duration) { mFadeOutTime=duration; } + void setLoudnessVector(const std::vector& loudnessVector, float loudnessFPS); + + /// Get loudness at the current time position on a [0,1] scale. + /// Requires that loudnessVector was filled in by the user. + float getCurrentLoudness(); + MWBase::SoundManager::PlayType getPlayType() const { return (MWBase::SoundManager::PlayType)(mFlags&MWBase::SoundManager::Play_TypeMask); } @@ -44,6 +53,7 @@ namespace MWSound , mMaxDistance(maxdist) , mFlags(flags) , mFadeOutTime(0) + , mLoudnessFPS(20) { } virtual ~Sound() { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 91e25db52..a9a999a5c 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -28,7 +28,7 @@ namespace MWSound virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags, float offset) = 0; /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float vol, float basevol, float pitch, float min, float max, int flags, float offset) = 0; + float vol, float basevol, float pitch, float min, float max, int flags, float offset, bool extractLoudness=false) = 0; virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags) = 0; virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index ba7b4f3ba..fced81f7b 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -256,7 +256,7 @@ namespace MWSound const Ogre::Vector3 objpos(pos.pos); MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, - 20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0); + 20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0, true); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) @@ -265,6 +265,21 @@ namespace MWSound } } + float SoundManager::getSaySoundLoudness(const MWWorld::Ptr &ptr) const + { + SoundMap::const_iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(snditer->second.first == ptr && snditer->second.second == "_say_sound") + break; + ++snditer; + } + if (snditer == mActiveSounds.end()) + return 0.f; + + return snditer->first->getCurrentLoudness(); + } + void SoundManager::say(const std::string& filename) { if(!mOutput->isInitialized()) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 380cfe255..250cb0d51 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -110,6 +110,11 @@ namespace MWSound virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()); ///< Stop an actor speaking + virtual float getSaySoundLoudness(const MWWorld::Ptr& reference) const; + ///< Check the currently playing say sound for this actor + /// and get an average loudness value (scale [0,1]) at the current time position. + /// If the actor is not saying anything, returns 0. + virtual MWBase::SoundPtr playTrack(const DecoderPtr& decoder, PlayType type); ///< Play a 2D audio track, using a custom decoder From 625f9a35e607459180860201e34c29ae1bd0b5a6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 01:16:08 +0200 Subject: [PATCH 0151/1983] Implement NPC eye blinking (Fixes #1721) --- apps/openmw/mwrender/npcanimation.cpp | 38 +++++++++++++++++++++++---- apps/openmw/mwrender/npcanimation.hpp | 8 ++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 597d0c2df..e2a01f723 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -67,19 +67,45 @@ namespace MWRender { HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference) - : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0) + : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mValue(0) { + resetBlinkTimer(); } -float HeadAnimationTime::getValue() const +void HeadAnimationTime::resetBlinkTimer() +{ + mBlinkTimer = -(2 + (std::rand() / double(RAND_MAX*1.0)) * 6); +} + +void HeadAnimationTime::update(float dt) { - // TODO: Handle eye blinking if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) - return mBlinkStop; + { + mBlinkTimer += dt; + + float duration = mBlinkStop - mBlinkStart; + + if (mBlinkTimer >= 0 && mBlinkTimer <= duration) + { + mValue = mBlinkStart + mBlinkTimer; + } + else + mValue = mBlinkStop; + + if (mBlinkTimer > duration) + resetBlinkTimer(); + } else - return mTalkStart + + { + mValue = mTalkStart + (mTalkStop - mTalkStart) * std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*4); // Rescale a bit (most voices are not very loud) + } +} + +float HeadAnimationTime::getValue() const +{ + return mValue; } void HeadAnimationTime::setTalkStart(float value) @@ -541,6 +567,8 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { Ogre::Vector3 ret = Animation::runAnimation(timepassed); + mHeadAnimationTime->update(timepassed); + Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); if(mViewMode == VM_FirstPerson) { diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 057f67e2f..95cd35ddb 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -23,9 +23,17 @@ private: float mTalkStop; float mBlinkStart; float mBlinkStop; + + float mBlinkTimer; + + float mValue; +private: + void resetBlinkTimer(); public: HeadAnimationTime(MWWorld::Ptr reference); + void update(float dt); + void setTalkStart(float value); void setTalkStop(float value); void setBlinkStart(float value); From 0943ff08863aca78c0ac22ce68b2bf5468dc2782 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 14:19:12 +0200 Subject: [PATCH 0152/1983] Fix normalizing sample values --- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwsound/loudness.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e2a01f723..0a9b56b33 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -99,7 +99,7 @@ void HeadAnimationTime::update(float dt) { mValue = mTalkStart + (mTalkStop - mTalkStart) * - std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*4); // Rescale a bit (most voices are not very loud) + std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*2); // Rescale a bit (most voices are not very loud) } } diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index 88c706a91..ebe3bf1ec 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -25,16 +25,16 @@ namespace MWSound // get sample on a scale from -1 to 1 float value = 0; if (type == SampleType_UInt8) - value = data[sample*advance]/128.f; + value = ((char)(data[sample*advance]^0x80))/128.f; else if (type == SampleType_Int16) { value = *reinterpret_cast(&data[sample*advance]); - value /= float(std::numeric_limits().max()); + value /= float(std::numeric_limits().max()); } else if (type == SampleType_Float32) { value = *reinterpret_cast(&data[sample*advance]); - value /= std::numeric_limits().max(); + value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already. } sum += value*value; From a59620f643ee54d428e25237d427cc4116179065 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 14:32:44 +0200 Subject: [PATCH 0153/1983] Cache loudness vector in the buffer cache --- apps/openmw/mwsound/openal_output.cpp | 92 +++++++++++++-------------- apps/openmw/mwsound/openal_output.hpp | 10 ++- 2 files changed, 52 insertions(+), 50 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index b20bd4f7e..3d2795ce1 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -739,7 +739,7 @@ void OpenAL_Output::deinit() mUnusedBuffers.clear(); while(!mBufferCache.empty()) { - alDeleteBuffers(1, &mBufferCache.begin()->second); + alDeleteBuffers(1, &mBufferCache.begin()->second.mALBuffer); mBufferCache.erase(mBufferCache.begin()); } @@ -755,14 +755,14 @@ void OpenAL_Output::deinit() } -ALuint OpenAL_Output::getBuffer(const std::string &fname, std::vector* loudnessBuffer) +const CachedSound& OpenAL_Output::getBuffer(const std::string &fname) { ALuint buf = 0; NameMap::iterator iditer = mBufferCache.find(fname); if(iditer != mBufferCache.end()) { - buf = iditer->second; + buf = iditer->second.mALBuffer; if(mBufferRefs[buf]++ == 0) { IDDq::iterator iter = std::find(mUnusedBuffers.begin(), @@ -770,12 +770,11 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname, std::vector* lo if(iter != mUnusedBuffers.end()) mUnusedBuffers.erase(iter); } + + return iditer->second; } throwALerror(); - if (buf != 0 && loudnessBuffer == NULL) - return buf; - std::vector data; ChannelConfig chans; SampleType type; @@ -801,52 +800,49 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname, std::vector* lo decoder->readAll(data); decoder->close(); - if (loudnessBuffer != NULL) - { - analyzeLoudness(data, srate, chans, type, *loudnessBuffer, loudnessFPS); - } + CachedSound cached; + analyzeLoudness(data, srate, chans, type, cached.mLoudnessVector, loudnessFPS); - if (buf == 0) - { - alGenBuffers(1, &buf); - throwALerror(); + alGenBuffers(1, &buf); + throwALerror(); - alBufferData(buf, format, &data[0], data.size(), srate); - mBufferCache[fname] = buf; - mBufferRefs[buf] = 1; + alBufferData(buf, format, &data[0], data.size(), srate); + mBufferRefs[buf] = 1; + cached.mALBuffer = buf; + mBufferCache[fname] = cached; - ALint bufsize = 0; - alGetBufferi(buf, AL_SIZE, &bufsize); - mBufferCacheMemSize += bufsize; + ALint bufsize = 0; + alGetBufferi(buf, AL_SIZE, &bufsize); + mBufferCacheMemSize += bufsize; - // NOTE: Max buffer cache: 15MB - while(mBufferCacheMemSize > 15*1024*1024) + // NOTE: Max buffer cache: 15MB + while(mBufferCacheMemSize > 15*1024*1024) + { + if(mUnusedBuffers.empty()) { - if(mUnusedBuffers.empty()) - { - std::cout <<"No more unused buffers to clear!"<< std::endl; - break; - } - - ALuint oldbuf = mUnusedBuffers.front(); - mUnusedBuffers.pop_front(); + std::cout <<"No more unused buffers to clear!"<< std::endl; + break; + } - NameMap::iterator nameiter = mBufferCache.begin(); - while(nameiter != mBufferCache.end()) - { - if(nameiter->second == oldbuf) - mBufferCache.erase(nameiter++); - else - ++nameiter; - } + ALuint oldbuf = mUnusedBuffers.front(); + mUnusedBuffers.pop_front(); - bufsize = 0; - alGetBufferi(oldbuf, AL_SIZE, &bufsize); - alDeleteBuffers(1, &oldbuf); - mBufferCacheMemSize -= bufsize; + NameMap::iterator nameiter = mBufferCache.begin(); + while(nameiter != mBufferCache.end()) + { + if(nameiter->second.mALBuffer == oldbuf) + mBufferCache.erase(nameiter++); + else + ++nameiter; } + + bufsize = 0; + alGetBufferi(oldbuf, AL_SIZE, &bufsize); + alDeleteBuffers(1, &oldbuf); + mBufferCacheMemSize -= bufsize; } - return buf; + + return mBufferCache[fname]; } void OpenAL_Output::bufferFinished(ALuint buf) @@ -870,7 +866,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, f try { - buf = getBuffer(fname); + buf = getBuffer(fname).mALBuffer; sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags)); } catch(std::exception&) @@ -909,12 +905,12 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre try { - std::vector loudnessVector; - - buf = getBuffer(fname, extractLoudness ? &loudnessVector : NULL); + const CachedSound& cached = getBuffer(fname); + buf = cached.mALBuffer; sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); - sound->setLoudnessVector(loudnessVector, loudnessFPS); + if (extractLoudness) + sound->setLoudnessVector(cached.mLoudnessVector, loudnessFPS); } catch(std::exception&) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index f96c588cf..be12bfbec 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -16,6 +16,12 @@ namespace MWSound class SoundManager; class Sound; + struct CachedSound + { + ALuint mALBuffer; + std::vector mLoudnessVector; + }; + class OpenAL_Output : public Sound_Output { ALCdevice *mDevice; @@ -25,7 +31,7 @@ namespace MWSound IDDq mFreeSources; IDDq mUnusedBuffers; - typedef std::map NameMap; + typedef std::map NameMap; NameMap mBufferCache; typedef std::map IDRefMap; @@ -36,7 +42,7 @@ namespace MWSound typedef std::vector SoundVec; SoundVec mActiveSounds; - ALuint getBuffer(const std::string &fname, std::vector* loudnessBuffer=NULL); + const CachedSound& getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); Environment mLastEnvironment; From 1a6db097edd224b5a4a8b70b9de46e52f34f7d96 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 15:32:22 +0200 Subject: [PATCH 0154/1983] Fix dangling MWWorld::Ptrs in enchanting dialog after loading save game (Fixes #1722) --- apps/openmw/mwgui/enchantingdialog.cpp | 23 +++++++++++++++++------ apps/openmw/mwgui/enchantingdialog.hpp | 2 ++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index d7e79e7a2..e89777bee 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -58,9 +58,6 @@ namespace MWGui void EnchantingDialog::open() { center(); - - setSoulGem(MWWorld::Ptr()); - setItem(MWWorld::Ptr()); } void EnchantingDialog::setSoulGem(const MWWorld::Ptr &gem) @@ -78,7 +75,6 @@ namespace MWGui mSoulBox->setUserData(gem); mEnchanting.setSoulGem(gem); } - updateLabels(); } void EnchantingDialog::setItem(const MWWorld::Ptr &item) @@ -96,7 +92,6 @@ namespace MWGui mItemBox->setUserData(item); mEnchanting.setOldItem(item); } - updateLabels(); } void EnchantingDialog::exit() @@ -148,6 +143,9 @@ namespace MWGui mPtr = actor; + setSoulGem(MWWorld::Ptr()); + setItem(MWWorld::Ptr()); + startEditing (); mPrice->setVisible(true); mPriceText->setVisible(true); @@ -167,6 +165,7 @@ namespace MWGui startEditing(); setSoulGem(soulgem); + setItem(MWWorld::Ptr()); mPrice->setVisible(false); mPriceText->setVisible(false); @@ -177,6 +176,16 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); + resetReference(); + } + + void EnchantingDialog::resetReference() + { + ReferenceInterface::resetReference(); + setItem(MWWorld::Ptr()); + setSoulGem(MWWorld::Ptr()); + mPtr = MWWorld::Ptr(); + mEnchanting.setEnchanter(MWWorld::Ptr()); } void EnchantingDialog::onCancelButtonClicked(MyGUI::Widget* sender) @@ -219,8 +228,8 @@ namespace MWGui void EnchantingDialog::onSoulSelected(MWWorld::Ptr item) { mItemSelectionDialog->setVisible(false); - mEnchanting.setSoulGem(item); + mEnchanting.setSoulGem(item); if(mEnchanting.getGemCharge()==0) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage32}"); @@ -228,6 +237,7 @@ namespace MWGui } setSoulGem(item); + updateLabels(); } void EnchantingDialog::onSoulCancel() @@ -252,6 +262,7 @@ namespace MWGui else { setSoulGem(MWWorld::Ptr()); + updateLabels(); } } diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp index b75ae8280..5b67d199b 100644 --- a/apps/openmw/mwgui/enchantingdialog.hpp +++ b/apps/openmw/mwgui/enchantingdialog.hpp @@ -29,6 +29,8 @@ namespace MWGui void startEnchanting(MWWorld::Ptr actor); void startSelfEnchanting(MWWorld::Ptr soulgem); + virtual void resetReference(); + protected: virtual void onReferenceUnavailable(); virtual void notifyEffectsChanged (); From 9e48d56244e84cdcc9c0177c8bd226ba4396a929 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 15:55:58 +0200 Subject: [PATCH 0155/1983] Reset RefNum when copying an object (Fixes #1723) --- apps/openmw/mwworld/cellref.cpp | 6 ++++++ apps/openmw/mwworld/cellref.hpp | 3 +++ apps/openmw/mwworld/containerstore.cpp | 1 + apps/openmw/mwworld/worldimp.cpp | 1 + 4 files changed, 11 insertions(+) diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index cdf08e6ed..c0b3bb6af 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -10,6 +10,12 @@ namespace MWWorld return mCellRef.mRefNum; } + void CellRef::unsetRefNum() + { + mCellRef.mRefNum.mContentFile = -1; + mCellRef.mRefNum.mIndex = 0; + } + std::string CellRef::getRefId() const { return mCellRef.mRefID; diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 577655739..d2e4fdf1c 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -25,6 +25,9 @@ namespace MWWorld // Note: Currently unused for items in containers ESM::RefNum getRefNum() const; + // Set RefNum to its default state. + void unsetRefNum(); + // Id of object being referenced std::string getRefId() const; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 18ebd82db..3188c1cd5 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -255,6 +255,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr pos.pos[1] = 0; pos.pos[2] = 0; item.getCellRef().setPosition(pos); + item.getCellRef().unsetRefNum(); // destroy link to content file std::string script = item.getClass().getScript(item); if(script != "") diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ebbc16c23..d1207dd8b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1718,6 +1718,7 @@ namespace MWWorld localRotation.rot[2] = 0; dropped.getRefData().setLocalRotation(localRotation); dropped.getCellRef().setPosition(pos); + dropped.getCellRef().unsetRefNum(); if (mWorldScene->isCellActive(*cell)) { if (dropped.getRefData().isEnabled()) { From ccde462308e6ad956c9f217123873923459250b2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 19:01:14 +0200 Subject: [PATCH 0156/1983] Fix typo (RefId -> RefNum) --- apps/esmtool/record.cpp | 2 +- components/esm/loadcell.cpp | 10 +++++----- components/esm/loadcell.hpp | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index f8bc2af61..caf67e619 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -513,7 +513,7 @@ void Record::print() else std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; - std::cout << " RefId counter: " << mData.mRefIdCounter << std::endl; + std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; } diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 0a25fce84..91da936a4 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -58,7 +58,7 @@ void Cell::load(ESMReader &esm, bool saveContext) void Cell::loadCell(ESMReader &esm, bool saveContext) { - mRefIdCounter = 0; + mRefNumCounter = 0; if (mData.mFlags & Interior) { @@ -92,7 +92,7 @@ void Cell::loadCell(ESMReader &esm, bool saveContext) esm.getHNOT(mMapColor, "NAM5"); } if (esm.isNextSub("NAM0")) { - esm.getHT(mRefIdCounter); + esm.getHT(mRefNumCounter); } if (saveContext) { @@ -150,8 +150,8 @@ void Cell::save(ESMWriter &esm) const esm.writeHNT("NAM5", mMapColor); } - if (mRefIdCounter != 0) - esm.writeHNT("NAM0", mRefIdCounter); + if (mRefNumCounter != 0) + esm.writeHNT("NAM0", mRefNumCounter); } void Cell::restore(ESMReader &esm, int iCtx) const @@ -220,7 +220,7 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mWater = 0; mWaterInt = false; mMapColor = 0; - mRefIdCounter = 0; + mRefNumCounter = 0; mData.mFlags = 0; mData.mX = 0; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 5f889a55b..f40b3db61 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -94,10 +94,10 @@ struct Cell float mWater; // Water level bool mWaterInt; int mMapColor; - // Counter for RefIds. This is only used during content file editing and has no impact on gameplay. - // It prevents overwriting previous refIDs, even if they were deleted. + // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. + // It prevents overwriting previous refNums, even if they were deleted. // as that would collide with refs when a content file is upgraded. - int mRefIdCounter; + int mRefNumCounter; // References "leased" from another cell (i.e. a different cell // introduced this ref, and it has been moved here by a plugin) From 543bb22e8f4b8a0c31c1920b610c7a0206916f74 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 19:01:40 +0200 Subject: [PATCH 0157/1983] Implement collision script instructions (Fixes #1111) --- apps/openmw/mwbase/world.hpp | 9 +++ apps/openmw/mwscript/docs/vmformat.txt | 10 +++- apps/openmw/mwscript/miscextensions.cpp | 62 ++++++++++++++++++++ apps/openmw/mwworld/physicssystem.cpp | 76 ++++++++++++++++++++++++- apps/openmw/mwworld/physicssystem.hpp | 22 +++++++ apps/openmw/mwworld/worldimp.cpp | 60 ++++++++++++++++--- apps/openmw/mwworld/worldimp.hpp | 9 +++ components/compiler/extensions0.cpp | 4 ++ components/compiler/opcodes.hpp | 8 +++ libs/openengine/bullet/physic.cpp | 16 ------ libs/openengine/bullet/physic.hpp | 2 - libs/openengine/bullet/trace.cpp | 2 + libs/openengine/bullet/trace.h | 1 + 13 files changed, 252 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 29f326a1b..62da2682d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -404,6 +404,15 @@ namespace MWBase virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::Ptr& object) = 0; ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::Ptr& object, float dmgPerSecond) = 0; + ///< Apply a health difference to any actors standing on \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual void hurtCollidingActors (const MWWorld::Ptr& object, float dmgPerSecond) = 0; + ///< Apply a health difference to any actors colliding with \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual float getWindSpeed() = 0; virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 8c9d6e480..4b501b20f 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -405,5 +405,13 @@ op 0x200024c: Face op 0x200024d: Face, explicit op 0x200024e: GetStat (dummy function) op 0x200024f: GetStat (dummy function), explicit +op 0x2000250: GetCollidingPC +op 0x2000251: GetCollidingPC, explicit +op 0x2000252: GetCollidingActor +op 0x2000253: GetCollidingActor, explicit +op 0x2000254: HurtStandingActor +op 0x2000255: HurtStandingActor, explicit +op 0x2000256: HurtCollidingActor +op 0x2000257: HurtCollidingActor, explicit -opcodes 0x2000250-0x3ffffff unused +opcodes 0x2000258-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 4e0257d82..36848d44c 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -611,6 +611,60 @@ namespace MWScript } }; + template + class OpGetCollidingPc : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push (MWBase::Environment::get().getWorld()->getPlayerStandingOn(ptr)); + } + }; + + template + class OpGetCollidingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push (MWBase::Environment::get().getWorld()->getActorStandingOn(ptr)); + } + }; + + template + class OpHurtStandingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + float healthDiffPerSecond = runtime[0].mFloat; + runtime.pop(); + + MWBase::Environment::get().getWorld()->hurtStandingActors(ptr, healthDiffPerSecond); + } + }; + + template + class OpHurtCollidingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + float healthDiffPerSecond = runtime[0].mFloat; + runtime.pop(); + + MWBase::Environment::get().getWorld()->hurtCollidingActors(ptr, healthDiffPerSecond); + } + }; + class OpGetWindSpeed : public Interpreter::Opcode0 { public: @@ -967,6 +1021,14 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPcExplicit, new OpGetStandingPc); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActor, new OpGetStandingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActorExplicit, new OpGetStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPc, new OpGetCollidingPc); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPcExplicit, new OpGetCollidingPc); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActor, new OpGetCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActorExplicit, new OpGetCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActor, new OpHurtStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActorExplicit, new OpHurtStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActor, new OpHurtCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActorExplicit, new OpHurtCollidingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index daad5b0e6..02a198f3d 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -241,7 +241,9 @@ namespace MWWorld } static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, - bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine) + bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine + , std::map& collisionTracker + , std::map& standingCollisionTracker) { const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); @@ -318,6 +320,11 @@ namespace MWWorld tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine); // check if down 2 possible if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) { + const btCollisionObject* standingOn = tracer.mHitObject; + if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) + { + standingCollisionTracker[ptr.getRefData().getHandle()] = body->mName; + } isOnGround = true; // if we're on the ground, don't try to fall any more velocity.z = std::max(0.0f, velocity.z); @@ -376,6 +383,14 @@ namespace MWWorld remainingTime *= (1.0f-tracer.mFraction); // FIXME: remainingTime is no longer used so don't set it? break; } + else + { + const btCollisionObject* standingOn = tracer.mHitObject; + if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) + { + collisionTracker[ptr.getRefData().getHandle()] = body->mName; + } + } } else { @@ -771,6 +786,10 @@ namespace MWWorld const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt) { + // Collision events are only tracked for a single frame, so reset first + mCollisions.clear(); + mStandingCollisions.clear(); + mMovementResults.clear(); mTimeAccum += dt; @@ -810,7 +829,7 @@ namespace MWWorld Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum, world->isFlying(iter->first), - waterlevel, slowFall, mEngine); + waterlevel, slowFall, mEngine, mCollisions, mStandingCollisions); if (waterCollision) mEngine->mDynamicsWorld->removeCollisionObject(&object); @@ -837,4 +856,57 @@ namespace MWWorld mEngine->stepSimulation(dt); } + + bool PhysicsSystem::isActorStandingOn(const Ptr &actor, const Ptr &object) const + { + const std::string& actorHandle = actor.getRefData().getHandle(); + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mStandingCollisions.begin(); + it != mStandingCollisions.end(); ++it) + { + if (it->first == actorHandle && it->second == objectHandle) + return true; + } + return false; + } + + void PhysicsSystem::getActorsStandingOn(const Ptr &object, std::vector &out) const + { + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mStandingCollisions.begin(); + it != mStandingCollisions.end(); ++it) + { + if (it->second == objectHandle) + out.push_back(it->first); + } + } + + bool PhysicsSystem::isActorCollidingWith(const Ptr &actor, const Ptr &object) const + { + const std::string& actorHandle = actor.getRefData().getHandle(); + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mCollisions.begin(); + it != mCollisions.end(); ++it) + { + if (it->first == actorHandle && it->second == objectHandle) + return true; + } + return false; + } + + void PhysicsSystem::getActorsCollidingWith(const Ptr &object, std::vector &out) const + { + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mCollisions.begin(); + it != mCollisions.end(); ++it) + { + if (it->second == objectHandle) + out.push_back(it->first); + } + } + } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 8e0be95d5..ac60a80b5 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -87,12 +87,34 @@ namespace MWWorld const PtrVelocityList& applyQueuedMovement(float dt); + /// Return true if \a actor has been standing on \a object in this frame + /// This will trigger whenever the object is directly below the actor. + /// It doesn't matter if the actor is stationary or moving. + bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::Ptr& object) const; + + /// Get the handle of all actors standing on \a object in this frame. + void getActorsStandingOn(const MWWorld::Ptr& object, std::vector& out) const; + + /// Return true if \a actor has collided with \a object in this frame. + /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. + bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::Ptr& object) const; + + /// Get the handle of all actors colliding with \a object in this frame. + void getActorsCollidingWith(const MWWorld::Ptr& object, std::vector& out) const; + private: OEngine::Render::OgreRenderer &mRender; OEngine::Physic::PhysicEngine* mEngine; std::map handleToMesh; + // Tracks all movement collisions happening during a single frame. + // This will detect e.g. running against a vertical wall. It will not detect climbing up stairs, + // stepping up small objects, etc. + std::map mCollisions; + + std::map mStandingCollisions; + PtrVelocityList mMovementQueue; PtrVelocityList mMovementResults; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d1207dd8b..1c9faa4a7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1995,18 +1995,62 @@ namespace MWWorld bool World::getPlayerStandingOn (const MWWorld::Ptr& object) { - MWWorld::Ptr player = mPlayer->getPlayer(); - if (!mPhysEngine->getCharacter("player")->getOnGround()) - return false; - btVector3 from (player.getRefData().getPosition().pos[0], player.getRefData().getPosition().pos[1], player.getRefData().getPosition().pos[2]); - btVector3 to = from - btVector3(0,0,5); - std::pair result = mPhysEngine->rayTest(from, to); - return result.first == object.getRefData().getBaseNode()->getName(); + MWWorld::Ptr player = getPlayerPtr(); + return mPhysics->isActorStandingOn(player, object); } bool World::getActorStandingOn (const MWWorld::Ptr& object) { - return mPhysEngine->isAnyActorStandingOn(object.getRefData().getBaseNode()->getName()); + std::vector actors; + mPhysics->getActorsStandingOn(object, actors); + return !actors.empty(); + } + + bool World::getPlayerCollidingWith (const MWWorld::Ptr& object) + { + MWWorld::Ptr player = getPlayerPtr(); + return mPhysics->isActorCollidingWith(player, object); + } + + bool World::getActorCollidingWith (const MWWorld::Ptr& object) + { + std::vector actors; + mPhysics->getActorsCollidingWith(object, actors); + return !actors.empty(); + } + + void World::hurtStandingActors(const Ptr &object, float healthPerSecond) + { + std::vector actors; + mPhysics->getActorsStandingOn(object, actors); + for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + { + MWWorld::Ptr actor = searchPtrViaHandle(*it); // Collision events are from the last frame, actor might no longer exist + if (actor.isEmpty()) + continue; + + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat health = stats.getHealth(); + health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); + stats.setHealth(health); + } + } + + void World::hurtCollidingActors(const Ptr &object, float healthPerSecond) + { + std::vector actors; + mPhysics->getActorsCollidingWith(object, actors); + for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + { + MWWorld::Ptr actor = searchPtrViaHandle(*it); // Collision events are from the last frame, actor might no longer exist + if (actor.isEmpty()) + continue; + + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat health = stats.getHealth(); + health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); + stats.setHealth(health); + } } float World::getWindSpeed() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 5c44388ca..c4c8b588e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -479,6 +479,15 @@ namespace MWWorld virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::Ptr& object); ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::Ptr& object); ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::Ptr& object, float dmgPerSecond); + ///< Apply a health difference to any actors standing on \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual void hurtCollidingActors (const MWWorld::Ptr& object, float dmgPerSecond); + ///< Apply a health difference to any actors colliding with \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual float getWindSpeed(); virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index ef4fe4fbd..40246d1dc 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -272,6 +272,10 @@ namespace Compiler extensions.registerInstruction ("fall", "", opcodeFall, opcodeFallExplicit); extensions.registerFunction ("getstandingpc", 'l', "", opcodeGetStandingPc, opcodeGetStandingPcExplicit); extensions.registerFunction ("getstandingactor", 'l', "", opcodeGetStandingActor, opcodeGetStandingActorExplicit); + extensions.registerFunction ("getcollidingpc", 'l', "", opcodeGetCollidingPc, opcodeGetCollidingPcExplicit); + extensions.registerFunction ("getcollidingactor", 'l', "", opcodeGetCollidingActor, opcodeGetCollidingActorExplicit); + extensions.registerInstruction ("hurtstandingactor", "f", opcodeHurtStandingActor, opcodeHurtStandingActorExplicit); + extensions.registerInstruction ("hurtcollidingactor", "f", opcodeHurtCollidingActor, opcodeHurtCollidingActorExplicit); extensions.registerFunction ("getwindspeed", 'f', "", opcodeGetWindSpeed); extensions.registerFunction ("hitonme", 'l', "S", opcodeHitOnMe, opcodeHitOnMeExplicit); extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 9f3ed3574..828d736c4 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -238,6 +238,14 @@ namespace Compiler const int opcodeGetStandingPcExplicit = 0x200020d; const int opcodeGetStandingActor = 0x200020e; const int opcodeGetStandingActorExplicit = 0x200020f; + const int opcodeGetCollidingPc = 0x2000250; + const int opcodeGetCollidingPcExplicit = 0x2000251; + const int opcodeGetCollidingActor = 0x2000252; + const int opcodeGetCollidingActorExplicit = 0x2000253; + const int opcodeHurtStandingActor = 0x2000254; + const int opcodeHurtStandingActorExplicit = 0x2000255; + const int opcodeHurtCollidingActor = 0x2000256; + const int opcodeHurtCollidingActorExplicit = 0x2000257; const int opcodeGetWindSpeed = 0x2000212; const int opcodePlayBink = 0x20001f7; const int opcodeGoToJail = 0x2000235; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 954d7c283..c95552cb7 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -846,21 +846,5 @@ namespace Physic } } - bool PhysicEngine::isAnyActorStandingOn (const std::string& objectName) - { - for (PhysicActorContainer::iterator it = mActorMap.begin(); it != mActorMap.end(); ++it) - { - if (!it->second->getOnGround()) - continue; - Ogre::Vector3 pos = it->second->getPosition(); - btVector3 from (pos.x, pos.y, pos.z); - btVector3 to = from - btVector3(0,0,5); - std::pair result = rayTest(from, to); - if (result.first == objectName) - return true; - } - return false; - } - } } diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 09bff4b04..590b56c01 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -296,8 +296,6 @@ namespace Physic void setSceneManager(Ogre::SceneManager* sceneMgr); - bool isAnyActorStandingOn (const std::string& objectName); - /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). * If \a normal is non-NULL, the hit normal will be written there (if there is a hit) diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index ad140f1f7..de5fecfca 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -81,12 +81,14 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, mFraction = newTraceCallback.m_closestHitFraction; mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mEndPos = (end-start)*mFraction + start; + mHitObject = newTraceCallback.m_hitCollisionObject; } else { mEndPos = end; mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); mFraction = 1.0f; + mHitObject = NULL; } } diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index b9fbce64d..f499f4a27 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -18,6 +18,7 @@ namespace Physic { Ogre::Vector3 mEndPos; Ogre::Vector3 mPlaneNormal; + const btCollisionObject* mHitObject; float mFraction; From f754e06be955499596be03c2c5817670d805431a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aciubid=C5=82o?= Date: Wed, 30 Jul 2014 07:47:09 +0100 Subject: [PATCH 0158/1983] Fix for bug 1685 --- apps/openmw/mwmechanics/aiwander.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 27dad88cd..3b093d6f6 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -428,10 +428,10 @@ namespace MWMechanics if (mSaidGreeting == Greet_None) { - if (playerDistSqr <= helloDistance*helloDistance) + if ((playerDistSqr <= helloDistance*helloDistance) && MWBase::Environment::get().getWorld()->getLOS(player, actor) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) greetingTimer++; - // TODO: check if actor is aware / has line of sight if (greetingTimer >= GREETING_SHOULD_START) { mSaidGreeting = Greet_InProgress; From f25e0b13b24a643de6233d38805666fed4d9d6bf Mon Sep 17 00:00:00 2001 From: crysthala Date: Wed, 30 Jul 2014 07:55:21 -0500 Subject: [PATCH 0159/1983] render window controls icons eyeball dude: first person flying eye: free cam orbit2: orbiting cam --- files/opencs/eyeballdude.png | Bin 0 -> 7350 bytes files/opencs/flying eye.png | Bin 0 -> 8123 bytes files/opencs/orbit2.png | Bin 0 -> 8016 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 files/opencs/eyeballdude.png create mode 100644 files/opencs/flying eye.png create mode 100644 files/opencs/orbit2.png diff --git a/files/opencs/eyeballdude.png b/files/opencs/eyeballdude.png new file mode 100644 index 0000000000000000000000000000000000000000..c782880f321d907c75b0a60df8fef7eda7bee33a GIT binary patch literal 7350 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 literal 0 HcmV?d00001 diff --git a/files/opencs/flying eye.png b/files/opencs/flying eye.png new file mode 100644 index 0000000000000000000000000000000000000000..e379d7fad047497b3041321ab90fc1d1fe467fc4 GIT binary patch literal 8123 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 SIr3Kk0000 Date: Wed, 30 Jul 2014 02:32:05 +0200 Subject: [PATCH 0160/1983] Show sMagicInvalidTarget for an invalid soultrap target (Fixes #1728) --- apps/openmw/mwmechanics/spellcasting.cpp | 55 +++++++++++++++++------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 94b479007..4d05e5446 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -230,6 +230,44 @@ namespace MWMechanics return -(resistance-100) / 100.f; } + /// Check if the given affect can be applied to the target. If \a castByPlayer, emits a message box on failure. + bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, bool castByPlayer) + { + switch (effectId) + { + case ESM::MagicEffect::Levitate: + if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + return false; + } + break; + case ESM::MagicEffect::Soultrap: + if ((target.getClass().isActor() && target.getClass().isNpc()) + || (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); + return false; + } + break; + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + case ESM::MagicEffect::Mark: + case ESM::MagicEffect::Recall: + if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + return false; + } + break; + } + + return true; + } + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target) : mCaster(caster) , mTarget(target) @@ -318,23 +356,8 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); - if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + if (!checkEffectTarget(effectIt->mEffectID, target, castByPlayer)) continue; - } - - if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled() && - (effectIt->mEffectID == ESM::MagicEffect::AlmsiviIntervention || - effectIt->mEffectID == ESM::MagicEffect::DivineIntervention || - effectIt->mEffectID == ESM::MagicEffect::Mark || - effectIt->mEffectID == ESM::MagicEffect::Recall)) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - continue; - } // If player is healing someone, show the target's HP bar if (castByPlayer && target != caster From d1feb9ef0261b6ee6f4a0d4ca3c1d5a6cd6d3c36 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 30 Jul 2014 03:39:43 +0200 Subject: [PATCH 0161/1983] Fix # in book text being interpreted as MyGUI color code --- apps/openmw/mwgui/formatting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 4d3d04ced..1dded94fe 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -406,7 +406,7 @@ namespace MWGui box->setTextAlign(mTextStyle.mTextAlign); box->setTextColour(mTextStyle.mColour); box->setFontName(mTextStyle.mFont); - box->setCaption(realText); + box->setCaption(MyGUI::TextIterator::toTagsString(realText)); box->setSize(box->getSize().width, box->getTextSize().height); mHeight += box->getTextSize().height; From 261e755e73e7534d3b3dd59a3f291c4b992d36f2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 30 Jul 2014 04:43:47 +0200 Subject: [PATCH 0162/1983] Font hacking again (Fixes #1506) --- apps/openmw/mwgui/fontloader.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index 92d9a25b6..13f4e41f2 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -262,11 +262,32 @@ namespace MWGui // in the cp437 encoding of the font. Fall back to similar available characters. if (mEncoding == ToUTF8::CP437) { - std::multimap additional; + std::multimap additional; // additional.insert(std::make_pair(39, 0x2019)); // apostrophe additional.insert(std::make_pair(45, 0x2013)); // dash + additional.insert(std::make_pair(45, 0x2014)); // dash additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark + additional.insert(std::make_pair(44, 0x201A)); + additional.insert(std::make_pair(44, 0x201E)); + additional.insert(std::make_pair(43, 0x2020)); + additional.insert(std::make_pair(94, 0x02C6)); + additional.insert(std::make_pair(37, 0x2030)); + additional.insert(std::make_pair(83, 0x0160)); + additional.insert(std::make_pair(60, 0x2039)); + additional.insert(std::make_pair(79, 0x0152)); + additional.insert(std::make_pair(90, 0x017D)); + additional.insert(std::make_pair(39, 0x2019)); + additional.insert(std::make_pair(126, 0x02DC)); + additional.insert(std::make_pair(84, 0x2122)); + additional.insert(std::make_pair(83, 0x0161)); + additional.insert(std::make_pair(62, 0x203A)); + additional.insert(std::make_pair(111, 0x0153)); + additional.insert(std::make_pair(122, 0x017E)); + additional.insert(std::make_pair(89, 0x0178)); + additional.insert(std::make_pair(156, 0x00A2)); + additional.insert(std::make_pair(46, 0x2026)); + for (std::multimap::iterator it = additional.begin(); it != additional.end(); ++it) { if (it->first != i) From 7a5f73de9e03fa3799a5f46d46ea4215bc70502b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 30 Jul 2014 17:02:23 +0200 Subject: [PATCH 0163/1983] added navigation mode icons to buttons --- apps/opencs/view/render/worldspacewidget.cpp | 6 +++--- files/opencs/resources.qrc | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index d3413a29d..57ed06212 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -61,7 +61,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( /// \todo replace icons /// \todo consider user-defined button-mapping - tool->addButton (":door.png", "1st", + tool->addButton (":scenetoolbar/1st-person", "1st", "First Person" "
  • Mouse-Look while holding the left button
  • " "
  • WASD movement keys
  • " @@ -70,7 +70,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "
  • Camera is held upright
  • " "
  • Hold shift to speed up movement
  • " "
"); - tool->addButton (":GMST.png", "free", + tool->addButton (":scenetoolbar/free-camera", "free", "Free Camera" "
  • Mouse-Look while holding the left button
  • " "
  • Stafing (also vertically) via WASD or by holding the left mouse button and control
  • " @@ -78,7 +78,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( "
  • Roll camera with Q and E keys
  • " "
  • Hold shift to speed up movement
  • " "
"); - tool->addButton (":Info.png", "orbit", + tool->addButton (":scenetoolbar/orbiting-camera", "orbit", "Orbiting Camera" "